]>
Commit | Line | Data |
---|---|---|
b6f6e3d3 AL |
1 | /* |
2 | * SMBIOS Support | |
3 | * | |
4 | * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. | |
4f953d2f | 5 | * Copyright (C) 2013 Red Hat, Inc. |
b6f6e3d3 AL |
6 | * |
7 | * Authors: | |
8 | * Alex Williamson <alex.williamson@hp.com> | |
4f953d2f | 9 | * Markus Armbruster <armbru@redhat.com> |
b6f6e3d3 AL |
10 | * |
11 | * This work is licensed under the terms of the GNU GPL, version 2. See | |
12 | * the COPYING file in the top-level directory. | |
13 | * | |
6b620ca3 PB |
14 | * Contributions after 2012-01-13 are licensed under the terms of the |
15 | * GNU GPL, version 2 or (at your option) any later version. | |
b6f6e3d3 AL |
16 | */ |
17 | ||
4f953d2f | 18 | #include "qemu/config-file.h" |
5bb95e41 | 19 | #include "qemu/error-report.h" |
9c17d615 | 20 | #include "sysemu/sysemu.h" |
0d09e41a | 21 | #include "hw/i386/smbios.h" |
83c9f4ca | 22 | #include "hw/loader.h" |
b6f6e3d3 AL |
23 | |
24 | /* | |
25 | * Structures shared with the BIOS | |
26 | */ | |
27 | struct smbios_header { | |
28 | uint16_t length; | |
29 | uint8_t type; | |
541dc0d4 | 30 | } QEMU_PACKED; |
b6f6e3d3 AL |
31 | |
32 | struct smbios_field { | |
33 | struct smbios_header header; | |
34 | uint8_t type; | |
35 | uint16_t offset; | |
36 | uint8_t data[]; | |
541dc0d4 | 37 | } QEMU_PACKED; |
b6f6e3d3 AL |
38 | |
39 | struct smbios_table { | |
40 | struct smbios_header header; | |
41 | uint8_t data[]; | |
541dc0d4 | 42 | } QEMU_PACKED; |
b6f6e3d3 AL |
43 | |
44 | #define SMBIOS_FIELD_ENTRY 0 | |
45 | #define SMBIOS_TABLE_ENTRY 1 | |
46 | ||
b6f6e3d3 AL |
47 | static uint8_t *smbios_entries; |
48 | static size_t smbios_entries_len; | |
09c0848e | 49 | static int smbios_type4_count = 0; |
fc3b3295 | 50 | static bool smbios_immutable; |
09c0848e | 51 | |
ec2df8c1 MA |
52 | static struct { |
53 | bool seen; | |
54 | int headertype; | |
55 | Location loc; | |
56 | } first_opt[2]; | |
57 | ||
fc3b3295 MA |
58 | static struct { |
59 | const char *vendor, *version, *date; | |
60 | bool have_major_minor; | |
61 | uint8_t major, minor; | |
62 | } type0; | |
63 | ||
64 | static struct { | |
65 | const char *manufacturer, *product, *version, *serial, *sku, *family; | |
66 | /* uuid is in qemu_uuid[] */ | |
67 | } type1; | |
68 | ||
4f953d2f MA |
69 | static QemuOptsList qemu_smbios_opts = { |
70 | .name = "smbios", | |
71 | .head = QTAILQ_HEAD_INITIALIZER(qemu_smbios_opts.head), | |
72 | .desc = { | |
73 | /* | |
74 | * no elements => accept any params | |
75 | * validation will happen later | |
76 | */ | |
77 | { /* end of list */ } | |
78 | } | |
79 | }; | |
80 | ||
81 | static const QemuOptDesc qemu_smbios_file_opts[] = { | |
82 | { | |
83 | .name = "file", | |
84 | .type = QEMU_OPT_STRING, | |
85 | .help = "binary file containing an SMBIOS element", | |
86 | }, | |
87 | { /* end of list */ } | |
88 | }; | |
89 | ||
90 | static const QemuOptDesc qemu_smbios_type0_opts[] = { | |
91 | { | |
92 | .name = "type", | |
93 | .type = QEMU_OPT_NUMBER, | |
94 | .help = "SMBIOS element type", | |
95 | },{ | |
96 | .name = "vendor", | |
97 | .type = QEMU_OPT_STRING, | |
98 | .help = "vendor name", | |
99 | },{ | |
100 | .name = "version", | |
101 | .type = QEMU_OPT_STRING, | |
102 | .help = "version number", | |
103 | },{ | |
104 | .name = "date", | |
105 | .type = QEMU_OPT_STRING, | |
106 | .help = "release date", | |
107 | },{ | |
108 | .name = "release", | |
109 | .type = QEMU_OPT_STRING, | |
110 | .help = "revision number", | |
111 | }, | |
112 | { /* end of list */ } | |
113 | }; | |
114 | ||
115 | static const QemuOptDesc qemu_smbios_type1_opts[] = { | |
116 | { | |
117 | .name = "type", | |
118 | .type = QEMU_OPT_NUMBER, | |
119 | .help = "SMBIOS element type", | |
120 | },{ | |
121 | .name = "manufacturer", | |
122 | .type = QEMU_OPT_STRING, | |
123 | .help = "manufacturer name", | |
124 | },{ | |
125 | .name = "product", | |
126 | .type = QEMU_OPT_STRING, | |
127 | .help = "product name", | |
128 | },{ | |
129 | .name = "version", | |
130 | .type = QEMU_OPT_STRING, | |
131 | .help = "version number", | |
132 | },{ | |
133 | .name = "serial", | |
134 | .type = QEMU_OPT_STRING, | |
135 | .help = "serial number", | |
136 | },{ | |
137 | .name = "uuid", | |
138 | .type = QEMU_OPT_STRING, | |
139 | .help = "UUID", | |
140 | },{ | |
141 | .name = "sku", | |
142 | .type = QEMU_OPT_STRING, | |
143 | .help = "SKU number", | |
144 | },{ | |
145 | .name = "family", | |
146 | .type = QEMU_OPT_STRING, | |
147 | .help = "family name", | |
148 | }, | |
149 | { /* end of list */ } | |
150 | }; | |
151 | ||
152 | static void smbios_register_config(void) | |
153 | { | |
154 | qemu_add_opts(&qemu_smbios_opts); | |
155 | } | |
156 | ||
157 | machine_init(smbios_register_config); | |
158 | ||
09c0848e BK |
159 | static void smbios_validate_table(void) |
160 | { | |
161 | if (smbios_type4_count && smbios_type4_count != smp_cpus) { | |
5bb95e41 | 162 | error_report("Number of SMBIOS Type 4 tables must match cpu count"); |
09c0848e BK |
163 | exit(1); |
164 | } | |
165 | } | |
b6f6e3d3 | 166 | |
b6f6e3d3 AL |
167 | /* |
168 | * To avoid unresolvable overlaps in data, don't allow both | |
169 | * tables and fields for the same smbios type. | |
170 | */ | |
171 | static void smbios_check_collision(int type, int entry) | |
172 | { | |
ec2df8c1 MA |
173 | if (type < ARRAY_SIZE(first_opt)) { |
174 | if (first_opt[type].seen) { | |
175 | if (first_opt[type].headertype != entry) { | |
176 | error_report("Can't mix file= and type= for same type"); | |
177 | loc_push_restore(&first_opt[type].loc); | |
178 | error_report("This is the conflicting setting"); | |
179 | loc_pop(&first_opt[type].loc); | |
b6f6e3d3 AL |
180 | exit(1); |
181 | } | |
ec2df8c1 MA |
182 | } else { |
183 | first_opt[type].seen = true; | |
184 | first_opt[type].headertype = entry; | |
185 | loc_save(&first_opt[type].loc); | |
b6f6e3d3 | 186 | } |
b6f6e3d3 AL |
187 | } |
188 | } | |
189 | ||
fc3b3295 | 190 | static void smbios_add_field(int type, int offset, const void *data, size_t len) |
b6f6e3d3 AL |
191 | { |
192 | struct smbios_field *field; | |
193 | ||
b6f6e3d3 AL |
194 | if (!smbios_entries) { |
195 | smbios_entries_len = sizeof(uint16_t); | |
7267c094 | 196 | smbios_entries = g_malloc0(smbios_entries_len); |
b6f6e3d3 | 197 | } |
7267c094 | 198 | smbios_entries = g_realloc(smbios_entries, smbios_entries_len + |
b6f6e3d3 AL |
199 | sizeof(*field) + len); |
200 | field = (struct smbios_field *)(smbios_entries + smbios_entries_len); | |
201 | field->header.type = SMBIOS_FIELD_ENTRY; | |
202 | field->header.length = cpu_to_le16(sizeof(*field) + len); | |
203 | ||
204 | field->type = type; | |
205 | field->offset = cpu_to_le16(offset); | |
206 | memcpy(field->data, data, len); | |
207 | ||
208 | smbios_entries_len += sizeof(*field) + len; | |
209 | (*(uint16_t *)smbios_entries) = | |
210 | cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); | |
211 | } | |
212 | ||
e26d3e73 | 213 | static void smbios_maybe_add_str(int type, int offset, const char *data) |
b6f6e3d3 | 214 | { |
e26d3e73 MA |
215 | if (data) { |
216 | smbios_add_field(type, offset, data, strlen(data) + 1); | |
4f953d2f | 217 | } |
e26d3e73 MA |
218 | } |
219 | ||
220 | static void smbios_build_type_0_fields(void) | |
221 | { | |
222 | smbios_maybe_add_str(0, offsetof(struct smbios_type_0, vendor_str), | |
223 | type0.vendor); | |
224 | smbios_maybe_add_str(0, offsetof(struct smbios_type_0, bios_version_str), | |
225 | type0.version); | |
226 | smbios_maybe_add_str(0, offsetof(struct smbios_type_0, | |
b6f6e3d3 | 227 | bios_release_date_str), |
e26d3e73 | 228 | type0.date); |
fc3b3295 | 229 | if (type0.have_major_minor) { |
b6f6e3d3 | 230 | smbios_add_field(0, offsetof(struct smbios_type_0, |
ebc85e3f | 231 | system_bios_major_release), |
fc3b3295 | 232 | &type0.major, 1); |
b6f6e3d3 | 233 | smbios_add_field(0, offsetof(struct smbios_type_0, |
ebc85e3f | 234 | system_bios_minor_release), |
fc3b3295 | 235 | &type0.minor, 1); |
b6f6e3d3 AL |
236 | } |
237 | } | |
238 | ||
fc3b3295 | 239 | static void smbios_build_type_1_fields(void) |
b6f6e3d3 | 240 | { |
e26d3e73 MA |
241 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, manufacturer_str), |
242 | type1.manufacturer); | |
243 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, product_name_str), | |
244 | type1.product); | |
245 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, version_str), | |
246 | type1.version); | |
247 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, serial_number_str), | |
248 | type1.serial); | |
249 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, sku_number_str), | |
250 | type1.sku); | |
251 | smbios_maybe_add_str(1, offsetof(struct smbios_type_1, family_str), | |
252 | type1.family); | |
fc3b3295 MA |
253 | if (qemu_uuid_set) { |
254 | smbios_add_field(1, offsetof(struct smbios_type_1, uuid), | |
255 | qemu_uuid, 16); | |
256 | } | |
257 | } | |
258 | ||
259 | uint8_t *smbios_get_table(size_t *length) | |
260 | { | |
261 | if (!smbios_immutable) { | |
262 | smbios_build_type_0_fields(); | |
263 | smbios_build_type_1_fields(); | |
264 | smbios_validate_table(); | |
265 | smbios_immutable = true; | |
266 | } | |
267 | *length = smbios_entries_len; | |
268 | return smbios_entries; | |
269 | } | |
270 | ||
271 | static void save_opt(const char **dest, QemuOpts *opts, const char *name) | |
272 | { | |
273 | const char *val = qemu_opt_get(opts, name); | |
274 | ||
275 | if (val) { | |
276 | *dest = val; | |
4f953d2f | 277 | } |
b6f6e3d3 AL |
278 | } |
279 | ||
4f953d2f | 280 | void smbios_entry_add(QemuOpts *opts) |
b6f6e3d3 | 281 | { |
4f953d2f MA |
282 | Error *local_err = NULL; |
283 | const char *val; | |
b6f6e3d3 | 284 | |
fc3b3295 | 285 | assert(!smbios_immutable); |
4f953d2f MA |
286 | val = qemu_opt_get(opts, "file"); |
287 | if (val) { | |
b6f6e3d3 AL |
288 | struct smbios_structure_header *header; |
289 | struct smbios_table *table; | |
4f953d2f MA |
290 | int size; |
291 | ||
292 | qemu_opts_validate(opts, qemu_smbios_file_opts, &local_err); | |
293 | if (local_err) { | |
294 | error_report("%s", error_get_pretty(local_err)); | |
295 | exit(1); | |
296 | } | |
b6f6e3d3 | 297 | |
4f953d2f | 298 | size = get_image_size(val); |
09c0848e | 299 | if (size == -1 || size < sizeof(struct smbios_structure_header)) { |
4f953d2f | 300 | error_report("Cannot read SMBIOS file %s", val); |
b6f6e3d3 AL |
301 | exit(1); |
302 | } | |
303 | ||
304 | if (!smbios_entries) { | |
305 | smbios_entries_len = sizeof(uint16_t); | |
7267c094 | 306 | smbios_entries = g_malloc0(smbios_entries_len); |
b6f6e3d3 AL |
307 | } |
308 | ||
7267c094 | 309 | smbios_entries = g_realloc(smbios_entries, smbios_entries_len + |
b6f6e3d3 AL |
310 | sizeof(*table) + size); |
311 | table = (struct smbios_table *)(smbios_entries + smbios_entries_len); | |
312 | table->header.type = SMBIOS_TABLE_ENTRY; | |
313 | table->header.length = cpu_to_le16(sizeof(*table) + size); | |
314 | ||
4f953d2f MA |
315 | if (load_image(val, table->data) != size) { |
316 | error_report("Failed to load SMBIOS file %s", val); | |
b6f6e3d3 AL |
317 | exit(1); |
318 | } | |
319 | ||
320 | header = (struct smbios_structure_header *)(table->data); | |
321 | smbios_check_collision(header->type, SMBIOS_TABLE_ENTRY); | |
09c0848e BK |
322 | if (header->type == 4) { |
323 | smbios_type4_count++; | |
324 | } | |
b6f6e3d3 AL |
325 | |
326 | smbios_entries_len += sizeof(*table) + size; | |
327 | (*(uint16_t *)smbios_entries) = | |
328 | cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); | |
351a6a73 | 329 | return; |
b6f6e3d3 AL |
330 | } |
331 | ||
4f953d2f MA |
332 | val = qemu_opt_get(opts, "type"); |
333 | if (val) { | |
334 | unsigned long type = strtoul(val, NULL, 0); | |
335 | ||
fc3b3295 MA |
336 | smbios_check_collision(type, SMBIOS_FIELD_ENTRY); |
337 | ||
b6f6e3d3 AL |
338 | switch (type) { |
339 | case 0: | |
4f953d2f MA |
340 | qemu_opts_validate(opts, qemu_smbios_type0_opts, &local_err); |
341 | if (local_err) { | |
342 | error_report("%s", error_get_pretty(local_err)); | |
343 | exit(1); | |
344 | } | |
fc3b3295 MA |
345 | save_opt(&type0.vendor, opts, "vendor"); |
346 | save_opt(&type0.version, opts, "version"); | |
347 | save_opt(&type0.date, opts, "date"); | |
348 | ||
349 | val = qemu_opt_get(opts, "release"); | |
350 | if (val) { | |
351 | if (sscanf(val, "%hhu.%hhu", &type0.major, &type0.minor) != 2) { | |
352 | error_report("Invalid release"); | |
353 | exit(1); | |
354 | } | |
355 | type0.have_major_minor = true; | |
356 | } | |
351a6a73 | 357 | return; |
b6f6e3d3 | 358 | case 1: |
4f953d2f MA |
359 | qemu_opts_validate(opts, qemu_smbios_type1_opts, &local_err); |
360 | if (local_err) { | |
361 | error_report("%s", error_get_pretty(local_err)); | |
362 | exit(1); | |
363 | } | |
fc3b3295 MA |
364 | save_opt(&type1.manufacturer, opts, "manufacturer"); |
365 | save_opt(&type1.product, opts, "product"); | |
366 | save_opt(&type1.version, opts, "version"); | |
367 | save_opt(&type1.serial, opts, "serial"); | |
368 | save_opt(&type1.sku, opts, "sku"); | |
369 | save_opt(&type1.family, opts, "family"); | |
370 | ||
371 | val = qemu_opt_get(opts, "uuid"); | |
372 | if (val) { | |
373 | if (qemu_uuid_parse(val, qemu_uuid) != 0) { | |
374 | error_report("Invalid UUID"); | |
375 | exit(1); | |
376 | } | |
377 | qemu_uuid_set = true; | |
378 | } | |
351a6a73 | 379 | return; |
b6f6e3d3 | 380 | default: |
5bb95e41 MA |
381 | error_report("Don't know how to build fields for SMBIOS type %ld", |
382 | type); | |
b6f6e3d3 AL |
383 | exit(1); |
384 | } | |
385 | } | |
386 | ||
5bb95e41 | 387 | error_report("Must specify type= or file="); |
351a6a73 | 388 | exit(1); |
b6f6e3d3 | 389 | } |