]>
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 | ||
fc3b3295 | 213 | static void smbios_build_type_0_fields(void) |
b6f6e3d3 | 214 | { |
fc3b3295 | 215 | if (type0.vendor) { |
b6f6e3d3 | 216 | smbios_add_field(0, offsetof(struct smbios_type_0, vendor_str), |
fc3b3295 | 217 | type0.vendor, strlen(type0.vendor) + 1); |
4f953d2f | 218 | } |
fc3b3295 | 219 | if (type0.version) { |
b6f6e3d3 | 220 | smbios_add_field(0, offsetof(struct smbios_type_0, bios_version_str), |
fc3b3295 | 221 | type0.version, strlen(type0.version) + 1); |
4f953d2f | 222 | } |
fc3b3295 | 223 | if (type0.date) { |
b6f6e3d3 AL |
224 | smbios_add_field(0, offsetof(struct smbios_type_0, |
225 | bios_release_date_str), | |
fc3b3295 | 226 | type0.date, strlen(type0.date) + 1); |
4f953d2f | 227 | } |
fc3b3295 | 228 | if (type0.have_major_minor) { |
b6f6e3d3 | 229 | smbios_add_field(0, offsetof(struct smbios_type_0, |
ebc85e3f | 230 | system_bios_major_release), |
fc3b3295 | 231 | &type0.major, 1); |
b6f6e3d3 | 232 | smbios_add_field(0, offsetof(struct smbios_type_0, |
ebc85e3f | 233 | system_bios_minor_release), |
fc3b3295 | 234 | &type0.minor, 1); |
b6f6e3d3 AL |
235 | } |
236 | } | |
237 | ||
fc3b3295 | 238 | static void smbios_build_type_1_fields(void) |
b6f6e3d3 | 239 | { |
fc3b3295 | 240 | if (type1.manufacturer) { |
b6f6e3d3 | 241 | smbios_add_field(1, offsetof(struct smbios_type_1, manufacturer_str), |
fc3b3295 | 242 | type1.manufacturer, strlen(type1.manufacturer) + 1); |
4f953d2f | 243 | } |
fc3b3295 | 244 | if (type1.product) { |
b6f6e3d3 | 245 | smbios_add_field(1, offsetof(struct smbios_type_1, product_name_str), |
fc3b3295 | 246 | type1.product, strlen(type1.product) + 1); |
4f953d2f | 247 | } |
fc3b3295 | 248 | if (type1.version) { |
b6f6e3d3 | 249 | smbios_add_field(1, offsetof(struct smbios_type_1, version_str), |
fc3b3295 | 250 | type1.version, strlen(type1.version) + 1); |
4f953d2f | 251 | } |
fc3b3295 | 252 | if (type1.serial) { |
b6f6e3d3 | 253 | smbios_add_field(1, offsetof(struct smbios_type_1, serial_number_str), |
fc3b3295 | 254 | type1.serial, strlen(type1.serial) + 1); |
b6f6e3d3 | 255 | } |
fc3b3295 | 256 | if (type1.sku) { |
b6f6e3d3 | 257 | smbios_add_field(1, offsetof(struct smbios_type_1, sku_number_str), |
fc3b3295 | 258 | type1.sku, strlen(type1.sku) + 1); |
4f953d2f | 259 | } |
fc3b3295 | 260 | if (type1.family) { |
b6f6e3d3 | 261 | smbios_add_field(1, offsetof(struct smbios_type_1, family_str), |
fc3b3295 MA |
262 | type1.family, strlen(type1.family) + 1); |
263 | } | |
264 | if (qemu_uuid_set) { | |
265 | smbios_add_field(1, offsetof(struct smbios_type_1, uuid), | |
266 | qemu_uuid, 16); | |
267 | } | |
268 | } | |
269 | ||
270 | uint8_t *smbios_get_table(size_t *length) | |
271 | { | |
272 | if (!smbios_immutable) { | |
273 | smbios_build_type_0_fields(); | |
274 | smbios_build_type_1_fields(); | |
275 | smbios_validate_table(); | |
276 | smbios_immutable = true; | |
277 | } | |
278 | *length = smbios_entries_len; | |
279 | return smbios_entries; | |
280 | } | |
281 | ||
282 | static void save_opt(const char **dest, QemuOpts *opts, const char *name) | |
283 | { | |
284 | const char *val = qemu_opt_get(opts, name); | |
285 | ||
286 | if (val) { | |
287 | *dest = val; | |
4f953d2f | 288 | } |
b6f6e3d3 AL |
289 | } |
290 | ||
4f953d2f | 291 | void smbios_entry_add(QemuOpts *opts) |
b6f6e3d3 | 292 | { |
4f953d2f MA |
293 | Error *local_err = NULL; |
294 | const char *val; | |
b6f6e3d3 | 295 | |
fc3b3295 | 296 | assert(!smbios_immutable); |
4f953d2f MA |
297 | val = qemu_opt_get(opts, "file"); |
298 | if (val) { | |
b6f6e3d3 AL |
299 | struct smbios_structure_header *header; |
300 | struct smbios_table *table; | |
4f953d2f MA |
301 | int size; |
302 | ||
303 | qemu_opts_validate(opts, qemu_smbios_file_opts, &local_err); | |
304 | if (local_err) { | |
305 | error_report("%s", error_get_pretty(local_err)); | |
306 | exit(1); | |
307 | } | |
b6f6e3d3 | 308 | |
4f953d2f | 309 | size = get_image_size(val); |
09c0848e | 310 | if (size == -1 || size < sizeof(struct smbios_structure_header)) { |
4f953d2f | 311 | error_report("Cannot read SMBIOS file %s", val); |
b6f6e3d3 AL |
312 | exit(1); |
313 | } | |
314 | ||
315 | if (!smbios_entries) { | |
316 | smbios_entries_len = sizeof(uint16_t); | |
7267c094 | 317 | smbios_entries = g_malloc0(smbios_entries_len); |
b6f6e3d3 AL |
318 | } |
319 | ||
7267c094 | 320 | smbios_entries = g_realloc(smbios_entries, smbios_entries_len + |
b6f6e3d3 AL |
321 | sizeof(*table) + size); |
322 | table = (struct smbios_table *)(smbios_entries + smbios_entries_len); | |
323 | table->header.type = SMBIOS_TABLE_ENTRY; | |
324 | table->header.length = cpu_to_le16(sizeof(*table) + size); | |
325 | ||
4f953d2f MA |
326 | if (load_image(val, table->data) != size) { |
327 | error_report("Failed to load SMBIOS file %s", val); | |
b6f6e3d3 AL |
328 | exit(1); |
329 | } | |
330 | ||
331 | header = (struct smbios_structure_header *)(table->data); | |
332 | smbios_check_collision(header->type, SMBIOS_TABLE_ENTRY); | |
09c0848e BK |
333 | if (header->type == 4) { |
334 | smbios_type4_count++; | |
335 | } | |
b6f6e3d3 AL |
336 | |
337 | smbios_entries_len += sizeof(*table) + size; | |
338 | (*(uint16_t *)smbios_entries) = | |
339 | cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); | |
351a6a73 | 340 | return; |
b6f6e3d3 AL |
341 | } |
342 | ||
4f953d2f MA |
343 | val = qemu_opt_get(opts, "type"); |
344 | if (val) { | |
345 | unsigned long type = strtoul(val, NULL, 0); | |
346 | ||
fc3b3295 MA |
347 | smbios_check_collision(type, SMBIOS_FIELD_ENTRY); |
348 | ||
b6f6e3d3 AL |
349 | switch (type) { |
350 | case 0: | |
4f953d2f MA |
351 | qemu_opts_validate(opts, qemu_smbios_type0_opts, &local_err); |
352 | if (local_err) { | |
353 | error_report("%s", error_get_pretty(local_err)); | |
354 | exit(1); | |
355 | } | |
fc3b3295 MA |
356 | save_opt(&type0.vendor, opts, "vendor"); |
357 | save_opt(&type0.version, opts, "version"); | |
358 | save_opt(&type0.date, opts, "date"); | |
359 | ||
360 | val = qemu_opt_get(opts, "release"); | |
361 | if (val) { | |
362 | if (sscanf(val, "%hhu.%hhu", &type0.major, &type0.minor) != 2) { | |
363 | error_report("Invalid release"); | |
364 | exit(1); | |
365 | } | |
366 | type0.have_major_minor = true; | |
367 | } | |
351a6a73 | 368 | return; |
b6f6e3d3 | 369 | case 1: |
4f953d2f MA |
370 | qemu_opts_validate(opts, qemu_smbios_type1_opts, &local_err); |
371 | if (local_err) { | |
372 | error_report("%s", error_get_pretty(local_err)); | |
373 | exit(1); | |
374 | } | |
fc3b3295 MA |
375 | save_opt(&type1.manufacturer, opts, "manufacturer"); |
376 | save_opt(&type1.product, opts, "product"); | |
377 | save_opt(&type1.version, opts, "version"); | |
378 | save_opt(&type1.serial, opts, "serial"); | |
379 | save_opt(&type1.sku, opts, "sku"); | |
380 | save_opt(&type1.family, opts, "family"); | |
381 | ||
382 | val = qemu_opt_get(opts, "uuid"); | |
383 | if (val) { | |
384 | if (qemu_uuid_parse(val, qemu_uuid) != 0) { | |
385 | error_report("Invalid UUID"); | |
386 | exit(1); | |
387 | } | |
388 | qemu_uuid_set = true; | |
389 | } | |
351a6a73 | 390 | return; |
b6f6e3d3 | 391 | default: |
5bb95e41 MA |
392 | error_report("Don't know how to build fields for SMBIOS type %ld", |
393 | type); | |
b6f6e3d3 AL |
394 | exit(1); |
395 | } | |
396 | } | |
397 | ||
5bb95e41 | 398 | error_report("Must specify type= or file="); |
351a6a73 | 399 | exit(1); |
b6f6e3d3 | 400 | } |