]>
Commit | Line | Data |
---|---|---|
bc702329 MT |
1 | /* Dynamic linker/loader of ACPI tables |
2 | * | |
3 | * Copyright (C) 2013 Red Hat Inc | |
4 | * | |
5 | * Author: Michael S. Tsirkin <[email protected]> | |
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 | ||
b6a0aa05 | 21 | #include "qemu/osdep.h" |
c428c5a2 | 22 | #include "qemu-common.h" |
0058ae1d | 23 | #include "hw/acpi/bios-linker-loader.h" |
bc702329 MT |
24 | #include "hw/nvram/fw_cfg.h" |
25 | ||
bc702329 MT |
26 | #include "qemu/bswap.h" |
27 | ||
b54ca0c3 MT |
28 | /* |
29 | * Linker/loader is a paravirtualized interface that passes commands to guest. | |
30 | * The commands can be used to request guest to | |
31 | * - allocate memory chunks and initialize them from QEMU FW CFG files | |
32 | * - link allocated chunks by storing pointer to one chunk into another | |
33 | * - calculate ACPI checksum of part of the chunk and store into same chunk | |
34 | */ | |
bc702329 MT |
35 | #define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH |
36 | ||
37 | struct BiosLinkerLoaderEntry { | |
38 | uint32_t command; | |
39 | union { | |
40 | /* | |
41 | * COMMAND_ALLOCATE - allocate a table from @alloc.file | |
42 | * subject to @alloc.align alignment (must be power of 2) | |
43 | * and @alloc.zone (can be HIGH or FSEG) requirements. | |
44 | * | |
45 | * Must appear exactly once for each file, and before | |
46 | * this file is referenced by any other command. | |
47 | */ | |
48 | struct { | |
49 | char file[BIOS_LINKER_LOADER_FILESZ]; | |
50 | uint32_t align; | |
51 | uint8_t zone; | |
52 | } alloc; | |
53 | ||
54 | /* | |
55 | * COMMAND_ADD_POINTER - patch the table (originating from | |
56 | * @dest_file) at @pointer.offset, by adding a pointer to the table | |
57 | * originating from @src_file. 1,2,4 or 8 byte unsigned | |
58 | * addition is used depending on @pointer.size. | |
59 | */ | |
60 | struct { | |
61 | char dest_file[BIOS_LINKER_LOADER_FILESZ]; | |
62 | char src_file[BIOS_LINKER_LOADER_FILESZ]; | |
63 | uint32_t offset; | |
64 | uint8_t size; | |
65 | } pointer; | |
66 | ||
67 | /* | |
68 | * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by | |
69 | * @cksum_start and @cksum_length fields, | |
70 | * and then add the value at @cksum.offset. | |
71 | * Checksum simply sums -X for each byte X in the range | |
72 | * using 8-bit math. | |
73 | */ | |
74 | struct { | |
75 | char file[BIOS_LINKER_LOADER_FILESZ]; | |
76 | uint32_t offset; | |
77 | uint32_t start; | |
78 | uint32_t length; | |
79 | } cksum; | |
80 | ||
81 | /* padding */ | |
82 | char pad[124]; | |
83 | }; | |
84 | } QEMU_PACKED; | |
85 | typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry; | |
86 | ||
87 | enum { | |
88 | BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, | |
89 | BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, | |
90 | BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, | |
91 | }; | |
92 | ||
93 | enum { | |
94 | BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, | |
95 | BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, | |
96 | }; | |
97 | ||
b54ca0c3 MT |
98 | /* |
99 | * bios_linker_loader_init: allocate a new linker file blob array. | |
100 | * | |
101 | * After initialization, linker commands can be added, and will | |
102 | * be stored in the array. | |
103 | */ | |
bc702329 MT |
104 | GArray *bios_linker_loader_init(void) |
105 | { | |
b15654c2 | 106 | return g_array_new(false, true /* clear */, 1); |
bc702329 MT |
107 | } |
108 | ||
109 | /* Free linker wrapper and return the linker array. */ | |
110 | void *bios_linker_loader_cleanup(GArray *linker) | |
111 | { | |
112 | return g_array_free(linker, false); | |
113 | } | |
114 | ||
b54ca0c3 MT |
115 | /* |
116 | * bios_linker_loader_alloc: ask guest to load file into guest memory. | |
117 | * | |
118 | * @linker: linker file blob array | |
119 | * @file: file to be loaded | |
120 | * @alloc_align: required minimal alignment in bytes. Must be a power of 2. | |
121 | * @alloc_fseg: request allocation in FSEG zone (useful for the RSDP ACPI table) | |
122 | * | |
123 | * Note: this command must precede any other linker command using this file. | |
124 | */ | |
bc702329 MT |
125 | void bios_linker_loader_alloc(GArray *linker, |
126 | const char *file, | |
127 | uint32_t alloc_align, | |
128 | bool alloc_fseg) | |
129 | { | |
130 | BiosLinkerLoaderEntry entry; | |
131 | ||
b54ca0c3 MT |
132 | assert(!(alloc_align & (alloc_align - 1))); |
133 | ||
bc702329 MT |
134 | memset(&entry, 0, sizeof entry); |
135 | strncpy(entry.alloc.file, file, sizeof entry.alloc.file - 1); | |
136 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE); | |
137 | entry.alloc.align = cpu_to_le32(alloc_align); | |
138 | entry.alloc.zone = cpu_to_le32(alloc_fseg ? | |
139 | BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG : | |
140 | BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH); | |
141 | ||
142 | /* Alloc entries must come first, so prepend them */ | |
b15654c2 | 143 | g_array_prepend_vals(linker, &entry, sizeof entry); |
bc702329 MT |
144 | } |
145 | ||
b54ca0c3 MT |
146 | /* |
147 | * bios_linker_loader_add_checksum: ask guest to add checksum of file data | |
148 | * into (same) file at the specified pointer. | |
149 | * | |
150 | * Checksum calculation simply sums -X for each byte X in the range | |
151 | * using 8-bit math (i.e. ACPI checksum). | |
152 | * | |
153 | * @linker: linker file blob array | |
154 | * @file: file that includes the checksum to be calculated | |
155 | * and the data to be checksummed | |
156 | * @table: @file blob contents | |
157 | * @start, @size: range of data to checksum | |
158 | * @checksum: location of the checksum to be patched within file blob | |
159 | * | |
160 | * Notes: | |
161 | * - checksum byte initial value must have been pushed into @table | |
162 | * and reside at address @checksum. | |
163 | * - @size bytes must have been pushed into @table and reside at address | |
164 | * @start. | |
165 | * - Guest calculates checksum of specified range of data, result is added to | |
166 | * initial value at @checksum into copy of @file in Guest memory. | |
167 | * - Range might include the checksum itself. | |
168 | * - To avoid confusion, caller must always put 0x0 at @checksum. | |
169 | * - @file must be loaded into Guest memory using bios_linker_loader_alloc | |
170 | */ | |
bc702329 | 171 | void bios_linker_loader_add_checksum(GArray *linker, const char *file, |
b54ca0c3 | 172 | GArray *table, |
bc702329 MT |
173 | void *start, unsigned size, |
174 | uint8_t *checksum) | |
175 | { | |
176 | BiosLinkerLoaderEntry entry; | |
b54ca0c3 MT |
177 | ptrdiff_t checksum_offset = (gchar *)checksum - table->data; |
178 | ptrdiff_t start_offset = (gchar *)start - table->data; | |
179 | ||
180 | assert(checksum_offset >= 0); | |
181 | assert(start_offset >= 0); | |
182 | assert(checksum_offset + 1 <= table->len); | |
183 | assert(start_offset + size <= table->len); | |
184 | assert(*checksum == 0x0); | |
bc702329 MT |
185 | |
186 | memset(&entry, 0, sizeof entry); | |
187 | strncpy(entry.cksum.file, file, sizeof entry.cksum.file - 1); | |
188 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM); | |
b54ca0c3 MT |
189 | entry.cksum.offset = cpu_to_le32(checksum_offset); |
190 | entry.cksum.start = cpu_to_le32(start_offset); | |
bc702329 MT |
191 | entry.cksum.length = cpu_to_le32(size); |
192 | ||
b15654c2 | 193 | g_array_append_vals(linker, &entry, sizeof entry); |
bc702329 MT |
194 | } |
195 | ||
b54ca0c3 MT |
196 | /* |
197 | * bios_linker_loader_add_pointer: ask guest to add address of source file | |
198 | * into destination file at the specified pointer. | |
199 | * | |
200 | * @linker: linker file blob array | |
201 | * @dest_file: destination file that must be changed | |
202 | * @src_file: source file who's address must be taken | |
203 | * @table: @dest_file blob contents array | |
204 | * @pointer: location of the pointer to be patched within destination file blob | |
205 | * @pointer_size: size of pointer to be patched, in bytes | |
206 | * | |
207 | * Notes: | |
208 | * - @pointer_size bytes must have been pushed into @table | |
209 | * and reside at address @pointer. | |
210 | * - Guest address is added to initial value at @pointer | |
211 | * into copy of @dest_file in Guest memory. | |
212 | * e.g. to get start of src_file in guest memory, put 0x0 there | |
213 | * to get address of a field at offset 0x10 in src_file, put 0x10 there | |
214 | * - Both @dest_file and @src_file must be | |
215 | * loaded into Guest memory using bios_linker_loader_alloc | |
216 | */ | |
bc702329 MT |
217 | void bios_linker_loader_add_pointer(GArray *linker, |
218 | const char *dest_file, | |
219 | const char *src_file, | |
220 | GArray *table, void *pointer, | |
221 | uint8_t pointer_size) | |
222 | { | |
223 | BiosLinkerLoaderEntry entry; | |
b54ca0c3 MT |
224 | ptrdiff_t offset = (gchar *)pointer - table->data; |
225 | ||
226 | assert(offset >= 0); | |
227 | assert(offset + pointer_size <= table->len); | |
bc702329 MT |
228 | |
229 | memset(&entry, 0, sizeof entry); | |
230 | strncpy(entry.pointer.dest_file, dest_file, | |
231 | sizeof entry.pointer.dest_file - 1); | |
232 | strncpy(entry.pointer.src_file, src_file, | |
233 | sizeof entry.pointer.src_file - 1); | |
234 | entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER); | |
12e63900 | 235 | entry.pointer.offset = cpu_to_le32(offset); |
bc702329 MT |
236 | entry.pointer.size = pointer_size; |
237 | assert(pointer_size == 1 || pointer_size == 2 || | |
238 | pointer_size == 4 || pointer_size == 8); | |
239 | ||
b15654c2 | 240 | g_array_append_vals(linker, &entry, sizeof entry); |
bc702329 | 241 | } |