Kevin O'Connor | e916182 | 2014-04-05 09:11:45 -0400 | [diff] [blame] | 1 | // Support for manipulating bios tables (pir, mptable, acpi, smbios). |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 2 | // |
| 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'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 7 | #include "byteorder.h" // le32_to_cpu |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 8 | #include "config.h" // CONFIG_* |
Kevin O'Connor | 9dea590 | 2013-09-14 20:23:54 -0400 | [diff] [blame] | 9 | #include "malloc.h" // malloc_fseg |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 10 | #include "output.h" // dprintf |
Kevin O'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 11 | #include "hw/pci.h" // pci_config_writeb |
Kevin O'Connor | 5a7545c | 2013-09-14 22:54:44 -0400 | [diff] [blame] | 12 | #include "std/acpi.h" // struct rsdp_descriptor |
Kevin O'Connor | 3d0dfe1 | 2013-09-14 22:48:04 -0400 | [diff] [blame] | 13 | #include "std/mptable.h" // MPTABLE_SIGNATURE |
Kevin O'Connor | c5e06fb | 2013-09-14 22:22:28 -0400 | [diff] [blame] | 14 | #include "std/pirtable.h" // struct pir_header |
Kevin O'Connor | b37a528 | 2013-09-14 22:45:05 -0400 | [diff] [blame] | 15 | #include "std/smbios.h" // struct smbios_entry_point |
Kevin O'Connor | fa9c66a | 2013-09-14 19:10:40 -0400 | [diff] [blame] | 16 | #include "string.h" // memcpy |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 17 | #include "util.h" // copy_table |
Kevin O'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 18 | #include "x86.h" // outb |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 19 | |
Kevin O'Connor | 4d053eb | 2012-06-09 13:08:02 -0400 | [diff] [blame] | 20 | static void |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 21 | copy_pir(void *pos) |
| 22 | { |
| 23 | struct pir_header *p = pos; |
| 24 | if (p->signature != PIR_SIGNATURE) |
| 25 | return; |
Kevin O'Connor | bfa02cd | 2012-06-09 13:36:45 -0400 | [diff] [blame] | 26 | if (PirAddr) |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 27 | return; |
| 28 | if (p->size < sizeof(*p)) |
| 29 | return; |
| 30 | if (checksum(pos, p->size) != 0) |
| 31 | return; |
| 32 | void *newpos = malloc_fseg(p->size); |
| 33 | if (!newpos) { |
| 34 | warn_noalloc(); |
| 35 | return; |
| 36 | } |
| 37 | dprintf(1, "Copying PIR from %p to %p\n", pos, newpos); |
| 38 | memcpy(newpos, pos, p->size); |
Kevin O'Connor | bfa02cd | 2012-06-09 13:36:45 -0400 | [diff] [blame] | 39 | PirAddr = newpos; |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 40 | } |
| 41 | |
Kevin O'Connor | 4d053eb | 2012-06-09 13:08:02 -0400 | [diff] [blame] | 42 | static void |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 43 | copy_mptable(void *pos) |
| 44 | { |
| 45 | struct mptable_floating_s *p = pos; |
| 46 | if (p->signature != MPTABLE_SIGNATURE) |
| 47 | return; |
| 48 | if (!p->physaddr) |
| 49 | return; |
| 50 | if (checksum(pos, sizeof(*p)) != 0) |
| 51 | return; |
| 52 | u32 length = p->length * 16; |
| 53 | u16 mpclength = ((struct mptable_config_s *)p->physaddr)->length; |
| 54 | struct mptable_floating_s *newpos = malloc_fseg(length + mpclength); |
| 55 | if (!newpos) { |
| 56 | warn_noalloc(); |
| 57 | return; |
| 58 | } |
| 59 | dprintf(1, "Copying MPTABLE from %p/%x to %p\n", pos, p->physaddr, newpos); |
| 60 | memcpy(newpos, pos, length); |
| 61 | newpos->physaddr = (u32)newpos + length; |
| 62 | newpos->checksum -= checksum(newpos, sizeof(*newpos)); |
| 63 | memcpy((void*)newpos + length, (void*)p->physaddr, mpclength); |
| 64 | } |
| 65 | |
Kevin O'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 66 | |
| 67 | /**************************************************************** |
| 68 | * ACPI |
| 69 | ****************************************************************/ |
| 70 | |
Michael S. Tsirkin | ff5f792 | 2013-10-03 16:30:35 +0300 | [diff] [blame] | 71 | static int |
| 72 | get_acpi_rsdp_length(void *pos, unsigned size) |
| 73 | { |
| 74 | struct rsdp_descriptor *p = pos; |
| 75 | if (p->signature != RSDP_SIGNATURE) |
| 76 | return -1; |
| 77 | u32 length = 20; |
| 78 | if (length > size) |
| 79 | return -1; |
| 80 | if (checksum(pos, length) != 0) |
| 81 | return -1; |
| 82 | if (p->revision > 1) { |
| 83 | length = p->length; |
| 84 | if (length > size) |
| 85 | return -1; |
| 86 | if (checksum(pos, length) != 0) |
| 87 | return -1; |
| 88 | } |
| 89 | return length; |
| 90 | } |
| 91 | |
Kevin O'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 92 | struct rsdp_descriptor *RsdpAddr; |
| 93 | |
Kevin O'Connor | 4d053eb | 2012-06-09 13:08:02 -0400 | [diff] [blame] | 94 | static void |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 95 | copy_acpi_rsdp(void *pos) |
| 96 | { |
| 97 | if (RsdpAddr) |
| 98 | return; |
Michael S. Tsirkin | ff5f792 | 2013-10-03 16:30:35 +0300 | [diff] [blame] | 99 | int length = get_acpi_rsdp_length(pos, -1); |
| 100 | if (length < 0) |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 101 | return; |
Ian Campbell | 1442c31 | 2011-06-01 11:00:28 +0100 | [diff] [blame] | 102 | void *newpos = malloc_fseg(length); |
| 103 | if (!newpos) { |
| 104 | warn_noalloc(); |
| 105 | return; |
| 106 | } |
| 107 | dprintf(1, "Copying ACPI RSDP from %p to %p\n", pos, newpos); |
| 108 | memcpy(newpos, pos, length); |
| 109 | RsdpAddr = newpos; |
| 110 | } |
Ian Campbell | 74c7878 | 2011-06-01 11:00:29 +0100 | [diff] [blame] | 111 | |
Kevin O'Connor | 836b4d8 | 2014-04-07 15:42:04 -0400 | [diff] [blame] | 112 | void *find_acpi_rsdp(void) |
| 113 | { |
| 114 | extern u8 zonefseg_start[], zonefseg_end[]; |
| 115 | unsigned long start = (unsigned long)zonefseg_start; |
| 116 | unsigned long end = (unsigned long)zonefseg_end; |
| 117 | unsigned long pos; |
| 118 | |
| 119 | for (pos = ALIGN(start, 0x10); pos <= ALIGN_DOWN(end, 0x10); pos += 0x10) |
| 120 | if (get_acpi_rsdp_length((void *)pos, end - pos) >= 0) |
| 121 | return (void *)pos; |
| 122 | |
| 123 | return NULL; |
| 124 | } |
| 125 | |
| 126 | static struct fadt_descriptor_rev1 * |
| 127 | find_fadt(void) |
| 128 | { |
| 129 | dprintf(4, "rsdp=%p\n", RsdpAddr); |
| 130 | if (!RsdpAddr || RsdpAddr->signature != RSDP_SIGNATURE) |
| 131 | return NULL; |
| 132 | struct rsdt_descriptor_rev1 *rsdt = (void*)RsdpAddr->rsdt_physical_address; |
| 133 | dprintf(4, "rsdt=%p\n", rsdt); |
| 134 | if (!rsdt || rsdt->signature != RSDT_SIGNATURE) |
| 135 | return NULL; |
| 136 | void *end = (void*)rsdt + rsdt->length; |
| 137 | int i; |
| 138 | for (i=0; (void*)&rsdt->table_offset_entry[i] < end; i++) { |
| 139 | struct fadt_descriptor_rev1 *fadt = (void*)rsdt->table_offset_entry[i]; |
| 140 | if (!fadt || fadt->signature != FACP_SIGNATURE) |
| 141 | continue; |
| 142 | dprintf(4, "fadt=%p\n", fadt); |
| 143 | return fadt; |
| 144 | } |
| 145 | dprintf(4, "no fadt found\n"); |
| 146 | return NULL; |
| 147 | } |
| 148 | |
| 149 | u32 |
| 150 | find_resume_vector(void) |
| 151 | { |
| 152 | struct fadt_descriptor_rev1 *fadt = find_fadt(); |
| 153 | if (!fadt) |
| 154 | return 0; |
| 155 | struct facs_descriptor_rev1 *facs = (void*)fadt->firmware_ctrl; |
| 156 | dprintf(4, "facs=%p\n", facs); |
| 157 | if (! facs || facs->signature != FACS_SIGNATURE) |
| 158 | return 0; |
| 159 | // Found it. |
| 160 | dprintf(4, "resume addr=%d\n", facs->firmware_waking_vector); |
| 161 | return facs->firmware_waking_vector; |
| 162 | } |
| 163 | |
| 164 | static struct acpi_20_generic_address acpi_reset_reg; |
| 165 | static u8 acpi_reset_val; |
| 166 | u32 acpi_pm1a_cnt VARFSEG; |
| 167 | |
| 168 | #define acpi_ga_to_bdf(addr) pci_to_bdf(0, (addr >> 32) & 0xffff, (addr >> 16) & 0xffff) |
| 169 | |
| 170 | void |
| 171 | acpi_reboot(void) |
| 172 | { |
| 173 | // Check it passed the sanity checks in acpi_set_reset_reg() and was set |
| 174 | if (acpi_reset_reg.register_bit_width != 8) |
| 175 | return; |
| 176 | |
| 177 | u64 addr = le64_to_cpu(acpi_reset_reg.address); |
| 178 | |
| 179 | dprintf(1, "ACPI hard reset %d:%llx (%x)\n", |
| 180 | acpi_reset_reg.address_space_id, addr, acpi_reset_val); |
| 181 | |
| 182 | switch (acpi_reset_reg.address_space_id) { |
| 183 | case 0: // System Memory |
| 184 | writeb((void *)(u32)addr, acpi_reset_val); |
| 185 | break; |
| 186 | case 1: // System I/O |
| 187 | outb(acpi_reset_val, addr); |
| 188 | break; |
| 189 | case 2: // PCI config space |
| 190 | pci_config_writeb(acpi_ga_to_bdf(addr), addr & 0xffff, acpi_reset_val); |
| 191 | break; |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | static void |
| 196 | acpi_set_reset_reg(struct acpi_20_generic_address *reg, u8 val) |
| 197 | { |
| 198 | if (!reg || reg->address_space_id > 2 || |
| 199 | reg->register_bit_width != 8 || reg->register_bit_offset) |
| 200 | return; |
| 201 | |
| 202 | acpi_reset_reg = *reg; |
| 203 | acpi_reset_val = val; |
| 204 | } |
| 205 | |
| 206 | void |
| 207 | find_acpi_features(void) |
| 208 | { |
| 209 | struct fadt_descriptor_rev1 *fadt = find_fadt(); |
| 210 | if (!fadt) |
| 211 | return; |
| 212 | u32 pm_tmr = le32_to_cpu(fadt->pm_tmr_blk); |
| 213 | u32 pm1a_cnt = le32_to_cpu(fadt->pm1a_cnt_blk); |
| 214 | dprintf(4, "pm_tmr_blk=%x\n", pm_tmr); |
| 215 | if (pm_tmr) |
| 216 | pmtimer_setup(pm_tmr); |
| 217 | if (pm1a_cnt) |
| 218 | acpi_pm1a_cnt = pm1a_cnt; |
| 219 | |
| 220 | // Theoretically we should check the 'reset_reg_sup' flag, but Windows |
| 221 | // doesn't and thus nobody seems to *set* it. If the table is large enough |
| 222 | // to include it, let the sanity checks in acpi_set_reset_reg() suffice. |
| 223 | if (fadt->length >= 129) { |
| 224 | void *p = fadt; |
| 225 | acpi_set_reset_reg(p + 116, *(u8 *)(p + 128)); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | |
| 230 | /**************************************************************** |
| 231 | * SMBIOS |
| 232 | ****************************************************************/ |
| 233 | |
Kevin O'Connor | d18c9f0 | 2014-04-07 15:48:12 -0400 | [diff] [blame^] | 234 | struct smbios_entry_point *SMBiosAddr; |
| 235 | |
David Woodhouse | 38b24db | 2013-01-25 19:33:58 -0600 | [diff] [blame] | 236 | void |
Ian Campbell | 74c7878 | 2011-06-01 11:00:29 +0100 | [diff] [blame] | 237 | copy_smbios(void *pos) |
| 238 | { |
Kevin O'Connor | 83012de | 2011-08-28 12:42:15 -0400 | [diff] [blame] | 239 | if (SMBiosAddr) |
| 240 | return; |
Ian Campbell | 74c7878 | 2011-06-01 11:00:29 +0100 | [diff] [blame] | 241 | struct smbios_entry_point *p = pos; |
| 242 | if (memcmp(p->anchor_string, "_SM_", 4)) |
| 243 | return; |
| 244 | if (checksum(pos, 0x10) != 0) |
| 245 | return; |
| 246 | if (memcmp(p->intermediate_anchor_string, "_DMI_", 5)) |
| 247 | return; |
| 248 | if (checksum(pos+0x10, p->length-0x10) != 0) |
| 249 | return; |
Ian Campbell | ee2bc46 | 2011-06-14 15:22:09 +0100 | [diff] [blame] | 250 | struct smbios_entry_point *newpos = malloc_fseg(p->length); |
Ian Campbell | 74c7878 | 2011-06-01 11:00:29 +0100 | [diff] [blame] | 251 | if (!newpos) { |
| 252 | warn_noalloc(); |
| 253 | return; |
| 254 | } |
| 255 | dprintf(1, "Copying SMBIOS entry point from %p to %p\n", pos, newpos); |
| 256 | memcpy(newpos, pos, p->length); |
Kevin O'Connor | 83012de | 2011-08-28 12:42:15 -0400 | [diff] [blame] | 257 | SMBiosAddr = newpos; |
Ian Campbell | 74c7878 | 2011-06-01 11:00:29 +0100 | [diff] [blame] | 258 | } |
Kevin O'Connor | 4d053eb | 2012-06-09 13:08:02 -0400 | [diff] [blame] | 259 | |
| 260 | void |
Kevin O'Connor | d18c9f0 | 2014-04-07 15:48:12 -0400 | [diff] [blame^] | 261 | display_uuid(void) |
| 262 | { |
| 263 | u32 addr, end; |
| 264 | u8 *uuid; |
| 265 | u8 empty_uuid[16] = { 0 }; |
| 266 | |
| 267 | if (SMBiosAddr == NULL) |
| 268 | return; |
| 269 | |
| 270 | addr = SMBiosAddr->structure_table_address; |
| 271 | end = addr + SMBiosAddr->structure_table_length; |
| 272 | |
| 273 | /* the following takes care of any initial wraparound too */ |
| 274 | while (addr < end) { |
| 275 | const struct smbios_structure_header *hdr; |
| 276 | |
| 277 | /* partial structure header */ |
| 278 | if (end - addr < sizeof(struct smbios_structure_header)) |
| 279 | return; |
| 280 | |
| 281 | hdr = (struct smbios_structure_header *)addr; |
| 282 | |
| 283 | /* partial structure */ |
| 284 | if (end - addr < hdr->length) |
| 285 | return; |
| 286 | |
| 287 | /* any Type 1 structure version will do that has the UUID */ |
| 288 | if (hdr->type == 1 && |
| 289 | hdr->length >= offsetof(struct smbios_type_1, uuid) + 16) |
| 290 | break; |
| 291 | |
| 292 | /* done with formatted area, skip string-set */ |
| 293 | addr += hdr->length; |
| 294 | |
| 295 | while (end - addr >= 2 && |
| 296 | (*(u8 *)addr != '\0' || |
| 297 | *(u8 *)(addr+1) != '\0')) |
| 298 | ++addr; |
| 299 | |
| 300 | /* structure terminator not found */ |
| 301 | if (end - addr < 2) |
| 302 | return; |
| 303 | |
| 304 | addr += 2; |
| 305 | } |
| 306 | |
| 307 | /* parsing finished or skipped entirely, UUID not found */ |
| 308 | if (addr >= end) |
| 309 | return; |
| 310 | |
| 311 | uuid = (u8 *)(addr + offsetof(struct smbios_type_1, uuid)); |
| 312 | if (memcmp(uuid, empty_uuid, sizeof empty_uuid) == 0) |
| 313 | return; |
| 314 | |
| 315 | printf("Machine UUID" |
| 316 | " %02x%02x%02x%02x" |
| 317 | "-%02x%02x" |
| 318 | "-%02x%02x" |
| 319 | "-%02x%02x" |
| 320 | "-%02x%02x%02x%02x%02x%02x\n" |
| 321 | , uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3] |
| 322 | , uuid[ 4], uuid[ 5] |
| 323 | , uuid[ 6], uuid[ 7] |
| 324 | , uuid[ 8], uuid[ 9] |
| 325 | , uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); |
| 326 | } |
| 327 | |
| 328 | |
| 329 | void |
Kevin O'Connor | 4d053eb | 2012-06-09 13:08:02 -0400 | [diff] [blame] | 330 | copy_table(void *pos) |
| 331 | { |
| 332 | copy_pir(pos); |
| 333 | copy_mptable(pos); |
| 334 | copy_acpi_rsdp(pos); |
| 335 | copy_smbios(pos); |
| 336 | } |