1 | /* Dynamic linker/loader of ACPI tables |
2 | * |
3 | * Copyright (C) 2013 Red Hat Inc |
4 | * |
5 | * Author: Michael S. Tsirkin <mst@redhat.com> |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | |
12 | * This program is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. |
16 | |
17 | * You should have received a copy of the GNU General Public License along |
18 | * with this program; if not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include "qemu/osdep.h" |
22 | #include "hw/acpi/bios-linker-loader.h" |
23 | #include "hw/nvram/fw_cfg.h" |
24 | |
25 | #include "qemu/bswap.h" |
26 | |
27 | /* |
28 | * Linker/loader is a paravirtualized interface that passes commands to guest. |
29 | * The commands can be used to request guest to |
30 | * - allocate memory chunks and initialize them from QEMU FW CFG files |
31 | * - link allocated chunks by storing pointer to one chunk into another |
32 | * - calculate ACPI checksum of part of the chunk and store into same chunk |
33 | */ |
34 | #define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH |
35 | |
36 | struct BiosLinkerLoaderEntry { |
37 | uint32_t command; |
38 | union { |
39 | /* |
40 | * COMMAND_ALLOCATE - allocate a table from @alloc.file |
41 | * subject to @alloc.align alignment (must be power of 2) |
42 | * and @alloc.zone (can be HIGH or FSEG) requirements. |
43 | * |
44 | * Must appear exactly once for each file, and before |
45 | * this file is referenced by any other command. |
46 | */ |
47 | struct { |
48 | char file[BIOS_LINKER_LOADER_FILESZ]; |
49 | uint32_t align; |
50 | uint8_t zone; |
51 | } alloc; |
52 | |
53 | /* |
54 | * COMMAND_ADD_POINTER - patch the table (originating from |
55 | * @dest_file) at @pointer.offset, by adding a pointer to the table |
56 | * originating from @src_file. 1,2,4 or 8 byte unsigned |
57 | * addition is used depending on @pointer.size. |
58 | */ |
59 | struct { |
60 | char dest_file[BIOS_LINKER_LOADER_FILESZ]; |
61 | char src_file[BIOS_LINKER_LOADER_FILESZ]; |
62 | uint32_t offset; |
63 | uint8_t size; |
64 | } pointer; |
65 | |
66 | /* |
67 | * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by |
68 | * @cksum_start and @cksum_length fields, |
69 | * and then add the value at @cksum.offset. |
70 | * Checksum simply sums -X for each byte X in the range |
71 | * using 8-bit math. |
72 | */ |
73 | struct { |
74 | char file[BIOS_LINKER_LOADER_FILESZ]; |
75 | uint32_t offset; |
76 | uint32_t start; |
77 | uint32_t length; |
78 | } cksum; |
79 | |
80 | /* |
81 | * COMMAND_WRITE_POINTER - write the fw_cfg file (originating from |
82 | * @dest_file) at @wr_pointer.offset, by adding a pointer to |
83 | * @src_offset within the table originating from @src_file. |
84 | * 1,2,4 or 8 byte unsigned addition is used depending on |
85 | * @wr_pointer.size. |
86 | */ |
87 | struct { |
88 | char dest_file[BIOS_LINKER_LOADER_FILESZ]; |
89 | char src_file[BIOS_LINKER_LOADER_FILESZ]; |
90 | uint32_t dst_offset; |
91 | uint32_t src_offset; |
92 | uint8_t size; |
93 | } wr_pointer; |
94 | |
95 | /* padding */ |
96 | char pad[124]; |
97 | }; |
98 | } QEMU_PACKED; |
99 | typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry; |
100 | |
101 | enum { |
102 | BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, |
103 | BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, |
104 | BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, |
105 | BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER = 0x4, |
106 | }; |
107 | |
108 | enum { |
109 | BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, |
110 | BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, |
111 | }; |
112 | |
113 | /* |
114 | * BiosLinkerFileEntry: |
115 | * |
116 | * An internal type used for book-keeping file entries |
117 | */ |
118 | typedef struct BiosLinkerFileEntry { |
119 | char *name; /* file name */ |
120 | GArray *blob; /* data accosiated with @name */ |
121 | } BiosLinkerFileEntry; |
122 | |
123 | /* |
124 | * bios_linker_loader_init: allocate a new linker object instance. |
125 | * |
126 | * After initialization, linker commands can be added, and will |
127 | * be stored in the linker.cmd_blob array. |
128 | */ |
129 | BIOSLinker *bios_linker_loader_init(void) |
130 | { |
131 | BIOSLinker *linker = g_new(BIOSLinker, 1); |
132 | |
133 | linker->cmd_blob = g_array_new(false, true /* clear */, 1); |
134 | linker->file_list = g_array_new(false, true /* clear */, |
135 | sizeof(BiosLinkerFileEntry)); |
136 | return linker; |
137 | } |
138 | |
139 | /* Free linker wrapper */ |
140 | void bios_linker_loader_cleanup(BIOSLinker *linker) |
141 | { |
142 | int i; |
143 | BiosLinkerFileEntry *entry; |
144 | |
145 | g_array_free(linker->cmd_blob, true); |
146 | |
147 | for (i = 0; i < linker->file_list->len; i++) { |
148 | entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i); |
149 | g_free(entry->name); |
150 | } |
151 | g_array_free(linker->file_list, true); |
152 | g_free(linker); |
153 | } |
154 | |
155 | static const BiosLinkerFileEntry * |
156 | bios_linker_find_file(const BIOSLinker *linker, const char *name) |
157 | { |
158 | int i; |
159 | BiosLinkerFileEntry *entry; |
160 | |
161 | for (i = 0; i < linker->file_list->len; i++) { |
162 | entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i); |
163 | if (!strcmp(entry->name, name)) { |
164 | return entry; |
165 | } |
166 | } |
167 | return NULL; |
168 | } |
169 | |
170 | /* |
171 | * board code must realize fw_cfg first, as a fixed device, before |
172 | * another device realize function call bios_linker_loader_can_write_pointer() |
173 | */ |
174 | bool bios_linker_loader_can_write_pointer(void) |
175 | { |
176 | FWCfgState *fw_cfg = fw_cfg_find(); |
177 | return fw_cfg && fw_cfg_dma_enabled(fw_cfg); |
178 | } |
179 | |
180 | /* |
181 | * bios_linker_loader_alloc: ask guest to load file into guest memory. |
182 | * |
183 | * @linker: linker object instance |
184 | * @file_name: name of the file blob to be loaded |
185 | * @file_blob: pointer to blob corresponding to @file_name |
186 | * @alloc_align: required minimal alignment in bytes. Must be a power of 2. |
187 | * @alloc_fseg: request allocation in FSEG zone (useful for the RSDP ACPI table) |
188 | * |
189 | * Note: this command must precede any other linker command using this file. |
190 | */ |
191 | void bios_linker_loader_alloc(BIOSLinker *linker, |
192 | const char *file_name, |
193 | GArray *file_blob, |
194 | uint32_t alloc_align, |
195 | bool alloc_fseg) |
196 | { |
197 | BiosLinkerLoaderEntry entry; |
198 | BiosLinkerFileEntry file = { g_strdup(file_name), file_blob}; |
199 | |
200 | assert(!(alloc_align & (alloc_align - 1))); |
201 | |
202 | assert(!bios_linker_find_file(linker, file_name)); |
203 | g_array_append_val(linker->file_list, file); |
204 | |
205 | memset(&entry, 0, sizeof entry); |
206 | strncpy(entry.alloc.file, file_name, sizeof entry.alloc.file - 1); |
207 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE); |
208 | entry.alloc.align = cpu_to_le32(alloc_align); |
209 | entry.alloc.zone = alloc_fseg ? BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG : |
210 | BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH; |
211 | |
212 | /* Alloc entries must come first, so prepend them */ |
213 | g_array_prepend_vals(linker->cmd_blob, &entry, sizeof entry); |
214 | } |
215 | |
216 | /* |
217 | * bios_linker_loader_add_checksum: ask guest to add checksum of ACPI |
218 | * table in the specified file at the specified offset. |
219 | * |
220 | * Checksum calculation simply sums -X for each byte X in the range |
221 | * using 8-bit math (i.e. ACPI checksum). |
222 | * |
223 | * @linker: linker object instance |
224 | * @file: file that includes the checksum to be calculated |
225 | * and the data to be checksummed |
226 | * @start_offset, @size: range of data in the file to checksum, |
227 | * relative to the start of file blob |
228 | * @checksum_offset: location of the checksum to be patched within file blob, |
229 | * relative to the start of file blob |
230 | */ |
231 | void bios_linker_loader_add_checksum(BIOSLinker *linker, const char *file_name, |
232 | unsigned start_offset, unsigned size, |
233 | unsigned checksum_offset) |
234 | { |
235 | BiosLinkerLoaderEntry entry; |
236 | const BiosLinkerFileEntry *file = bios_linker_find_file(linker, file_name); |
237 | |
238 | assert(file); |
239 | assert(start_offset < file->blob->len); |
240 | assert(start_offset + size <= file->blob->len); |
241 | assert(checksum_offset >= start_offset); |
242 | assert(checksum_offset + 1 <= start_offset + size); |
243 | |
244 | *(file->blob->data + checksum_offset) = 0; |
245 | memset(&entry, 0, sizeof entry); |
246 | strncpy(entry.cksum.file, file_name, sizeof entry.cksum.file - 1); |
247 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM); |
248 | entry.cksum.offset = cpu_to_le32(checksum_offset); |
249 | entry.cksum.start = cpu_to_le32(start_offset); |
250 | entry.cksum.length = cpu_to_le32(size); |
251 | |
252 | g_array_append_vals(linker->cmd_blob, &entry, sizeof entry); |
253 | } |
254 | |
255 | /* |
256 | * bios_linker_loader_add_pointer: ask guest to patch address in |
257 | * destination file with a pointer to source file |
258 | * |
259 | * @linker: linker object instance |
260 | * @dest_file: destination file that must be changed |
261 | * @dst_patched_offset: location within destination file blob to be patched |
262 | * with the pointer to @src_file+@src_offset (i.e. source |
263 | * blob allocated in guest memory + @src_offset), in bytes |
264 | * @dst_patched_offset_size: size of the pointer to be patched |
265 | * at @dst_patched_offset in @dest_file blob, in bytes |
266 | * @src_file: source file who's address must be taken |
267 | * @src_offset: location within source file blob to which |
268 | * @dest_file+@dst_patched_offset will point to after |
269 | * firmware's executed ADD_POINTER command |
270 | */ |
271 | void bios_linker_loader_add_pointer(BIOSLinker *linker, |
272 | const char *dest_file, |
273 | uint32_t dst_patched_offset, |
274 | uint8_t dst_patched_size, |
275 | const char *src_file, |
276 | uint32_t src_offset) |
277 | { |
278 | uint64_t le_src_offset; |
279 | BiosLinkerLoaderEntry entry; |
280 | const BiosLinkerFileEntry *dst_file = |
281 | bios_linker_find_file(linker, dest_file); |
282 | const BiosLinkerFileEntry *source_file = |
283 | bios_linker_find_file(linker, src_file); |
284 | |
285 | assert(dst_file); |
286 | assert(source_file); |
287 | assert(dst_patched_offset < dst_file->blob->len); |
288 | assert(dst_patched_offset + dst_patched_size <= dst_file->blob->len); |
289 | assert(src_offset < source_file->blob->len); |
290 | |
291 | memset(&entry, 0, sizeof entry); |
292 | strncpy(entry.pointer.dest_file, dest_file, |
293 | sizeof entry.pointer.dest_file - 1); |
294 | strncpy(entry.pointer.src_file, src_file, |
295 | sizeof entry.pointer.src_file - 1); |
296 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER); |
297 | entry.pointer.offset = cpu_to_le32(dst_patched_offset); |
298 | entry.pointer.size = dst_patched_size; |
299 | assert(dst_patched_size == 1 || dst_patched_size == 2 || |
300 | dst_patched_size == 4 || dst_patched_size == 8); |
301 | |
302 | le_src_offset = cpu_to_le64(src_offset); |
303 | memcpy(dst_file->blob->data + dst_patched_offset, |
304 | &le_src_offset, dst_patched_size); |
305 | |
306 | g_array_append_vals(linker->cmd_blob, &entry, sizeof entry); |
307 | } |
308 | |
309 | /* |
310 | * bios_linker_loader_write_pointer: ask guest to write a pointer to the |
311 | * source file into the destination file, and write it back to QEMU via |
312 | * fw_cfg DMA. |
313 | * |
314 | * @linker: linker object instance |
315 | * @dest_file: destination file that must be written |
316 | * @dst_patched_offset: location within destination file blob to be patched |
317 | * with the pointer to @src_file, in bytes |
318 | * @dst_patched_offset_size: size of the pointer to be patched |
319 | * at @dst_patched_offset in @dest_file blob, in bytes |
320 | * @src_file: source file who's address must be taken |
321 | * @src_offset: location within source file blob to which |
322 | * @dest_file+@dst_patched_offset will point to after |
323 | * firmware's executed WRITE_POINTER command |
324 | */ |
325 | void bios_linker_loader_write_pointer(BIOSLinker *linker, |
326 | const char *dest_file, |
327 | uint32_t dst_patched_offset, |
328 | uint8_t dst_patched_size, |
329 | const char *src_file, |
330 | uint32_t src_offset) |
331 | { |
332 | BiosLinkerLoaderEntry entry; |
333 | const BiosLinkerFileEntry *source_file = |
334 | bios_linker_find_file(linker, src_file); |
335 | |
336 | assert(source_file); |
337 | assert(src_offset < source_file->blob->len); |
338 | memset(&entry, 0, sizeof entry); |
339 | strncpy(entry.wr_pointer.dest_file, dest_file, |
340 | sizeof entry.wr_pointer.dest_file - 1); |
341 | strncpy(entry.wr_pointer.src_file, src_file, |
342 | sizeof entry.wr_pointer.src_file - 1); |
343 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER); |
344 | entry.wr_pointer.dst_offset = cpu_to_le32(dst_patched_offset); |
345 | entry.wr_pointer.src_offset = cpu_to_le32(src_offset); |
346 | entry.wr_pointer.size = dst_patched_size; |
347 | assert(dst_patched_size == 1 || dst_patched_size == 2 || |
348 | dst_patched_size == 4 || dst_patched_size == 8); |
349 | |
350 | g_array_append_vals(linker->cmd_blob, &entry, sizeof entry); |
351 | } |
352 | |