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