blob: 450aca282e4541a79ddf911791de964b40dde610 [file] [log] [blame]
Kevin O'Connore9161822014-04-05 09:11:45 -04001// Support for manipulating bios tables (pir, mptable, acpi, smbios).
Ian Campbell1442c312011-06-01 11:00:28 +01002//
3// Copyright (C) 2008,2009 Kevin O'Connor <kevin@koconnor.net>
4//
5// This file may be distributed under the terms of the GNU LGPLv3 license.
6
Kevin O'Connor836b4d82014-04-07 15:42:04 -04007#include "byteorder.h" // le32_to_cpu
Kevin O'Connor2d2fa312013-09-14 21:55:26 -04008#include "config.h" // CONFIG_*
Kevin O'Connor9dea5902013-09-14 20:23:54 -04009#include "malloc.h" // malloc_fseg
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040010#include "output.h" // dprintf
Kevin O'Connor836b4d82014-04-07 15:42:04 -040011#include "hw/pci.h" // pci_config_writeb
Kevin O'Connor5a7545c2013-09-14 22:54:44 -040012#include "std/acpi.h" // struct rsdp_descriptor
Kevin O'Connor3d0dfe12013-09-14 22:48:04 -040013#include "std/mptable.h" // MPTABLE_SIGNATURE
Kevin O'Connorc5e06fb2013-09-14 22:22:28 -040014#include "std/pirtable.h" // struct pir_header
Kevin O'Connorb37a5282013-09-14 22:45:05 -040015#include "std/smbios.h" // struct smbios_entry_point
Gabriel L. Somlod85c22e2014-04-23 15:04:49 -040016#include "romfile.h"
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040017#include "string.h" // memcpy
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040018#include "util.h" // copy_table
Kevin O'Connor836b4d82014-04-07 15:42:04 -040019#include "x86.h" // outb
Ian Campbell1442c312011-06-01 11:00:28 +010020
Kevin O'Connor19646a42014-04-07 15:55:23 -040021struct pir_header *PirAddr VARFSEG;
22
Kevin O'Connor3bd2dc62014-04-07 16:35:18 -040023void
Ian Campbell1442c312011-06-01 11:00:28 +010024copy_pir(void *pos)
25{
26 struct pir_header *p = pos;
27 if (p->signature != PIR_SIGNATURE)
28 return;
Kevin O'Connorbfa02cd2012-06-09 13:36:45 -040029 if (PirAddr)
Ian Campbell1442c312011-06-01 11:00:28 +010030 return;
31 if (p->size < sizeof(*p))
32 return;
33 if (checksum(pos, p->size) != 0)
34 return;
35 void *newpos = malloc_fseg(p->size);
36 if (!newpos) {
37 warn_noalloc();
38 return;
39 }
40 dprintf(1, "Copying PIR from %p to %p\n", pos, newpos);
41 memcpy(newpos, pos, p->size);
Kevin O'Connorbfa02cd2012-06-09 13:36:45 -040042 PirAddr = newpos;
Ian Campbell1442c312011-06-01 11:00:28 +010043}
44
Kevin O'Connor3bd2dc62014-04-07 16:35:18 -040045void
Ian Campbell1442c312011-06-01 11:00:28 +010046copy_mptable(void *pos)
47{
48 struct mptable_floating_s *p = pos;
49 if (p->signature != MPTABLE_SIGNATURE)
50 return;
51 if (!p->physaddr)
52 return;
53 if (checksum(pos, sizeof(*p)) != 0)
54 return;
55 u32 length = p->length * 16;
56 u16 mpclength = ((struct mptable_config_s *)p->physaddr)->length;
Kevin O'Connor3bd2dc62014-04-07 16:35:18 -040057 // Allocate final memory location. (In theory the config
58 // structure can go in high memory, but Linux kernels before
59 // v2.6.30 crash with that.)
Ian Campbell1442c312011-06-01 11:00:28 +010060 struct mptable_floating_s *newpos = malloc_fseg(length + mpclength);
61 if (!newpos) {
62 warn_noalloc();
63 return;
64 }
65 dprintf(1, "Copying MPTABLE from %p/%x to %p\n", pos, p->physaddr, newpos);
66 memcpy(newpos, pos, length);
67 newpos->physaddr = (u32)newpos + length;
68 newpos->checksum -= checksum(newpos, sizeof(*newpos));
69 memcpy((void*)newpos + length, (void*)p->physaddr, mpclength);
70}
71
Kevin O'Connor836b4d82014-04-07 15:42:04 -040072
73/****************************************************************
74 * ACPI
75 ****************************************************************/
76
Michael S. Tsirkinff5f7922013-10-03 16:30:35 +030077static int
78get_acpi_rsdp_length(void *pos, unsigned size)
79{
80 struct rsdp_descriptor *p = pos;
81 if (p->signature != RSDP_SIGNATURE)
82 return -1;
83 u32 length = 20;
84 if (length > size)
85 return -1;
86 if (checksum(pos, length) != 0)
87 return -1;
88 if (p->revision > 1) {
89 length = p->length;
90 if (length > size)
91 return -1;
92 if (checksum(pos, length) != 0)
93 return -1;
94 }
95 return length;
96}
97
Kevin O'Connor836b4d82014-04-07 15:42:04 -040098struct rsdp_descriptor *RsdpAddr;
99
Kevin O'Connor3bd2dc62014-04-07 16:35:18 -0400100void
Ian Campbell1442c312011-06-01 11:00:28 +0100101copy_acpi_rsdp(void *pos)
102{
103 if (RsdpAddr)
104 return;
Michael S. Tsirkinff5f7922013-10-03 16:30:35 +0300105 int length = get_acpi_rsdp_length(pos, -1);
106 if (length < 0)
Ian Campbell1442c312011-06-01 11:00:28 +0100107 return;
Ian Campbell1442c312011-06-01 11:00:28 +0100108 void *newpos = malloc_fseg(length);
109 if (!newpos) {
110 warn_noalloc();
111 return;
112 }
113 dprintf(1, "Copying ACPI RSDP from %p to %p\n", pos, newpos);
114 memcpy(newpos, pos, length);
115 RsdpAddr = newpos;
116}
Ian Campbell74c78782011-06-01 11:00:29 +0100117
Kevin O'Connor836b4d82014-04-07 15:42:04 -0400118void *find_acpi_rsdp(void)
119{
120 extern u8 zonefseg_start[], zonefseg_end[];
121 unsigned long start = (unsigned long)zonefseg_start;
122 unsigned long end = (unsigned long)zonefseg_end;
123 unsigned long pos;
124
125 for (pos = ALIGN(start, 0x10); pos <= ALIGN_DOWN(end, 0x10); pos += 0x10)
126 if (get_acpi_rsdp_length((void *)pos, end - pos) >= 0)
127 return (void *)pos;
128
129 return NULL;
130}
131
132static struct fadt_descriptor_rev1 *
133find_fadt(void)
134{
135 dprintf(4, "rsdp=%p\n", RsdpAddr);
136 if (!RsdpAddr || RsdpAddr->signature != RSDP_SIGNATURE)
137 return NULL;
138 struct rsdt_descriptor_rev1 *rsdt = (void*)RsdpAddr->rsdt_physical_address;
139 dprintf(4, "rsdt=%p\n", rsdt);
140 if (!rsdt || rsdt->signature != RSDT_SIGNATURE)
141 return NULL;
142 void *end = (void*)rsdt + rsdt->length;
143 int i;
144 for (i=0; (void*)&rsdt->table_offset_entry[i] < end; i++) {
145 struct fadt_descriptor_rev1 *fadt = (void*)rsdt->table_offset_entry[i];
146 if (!fadt || fadt->signature != FACP_SIGNATURE)
147 continue;
148 dprintf(4, "fadt=%p\n", fadt);
149 return fadt;
150 }
151 dprintf(4, "no fadt found\n");
152 return NULL;
153}
154
155u32
156find_resume_vector(void)
157{
158 struct fadt_descriptor_rev1 *fadt = find_fadt();
159 if (!fadt)
160 return 0;
161 struct facs_descriptor_rev1 *facs = (void*)fadt->firmware_ctrl;
162 dprintf(4, "facs=%p\n", facs);
163 if (! facs || facs->signature != FACS_SIGNATURE)
164 return 0;
165 // Found it.
166 dprintf(4, "resume addr=%d\n", facs->firmware_waking_vector);
167 return facs->firmware_waking_vector;
168}
169
170static struct acpi_20_generic_address acpi_reset_reg;
171static u8 acpi_reset_val;
172u32 acpi_pm1a_cnt VARFSEG;
Gerd Hoffmanna217de92014-05-13 14:01:22 +0200173u16 acpi_pm_base = 0xb000;
Kevin O'Connor836b4d82014-04-07 15:42:04 -0400174
175#define acpi_ga_to_bdf(addr) pci_to_bdf(0, (addr >> 32) & 0xffff, (addr >> 16) & 0xffff)
176
177void
178acpi_reboot(void)
179{
180 // Check it passed the sanity checks in acpi_set_reset_reg() and was set
181 if (acpi_reset_reg.register_bit_width != 8)
182 return;
183
184 u64 addr = le64_to_cpu(acpi_reset_reg.address);
185
186 dprintf(1, "ACPI hard reset %d:%llx (%x)\n",
187 acpi_reset_reg.address_space_id, addr, acpi_reset_val);
188
189 switch (acpi_reset_reg.address_space_id) {
190 case 0: // System Memory
Kevin O'Connor028f3482014-04-11 12:08:33 -0400191 writeb((void *)(u32)addr, acpi_reset_val);
Kevin O'Connor836b4d82014-04-07 15:42:04 -0400192 break;
193 case 1: // System I/O
194 outb(acpi_reset_val, addr);
195 break;
196 case 2: // PCI config space
197 pci_config_writeb(acpi_ga_to_bdf(addr), addr & 0xffff, acpi_reset_val);
198 break;
199 }
200}
201
202static void
203acpi_set_reset_reg(struct acpi_20_generic_address *reg, u8 val)
204{
205 if (!reg || reg->address_space_id > 2 ||
206 reg->register_bit_width != 8 || reg->register_bit_offset)
207 return;
208
209 acpi_reset_reg = *reg;
210 acpi_reset_val = val;
211}
212
213void
214find_acpi_features(void)
215{
216 struct fadt_descriptor_rev1 *fadt = find_fadt();
217 if (!fadt)
218 return;
219 u32 pm_tmr = le32_to_cpu(fadt->pm_tmr_blk);
220 u32 pm1a_cnt = le32_to_cpu(fadt->pm1a_cnt_blk);
221 dprintf(4, "pm_tmr_blk=%x\n", pm_tmr);
222 if (pm_tmr)
223 pmtimer_setup(pm_tmr);
224 if (pm1a_cnt)
225 acpi_pm1a_cnt = pm1a_cnt;
226
227 // Theoretically we should check the 'reset_reg_sup' flag, but Windows
228 // doesn't and thus nobody seems to *set* it. If the table is large enough
229 // to include it, let the sanity checks in acpi_set_reset_reg() suffice.
230 if (fadt->length >= 129) {
231 void *p = fadt;
232 acpi_set_reset_reg(p + 116, *(u8 *)(p + 128));
233 }
234}
235
236
237/****************************************************************
238 * SMBIOS
239 ****************************************************************/
240
Kevin O'Connor413d1c32014-04-12 11:56:21 -0400241// Iterator for each sub-table in the smbios blob.
242void *
243smbios_next(struct smbios_entry_point *smbios, void *prev)
244{
245 if (!smbios)
246 return NULL;
247 void *start = (void*)smbios->structure_table_address;
248 void *end = start + smbios->structure_table_length;
249
250 if (!prev) {
251 prev = start;
252 } else {
253 struct smbios_structure_header *hdr = prev;
254 if (prev + sizeof(*hdr) > end)
255 return NULL;
256 prev += hdr->length + 2;
257 while (prev < end && (*(u8*)(prev-1) != '\0' || *(u8*)(prev-2) != '\0'))
258 prev++;
259 }
260 struct smbios_structure_header *hdr = prev;
261 if (prev >= end || prev + sizeof(*hdr) >= end || prev + hdr->length >= end)
262 return NULL;
263 return prev;
264}
265
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400266struct smbios_entry_point *SMBiosAddr;
267
David Woodhouse38b24db2013-01-25 19:33:58 -0600268void
Ian Campbell74c78782011-06-01 11:00:29 +0100269copy_smbios(void *pos)
270{
Kevin O'Connor83012de2011-08-28 12:42:15 -0400271 if (SMBiosAddr)
272 return;
Ian Campbell74c78782011-06-01 11:00:29 +0100273 struct smbios_entry_point *p = pos;
Kevin O'Connor9d3c0632015-04-09 11:57:44 -0400274 if (p->signature != SMBIOS_SIGNATURE)
Ian Campbell74c78782011-06-01 11:00:29 +0100275 return;
276 if (checksum(pos, 0x10) != 0)
277 return;
278 if (memcmp(p->intermediate_anchor_string, "_DMI_", 5))
279 return;
280 if (checksum(pos+0x10, p->length-0x10) != 0)
281 return;
Ian Campbellee2bc462011-06-14 15:22:09 +0100282 struct smbios_entry_point *newpos = malloc_fseg(p->length);
Ian Campbell74c78782011-06-01 11:00:29 +0100283 if (!newpos) {
284 warn_noalloc();
285 return;
286 }
287 dprintf(1, "Copying SMBIOS entry point from %p to %p\n", pos, newpos);
288 memcpy(newpos, pos, p->length);
Kevin O'Connor83012de2011-08-28 12:42:15 -0400289 SMBiosAddr = newpos;
Ian Campbell74c78782011-06-01 11:00:29 +0100290}
Kevin O'Connor4d053eb2012-06-09 13:08:02 -0400291
292void
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400293display_uuid(void)
294{
Kevin O'Connor413d1c32014-04-12 11:56:21 -0400295 struct smbios_type_1 *tbl = smbios_next(SMBiosAddr, NULL);
296 int minlen = offsetof(struct smbios_type_1, uuid) + sizeof(tbl->uuid);
297 for (; tbl; tbl = smbios_next(SMBiosAddr, tbl))
298 if (tbl->header.type == 1 && tbl->header.length >= minlen) {
299 u8 *uuid = tbl->uuid;
300 u8 empty_uuid[sizeof(tbl->uuid)] = { 0 };
301 if (memcmp(uuid, empty_uuid, sizeof(empty_uuid)) == 0)
302 return;
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400303
Kevin O'Connor413d1c32014-04-12 11:56:21 -0400304 printf("Machine UUID"
305 " %02x%02x%02x%02x"
306 "-%02x%02x"
307 "-%02x%02x"
308 "-%02x%02x"
309 "-%02x%02x%02x%02x%02x%02x\n"
310 , uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3]
311 , uuid[ 4], uuid[ 5]
312 , uuid[ 6], uuid[ 7]
313 , uuid[ 8], uuid[ 9]
314 , uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400315 return;
Kevin O'Connor413d1c32014-04-12 11:56:21 -0400316 }
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400317}
318
Gabriel L. Somlod85c22e2014-04-23 15:04:49 -0400319#define set_str_field_or_skip(type, field, value) \
320 do { \
321 int size = (value != NULL) ? strlen(value) + 1 : 0; \
322 if (size > 1) { \
323 memcpy(end, value, size); \
324 end += size; \
325 p->field = ++str_index; \
326 } else { \
327 p->field = 0; \
328 } \
329 } while (0)
330
331static void *
332smbios_new_type_0(void *start,
333 const char *vendor, const char *version, const char *date)
334{
335 struct smbios_type_0 *p = (struct smbios_type_0 *)start;
336 char *end = (char *)start + sizeof(struct smbios_type_0);
337 int str_index = 0;
338
339 p->header.type = 0;
340 p->header.length = sizeof(struct smbios_type_0);
341 p->header.handle = 0;
342
343 set_str_field_or_skip(0, vendor_str, vendor);
344 set_str_field_or_skip(0, bios_version_str, version);
345 p->bios_starting_address_segment = 0xe800;
346 set_str_field_or_skip(0, bios_release_date_str, date);
347
348 p->bios_rom_size = 0; /* FIXME */
349
350 /* BIOS characteristics not supported */
351 memset(p->bios_characteristics, 0, 8);
352 p->bios_characteristics[0] = 0x08;
353
354 /* Enable targeted content distribution (needed for SVVP) */
355 p->bios_characteristics_extension_bytes[0] = 0;
356 p->bios_characteristics_extension_bytes[1] = 4;
357
358 p->system_bios_major_release = 0;
359 p->system_bios_minor_release = 0;
360 p->embedded_controller_major_release = 0xFF;
361 p->embedded_controller_minor_release = 0xFF;
362
363 *end = 0;
364 end++;
365 if (!str_index) {
366 *end = 0;
367 end++;
368 }
369
370 return end;
371}
372
373#define BIOS_NAME "SeaBIOS"
374#define BIOS_DATE "04/01/2014"
375
376static int
377smbios_romfile_setup(void)
378{
379 struct romfile_s *f_anchor = romfile_find("etc/smbios/smbios-anchor");
380 struct romfile_s *f_tables = romfile_find("etc/smbios/smbios-tables");
381 struct smbios_entry_point ep;
382 struct smbios_type_0 *t0;
383 u16 qtables_len, need_t0 = 1;
384 u8 *qtables, *tables;
385
386 if (!f_anchor || !f_tables || f_anchor->size != sizeof(ep))
387 return 0;
388
389 f_anchor->copy(f_anchor, &ep, f_anchor->size);
390
391 if (f_tables->size != ep.structure_table_length)
392 return 0;
393
394 qtables = malloc_tmphigh(f_tables->size);
395 if (!qtables) {
396 warn_noalloc();
397 return 0;
398 }
399 f_tables->copy(f_tables, qtables, f_tables->size);
400 ep.structure_table_address = (u32)qtables; /* for smbios_next(), below */
401
402 /* did we get a type 0 structure ? */
403 for (t0 = smbios_next(&ep, NULL); t0; t0 = smbios_next(&ep, t0))
404 if (t0->header.type == 0) {
405 need_t0 = 0;
406 break;
407 }
408
409 qtables_len = ep.structure_table_length;
410 if (need_t0) {
411 /* common case: add our own type 0, with 3 strings and 4 '\0's */
412 u16 t0_len = sizeof(struct smbios_type_0) + strlen(BIOS_NAME) +
413 strlen(VERSION) + strlen(BIOS_DATE) + 4;
414 ep.structure_table_length += t0_len;
415 if (t0_len > ep.max_structure_size)
416 ep.max_structure_size = t0_len;
417 ep.number_of_structures++;
418 }
419
420 /* allocate final blob and record its address in the entry point */
421 if (ep.structure_table_length > BUILD_MAX_SMBIOS_FSEG)
422 tables = malloc_high(ep.structure_table_length);
423 else
424 tables = malloc_fseg(ep.structure_table_length);
425 if (!tables) {
426 warn_noalloc();
427 free(qtables);
428 return 0;
429 }
430 ep.structure_table_address = (u32)tables;
431
432 /* populate final blob */
433 if (need_t0)
434 tables = smbios_new_type_0(tables, BIOS_NAME, VERSION, BIOS_DATE);
435 memcpy(tables, qtables, qtables_len);
436 free(qtables);
437
438 /* finalize entry point */
439 ep.checksum -= checksum(&ep, 0x10);
440 ep.intermediate_checksum -= checksum((void *)&ep + 0x10, ep.length - 0x10);
441
442 copy_smbios(&ep);
443 return 1;
444}
445
446void
447smbios_setup(void)
448{
449 if (smbios_romfile_setup())
450 return;
451 smbios_legacy_setup();
452}
Kevin O'Connord18c9f02014-04-07 15:48:12 -0400453
454void
Kevin O'Connor4d053eb2012-06-09 13:08:02 -0400455copy_table(void *pos)
456{
457 copy_pir(pos);
458 copy_mptable(pos);
459 copy_acpi_rsdp(pos);
460 copy_smbios(pos);
461}