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