Source code for adi_lg_plugins.strategies.bootfpgasoc

import enum
import time

import attr
from labgrid.factory import target_factory
from labgrid.step import step
from labgrid.strategy import Strategy, StrategyError, never_retry


[docs] class Status(enum.Enum): """Boot strategy state machine states. Attributes: unknown: Initial state before any operations. powered_off: Device is powered off. sd_mux_to_host: SD card muxed to host for file operations. update_boot_files: Copying boot files to SD card. sd_mux_to_dut: SD card muxed to device for booting. booting: Device powered on, boot in progress. booted: Linux kernel has booted, waiting for user space. shell: Interactive shell session available. soft_off: Device being shut down gracefully. """ unknown = 0 powered_off = 1 sd_mux_to_host = 2 update_boot_files = 3 sd_mux_to_dut = 4 booting = 5 booted = 6 shell = 7 soft_off = 8
[docs] @target_factory.reg_driver @attr.s(eq=False) class BootFPGASoC(Strategy): """BootFPGASoC strategy for FPGA SoC devices using Kuiper releases. This strategy works by using an SD card mux to flash a Kuiper release image onto the device's SD card, and move the necessary boot files into the appropriate locations before booting the device. Flashing a full image is set through the `update_image` attribute. The following bindings must be present on the target: - PowerProtocol (any power control protocol) - SDMuxDriver (to switch SD card between host and DUT) - MassStorageDriver (to copy boot files to the SD card) - ADIShellDriver (to interact with the device shell after boot) - KuiperDLDriver (to download and manage Kuiper release files) Optionally, an ImageWriter driver can be used to flash the full image. This is controlled by the `update_image` attribute. Therefore, physical connections must be set up to allow: - Power control of the device - SD card access from the host (via SD mux) - Shell access to the device (e.g., via serial console) Args: reached_linux_marker (str): String to expect in the shell to confirm Linux has booted. update_image (bool): Whether to flash the full Kuiper image to the SD card. wait_for_linux_prompt_timeout (int): Timeout in seconds to wait for Linux prompt after boot. """ bindings = { "power": "PowerProtocol", "shell": "ADIShellDriver", "sdmux": "USBSDMuxDriver", "mass_storage": "MassStorageDriver", "image_writer": {"USBStorageDriver", None}, "kuiper": "KuiperDLDriver", } status = attr.ib(default=Status.unknown) reached_linux_marker = attr.ib(default="analog") update_image = attr.ib(default=False) wait_for_linux_prompt_timeout = attr.ib(default=60) boot_log = attr.ib(default="", init=False) debug_write_boot_log = attr.ib(default=False) def __attrs_post_init__(self): super().__attrs_post_init__() self.logger.info("BootFPGASoC strategy initialized") if self.kuiper: self.logger.info("Preloading Kuiper boot files") self.target.activate(self.kuiper) self.kuiper.get_boot_files_from_release() self.target.deactivate(self.kuiper)
[docs] @never_retry @step() def transition(self, status, *, step): """Transition the strategy to a new state. This method manages state transitions for the boot process. It handles power control, SD mux switching, boot file updates, and device activation in the correct sequence. Args: status (Status or str): Target state to transition to. Can be a Status enum value or its string representation (e.g., "shell", "booted"). step: Labgrid step decorator context (injected automatically). Raises: StrategyError: If the transition is invalid or fails. Example: >>> strategy.transition("shell") # Transition to shell state >>> strategy.transition(Status.soft_off) # Power off the device Note: State transitions are sequential. Requesting a state that requires intermediate states will automatically transition through them. """ if not isinstance(status, Status): status = Status[status] self.logger.info(f"Transitioning to {status} (Existing status: {self.status})") if status == Status.unknown: raise StrategyError(f"can not transition to {status}") elif status == self.status: step.skip("nothing to do") return # nothing to do elif status == Status.powered_off: self.target.deactivate(self.shell) self.target.activate(self.power) self.power.off() self.logger.info("Device powered off") elif status == Status.sd_mux_to_host: self.transition(Status.powered_off) self.target.activate(self.sdmux) self.logger.info("Muxing SD card to host...") self.sdmux.set_mode("host") time.sleep(5) self.logger.info("SD card muxed to host") elif status == Status.update_boot_files: self.transition(Status.sd_mux_to_host) if self.image_writer and self.update_image: self.logger.info( "Writing full Kuiper image to SD card (this may take several minutes)..." ) self.target.activate(self.image_writer) from labgrid.driver.usbstoragedriver import Mode self.image_writer.write_image(mode=Mode.BMAPTOOL) # self.image_writer.write_image() self.target.deactivate(self.image_writer) self.logger.info("Image written successfully") self.logger.info("Updating boot files on SD card...") self.target.activate(self.mass_storage) self.mass_storage.mount_partition() for boot_file in self.kuiper._boot_files: self.logger.info(f"Copying {boot_file} to SD card...") self.mass_storage.copy_file(boot_file, "/") self.mass_storage.unmount_partition() self.target.deactivate(self.mass_storage) self.logger.info("Boot files updated successfully") elif status == Status.sd_mux_to_dut: self.transition(Status.update_boot_files) self.logger.info("Muxing SD card back to DUT...") self.sdmux.set_mode("dut") time.sleep(5) self.logger.info("SD card muxed to DUT") elif status == Status.booting: self.transition(Status.sd_mux_to_dut) self.target.activate(self.power) self.logger.info("Powering on device...") time.sleep(5) self.power.on() self.logger.info("Device powered on, booting...") elif status == Status.booted: self.transition(Status.booting) self.boot_log = "" # Reset boot log for this boot self.logger.info(f"Waiting for Linux boot and '{self.reached_linux_marker}' prompt...") self.shell.bypass_login = True self.target.activate(self.shell) # Check kernel start _, before, _, _ = self.shell.console.expect("Linux", timeout=30) if before: self.boot_log += before.decode("utf-8", errors="replace") # Check device prompt try: _, before, _, _ = self.shell.console.expect( self.reached_linux_marker, timeout=self.wait_for_linux_prompt_timeout ) # Adjust prompt as needed except Exception as e: if self.debug_write_boot_log: uart_log_filename = f"uart_log_{int(time.time())}.txt" with open(uart_log_filename, "wb") as f: f.write(self.shell.console._expect.before) self.logger.info(f"Wrote log file to {uart_log_filename}") raise e if before: self.boot_log += before.decode("utf-8", errors="replace") self.shell.bypass_login = False self.target.deactivate(self.shell) self.logger.info("Device booted successfully") elif status == Status.shell: self.transition(Status.booted) # self.shell.bypass_login = True self.logger.info("Preparing interactive shell...") self.target.activate(self.shell) # Post boot stuff... self.logger.info("Shell access ready") elif status == Status.soft_off: # Stage is relatively standalone try: self.activate(self.shell) self.shell.sendline("poweroff") self.shell.console.expect(".*Power down.*", timeout=30) self.target.deactivate(self.shell) time.sleep(10) except Exception as e: self.logger.debug(f"DEBUG Soft off failed: {e}") time.sleep(5) self.target.deactivate(self.shell) self.target.activate(self.power) self.power.off() self.logger.debug("DEBUG Soft powered off") else: raise StrategyError(f"no transition found from {self.status} to {status}") self.status = status