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