1 | # @file
|
---|
2 | # Script to Build OVMF UEFI firmware
|
---|
3 | #
|
---|
4 | # Copyright (c) Microsoft Corporation.
|
---|
5 | # SPDX-License-Identifier: BSD-2-Clause-Patent
|
---|
6 | ##
|
---|
7 | import os
|
---|
8 | import shutil
|
---|
9 | import logging
|
---|
10 | import io
|
---|
11 |
|
---|
12 | from edk2toolext.environment import shell_environment
|
---|
13 | from edk2toolext.environment.uefi_build import UefiBuilder
|
---|
14 | from edk2toolext.invocables.edk2_platform_build import BuildSettingsManager
|
---|
15 | from edk2toolext.invocables.edk2_setup import SetupSettingsManager, RequiredSubmodule
|
---|
16 | from edk2toolext.invocables.edk2_update import UpdateSettingsManager
|
---|
17 | from edk2toolext.invocables.edk2_pr_eval import PrEvalSettingsManager
|
---|
18 | from edk2toollib.utility_functions import RunCmd
|
---|
19 |
|
---|
20 |
|
---|
21 | # ####################################################################################### #
|
---|
22 | # Configuration for Update & Setup #
|
---|
23 | # ####################################################################################### #
|
---|
24 | class SettingsManager(UpdateSettingsManager, SetupSettingsManager, PrEvalSettingsManager):
|
---|
25 |
|
---|
26 | def GetPackagesSupported(self):
|
---|
27 | ''' return iterable of edk2 packages supported by this build.
|
---|
28 | These should be edk2 workspace relative paths '''
|
---|
29 | return CommonPlatform.PackagesSupported
|
---|
30 |
|
---|
31 | def GetArchitecturesSupported(self):
|
---|
32 | ''' return iterable of edk2 architectures supported by this build '''
|
---|
33 | return CommonPlatform.ArchSupported
|
---|
34 |
|
---|
35 | def GetTargetsSupported(self):
|
---|
36 | ''' return iterable of edk2 target tags supported by this build '''
|
---|
37 | return CommonPlatform.TargetsSupported
|
---|
38 |
|
---|
39 | def GetRequiredSubmodules(self):
|
---|
40 | ''' return iterable containing RequiredSubmodule objects.
|
---|
41 | If no RequiredSubmodules return an empty iterable
|
---|
42 | '''
|
---|
43 | rs = []
|
---|
44 |
|
---|
45 | # intentionally declare this one with recursive false to avoid overhead
|
---|
46 | rs.append(RequiredSubmodule(
|
---|
47 | "CryptoPkg/Library/OpensslLib/openssl", False))
|
---|
48 |
|
---|
49 | # To avoid maintenance of this file for every new submodule
|
---|
50 | # lets just parse the .gitmodules and add each if not already in list.
|
---|
51 | # The GetRequiredSubmodules is designed to allow a build to optimize
|
---|
52 | # the desired submodules but it isn't necessary for this repository.
|
---|
53 | result = io.StringIO()
|
---|
54 | ret = RunCmd("git", "config --file .gitmodules --get-regexp path", workingdir=self.GetWorkspaceRoot(), outstream=result)
|
---|
55 | # Cmd output is expected to look like:
|
---|
56 | # submodule.CryptoPkg/Library/OpensslLib/openssl.path CryptoPkg/Library/OpensslLib/openssl
|
---|
57 | # submodule.SoftFloat.path ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3
|
---|
58 | if ret == 0:
|
---|
59 | for line in result.getvalue().splitlines():
|
---|
60 | _, _, path = line.partition(" ")
|
---|
61 | if path is not None:
|
---|
62 | if path not in [x.path for x in rs]:
|
---|
63 | rs.append(RequiredSubmodule(path, True)) # add it with recursive since we don't know
|
---|
64 | return rs
|
---|
65 |
|
---|
66 | def SetArchitectures(self, list_of_requested_architectures):
|
---|
67 | ''' Confirm the requests architecture list is valid and configure SettingsManager
|
---|
68 | to run only the requested architectures.
|
---|
69 |
|
---|
70 | Raise Exception if a list_of_requested_architectures is not supported
|
---|
71 | '''
|
---|
72 | unsupported = set(list_of_requested_architectures) - set(self.GetArchitecturesSupported())
|
---|
73 | if(len(unsupported) > 0):
|
---|
74 | errorString = ( "Unsupported Architecture Requested: " + " ".join(unsupported))
|
---|
75 | logging.critical( errorString )
|
---|
76 | raise Exception( errorString )
|
---|
77 | self.ActualArchitectures = list_of_requested_architectures
|
---|
78 |
|
---|
79 | def GetWorkspaceRoot(self):
|
---|
80 | ''' get WorkspacePath '''
|
---|
81 | return CommonPlatform.WorkspaceRoot
|
---|
82 |
|
---|
83 | def GetActiveScopes(self):
|
---|
84 | ''' return tuple containing scopes that should be active for this process '''
|
---|
85 | return CommonPlatform.Scopes
|
---|
86 |
|
---|
87 | def FilterPackagesToTest(self, changedFilesList: list, potentialPackagesList: list) -> list:
|
---|
88 | ''' Filter other cases that this package should be built
|
---|
89 | based on changed files. This should cover things that can't
|
---|
90 | be detected as dependencies. '''
|
---|
91 | build_these_packages = []
|
---|
92 | possible_packages = potentialPackagesList.copy()
|
---|
93 | for f in changedFilesList:
|
---|
94 | # BaseTools files that might change the build
|
---|
95 | if "BaseTools" in f:
|
---|
96 | if os.path.splitext(f) not in [".txt", ".md"]:
|
---|
97 | build_these_packages = possible_packages
|
---|
98 | break
|
---|
99 |
|
---|
100 | # if the azure pipeline platform template file changed
|
---|
101 | if "platform-build-run-steps.yml" in f:
|
---|
102 | build_these_packages = possible_packages
|
---|
103 | break
|
---|
104 |
|
---|
105 | return build_these_packages
|
---|
106 |
|
---|
107 | def GetPlatformDscAndConfig(self) -> tuple:
|
---|
108 | ''' If a platform desires to provide its DSC then Policy 4 will evaluate if
|
---|
109 | any of the changes will be built in the dsc.
|
---|
110 |
|
---|
111 | The tuple should be (<workspace relative path to dsc file>, <input dictionary of dsc key value pairs>)
|
---|
112 | '''
|
---|
113 | dsc = CommonPlatform.GetDscName(",".join(self.ActualArchitectures))
|
---|
114 | return (f"OvmfPkg/{dsc}", {})
|
---|
115 |
|
---|
116 |
|
---|
117 | # ####################################################################################### #
|
---|
118 | # Actual Configuration for Platform Build #
|
---|
119 | # ####################################################################################### #
|
---|
120 | class PlatformBuilder( UefiBuilder, BuildSettingsManager):
|
---|
121 | def __init__(self):
|
---|
122 | UefiBuilder.__init__(self)
|
---|
123 |
|
---|
124 | def AddCommandLineOptions(self, parserObj):
|
---|
125 | ''' Add command line options to the argparser '''
|
---|
126 | parserObj.add_argument('-a', "--arch", dest="build_arch", type=str, default="IA32,X64",
|
---|
127 | help="Optional - CSV of architecture to build. IA32 will use IA32 for Pei & Dxe. "
|
---|
128 | "X64 will use X64 for both PEI and DXE. IA32,X64 will use IA32 for PEI and "
|
---|
129 | "X64 for DXE. default is IA32,X64")
|
---|
130 |
|
---|
131 | def RetrieveCommandLineOptions(self, args):
|
---|
132 | ''' Retrieve command line options from the argparser '''
|
---|
133 |
|
---|
134 | shell_environment.GetBuildVars().SetValue("TARGET_ARCH"," ".join(args.build_arch.upper().split(",")), "From CmdLine")
|
---|
135 | dsc = CommonPlatform.GetDscName(args.build_arch)
|
---|
136 | shell_environment.GetBuildVars().SetValue("ACTIVE_PLATFORM", f"OvmfPkg/{dsc}", "From CmdLine")
|
---|
137 |
|
---|
138 | def GetWorkspaceRoot(self):
|
---|
139 | ''' get WorkspacePath '''
|
---|
140 | return CommonPlatform.WorkspaceRoot
|
---|
141 |
|
---|
142 | def GetPackagesPath(self):
|
---|
143 | ''' Return a list of workspace relative paths that should be mapped as edk2 PackagesPath '''
|
---|
144 | return ()
|
---|
145 |
|
---|
146 | def GetActiveScopes(self):
|
---|
147 | ''' return tuple containing scopes that should be active for this process '''
|
---|
148 | return CommonPlatform.Scopes
|
---|
149 |
|
---|
150 | def GetName(self):
|
---|
151 | ''' Get the name of the repo, platform, or product being build '''
|
---|
152 | ''' Used for naming the log file, among others '''
|
---|
153 | # check the startup nsh flag and if set then rename the log file.
|
---|
154 | # this helps in CI so we don't overwrite the build log since running
|
---|
155 | # uses the stuart_build command.
|
---|
156 | if(shell_environment.GetBuildVars().GetValue("MAKE_STARTUP_NSH", "FALSE") == "TRUE"):
|
---|
157 | return "OvmfPkg_With_Run"
|
---|
158 | return "OvmfPkg"
|
---|
159 |
|
---|
160 | def GetLoggingLevel(self, loggerType):
|
---|
161 | ''' Get the logging level for a given type
|
---|
162 | base == lowest logging level supported
|
---|
163 | con == Screen logging
|
---|
164 | txt == plain text file logging
|
---|
165 | md == markdown file logging
|
---|
166 | '''
|
---|
167 | return logging.DEBUG
|
---|
168 |
|
---|
169 | def SetPlatformEnv(self):
|
---|
170 | logging.debug("PlatformBuilder SetPlatformEnv")
|
---|
171 | self.env.SetValue("PRODUCT_NAME", "OVMF", "Platform Hardcoded")
|
---|
172 | self.env.SetValue("MAKE_STARTUP_NSH", "FALSE", "Default to false")
|
---|
173 | self.env.SetValue("QEMU_HEADLESS", "FALSE", "Default to false")
|
---|
174 | self.env.SetValue("DISABLE_DEBUG_MACRO_CHECK", "TRUE", "Disable by default")
|
---|
175 | return 0
|
---|
176 |
|
---|
177 | def PlatformPreBuild(self):
|
---|
178 | return 0
|
---|
179 |
|
---|
180 | def PlatformPostBuild(self):
|
---|
181 | return 0
|
---|
182 |
|
---|
183 | def FlashRomImage(self):
|
---|
184 | VirtualDrive = os.path.join(self.env.GetValue("BUILD_OUTPUT_BASE"), "VirtualDrive")
|
---|
185 | VirtualDriveBoot = os.path.join(VirtualDrive, "EFI", "BOOT")
|
---|
186 | os.makedirs(VirtualDriveBoot, exist_ok=True)
|
---|
187 | OutputPath_FV = os.path.join(self.env.GetValue("BUILD_OUTPUT_BASE"), "FV")
|
---|
188 |
|
---|
189 | if (self.env.GetValue("QEMU_SKIP") and
|
---|
190 | self.env.GetValue("QEMU_SKIP").upper() == "TRUE"):
|
---|
191 | logging.info("skipping qemu boot test")
|
---|
192 | return 0
|
---|
193 |
|
---|
194 | # copy shell to VirtualDrive
|
---|
195 | for arch in self.env.GetValue("TARGET_ARCH").split():
|
---|
196 | src = os.path.join(self.env.GetValue("BUILD_OUTPUT_BASE"), arch, "Shell.efi")
|
---|
197 | dst = os.path.join(VirtualDriveBoot, f'BOOT{arch}.EFI')
|
---|
198 | if os.path.exists(src):
|
---|
199 | logging.info("copy %s -> %s", src, dst)
|
---|
200 | shutil.copyfile(src, dst)
|
---|
201 |
|
---|
202 | #
|
---|
203 | # QEMU must be on the path
|
---|
204 | #
|
---|
205 | cmd = "qemu-system-x86_64"
|
---|
206 | args = "-debugcon stdio" # write messages to stdio
|
---|
207 | args += " -global isa-debugcon.iobase=0x402" # debug messages out thru virtual io port
|
---|
208 | args += " -net none" # turn off network
|
---|
209 | args += " -smp 4"
|
---|
210 | args += " -cpu IvyBridge,+rdrand" # IvyBridge is the first CPU that supported
|
---|
211 | # RDRAND, which is required for dynamic
|
---|
212 | # stack cookies
|
---|
213 | args += f" -drive file=fat:rw:{VirtualDrive},format=raw,media=disk" # Mount disk with startup.nsh
|
---|
214 | # Provides Rng services to the Guest VM
|
---|
215 | args += " -device virtio-rng-pci"
|
---|
216 |
|
---|
217 | if (self.env.GetValue("QEMU_HEADLESS").upper() == "TRUE"):
|
---|
218 | args += " -display none" # no graphics
|
---|
219 |
|
---|
220 | if (self.env.GetBuildValue("SMM_REQUIRE") == "1"):
|
---|
221 | args += " -machine q35,smm=on" #,accel=(tcg|kvm)"
|
---|
222 | args += " --accel tcg,thread=single"
|
---|
223 | #args += " -m ..."
|
---|
224 | args += " -global driver=cfi.pflash01,property=secure,value=on"
|
---|
225 | args += " -drive if=pflash,format=raw,unit=0,file=" + os.path.join(OutputPath_FV, "OVMF_CODE.fd") + ",readonly=on"
|
---|
226 | args += " -drive if=pflash,format=raw,unit=1,file=" + os.path.join(OutputPath_FV, "OVMF_VARS.fd")
|
---|
227 | else:
|
---|
228 | args += " -pflash " + os.path.join(OutputPath_FV, "OVMF.fd") # path to firmware
|
---|
229 |
|
---|
230 |
|
---|
231 | if (self.env.GetValue("MAKE_STARTUP_NSH").upper() == "TRUE"):
|
---|
232 | f = open(os.path.join(VirtualDrive, "startup.nsh"), "w")
|
---|
233 | f.write("BOOT SUCCESS !!! \n")
|
---|
234 | ## add commands here
|
---|
235 | f.write("reset -s\n")
|
---|
236 | f.close()
|
---|
237 |
|
---|
238 | ret = RunCmd(cmd, args)
|
---|
239 |
|
---|
240 | if ret == 0xc0000005:
|
---|
241 | #for some reason getting a c0000005 on successful return
|
---|
242 | return 0
|
---|
243 |
|
---|
244 | return ret
|
---|