1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tests.firmwaredtbo; 17 18 import com.android.tradefed.device.ITestDevice; 19 import com.android.tradefed.log.LogUtil.CLog; 20 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 21 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 22 import com.android.tradefed.util.CommandResult; 23 import com.android.tradefed.util.CommandStatus; 24 import com.android.tradefed.util.FileUtil; 25 import com.android.tradefed.util.RunUtil; 26 import com.android.tradefed.util.TargetFileUtils; 27 import java.io.BufferedInputStream; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.RandomAccessFile; 33 import java.nio.ByteBuffer; 34 import java.nio.ByteOrder; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.zip.DataFormatException; 38 import java.util.zip.GZIPInputStream; 39 import java.util.zip.Inflater; 40 import org.junit.AfterClass; 41 import org.junit.Assert; 42 import org.junit.Assume; 43 import org.junit.Before; 44 import org.junit.BeforeClass; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 /* Validates DTBO partition and DT overlay application. */ 49 @RunWith(DeviceJUnit4ClassRunner.class) 50 public class FirmwareDtboVerification extends BaseHostJUnit4Test { 51 // Path to platform block devices 52 private static final String BLOCK_DEV_PATH = "/dev/block/platform"; 53 // Temporary dir in device. 54 private static final String DEVICE_TEMP_DIR = "/data/local/tmp/"; 55 // Path to device tree. 56 private static final String FDT_PATH = "/sys/firmware/fdt"; 57 // Indicates current slot suffix for A/B devices. 58 private static final String PROPERTY_SLOT_SUFFIX = "ro.boot.slot_suffix"; 59 // Offset in DT header to read compression info. 60 private static final int DT_HEADER_FLAGS_OFFSET = 16; 61 // Bit mask to get compression format from flags field of DT header for version 1 DTBO header. 62 private static final int COMPRESSION_FLAGS_BIT_MASK = 0x0f; 63 // FDT Magic. 64 private static final int FDT_MAGIC = 0xd00dfeed; 65 // mkdtboimg.py tool name 66 private static final String MKDTBOIMG_TOOL = "mkdtboimg.py"; 67 68 private static File mTemptFolder = null; 69 private ITestDevice mDevice; 70 private String mDeviceTestRoot = null; 71 72 private int NO_COMPRESSION = 0x00; 73 private int ZLIB_COMPRESSION = 0x01; 74 private int GZIP_COMPRESSION = 0x02; 75 76 @BeforeClass oneTimeSetup()77 public static void oneTimeSetup() throws Exception { 78 mTemptFolder = FileUtil.createTempDir("firmware-dtbo-verify"); 79 CLog.d("mTemptFolder: %s", mTemptFolder); 80 } 81 82 @Before setUp()83 public void setUp() throws Exception { 84 Assume.assumeFalse("Skipping test for x86 ABI", getAbi().getName().contains("x86")); 85 mDevice = getDevice(); 86 mDeviceTestRoot = new File(DEVICE_TEMP_DIR, getAbi().getBitness()).getAbsolutePath(); 87 } 88 89 @AfterClass postTestTearDown()90 public static void postTestTearDown() { 91 mTemptFolder.delete(); 92 } 93 94 /* Validates DTBO partition using mkdtboimg.py */ 95 @Test testCheckDTBOPartition()96 public void testCheckDTBOPartition() throws Exception { 97 // Dump dtbo image from device. 98 String slot_suffix = mDevice.getProperty(PROPERTY_SLOT_SUFFIX); 99 if (slot_suffix == null) { 100 slot_suffix = ""; 101 } 102 String currentDtboPartition = "dtbo" + slot_suffix; 103 String[] options = {"-type", "l"}; 104 ArrayList<String> dtboPaths = TargetFileUtils.findFile( 105 BLOCK_DEV_PATH, currentDtboPartition, Arrays.asList(options), mDevice); 106 CLog.d("DTBO path %s", dtboPaths); 107 Assert.assertFalse("Unable to find path to dtbo image on device.", dtboPaths.isEmpty()); 108 File hostDtboImage = new File(mTemptFolder, "dtbo"); 109 Assert.assertTrue("Pull " + dtboPaths.get(0) + " failed!", 110 mDevice.pullFile(dtboPaths.get(0), hostDtboImage)); 111 CLog.d("hostDtboImage is %s", hostDtboImage); 112 // Using mkdtboimg.py to extract dtbo image. 113 File mkdtboimgBin = getTestInformation().getDependencyFile(MKDTBOIMG_TOOL, false); 114 File unpackedDtbo = new File(mTemptFolder, "dumped_dtbo"); 115 RunUtil runUtil = new RunUtil(); 116 String[] cmds = {mkdtboimgBin.getAbsolutePath(), "dump", hostDtboImage.getAbsolutePath(), 117 "-b", unpackedDtbo.getAbsolutePath()}; 118 long timeoutMs = 5 * 60 * 1000; 119 CommandResult result = runUtil.runTimedCmd(timeoutMs, cmds); 120 Assert.assertEquals("Invalid DTBO Image: " + result.getStderr(), CommandStatus.SUCCESS, 121 result.getStatus()); 122 decompressDTEntries(hostDtboImage, unpackedDtbo); 123 } 124 125 /** 126 * Decompresses DT entries based on the flag field in the DT Entry header. 127 * 128 * @param hostDtboImage DTBO image on host. 129 * @param unpackedDtbo DTBO was unpacked. 130 */ decompressDTEntries(File hostDtboImage, File unpackedDtbo)131 private void decompressDTEntries(File hostDtboImage, File unpackedDtbo) 132 throws IOException, DataFormatException { 133 try (RandomAccessFile raf = new RandomAccessFile(hostDtboImage, "r")) { 134 byte[] bytes = new byte[4]; 135 int[] dtboItems = new int[8]; 136 final int DTBO_MAGIC_IDX = 0; 137 final int DTBO_TOTAL_SIZE_IDX = 1; 138 final int DTBO_HEADER_SIZE_IDX = 2; 139 final int DTBO_DT_ENTRY_SIZE_IDX = 3; 140 final int DTBO_DT_ENTRY_COUNT_IDX = 4; 141 final int DTBO_DT_ENTRY_OFFSET_IDX = 5; 142 final int DTBO_PAGE_SIZE_IDX = 6; 143 final int DTBO_VERSION_IDX = 7; 144 for (int i = 0; i < 8; i++) { 145 raf.read(bytes); 146 dtboItems[i] = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); 147 CLog.d("dtboItems[%s] is %s", i, dtboItems[i]); 148 } 149 if (dtboItems[DTBO_VERSION_IDX] > 0) { 150 for (int dt_entry_idx = 0; dt_entry_idx < dtboItems[DTBO_DT_ENTRY_COUNT_IDX]; 151 dt_entry_idx++) { 152 File dt_entry_file = new File( 153 String.format("%s.%s", unpackedDtbo.getAbsolutePath(), dt_entry_idx)); 154 CLog.d("Checking %s", dt_entry_file.getAbsolutePath()); 155 raf.seek(dtboItems[DTBO_DT_ENTRY_OFFSET_IDX] + DT_HEADER_FLAGS_OFFSET); 156 dtboItems[DTBO_DT_ENTRY_OFFSET_IDX] += dtboItems[DTBO_DT_ENTRY_SIZE_IDX]; 157 raf.read(bytes); 158 int flags = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); 159 int compression_format = flags & COMPRESSION_FLAGS_BIT_MASK; 160 CLog.d("compression_format= %s", compression_format); 161 if ((compression_format != ZLIB_COMPRESSION) 162 && (compression_format != GZIP_COMPRESSION)) { 163 Assert.assertEquals( 164 String.format("Unknown compression format %d", compression_format), 165 compression_format, NO_COMPRESSION); 166 } 167 try (InputStream fileInputStream = new FileInputStream(dt_entry_file)) { 168 byte[] cpio_header = new byte[6]; 169 if (compression_format == ZLIB_COMPRESSION) { 170 CLog.d("Decompressing %s with ZLIB_COMPRESSION", 171 dt_entry_file.getAbsolutePath()); 172 byte[] compressedData = new byte[(int) dt_entry_file.length()]; 173 fileInputStream.read(compressedData); 174 Inflater inflater = new Inflater(); 175 inflater.setInput(compressedData); 176 inflater.inflate(cpio_header); 177 } else if (compression_format == GZIP_COMPRESSION) { 178 CLog.d("Decompressing %s with GZIP_COMPRESSION", 179 dt_entry_file.getAbsolutePath()); 180 try (GZIPInputStream gzipStream = new GZIPInputStream( 181 new BufferedInputStream(fileInputStream))) { 182 gzipStream.read(cpio_header); 183 } 184 } else { 185 fileInputStream.read(cpio_header); 186 } 187 int fdt_magic = 188 ByteBuffer.wrap(cpio_header).order(ByteOrder.BIG_ENDIAN).getInt(); 189 CLog.d("fdt_magic: 0x%s", Integer.toHexString(fdt_magic)); 190 Assert.assertEquals( 191 "Bad FDT(Flattened Device Tree) Format", fdt_magic, FDT_MAGIC); 192 } 193 } 194 } 195 } 196 } 197 198 /* Verifies application of DT overlays. */ 199 @Test testVerifyOverlay()200 public void testVerifyOverlay() throws Exception { 201 // testVerifyOverlay depend on testCheckDTBOPartition, check if previous test artifacts 202 // exist, if not force run testCheckDTBOPartition(). 203 File hostDtboImage = new File(mTemptFolder, "dtbo"); 204 if (!hostDtboImage.exists()) { 205 testCheckDTBOPartition(); 206 } 207 String cmd = "cat /proc/cmdline |" 208 + "grep -o \"'androidboot.dtbo_idx=[^ ]*'\" |" 209 + "cut -d \"=\" -f 2 "; 210 CommandResult cmdResult = mDevice.executeShellV2Command(cmd); 211 Assert.assertEquals(String.format("Checking kernel dtbo index: %s", cmdResult.getStderr()), 212 cmdResult.getExitCode().intValue(), 0); 213 String overlay_idx_string = cmdResult.getStdout().replace("\n", ""); 214 CLog.d("overlay_idx_string=%s", overlay_idx_string); 215 Assert.assertNotEquals( 216 "Kernel command line missing androidboot.dtbo_idx", overlay_idx_string.length(), 0); 217 String verificationTestPath = null; 218 String ufdtVerifier = "ufdt_verify_overlay"; 219 ArrayList<String> matchedResults = 220 TargetFileUtils.findFile(DEVICE_TEMP_DIR, ufdtVerifier, null, mDevice); 221 for (String matchedResult : matchedResults) { 222 if (!mDevice.isDirectory(matchedResult)) { 223 verificationTestPath = matchedResult; 224 break; 225 } 226 } 227 String chmodCommand = String.format("chmod 755 %s", verificationTestPath); 228 CLog.d(chmodCommand); 229 cmdResult = mDevice.executeShellV2Command(chmodCommand); 230 Assert.assertEquals("Unable to chmod:" + cmdResult.getStderr(), cmdResult.getStatus(), 231 CommandStatus.SUCCESS); 232 String ufdtVerifierParent = new File(verificationTestPath).getParent(); 233 String remoteFinalDTPath = new File(ufdtVerifierParent, "final_dt").getAbsolutePath(); 234 String copyCommand = String.format("cp %s %s", FDT_PATH, remoteFinalDTPath); 235 CLog.d(copyCommand); 236 cmdResult = mDevice.executeShellV2Command(String.format(copyCommand)); 237 Assert.assertEquals("Unable to copy to " + remoteFinalDTPath, cmdResult.getStatus(), 238 CommandStatus.SUCCESS); 239 ArrayList<String> overlayArg = new ArrayList<>(); 240 for (String overlay_idx : overlay_idx_string.split(",")) { 241 String overlayFileName = "dumped_dtbo." + overlay_idx.replaceAll("\\s+$", ""); 242 File overlayFile = new File(mTemptFolder, overlayFileName); 243 // Push the dumped overlay dtbo files to the same direcly of ufdt_verify_overlay 244 File remoteOverLayFile = new File(ufdtVerifierParent, overlayFileName); 245 CLog.d("Push remoteOverLayFile %s", remoteOverLayFile); 246 if (!mDevice.pushFile(overlayFile, remoteOverLayFile.getAbsolutePath())) { 247 Assert.fail("Push " + overlayFile + "fail!"); 248 } 249 overlayArg.add(overlayFileName); 250 } 251 String verifyCmd = String.format("cd %s && ./ufdt_verify_overlay final_dt %s", 252 ufdtVerifierParent, String.join(" ", overlayArg)); 253 CLog.d(verifyCmd); 254 cmdResult = mDevice.executeShellV2Command(verifyCmd); 255 Assert.assertEquals("Incorrect Overlay Application:" + cmdResult.getStderr(), 256 cmdResult.getStatus(), CommandStatus.SUCCESS); 257 } 258 } 259