1# @file 2# Script to Build ArmVirtPkg UEFI firmware 3# 4# Copyright (c) Microsoft Corporation. 5# SPDX-License-Identifier: BSD-2-Clause-Patent 6## 7import os 8import logging 9import io 10 11from edk2toolext.environment import shell_environment 12from edk2toolext.environment.uefi_build import UefiBuilder 13from edk2toolext.invocables.edk2_platform_build import BuildSettingsManager 14from edk2toolext.invocables.edk2_setup import SetupSettingsManager, RequiredSubmodule 15from edk2toolext.invocables.edk2_update import UpdateSettingsManager 16from edk2toolext.invocables.edk2_pr_eval import PrEvalSettingsManager 17from edk2toollib.utility_functions import RunCmd 18from edk2toollib.utility_functions import GetHostInfo 19 20# ####################################################################################### # 21# Common Configuration # 22# ####################################################################################### # 23 24 25class CommonPlatform(): 26 ''' Common settings for this platform. Define static data here and use 27 for the different parts of stuart 28 ''' 29 PackagesSupported = ("ArmVirtPkg",) 30 ArchSupported = ("AARCH64", "ARM") 31 TargetsSupported = ("DEBUG", "RELEASE", "NOOPT") 32 Scopes = ('armvirt', 'edk2-build') 33 WorkspaceRoot = os.path.realpath(os.path.join( 34 os.path.dirname(os.path.abspath(__file__)), "..", "..")) 35 36 # ####################################################################################### # 37 # Configuration for Update & Setup # 38 # ####################################################################################### # 39 40 41class SettingsManager(UpdateSettingsManager, SetupSettingsManager, PrEvalSettingsManager): 42 43 def GetPackagesSupported(self): 44 ''' return iterable of edk2 packages supported by this build. 45 These should be edk2 workspace relative paths ''' 46 return CommonPlatform.PackagesSupported 47 48 def GetArchitecturesSupported(self): 49 ''' return iterable of edk2 architectures supported by this build ''' 50 return CommonPlatform.ArchSupported 51 52 def GetTargetsSupported(self): 53 ''' return iterable of edk2 target tags supported by this build ''' 54 return CommonPlatform.TargetsSupported 55 56 def GetRequiredSubmodules(self): 57 ''' return iterable containing RequiredSubmodule objects. 58 If no RequiredSubmodules return an empty iterable 59 ''' 60 rs = [] 61 62 # intentionally declare this one with recursive false to avoid overhead 63 rs.append(RequiredSubmodule( 64 "CryptoPkg/Library/OpensslLib/openssl", False)) 65 66 # To avoid maintenance of this file for every new submodule 67 # lets just parse the .gitmodules and add each if not already in list. 68 # The GetRequiredSubmodules is designed to allow a build to optimize 69 # the desired submodules but it isn't necessary for this repository. 70 result = io.StringIO() 71 ret = RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir=self.GetWorkspaceRoot(), outstream=result) 72 # Cmd output is expected to look like: 73 # submodule.CryptoPkg/Library/OpensslLib/openssl.path CryptoPkg/Library/OpensslLib/openssl 74 # submodule.SoftFloat.path ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3 75 if ret == 0: 76 for line in result.getvalue().splitlines(): 77 _, _, path = line.partition(" ") 78 if path is not None: 79 if path not in [x.path for x in rs]: 80 rs.append(RequiredSubmodule(path, True)) # add it with recursive since we don't know 81 return rs 82 83 def SetArchitectures(self, list_of_requested_architectures): 84 ''' Confirm the requests architecture list is valid and configure SettingsManager 85 to run only the requested architectures. 86 87 Raise Exception if a list_of_requested_architectures is not supported 88 ''' 89 unsupported = set(list_of_requested_architectures) - \ 90 set(self.GetArchitecturesSupported()) 91 if(len(unsupported) > 0): 92 errorString = ( 93 "Unsupported Architecture Requested: " + " ".join(unsupported)) 94 logging.critical(errorString) 95 raise Exception(errorString) 96 self.ActualArchitectures = list_of_requested_architectures 97 98 def GetWorkspaceRoot(self): 99 ''' get WorkspacePath ''' 100 return CommonPlatform.WorkspaceRoot 101 102 def GetActiveScopes(self): 103 ''' return tuple containing scopes that should be active for this process ''' 104 105 scopes = CommonPlatform.Scopes 106 ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "") 107 108 if GetHostInfo().os.upper() == "LINUX" and ActualToolChainTag.upper().startswith("GCC"): 109 if "AARCH64" in self.ActualArchitectures: 110 scopes += ("gcc_aarch64_linux",) 111 if "ARM" in self.ActualArchitectures: 112 scopes += ("gcc_arm_linux",) 113 return scopes 114 115 def FilterPackagesToTest(self, changedFilesList: list, potentialPackagesList: list) -> list: 116 ''' Filter other cases that this package should be built 117 based on changed files. This should cover things that can't 118 be detected as dependencies. ''' 119 build_these_packages = [] 120 possible_packages = potentialPackagesList.copy() 121 for f in changedFilesList: 122 # BaseTools files that might change the build 123 if "BaseTools" in f: 124 if os.path.splitext(f) not in [".txt", ".md"]: 125 build_these_packages = possible_packages 126 break 127 128 # if the azure pipeline platform template file changed 129 if "platform-build-run-steps.yml" in f: 130 build_these_packages = possible_packages 131 break 132 133 134 return build_these_packages 135 136 def GetPlatformDscAndConfig(self) -> tuple: 137 ''' If a platform desires to provide its DSC then Policy 4 will evaluate if 138 any of the changes will be built in the dsc. 139 140 The tuple should be (<workspace relative path to dsc file>, <input dictionary of dsc key value pairs>) 141 ''' 142 return (os.path.join("ArmVirtPkg", "ArmVirtQemu.dsc"), {}) 143 144 145 # ####################################################################################### # 146 # Actual Configuration for Platform Build # 147 # ####################################################################################### # 148 149 150class PlatformBuilder(UefiBuilder, BuildSettingsManager): 151 def __init__(self): 152 UefiBuilder.__init__(self) 153 154 def AddCommandLineOptions(self, parserObj): 155 ''' Add command line options to the argparser ''' 156 parserObj.add_argument('-a', "--arch", dest="build_arch", type=str, default="AARCH64", 157 help="Optional - Architecture to build. Default = AARCH64") 158 159 def RetrieveCommandLineOptions(self, args): 160 ''' Retrieve command line options from the argparser ''' 161 162 shell_environment.GetBuildVars().SetValue( 163 "TARGET_ARCH", args.build_arch.upper(), "From CmdLine") 164 165 shell_environment.GetBuildVars().SetValue( 166 "ACTIVE_PLATFORM", "ArmVirtPkg/ArmVirtQemu.dsc", "From CmdLine") 167 168 def GetWorkspaceRoot(self): 169 ''' get WorkspacePath ''' 170 return CommonPlatform.WorkspaceRoot 171 172 def GetPackagesPath(self): 173 ''' Return a list of workspace relative paths that should be mapped as edk2 PackagesPath ''' 174 return () 175 176 def GetActiveScopes(self): 177 ''' return tuple containing scopes that should be active for this process ''' 178 scopes = CommonPlatform.Scopes 179 ActualToolChainTag = shell_environment.GetBuildVars().GetValue("TOOL_CHAIN_TAG", "") 180 Arch = shell_environment.GetBuildVars().GetValue("TARGET_ARCH", "") 181 182 if GetHostInfo().os.upper() == "LINUX" and ActualToolChainTag.upper().startswith("GCC"): 183 if "AARCH64" == Arch: 184 scopes += ("gcc_aarch64_linux",) 185 elif "ARM" == Arch: 186 scopes += ("gcc_arm_linux",) 187 return scopes 188 189 def GetName(self): 190 ''' Get the name of the repo, platform, or product being build ''' 191 ''' Used for naming the log file, among others ''' 192 # check the startup nsh flag and if set then rename the log file. 193 # this helps in CI so we don't overwrite the build log since running 194 # uses the stuart_build command. 195 if(shell_environment.GetBuildVars().GetValue("MAKE_STARTUP_NSH", "FALSE") == "TRUE"): 196 return "ArmVirtPkg_With_Run" 197 return "ArmVirtPkg" 198 199 def GetLoggingLevel(self, loggerType): 200 ''' Get the logging level for a given type 201 base == lowest logging level supported 202 con == Screen logging 203 txt == plain text file logging 204 md == markdown file logging 205 ''' 206 return logging.DEBUG 207 208 def SetPlatformEnv(self): 209 logging.debug("PlatformBuilder SetPlatformEnv") 210 self.env.SetValue("PRODUCT_NAME", "ArmVirtQemu", "Platform Hardcoded") 211 self.env.SetValue("MAKE_STARTUP_NSH", "FALSE", "Default to false") 212 self.env.SetValue("QEMU_HEADLESS", "FALSE", "Default to false") 213 return 0 214 215 def PlatformPreBuild(self): 216 return 0 217 218 def PlatformPostBuild(self): 219 return 0 220 221 def FlashRomImage(self): 222 VirtualDrive = os.path.join(self.env.GetValue( 223 "BUILD_OUTPUT_BASE"), "VirtualDrive") 224 os.makedirs(VirtualDrive, exist_ok=True) 225 OutputPath_FV = os.path.join( 226 self.env.GetValue("BUILD_OUTPUT_BASE"), "FV") 227 Built_FV = os.path.join(OutputPath_FV, "QEMU_EFI.fd") 228 229 # pad fd to 64mb 230 with open(Built_FV, "ab") as fvfile: 231 fvfile.seek(0, os.SEEK_END) 232 additional = b'\0' * ((64 * 1024 * 1024)-fvfile.tell()) 233 fvfile.write(additional) 234 235 # QEMU must be on that path 236 237 # Unique Command and Args parameters per ARCH 238 if (self.env.GetValue("TARGET_ARCH").upper() == "AARCH64"): 239 cmd = "qemu-system-aarch64" 240 args = "-M virt" 241 args += " -cpu cortex-a57" # emulate cpu 242 elif(self.env.GetValue("TARGET_ARCH").upper() == "ARM"): 243 cmd = "qemu-system-arm" 244 args = "-M virt" 245 args += " -cpu cortex-a15" # emulate cpu 246 else: 247 raise NotImplementedError() 248 249 # Common Args 250 args += " -pflash " + Built_FV # path to fw 251 args += " -m 1024" # 1gb memory 252 # turn off network 253 args += " -net none" 254 # Serial messages out 255 args += " -serial stdio" 256 # Mount disk with startup.nsh 257 args += f" -drive file=fat:rw:{VirtualDrive},format=raw,media=disk" 258 259 # Conditional Args 260 if (self.env.GetValue("QEMU_HEADLESS").upper() == "TRUE"): 261 args += " -display none" # no graphics 262 263 if (self.env.GetValue("MAKE_STARTUP_NSH").upper() == "TRUE"): 264 f = open(os.path.join(VirtualDrive, "startup.nsh"), "w") 265 f.write("BOOT SUCCESS !!! \n") 266 # add commands here 267 f.write("reset -s\n") 268 f.close() 269 270 ret = RunCmd(cmd, args) 271 272 if ret == 0xc0000005: 273 # for some reason getting a c0000005 on successful return 274 return 0 275 276 return ret 277