Angel Pons | 585495e | 2020-04-03 01:21:38 +0200 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 2 | |
Kyösti Mälkki | 3695593 | 2019-03-03 17:43:29 +0200 | [diff] [blame] | 3 | #include <endian.h> |
Elyes HAOUAS | 70a03dd | 2019-12-02 20:47:50 +0100 | [diff] [blame] | 4 | #include <stdlib.h> |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 5 | #include <string.h> |
Gerd Hoffmann | e851a68 | 2013-11-13 12:56:30 +0100 | [diff] [blame] | 6 | #include <smbios.h> |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 7 | #include <console/console.h> |
| 8 | #include <arch/io.h> |
Furquan Shaikh | 76cedd2 | 2020-05-02 10:24:23 -0700 | [diff] [blame] | 9 | #include <acpi/acpi.h> |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 10 | #include <commonlib/endian.h> |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 11 | |
| 12 | #include "fw_cfg.h" |
| 13 | #include "fw_cfg_if.h" |
| 14 | |
| 15 | #define FW_CFG_PORT_CTL 0x0510 |
| 16 | #define FW_CFG_PORT_DATA 0x0511 |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 17 | #define FW_CFG_DMA_ADDR_HIGH 0x0514 |
| 18 | #define FW_CFG_DMA_ADDR_LOW 0x0518 |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 19 | |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 20 | static int fw_cfg_detected; |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 21 | static uint8_t fw_ver; |
| 22 | |
| 23 | static void fw_cfg_dma(int control, void *buf, int len); |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 24 | |
| 25 | static int fw_cfg_present(void) |
| 26 | { |
| 27 | static const char qsig[] = "QEMU"; |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 28 | unsigned char sig[FW_CFG_SIG_SIZE]; |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 29 | int detected = 0; |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 30 | |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 31 | if (fw_cfg_detected == 0) { |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 32 | fw_cfg_get(FW_CFG_SIGNATURE, sig, sizeof(sig)); |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 33 | detected = memcmp(sig, qsig, FW_CFG_SIG_SIZE) == 0; |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 34 | printk(BIOS_INFO, "QEMU: firmware config interface %s\n", |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 35 | detected ? "detected" : "not found"); |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 36 | if (detected) { |
| 37 | fw_cfg_get(FW_CFG_ID, &fw_ver, sizeof(fw_ver)); |
| 38 | printk(BIOS_INFO, "Firmware config version id: %d\n", fw_ver); |
| 39 | } |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 40 | fw_cfg_detected = detected + 1; |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 41 | } |
Thomas Heijligen | 0065b69 | 2019-01-10 19:42:21 +0100 | [diff] [blame] | 42 | return fw_cfg_detected - 1; |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 43 | } |
| 44 | |
Thomas Heijligen | a05f8a9 | 2019-01-10 12:55:59 +0100 | [diff] [blame] | 45 | static void fw_cfg_select(uint16_t entry) |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 46 | { |
| 47 | outw(entry, FW_CFG_PORT_CTL); |
Thomas Heijligen | a05f8a9 | 2019-01-10 12:55:59 +0100 | [diff] [blame] | 48 | } |
| 49 | |
| 50 | static void fw_cfg_read(void *dst, int dstlen) |
| 51 | { |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 52 | if (fw_ver & FW_CFG_VERSION_DMA) |
| 53 | fw_cfg_dma(FW_CFG_DMA_CTL_READ, dst, dstlen); |
| 54 | else |
| 55 | insb(FW_CFG_PORT_DATA, dst, dstlen); |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 56 | } |
| 57 | |
Thomas Heijligen | a05f8a9 | 2019-01-10 12:55:59 +0100 | [diff] [blame] | 58 | void fw_cfg_get(uint16_t entry, void *dst, int dstlen) |
| 59 | { |
| 60 | fw_cfg_select(entry); |
| 61 | fw_cfg_read(dst, dstlen); |
| 62 | } |
| 63 | |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 64 | static int fw_cfg_find_file(FWCfgFile *file, const char *name) |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 65 | { |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 66 | uint32_t count = 0; |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 67 | |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 68 | fw_cfg_select(FW_CFG_FILE_DIR); |
| 69 | fw_cfg_read(&count, sizeof(count)); |
| 70 | count = be32_to_cpu(count); |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 71 | |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 72 | for (int i = 0; i < count; i++) { |
| 73 | fw_cfg_read(file, sizeof(*file)); |
| 74 | if (strcmp(file->name, name) == 0) { |
| 75 | file->size = be32_to_cpu(file->size); |
| 76 | file->select = be16_to_cpu(file->select); |
Nico Huber | ef59c93 | 2020-07-14 12:19:07 +0200 | [diff] [blame] | 77 | printk(BIOS_INFO, "QEMU: firmware config: Found '%s'\n", name); |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 78 | return 0; |
| 79 | } |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 80 | } |
Nico Huber | ef59c93 | 2020-07-14 12:19:07 +0200 | [diff] [blame] | 81 | printk(BIOS_INFO, "QEMU: firmware config: Couldn't find '%s'\n", name); |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 82 | return -1; |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 83 | } |
| 84 | |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 85 | int fw_cfg_check_file(FWCfgFile *file, const char *name) |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 86 | { |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 87 | if (!fw_cfg_present()) |
| 88 | return -1; |
Thomas Heijligen | 721c8b4 | 2019-01-10 17:39:00 +0100 | [diff] [blame] | 89 | return fw_cfg_find_file(file, name); |
Gerd Hoffmann | 289b45f | 2013-06-12 10:18:58 +0200 | [diff] [blame] | 90 | } |
| 91 | |
Thomas Heijligen | da9aa6d | 2019-01-10 18:19:06 +0100 | [diff] [blame] | 92 | static int fw_cfg_e820_select(uint32_t *size) |
| 93 | { |
| 94 | FWCfgFile file; |
| 95 | |
| 96 | if (!fw_cfg_present() || fw_cfg_find_file(&file, "etc/e820")) |
| 97 | return -1; |
| 98 | fw_cfg_select(file.select); |
| 99 | *size = file.size; |
| 100 | return 0; |
| 101 | } |
| 102 | |
| 103 | static int fw_cfg_e820_read(FwCfgE820Entry *entry, uint32_t *size, |
| 104 | uint32_t *pos) |
| 105 | { |
| 106 | if (*pos + sizeof(*entry) > *size) |
| 107 | return -1; |
| 108 | |
| 109 | fw_cfg_read(entry, sizeof(*entry)); |
| 110 | *pos += sizeof(*entry); |
| 111 | return 0; |
| 112 | } |
| 113 | |
| 114 | /* returns tolud on success or 0 on failure */ |
| 115 | uintptr_t fw_cfg_tolud(void) |
| 116 | { |
| 117 | FwCfgE820Entry e; |
| 118 | uint64_t top = 0; |
| 119 | uint32_t size = 0, pos = 0; |
| 120 | |
Nico Huber | d2245d8 | 2020-07-14 12:21:19 +0200 | [diff] [blame] | 121 | if (fw_cfg_e820_select(&size) == 0) { |
Thomas Heijligen | da9aa6d | 2019-01-10 18:19:06 +0100 | [diff] [blame] | 122 | while (!fw_cfg_e820_read(&e, &size, &pos)) { |
| 123 | uint64_t limit = e.address + e.length; |
| 124 | if (e.type == 1 && limit < 4ULL * GiB && limit > top) |
| 125 | top = limit; |
| 126 | } |
| 127 | } |
| 128 | return (uintptr_t)top; |
| 129 | } |
| 130 | |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 131 | int fw_cfg_max_cpus(void) |
| 132 | { |
| 133 | unsigned short max_cpus; |
| 134 | |
| 135 | if (!fw_cfg_present()) |
Angel Pons | d16d00b | 2021-11-03 16:42:48 +0100 | [diff] [blame] | 136 | return 0; |
Gerd Hoffmann | 2b96203 | 2013-05-30 15:41:48 +0200 | [diff] [blame] | 137 | |
| 138 | fw_cfg_get(FW_CFG_MAX_CPUS, &max_cpus, sizeof(max_cpus)); |
| 139 | return max_cpus; |
| 140 | } |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 141 | |
| 142 | /* ---------------------------------------------------------------------- */ |
Gerd Hoffmann | 590e8d4 | 2013-12-03 09:40:39 +0100 | [diff] [blame] | 143 | |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 144 | /* |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 145 | * Starting with release 1.7 qemu provides ACPI tables via fw_cfg. |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 146 | * Main advantage is that new (virtual) hardware which needs acpi |
| 147 | * support JustWorks[tm] without having to patch & update the firmware |
| 148 | * (seabios, coreboot, ...) accordingly. |
| 149 | * |
| 150 | * Qemu provides a etc/table-loader file with instructions for the |
| 151 | * firmware: |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 152 | * - A "load" instruction to fetch ACPI data from fw_cfg. |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 153 | * - A "pointer" instruction to patch a pointer. This is needed to |
| 154 | * get table-to-table references right, it is basically a |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 155 | * primitive dynamic linker for ACPI tables. |
| 156 | * - A "checksum" instruction to generate ACPI table checksums. |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 157 | * |
| 158 | * If a etc/table-loader file is found we'll go try loading the acpi |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 159 | * tables from fw_cfg, otherwise we'll fallback to the ACPI tables |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 160 | * compiled in. |
| 161 | */ |
| 162 | |
| 163 | #define BIOS_LINKER_LOADER_FILESZ 56 |
| 164 | |
| 165 | struct BiosLinkerLoaderEntry { |
| 166 | uint32_t command; |
| 167 | union { |
| 168 | /* |
| 169 | * COMMAND_ALLOCATE - allocate a table from @alloc.file |
| 170 | * subject to @alloc.align alignment (must be power of 2) |
| 171 | * and @alloc.zone (can be HIGH or FSEG) requirements. |
| 172 | * |
| 173 | * Must appear exactly once for each file, and before |
| 174 | * this file is referenced by any other command. |
| 175 | */ |
| 176 | struct { |
| 177 | char file[BIOS_LINKER_LOADER_FILESZ]; |
| 178 | uint32_t align; |
| 179 | uint8_t zone; |
| 180 | } alloc; |
| 181 | |
| 182 | /* |
| 183 | * COMMAND_ADD_POINTER - patch the table (originating from |
| 184 | * @dest_file) at @pointer.offset, by adding a pointer to the table |
| 185 | * originating from @src_file. 1,2,4 or 8 byte unsigned |
| 186 | * addition is used depending on @pointer.size. |
| 187 | */ |
| 188 | struct { |
| 189 | char dest_file[BIOS_LINKER_LOADER_FILESZ]; |
| 190 | char src_file[BIOS_LINKER_LOADER_FILESZ]; |
| 191 | uint32_t offset; |
| 192 | uint8_t size; |
| 193 | } pointer; |
| 194 | |
| 195 | /* |
| 196 | * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by |
| 197 | * @cksum_start and @cksum_length fields, |
| 198 | * and then add the value at @cksum.offset. |
| 199 | * Checksum simply sums -X for each byte X in the range |
| 200 | * using 8-bit math. |
| 201 | */ |
| 202 | struct { |
| 203 | char file[BIOS_LINKER_LOADER_FILESZ]; |
| 204 | uint32_t offset; |
| 205 | uint32_t start; |
| 206 | uint32_t length; |
| 207 | } cksum; |
| 208 | |
| 209 | /* padding */ |
| 210 | char pad[124]; |
| 211 | }; |
Stefan Reinauer | 6a00113 | 2017-07-13 02:20:27 +0200 | [diff] [blame] | 212 | } __packed; |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 213 | typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry; |
| 214 | |
| 215 | enum { |
| 216 | BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, |
| 217 | BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, |
| 218 | BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, |
| 219 | }; |
| 220 | |
| 221 | enum { |
| 222 | BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, |
| 223 | BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, |
| 224 | }; |
| 225 | |
| 226 | unsigned long fw_cfg_acpi_tables(unsigned long start) |
| 227 | { |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 228 | FWCfgFile f; |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 229 | BiosLinkerLoaderEntry *s; |
| 230 | unsigned long *addrs, current; |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 231 | uint8_t *ptr; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 232 | int i, j, src, dst, max; |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 233 | |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 234 | if (fw_cfg_check_file(&f, "etc/table-loader")) |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 235 | return 0; |
| 236 | |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 237 | printk(BIOS_DEBUG, "QEMU: found ACPI tables in fw_cfg.\n"); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 238 | |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 239 | max = f.size / sizeof(*s); |
| 240 | s = malloc(f.size); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 241 | addrs = malloc(max * sizeof(*addrs)); |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 242 | fw_cfg_get(f.select, s, f.size); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 243 | |
| 244 | current = start; |
| 245 | for (i = 0; i < max && s[i].command != 0; i++) { |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 246 | void *cksum_data; |
| 247 | uint32_t cksum; |
| 248 | uint32_t addr4; |
| 249 | uint64_t addr8; |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 250 | switch (s[i].command) { |
| 251 | case BIOS_LINKER_LOADER_COMMAND_ALLOCATE: |
| 252 | current = ALIGN(current, s[i].alloc.align); |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 253 | if (fw_cfg_check_file(&f, s[i].alloc.file)) |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 254 | goto err; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 255 | |
Gerd Hoffmann | acfe1e5 | 2014-08-27 13:21:43 +0200 | [diff] [blame] | 256 | printk(BIOS_DEBUG, "QEMU: loading \"%s\" to 0x%lx (len %d)\n", |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 257 | s[i].alloc.file, current, f.size); |
Patrick Rudolph | 87abccd | 2019-02-23 11:45:01 +0100 | [diff] [blame] | 258 | fw_cfg_get(f.select, (void *)current, f.size); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 259 | addrs[i] = current; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 260 | current += f.size; |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 261 | break; |
| 262 | |
| 263 | case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER: |
| 264 | src = -1; dst = -1; |
| 265 | for (j = 0; j < i; j++) { |
| 266 | if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE) |
| 267 | continue; |
| 268 | if (strcmp(s[j].alloc.file, s[i].pointer.dest_file) == 0) |
| 269 | dst = j; |
| 270 | if (strcmp(s[j].alloc.file, s[i].pointer.src_file) == 0) |
| 271 | src = j; |
| 272 | } |
| 273 | if (src == -1 || dst == -1) |
| 274 | goto err; |
| 275 | |
| 276 | switch (s[i].pointer.size) { |
| 277 | case 4: |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 278 | ptr = (uint8_t *)addrs[dst]; |
| 279 | ptr += s[i].pointer.offset; |
| 280 | addr4 = read_le32(ptr); |
| 281 | addr4 += addrs[src]; |
| 282 | write_le32(ptr, addr4); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 283 | break; |
| 284 | |
| 285 | case 8: |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 286 | ptr = (uint8_t *)addrs[dst]; |
| 287 | ptr += s[i].pointer.offset; |
| 288 | addr8 = read_le64(ptr); |
| 289 | addr8 += addrs[src]; |
| 290 | write_le64(ptr, addr8); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 291 | break; |
| 292 | |
| 293 | default: |
| 294 | /* |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 295 | * Should not happen. ACPI knows 1 and 2 byte ptrs |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 296 | * too, but we are operating with 32bit offsets which |
| 297 | * would simply not fit in there ... |
| 298 | */ |
| 299 | printk(BIOS_DEBUG, "QEMU: acpi: unimplemented ptr size %d\n", |
| 300 | s[i].pointer.size); |
| 301 | goto err; |
| 302 | } |
| 303 | break; |
| 304 | |
| 305 | case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM: |
| 306 | dst = -1; |
| 307 | for (j = 0; j < i; j++) { |
| 308 | if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE) |
| 309 | continue; |
| 310 | if (strcmp(s[j].alloc.file, s[i].cksum.file) == 0) |
| 311 | dst = j; |
| 312 | } |
| 313 | if (dst == -1) |
| 314 | goto err; |
| 315 | |
Ryan Salsamendi | d4f994b | 2017-06-11 19:39:06 -0700 | [diff] [blame] | 316 | ptr = (uint8_t *)(addrs[dst] + s[i].cksum.offset); |
| 317 | cksum_data = (void *)(addrs[dst] + s[i].cksum.start); |
| 318 | cksum = acpi_checksum(cksum_data, s[i].cksum.length); |
Oleksii Kurochko | 4886a65 | 2018-04-20 15:38:57 +0300 | [diff] [blame] | 319 | write_le8(ptr, cksum); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 320 | break; |
| 321 | |
| 322 | default: |
| 323 | printk(BIOS_DEBUG, "QEMU: acpi: unknown script cmd 0x%x @ %p\n", |
| 324 | s[i].command, s+i); |
| 325 | goto err; |
| 326 | }; |
| 327 | } |
| 328 | |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 329 | printk(BIOS_DEBUG, "QEMU: loaded ACPI tables from fw_cfg.\n"); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 330 | free(s); |
| 331 | free(addrs); |
| 332 | return ALIGN(current, 16); |
| 333 | |
| 334 | err: |
Elyes HAOUAS | 8ab989e | 2016-07-30 17:46:17 +0200 | [diff] [blame] | 335 | printk(BIOS_DEBUG, "QEMU: loading ACPI tables from fw_cfg failed.\n"); |
Gerd Hoffmann | d69da84 | 2013-07-25 15:59:07 +0200 | [diff] [blame] | 336 | free(s); |
| 337 | free(addrs); |
| 338 | return 0; |
| 339 | } |
Gerd Hoffmann | e851a68 | 2013-11-13 12:56:30 +0100 | [diff] [blame] | 340 | |
| 341 | /* ---------------------------------------------------------------------- */ |
| 342 | /* pick up smbios information from fw_cfg */ |
| 343 | |
| 344 | static const char *type1_manufacturer; |
| 345 | static const char *type1_product_name; |
| 346 | static const char *type1_version; |
| 347 | static const char *type1_serial_number; |
| 348 | static const char *type1_family; |
| 349 | static u8 type1_uuid[16]; |
| 350 | |
| 351 | static void fw_cfg_smbios_init(void) |
| 352 | { |
| 353 | static int done = 0; |
| 354 | uint16_t i, count = 0; |
| 355 | FwCfgSmbios entry; |
| 356 | char *buf; |
| 357 | |
| 358 | if (done) |
| 359 | return; |
| 360 | done = 1; |
| 361 | |
| 362 | fw_cfg_get(FW_CFG_SMBIOS_ENTRIES, &count, sizeof(count)); |
| 363 | for (i = 0; i < count; i++) { |
| 364 | insb(FW_CFG_PORT_DATA, &entry, sizeof(entry)); |
| 365 | buf = malloc(entry.length - sizeof(entry)); |
| 366 | insb(FW_CFG_PORT_DATA, buf, entry.length - sizeof(entry)); |
| 367 | if (entry.headertype == SMBIOS_FIELD_ENTRY && |
| 368 | entry.tabletype == 1) { |
| 369 | switch (entry.fieldoffset) { |
| 370 | case offsetof(struct smbios_type1, manufacturer): |
| 371 | type1_manufacturer = strdup(buf); |
| 372 | break; |
| 373 | case offsetof(struct smbios_type1, product_name): |
| 374 | type1_product_name = strdup(buf); |
| 375 | break; |
| 376 | case offsetof(struct smbios_type1, version): |
| 377 | type1_version = strdup(buf); |
| 378 | break; |
| 379 | case offsetof(struct smbios_type1, serial_number): |
| 380 | type1_serial_number = strdup(buf); |
| 381 | break; |
| 382 | case offsetof(struct smbios_type1, family): |
| 383 | type1_family = strdup(buf); |
| 384 | break; |
| 385 | case offsetof(struct smbios_type1, uuid): |
| 386 | memcpy(type1_uuid, buf, 16); |
| 387 | break; |
| 388 | } |
| 389 | } |
| 390 | free(buf); |
| 391 | } |
| 392 | } |
| 393 | |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 394 | static unsigned long smbios_next(unsigned long current) |
| 395 | { |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 396 | struct smbios_header *header; |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 397 | int l, count = 0; |
| 398 | char *s; |
| 399 | |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 400 | header = (void *)current; |
| 401 | current += header->length; |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 402 | for (;;) { |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 403 | s = (void *)current; |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 404 | l = strlen(s); |
| 405 | if (!l) |
| 406 | return current + (count ? 1 : 2); |
| 407 | current += l + 1; |
| 408 | count++; |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | /* |
| 413 | * Starting with version 2.1 qemu provides a full set of smbios tables |
| 414 | * for the virtual hardware emulated, except type 0 (bios information). |
| 415 | * |
| 416 | * What we are going to do here is find the type0 table, keep it, and |
| 417 | * override everything else generated by coreboot with the qemu smbios |
| 418 | * tables. |
| 419 | * |
| 420 | * It's a bit hackish, but qemu is a special case (compared to real |
| 421 | * hardware) and this way we don't need special qemu support in the |
| 422 | * generic smbios code. |
| 423 | */ |
| 424 | unsigned long fw_cfg_smbios_tables(int *handle, unsigned long *current) |
| 425 | { |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 426 | FWCfgFile f; |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 427 | struct smbios_header *header; |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 428 | unsigned long start, end; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 429 | int ret, i, count = 1; |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 430 | char *str; |
| 431 | |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 432 | if (fw_cfg_check_file(&f, "etc/smbios/smbios-tables")) |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 433 | return 0; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 434 | |
| 435 | printk(BIOS_DEBUG, "QEMU: found smbios tables in fw_cfg (len %d).\n", f.size); |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 436 | |
| 437 | /* |
| 438 | * Search backwards for "coreboot" (first string in type0 table, |
| 439 | * see src/arch/x86/boot/smbios.c), then find type0 table. |
| 440 | */ |
| 441 | for (i = 0; i < 16384; i++) { |
| 442 | str = (char*)(*current - i); |
| 443 | if (strcmp(str, "coreboot") == 0) |
| 444 | break; |
| 445 | } |
| 446 | if (i == 16384) |
| 447 | return 0; |
| 448 | i += sizeof(struct smbios_type0) - 2; |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 449 | header = (struct smbios_header *)(*current - i); |
| 450 | if (header->type != SMBIOS_BIOS_INFORMATION || header->handle != 0) |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 451 | return 0; |
| 452 | printk(BIOS_DEBUG, "QEMU: coreboot type0 table found at 0x%lx.\n", |
| 453 | *current - i); |
| 454 | start = smbios_next(*current - i); |
| 455 | |
| 456 | /* |
| 457 | * Fetch smbios tables from qemu, go find the end marker. |
| 458 | * We'll exclude the end marker as coreboot will add one. |
| 459 | */ |
| 460 | printk(BIOS_DEBUG, "QEMU: loading smbios tables to 0x%lx\n", start); |
Patrick Rudolph | 87abccd | 2019-02-23 11:45:01 +0100 | [diff] [blame] | 461 | fw_cfg_get(f.select, (void *)start, f.size); |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 462 | end = start; |
| 463 | do { |
Angel Pons | fff1b2f | 2021-06-28 19:09:22 +0200 | [diff] [blame] | 464 | header = (struct smbios_header *)end; |
| 465 | if (header->type == SMBIOS_END_OF_TABLE) |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 466 | break; |
| 467 | end = smbios_next(end); |
| 468 | count++; |
Thomas Heijligen | bcd84fe | 2019-01-10 16:53:34 +0100 | [diff] [blame] | 469 | } while (end < start + f.size); |
Gerd Hoffmann | db9d169 | 2014-08-27 11:25:13 +0200 | [diff] [blame] | 470 | |
| 471 | /* final fixups. */ |
| 472 | ret = end - *current; |
| 473 | *current = end; |
| 474 | *handle = count; |
| 475 | return ret; |
| 476 | } |
| 477 | |
Gerd Hoffmann | e851a68 | 2013-11-13 12:56:30 +0100 | [diff] [blame] | 478 | const char *smbios_mainboard_manufacturer(void) |
| 479 | { |
| 480 | fw_cfg_smbios_init(); |
| 481 | return type1_manufacturer ?: CONFIG_MAINBOARD_SMBIOS_MANUFACTURER; |
| 482 | } |
| 483 | |
| 484 | const char *smbios_mainboard_product_name(void) |
| 485 | { |
| 486 | fw_cfg_smbios_init(); |
| 487 | return type1_product_name ?: CONFIG_MAINBOARD_SMBIOS_PRODUCT_NAME; |
| 488 | } |
| 489 | |
| 490 | const char *smbios_mainboard_version(void) |
| 491 | { |
| 492 | fw_cfg_smbios_init(); |
| 493 | return type1_version ?: CONFIG_MAINBOARD_VERSION; |
| 494 | } |
| 495 | |
| 496 | const char *smbios_mainboard_serial_number(void) |
| 497 | { |
| 498 | fw_cfg_smbios_init(); |
| 499 | return type1_serial_number ?: CONFIG_MAINBOARD_SERIAL_NUMBER; |
| 500 | } |
| 501 | |
Nico Huber | ebd8a4f | 2017-11-01 09:49:16 +0100 | [diff] [blame] | 502 | void smbios_system_set_uuid(u8 *uuid) |
Gerd Hoffmann | e851a68 | 2013-11-13 12:56:30 +0100 | [diff] [blame] | 503 | { |
| 504 | fw_cfg_smbios_init(); |
| 505 | memcpy(uuid, type1_uuid, 16); |
| 506 | } |
Himanshu Sahdev | 8dc95dd | 2019-09-12 12:02:54 +0530 | [diff] [blame] | 507 | |
| 508 | /* |
| 509 | * Configure DMA setup |
| 510 | */ |
| 511 | |
| 512 | static void fw_cfg_dma(int control, void *buf, int len) |
| 513 | { |
| 514 | volatile FwCfgDmaAccess dma; |
| 515 | uintptr_t dma_desc_addr; |
| 516 | uint32_t dma_desc_addr_hi, dma_desc_addr_lo; |
| 517 | |
| 518 | dma.control = be32_to_cpu(control); |
| 519 | dma.length = be32_to_cpu(len); |
| 520 | dma.address = be64_to_cpu((uintptr_t)buf); |
| 521 | |
| 522 | dma_desc_addr = (uintptr_t)&dma; |
| 523 | dma_desc_addr_lo = (uint32_t)(dma_desc_addr & 0xFFFFFFFFU); |
| 524 | dma_desc_addr_hi = sizeof(uintptr_t) > sizeof(uint32_t) |
| 525 | ? (uint32_t)(dma_desc_addr >> 32) : 0; |
| 526 | |
| 527 | // Skip writing high half if unnecessary. |
| 528 | if (dma_desc_addr_hi) |
| 529 | outl(be32_to_cpu(dma_desc_addr_hi), FW_CFG_DMA_ADDR_HIGH); |
| 530 | outl(be32_to_cpu(dma_desc_addr_lo), FW_CFG_DMA_ADDR_LOW); |
| 531 | |
| 532 | while (be32_to_cpu(dma.control) & ~FW_CFG_DMA_CTL_ERROR) |
| 533 | ; |
| 534 | } |