# Copyright 2020 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Types which provide many builds and composite them into a single binary.""" import logging import shutil import subprocess import zmake.build_config as build_config import zmake.multiproc import zmake.util as util class BasePacker: """Abstract base for all packers.""" def __init__(self, project): self.project = project def configs(self): """Get all of the build configurations necessary. Yields: 2-tuples of config name and a BuildConfig. """ yield "singleimage", build_config.BuildConfig() def pack_firmware(self, work_dir, jobclient, version_string=""): """Pack a firmware image. Config names from the configs generator are passed as keyword arguments, with each argument being set to the path of the build directory. Args: work_dir: A directory to write outputs and temporary files into. jobclient: A JobClient object to use. version_string: The version string, which may end up in certain parts of the outputs. Yields: 2-tuples of the path of each file in the work_dir (or any other directory) which should be copied into the output directory, and the output filename. """ raise NotImplementedError("Abstract method not implemented") def _get_max_image_bytes(self): """Get the maximum allowed image size (in bytes). This value will generally be found in CONFIG_FLASH_SIZE but may vary depending on the specific way things are being packed. Returns: The maximum allowed size of the image in bytes. """ raise NotImplementedError("Abstract method not implemented") def _is_size_bound(self, path): """Check whether the given path should be constrained by size. Generally, .elf files will be unconstrained while .bin files will be constrained. Args: path: A file's path to test. Returns: True if the file size should be checked. False otherwise. """ return path.suffix == ".bin" def _check_packed_file_size(self, file, dirs): """Check that a packed file passes size constraints. Args: file: A file to test. dirs: A map of the arguments to pass to _get_max_image_bytes Returns: The file if it passes the test. """ if not self._is_size_bound( file ) or file.stat().st_size <= self._get_max_image_bytes(**dirs): return file raise RuntimeError("Output file ({}) too large".format(file)) class ElfPacker(BasePacker): """Raw proxy for ELF output of a single build.""" def pack_firmware(self, work_dir, jobclient, singleimage, version_string=""): yield singleimage / "zephyr" / "zephyr.elf", "zephyr.elf" class RawBinPacker(BasePacker): """Raw proxy for zephyr.bin output of a single build.""" def pack_firmware(self, work_dir, jobclient, singleimage, version_string=""): yield singleimage / "zephyr" / "zephyr.bin", "zephyr.bin" class BinmanPacker(BasePacker): """Packer for RO/RW image to generate a .bin build using FMAP.""" ro_file = "zephyr.bin" rw_file = "zephyr.bin" def __init__(self, project): self.logger = logging.getLogger(self.__class__.__name__) super().__init__(project) def configs(self): yield "ro", build_config.BuildConfig(kconfig_defs={"CONFIG_CROS_EC_RO": "y"}) yield "rw", build_config.BuildConfig(kconfig_defs={"CONFIG_CROS_EC_RW": "y"}) def pack_firmware(self, work_dir, jobclient, ro, rw, version_string=""): """Pack RO and RW sections using Binman. Binman configuration is expected to be found in the RO build device-tree configuration. Args: work_dir: The directory used for packing. jobclient: The client used to run subprocesses. ro: Directory containing the RO image build. rw: Directory containing the RW image build. version_string: The version string to use in FRID/FWID. Yields: 2-tuples of the path of each file in the work_dir that should be copied into the output directory, and the output filename. """ dts_file_path = ro / "zephyr" / "zephyr.dts" # Copy the inputs into the work directory so that Binman can # find them under a hard-coded name. shutil.copy2(ro / "zephyr" / self.ro_file, work_dir / "zephyr_ro.bin") shutil.copy2(rw / "zephyr" / self.rw_file, work_dir / "zephyr_rw.bin") # Version in FRID/FWID can be at most 31 bytes long (32, minus # one for null character). if len(version_string) > 31: version_string = version_string[:31] proc = jobclient.popen( [ "binman", "-v", "5", "build", "-a", "version={}".format(version_string), "-d", dts_file_path, "-m", "-O", work_dir, ], cwd=work_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", ) zmake.multiproc.log_output(self.logger, logging.DEBUG, proc.stdout) zmake.multiproc.log_output(self.logger, logging.ERROR, proc.stderr) if proc.wait(timeout=60): raise OSError("Failed to run binman") yield work_dir / "zephyr.bin", "zephyr.bin" yield ro / "zephyr" / "zephyr.elf", "zephyr.ro.elf" yield rw / "zephyr" / "zephyr.elf", "zephyr.rw.elf" class NpcxPacker(BinmanPacker): """Packer for RO/RW image to generate a .bin build using FMAP. This expects that the build is setup to generate a zephyr.npcx.bin for the RO image, which should be packed using Nuvoton's loader format. """ ro_file = "zephyr.npcx.bin" npcx_monitor = "npcx_monitor.bin" # TODO(b/192401039): CONFIG_FLASH_SIZE is nuvoton-only. Since # binman already checks sizes, perhaps we can just remove this # code? def _get_max_image_bytes(self, ro, rw): ro_size = util.read_kconfig_autoconf_value( ro / "zephyr" / "include" / "generated", "CONFIG_FLASH_SIZE" ) rw_size = util.read_kconfig_autoconf_value( ro / "zephyr" / "include" / "generated", "CONFIG_FLASH_SIZE" ) return max(int(ro_size, 0), int(rw_size, 0)) * 1024 # This can probably be removed too and just rely on binman to # check the sizes... see the comment above. def pack_firmware(self, work_dir, jobclient, ro, rw, version_string=""): for path, output_file in super().pack_firmware( work_dir, jobclient, ro, rw, version_string=version_string, ): if output_file == "zephyr.bin": yield ( self._check_packed_file_size(path, {"ro": ro, "rw": rw}), "zephyr.bin", ) else: yield path, output_file # Include the NPCX monitor file as an output artifact. yield ro / self.npcx_monitor, self.npcx_monitor # A dictionary mapping packer config names to classes. packer_registry = { "binman": BinmanPacker, "elf": ElfPacker, "npcx": NpcxPacker, "raw": RawBinPacker, }