#!/usr/bin/env python3 # Xilinx QEMU wrapper to launch both PMU and APU instances (multiarch) import os import subprocess import sys import tempfile import re import shutil binpath = os.path.dirname(os.path.abspath(__file__)) mach_path = tempfile.mkdtemp() # Separate PMU and APU arguments APU_args = sys.argv[1:] PMU_args = [] PLM_args = [] bootbin_arg = None if '-pmu-args' in APU_args: pmu_args_idx = APU_args.index('-pmu-args') PMU_args = APU_args[pmu_args_idx+1].split() del APU_args[pmu_args_idx:pmu_args_idx+2] if '-plm-args' in APU_args: plm_args_idx = APU_args.index('-plm-args') PLM_args = APU_args[plm_args_idx+1].split() del APU_args[plm_args_idx:plm_args_idx+2] if '-bootbin' in APU_args: bootbin_args_idx = APU_args.index('-bootbin') bootbin_arg = APU_args[bootbin_args_idx+1] del APU_args[bootbin_args_idx:bootbin_args_idx+2] if PMU_args and PLM_args: sys.exit("\nError: -pmu-args can not be used with -plm-args\n") help_options = ['-h', '-help', '--help'] def help(status): print("AMD FPGA QEMU multiarch wrapper\nVersion 2024.2\n\nUsage:") print(f" {sys.argv[0]} [-pmu-args ]") print(f" {sys.argv[0]} [-plm-args ]\n") if status == 0: print(f"\n") if set(PMU_args).intersection(set(help_options)): print(f"PMU Options:\n") pmu_args_s = ' '.join(PMU_args) help_cmd = f'{binpath}/qemu-system-microblazeel {pmu_args_s}' elif set(PLM_args).intersection(set(help_options)): print(f"PLM Options:\n") plm_args_s = ' '.join(PLM_args) help_cmd = f'{binpath}/qemu-system-microblazeel {plm_args_s}' else: if not set(APU_args).intersection(set(help_options)): APU_args.append('-help') print(f"APU Options:\n") print(f" -bootbin - Use a boot.bin instead of individual firmware, device trees and bootloader\n") apu_args_s = ' '.join(APU_args) help_cmd = f'{binpath}/qemu-system-aarch64 {apu_args_s}' print(f"{help_cmd}\n") process = subprocess.Popen(help_cmd, shell=True, stderr=subprocess.PIPE) status = process.wait() sys.exit(status) if set(APU_args).intersection(set(help_options)) or set(PMU_args).intersection(set(help_options)) or set(PLM_args).intersection(set(help_options)): help(0) if not PMU_args and not PLM_args: help(1) if PMU_args: try: PMU_rom = PMU_args[PMU_args.index('-kernel')+1] except: PMU_rom = "" if not os.path.exists(PMU_rom): sys.exit(f'\nERROR: Missing PMU ROM: {PMU_rom}' '\nSee "meta-xilinx/README.qemu.md" for more information on accquiring the PMU ROM.\n') if bootbin_arg: if not os.path.isfile(bootbin_arg): print(f"\nERROR: bootbin file not found at {bootbin_arg}" " Please build and ospi_image or set QB_OSPI_BIN variable to prebuilt file\n") sys.exit(1) shutil.copyfile(bootbin_arg, f'{mach_path}/boot.bin') bootgen_command = [f'{binpath}/bootgen', '-arch', 'versal', '-dump', 'boot.bin'] subprocess.run(bootgen_command + ['bh'], check=True, cwd=mach_path, stdout=subprocess.DEVNULL) subprocess.run(bootgen_command + ['plm'], check=True, cwd=mach_path, stdout=subprocess.DEVNULL) subprocess.run(bootgen_command + ['pmc_cdo'], check=True, cwd=mach_path, stdout=subprocess.DEVNULL) bootgen_command = f"{binpath}/bootgen -arch versal -read {bootbin_arg}" result = subprocess.check_output(bootgen_command.split()) bootgen_output = result.decode().splitlines() for i, l in enumerate(bootgen_output): if 'PARTITION HEADER TABLE (pmc_subsys.0.0)' in l: plm_line = bootgen_output[i+4] if 'BOOT HEADER' in l: pmc_line = bootgen_output[i+6] plm_load_addr = re.search(r"exec_addr_lo \(0x10\) : (0x\w*)\s*", plm_line).group(1) pmc_load_addr = re.search(r"pmccdo_load_addr \(0x20\) : (0x\w*)", pmc_line).group(1) PLM_args.append(f"-device loader,file={mach_path}/boot_bh.bin,addr=0xF201E000,force-raw=on") PLM_args.append(f"-device loader,file={mach_path}/pmc_cdo.bin,addr={pmc_load_addr},force-raw=on") PLM_args.append(f"-device loader,file={mach_path}/plm.bin,addr={plm_load_addr},force-raw=on") PLM_args.append(f"-device loader,addr={plm_load_addr},cpu-num=1") # We need to switch tcp serial arguments (if they exist, e.g. qemurunner) to get the output tcp_serial_ports = [i for i, s in enumerate(APU_args) if 'tcp:127.0.0.1:' in s] #FIXME for next yocto release (dont need to switch ports anymore, they will be provided correctly upstream # We can only switch these if there are exactly two, otherwise we can't assume what is being executed so we leave it as is if len(tcp_serial_ports) == 2: APU_args[tcp_serial_ports[0]],APU_args[tcp_serial_ports[1]] = APU_args[tcp_serial_ports[1]],APU_args[tcp_serial_ports[0]] mb_cmd = "" if PMU_args: pmu_args_s = ' '.join(PMU_args) mb_cmd = f'{binpath}/qemu-system-microblazeel {pmu_args_s} -machine-path {mach_path}' print(f"PMU instance cmd: {mb_cmd}\n") if PLM_args: plm_args_s = ' '.join(PLM_args) mb_cmd = f'{binpath}/qemu-system-microblazeel {plm_args_s} -machine-path {mach_path}' print(f"PLM instance cmd: {mb_cmd}\n") apu_args_s = ' '.join(APU_args) apu_cmd = f'{binpath}/qemu-system-aarch64 {apu_args_s} -machine-path {mach_path}' print(f"APU instance cmd: {apu_cmd}\n") if mb_cmd: process_mb = subprocess.Popen(mb_cmd, shell=True, stderr=subprocess.PIPE) if apu_cmd: process_apu = subprocess.Popen(apu_cmd, shell=True, stderr=subprocess.PIPE) error_msg = "" if apu_cmd and process_apu.wait(): # We only check for failures on the MB instance if APU fails error_msg += '\nQEMU APU instance failed:\n%s' % process_apu.stderr.read().decode() if mb_cmd and process_mb.wait(): error_msg += '\nQEMU MB instance failed:\n%s' % process_mb.stderr.read().decode() shutil.rmtree(mach_path) sys.exit(error_msg)