1 /* 2 * Copyright 2023 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 17 package com.android.pvmfw.test.host; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static java.nio.ByteOrder.LITTLE_ENDIAN; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.util.Objects; 31 import java.nio.ByteBuffer; 32 33 /** pvmfw.bin with custom config payloads on host. */ 34 public final class Pvmfw { 35 private static final int SIZE_8B = 8; // 8 bytes 36 private static final int SIZE_4K = 4 << 10; // 4 KiB, PAGE_SIZE 37 private static final int BUFFER_SIZE = 1024; 38 private static final int HEADER_MAGIC = 0x666d7670; 39 private static final int HEADER_DEFAULT_VERSION = makeVersion(1, 2); 40 private static final int HEADER_FLAGS = 0; 41 42 private static final int PVMFW_ENTRY_BCC = 0; 43 private static final int PVMFW_ENTRY_DP = 1; 44 private static final int PVMFW_ENTRY_VM_DTBO = 2; 45 private static final int PVMFW_ENTRY_VM_REFERENCE_DT = 3; 46 private static final int PVMFW_ENTRY_MAX = 4; 47 48 @NonNull private final File mPvmfwBinFile; 49 private final File[] mEntries; 50 private final int mEntryCnt; 51 private final int mVersion; 52 makeVersion(int major, int minor)53 public static int makeVersion(int major, int minor) { 54 return ((major & 0xFFFF) << 16) | (minor & 0xFFFF); 55 } 56 Pvmfw( @onNull File pvmfwBinFile, @NonNull File bccFile, @Nullable File debugPolicyFile, @Nullable File vmDtboFile, @Nullable File vmReferenceDtFile, int version)57 private Pvmfw( 58 @NonNull File pvmfwBinFile, 59 @NonNull File bccFile, 60 @Nullable File debugPolicyFile, 61 @Nullable File vmDtboFile, 62 @Nullable File vmReferenceDtFile, 63 int version) { 64 mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile); 65 66 if (version >= makeVersion(1, 2)) { 67 mEntryCnt = PVMFW_ENTRY_VM_REFERENCE_DT + 1; 68 } else if (version >= makeVersion(1, 1)) { 69 mEntryCnt = PVMFW_ENTRY_VM_DTBO + 1; 70 } else { 71 mEntryCnt = PVMFW_ENTRY_DP + 1; 72 } 73 74 mEntries = new File[PVMFW_ENTRY_MAX]; 75 mEntries[PVMFW_ENTRY_BCC] = Objects.requireNonNull(bccFile); 76 mEntries[PVMFW_ENTRY_DP] = debugPolicyFile; 77 78 if (PVMFW_ENTRY_VM_DTBO < mEntryCnt) { 79 mEntries[PVMFW_ENTRY_VM_DTBO] = vmDtboFile; 80 } 81 if (PVMFW_ENTRY_VM_REFERENCE_DT < mEntryCnt) { 82 mEntries[PVMFW_ENTRY_VM_REFERENCE_DT] = Objects.requireNonNull(vmReferenceDtFile); 83 } 84 85 mVersion = version; 86 } 87 88 /** 89 * Serializes pvmfw.bin and its config, as written in the <a 90 * href="https://android.googlesource.com/platform/packages/modules/Virtualization/+/master/pvmfw/README.md">README.md</a> 91 */ serialize(@onNull File outFile)92 public void serialize(@NonNull File outFile) throws IOException { 93 Objects.requireNonNull(outFile); 94 95 int headerSize = alignTo(getHeaderSize(), SIZE_8B); 96 int[] entryOffsets = new int[mEntryCnt]; 97 int[] entrySizes = new int[mEntryCnt]; 98 99 entryOffsets[PVMFW_ENTRY_BCC] = headerSize; 100 entrySizes[PVMFW_ENTRY_BCC] = (int) mEntries[PVMFW_ENTRY_BCC].length(); 101 102 for (int i = 1; i < mEntryCnt; i++) { 103 entryOffsets[i] = alignTo(entryOffsets[i - 1] + entrySizes[i - 1], SIZE_8B); 104 entrySizes[i] = mEntries[i] == null ? 0 : (int) mEntries[i].length(); 105 } 106 107 int totalSize = alignTo(entryOffsets[mEntryCnt - 1] + entrySizes[mEntryCnt - 1], SIZE_8B); 108 109 ByteBuffer header = ByteBuffer.allocate(headerSize).order(LITTLE_ENDIAN); 110 header.putInt(HEADER_MAGIC); 111 header.putInt(mVersion); 112 header.putInt(totalSize); 113 header.putInt(HEADER_FLAGS); 114 for (int i = 0; i < mEntryCnt; i++) { 115 header.putInt(entryOffsets[i]); 116 header.putInt(entrySizes[i]); 117 } 118 119 try (FileOutputStream pvmfw = new FileOutputStream(outFile)) { 120 appendFile(pvmfw, mPvmfwBinFile); 121 padTo(pvmfw, SIZE_4K); 122 123 int baseOffset = (int) pvmfw.getChannel().size(); 124 pvmfw.write(header.array()); 125 126 for (int i = 0; i < mEntryCnt; i++) { 127 padTo(pvmfw, SIZE_8B); 128 if (mEntries[i] != null) { 129 assertThat((int) pvmfw.getChannel().size() - baseOffset) 130 .isEqualTo(entryOffsets[i]); 131 appendFile(pvmfw, mEntries[i]); 132 } 133 } 134 135 padTo(pvmfw, SIZE_4K); 136 } 137 } 138 appendFile(@onNull FileOutputStream out, @NonNull File inFile)139 private void appendFile(@NonNull FileOutputStream out, @NonNull File inFile) 140 throws IOException { 141 try (FileInputStream in = new FileInputStream(inFile)) { 142 in.transferTo(out); 143 } 144 } 145 padTo(@onNull FileOutputStream out, int size)146 private void padTo(@NonNull FileOutputStream out, int size) throws IOException { 147 int streamSize = (int) out.getChannel().size(); 148 for (int i = streamSize; i < alignTo(streamSize, size); i++) { 149 out.write(0); // write byte. 150 } 151 } 152 getHeaderSize()153 private int getHeaderSize() { 154 // Header + (entry offset, entry, size) * mEntryCnt 155 return Integer.BYTES * (4 + mEntryCnt * 2); 156 } 157 alignTo(int x, int size)158 private static int alignTo(int x, int size) { 159 return (x + size - 1) & ~(size - 1); 160 } 161 getMajorVersion(int version)162 private static int getMajorVersion(int version) { 163 return (version >> 16) & 0xFFFF; 164 } 165 getMinorVersion(int version)166 private static int getMinorVersion(int version) { 167 return version & 0xFFFF; 168 } 169 170 /** Builder for {@link Pvmfw}. */ 171 public static final class Builder { 172 @NonNull private final File mPvmfwBinFile; 173 @NonNull private final File mBccFile; 174 @Nullable private File mDebugPolicyFile; 175 @Nullable private File mVmDtboFile; 176 @Nullable private File mVmReferenceDtFile; 177 private int mVersion; 178 Builder(@onNull File pvmfwBinFile, @NonNull File bccFile)179 public Builder(@NonNull File pvmfwBinFile, @NonNull File bccFile) { 180 mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile); 181 mBccFile = Objects.requireNonNull(bccFile); 182 mVersion = HEADER_DEFAULT_VERSION; 183 } 184 185 @NonNull setDebugPolicyOverlay(@ullable File debugPolicyFile)186 public Builder setDebugPolicyOverlay(@Nullable File debugPolicyFile) { 187 mDebugPolicyFile = debugPolicyFile; 188 return this; 189 } 190 191 @NonNull setVmDtbo(@ullable File vmDtboFile)192 public Builder setVmDtbo(@Nullable File vmDtboFile) { 193 mVmDtboFile = vmDtboFile; 194 return this; 195 } 196 197 @NonNull setVmReferenceDt(@ullable File vmReferenceDtFile)198 public Builder setVmReferenceDt(@Nullable File vmReferenceDtFile) { 199 mVmReferenceDtFile = vmReferenceDtFile; 200 return this; 201 } 202 203 @NonNull setVersion(int major, int minor)204 public Builder setVersion(int major, int minor) { 205 mVersion = makeVersion(major, minor); 206 return this; 207 } 208 209 @NonNull build()210 public Pvmfw build() { 211 return new Pvmfw( 212 mPvmfwBinFile, 213 mBccFile, 214 mDebugPolicyFile, 215 mVmDtboFile, 216 mVmReferenceDtFile, 217 mVersion); 218 } 219 } 220 } 221