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