1#!/usr/bin/env python 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""VTS tests to verify DTBO partition/DT overlay application.""" 18 19import logging 20import os 21import shutil 22import struct 23import subprocess 24import tempfile 25import zlib 26 27from vts.runners.host import asserts 28from vts.runners.host import base_test 29from vts.runners.host import const 30from vts.runners.host import test_runner 31from vts.utils.python.android import api 32from vts.utils.python.file import target_file_utils 33from vts.utils.python.os import path_utils 34 35BLOCK_DEV_PATH = "/dev/block/platform" # path to platform block devices 36DEVICE_TEMP_DIR = "/data/local/tmp/" # temporary dir in device. 37FDT_PATH = "/sys/firmware/fdt" # path to device tree. 38PROPERTY_SLOT_SUFFIX = "ro.boot.slot_suffix" # indicates current slot suffix for A/B devices 39DTBO_HEADER_SIZE = 32 # size of dtbo header 40DT_HEADER_FLAGS_OFFSET = 16 # offset in DT header to read compression info 41COMPRESSION_FLAGS_BIT_MASK = 0x0f # Bit mask to get compression format from flags field of DT 42 # header for version 1 DTBO header. 43DECOMPRESS_WBIT_ARG = 47 # Automatically accepts either the zlib or gzip format. 44FDT_MAGIC = 0xd00dfeed # FDT Magic 45 46 47class CompressionFormat(object): 48 """Enum representing DT compression format for a DT entry. 49 """ 50 NO_COMPRESSION = 0x00 51 ZLIB_COMPRESSION = 0x01 52 GZIP_COMPRESSION = 0x02 53 54 55class VtsFirmwareDtboVerification(base_test.BaseTestClass): 56 """Validates DTBO partition and DT overlay application. 57 58 Attributes: 59 temp_dir: The temporary directory on host. 60 device_path: The temporary directory on device. 61 """ 62 63 def setUpClass(self): 64 """Initializes the DUT and creates temporary directories.""" 65 self.dut = self.android_devices[0] 66 self.shell = self.dut.shell 67 self.adb = self.dut.adb 68 self.temp_dir = tempfile.mkdtemp() 69 logging.info("Create %s", self.temp_dir) 70 self.device_path = str( 71 path_utils.JoinTargetPath(DEVICE_TEMP_DIR, self.abi_bitness)) 72 self.shell.Execute("mkdir %s -p" % self.device_path) 73 74 def setUp(self): 75 """Checks if the the preconditions to run the test are met.""" 76 asserts.skipIf("x86" in self.dut.cpu_abi, "Skipping test for x86 ABI") 77 78 def DecompressDTEntries(self, host_dtbo_image, unpacked_dtbo_path): 79 """Decompresses DT entries based on the flag field in the DT Entry header. 80 81 Args: 82 host_dtbo_image: Path to the DTBO image on host. 83 unpacked_dtbo_path: Path where DTBO was unpacked. 84 """ 85 86 with open(host_dtbo_image, "rb") as file_in: 87 buf = file_in.read(DTBO_HEADER_SIZE) 88 (magic, total_size, header_size, dt_entry_size, dt_entry_count, 89 dt_entries_offset, page_size, version) = struct.unpack_from(">8I", buf, 0) 90 if version > 0: 91 for dt_entry_idx in range(dt_entry_count): 92 file_in.seek(dt_entries_offset + 93 DT_HEADER_FLAGS_OFFSET) 94 dt_entries_offset += dt_entry_size 95 flags = struct.unpack(">I", file_in.read(1 * 4))[0] 96 compression_format = flags & COMPRESSION_FLAGS_BIT_MASK 97 if (compression_format not in [CompressionFormat.ZLIB_COMPRESSION, 98 CompressionFormat.GZIP_COMPRESSION]): 99 asserts.assertEqual(compression_format, CompressionFormat.NO_COMPRESSION, 100 "Unknown compression format %d" % compression_format) 101 continue 102 dt_entry_path = "%s.%s" % (unpacked_dtbo_path, dt_entry_idx) 103 logging.info("decompressing %s", dt_entry_path) 104 with open(dt_entry_path, "rb") as f: 105 unzipped_dtbo_file = zlib.decompress(f.read(), DECOMPRESS_WBIT_ARG) 106 with open(dt_entry_path, "r+b") as f: 107 f.write(unzipped_dtbo_file) 108 f.seek(0) 109 fdt_magic = struct.unpack(">I", f.read(1 * 4))[0] 110 asserts.assertEqual(fdt_magic, FDT_MAGIC, 111 "Bad FDT(Flattened Device Tree) Format") 112 113 def testCheckDTBOPartition(self): 114 """Validates DTBO partition using mkdtboimg.py.""" 115 try: 116 slot_suffix = str(self.dut.getProp(PROPERTY_SLOT_SUFFIX)) 117 except ValueError as e: 118 logging.exception(e) 119 slot_suffix = "" 120 current_dtbo_partition = "dtbo" + slot_suffix 121 dtbo_path = target_file_utils.FindFiles( 122 self.shell, BLOCK_DEV_PATH, current_dtbo_partition, "-type l") 123 logging.info("DTBO path %s", dtbo_path) 124 if not dtbo_path: 125 asserts.fail("Unable to find path to dtbo image on device.") 126 host_dtbo_image = os.path.join(self.temp_dir, "dtbo") 127 self.adb.pull("%s %s" % (dtbo_path[0], host_dtbo_image)) 128 mkdtboimg_bin_path = os.path.join("host", "bin", "mkdtboimg.py") 129 unpacked_dtbo_path = os.path.join(self.temp_dir, "dumped_dtbo") 130 dtbo_dump_cmd = [ 131 "%s" % mkdtboimg_bin_path, "dump", 132 "%s" % host_dtbo_image, "-b", 133 "%s" % unpacked_dtbo_path 134 ] 135 try: 136 subprocess.check_call(dtbo_dump_cmd) 137 except Exception as e: 138 logging.exception(e) 139 logging.error('dtbo_dump_cmd is: %s', dtbo_dump_cmd) 140 asserts.fail("Invalid DTBO Image") 141 # TODO(b/109892148) Delete code below once decompress option is enabled for mkdtboimg.py 142 self.DecompressDTEntries(host_dtbo_image, unpacked_dtbo_path) 143 144 def testVerifyOverlay(self): 145 """Verifies application of DT overlays.""" 146 overlay_idx_string = self.adb.shell( 147 "cat /proc/cmdline | " 148 "grep -o \"androidboot.dtbo_idx=[^ ]*\" |" 149 "cut -d \"=\" -f 2") 150 asserts.assertNotEqual( 151 len(overlay_idx_string), 0, 152 "Kernel command line missing androidboot.dtbo_idx") 153 overlay_idx_list = overlay_idx_string.split(",") 154 overlay_arg = [] 155 for idx in overlay_idx_list: 156 overlay_file = "dumped_dtbo." + idx.rstrip() 157 overlay_path = os.path.join(self.temp_dir, overlay_file) 158 self.adb.push(overlay_path, self.device_path) 159 overlay_arg.append(overlay_file) 160 final_dt_path = path_utils.JoinTargetPath(self.device_path, "final_dt") 161 self.shell.Execute("cp %s %s" % (FDT_PATH, final_dt_path)) 162 verification_test_path = path_utils.JoinTargetPath( 163 self.device_path, "ufdt_verify_overlay") 164 chmod_cmd = "chmod 755 %s" % verification_test_path 165 results = self.shell.Execute(chmod_cmd) 166 asserts.assertEqual(results[const.EXIT_CODE][0], 0, "Unable to chmod") 167 cd_cmd = "cd %s" % (self.device_path) 168 verify_cmd = "./ufdt_verify_overlay final_dt %s" % ( 169 " ".join(overlay_arg)) 170 cmd = str("%s && %s" % (cd_cmd, verify_cmd)) 171 logging.info(cmd) 172 results = self.shell.Execute(cmd) 173 asserts.assertEqual(results[const.EXIT_CODE][0], 0, 174 "Incorrect Overlay Application") 175 176 def tearDownClass(self): 177 """Deletes temporary directories.""" 178 shutil.rmtree(self.temp_dir) 179 self.shell.Execute("rm -rf %s" % self.device_path) 180 181 182if __name__ == "__main__": 183 test_runner.main() 184