| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* This file is part of the coreboot project. */ |
| |
| #include <endian.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <smbios.h> |
| #include <console/console.h> |
| #include <arch/io.h> |
| #include <arch/acpi.h> |
| #include <commonlib/endian.h> |
| |
| #include "fw_cfg.h" |
| #include "fw_cfg_if.h" |
| |
| #define FW_CFG_PORT_CTL 0x0510 |
| #define FW_CFG_PORT_DATA 0x0511 |
| #define FW_CFG_DMA_ADDR_HIGH 0x0514 |
| #define FW_CFG_DMA_ADDR_LOW 0x0518 |
| |
| static int fw_cfg_detected; |
| static uint8_t fw_ver; |
| |
| static void fw_cfg_dma(int control, void *buf, int len); |
| |
| static int fw_cfg_present(void) |
| { |
| static const char qsig[] = "QEMU"; |
| unsigned char sig[FW_CFG_SIG_SIZE]; |
| int detected = 0; |
| |
| if (fw_cfg_detected == 0) { |
| fw_cfg_get(FW_CFG_SIGNATURE, sig, sizeof(sig)); |
| detected = memcmp(sig, qsig, FW_CFG_SIG_SIZE) == 0; |
| printk(BIOS_INFO, "QEMU: firmware config interface %s\n", |
| detected ? "detected" : "not found"); |
| if (detected) { |
| fw_cfg_get(FW_CFG_ID, &fw_ver, sizeof(fw_ver)); |
| printk(BIOS_INFO, "Firmware config version id: %d\n", fw_ver); |
| } |
| fw_cfg_detected = detected + 1; |
| } |
| return fw_cfg_detected - 1; |
| } |
| |
| static void fw_cfg_select(uint16_t entry) |
| { |
| outw(entry, FW_CFG_PORT_CTL); |
| } |
| |
| static void fw_cfg_read(void *dst, int dstlen) |
| { |
| if (fw_ver & FW_CFG_VERSION_DMA) |
| fw_cfg_dma(FW_CFG_DMA_CTL_READ, dst, dstlen); |
| else |
| insb(FW_CFG_PORT_DATA, dst, dstlen); |
| } |
| |
| void fw_cfg_get(uint16_t entry, void *dst, int dstlen) |
| { |
| fw_cfg_select(entry); |
| fw_cfg_read(dst, dstlen); |
| } |
| |
| static int fw_cfg_find_file(FWCfgFile *file, const char *name) |
| { |
| uint32_t count = 0; |
| |
| fw_cfg_select(FW_CFG_FILE_DIR); |
| fw_cfg_read(&count, sizeof(count)); |
| count = be32_to_cpu(count); |
| |
| for (int i = 0; i < count; i++) { |
| fw_cfg_read(file, sizeof(*file)); |
| if (strcmp(file->name, name) == 0) { |
| file->size = be32_to_cpu(file->size); |
| file->select = be16_to_cpu(file->select); |
| return 0; |
| } |
| } |
| return -1; |
| } |
| |
| int fw_cfg_check_file(FWCfgFile *file, const char *name) |
| { |
| if (!fw_cfg_present()) |
| return -1; |
| return fw_cfg_find_file(file, name); |
| } |
| |
| static int fw_cfg_e820_select(uint32_t *size) |
| { |
| FWCfgFile file; |
| |
| if (!fw_cfg_present() || fw_cfg_find_file(&file, "etc/e820")) |
| return -1; |
| fw_cfg_select(file.select); |
| *size = file.size; |
| return 0; |
| } |
| |
| static int fw_cfg_e820_read(FwCfgE820Entry *entry, uint32_t *size, |
| uint32_t *pos) |
| { |
| if (*pos + sizeof(*entry) > *size) |
| return -1; |
| |
| fw_cfg_read(entry, sizeof(*entry)); |
| *pos += sizeof(*entry); |
| return 0; |
| } |
| |
| /* returns tolud on success or 0 on failure */ |
| uintptr_t fw_cfg_tolud(void) |
| { |
| FwCfgE820Entry e; |
| uint64_t top = 0; |
| uint32_t size = 0, pos = 0; |
| |
| if (fw_cfg_e820_select(&size)) { |
| while (!fw_cfg_e820_read(&e, &size, &pos)) { |
| uint64_t limit = e.address + e.length; |
| if (e.type == 1 && limit < 4ULL * GiB && limit > top) |
| top = limit; |
| } |
| } |
| return (uintptr_t)top; |
| } |
| |
| int fw_cfg_max_cpus(void) |
| { |
| unsigned short max_cpus; |
| |
| if (!fw_cfg_present()) |
| return -1; |
| |
| fw_cfg_get(FW_CFG_MAX_CPUS, &max_cpus, sizeof(max_cpus)); |
| return max_cpus; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| /* |
| * Starting with release 1.7 qemu provides ACPI tables via fw_cfg. |
| * Main advantage is that new (virtual) hardware which needs acpi |
| * support JustWorks[tm] without having to patch & update the firmware |
| * (seabios, coreboot, ...) accordingly. |
| * |
| * Qemu provides a etc/table-loader file with instructions for the |
| * firmware: |
| * - A "load" instruction to fetch ACPI data from fw_cfg. |
| * - A "pointer" instruction to patch a pointer. This is needed to |
| * get table-to-table references right, it is basically a |
| * primitive dynamic linker for ACPI tables. |
| * - A "checksum" instruction to generate ACPI table checksums. |
| * |
| * If a etc/table-loader file is found we'll go try loading the acpi |
| * tables from fw_cfg, otherwise we'll fallback to the ACPI tables |
| * compiled in. |
| */ |
| |
| #define BIOS_LINKER_LOADER_FILESZ 56 |
| |
| struct BiosLinkerLoaderEntry { |
| uint32_t command; |
| union { |
| /* |
| * COMMAND_ALLOCATE - allocate a table from @alloc.file |
| * subject to @alloc.align alignment (must be power of 2) |
| * and @alloc.zone (can be HIGH or FSEG) requirements. |
| * |
| * Must appear exactly once for each file, and before |
| * this file is referenced by any other command. |
| */ |
| struct { |
| char file[BIOS_LINKER_LOADER_FILESZ]; |
| uint32_t align; |
| uint8_t zone; |
| } alloc; |
| |
| /* |
| * COMMAND_ADD_POINTER - patch the table (originating from |
| * @dest_file) at @pointer.offset, by adding a pointer to the table |
| * originating from @src_file. 1,2,4 or 8 byte unsigned |
| * addition is used depending on @pointer.size. |
| */ |
| struct { |
| char dest_file[BIOS_LINKER_LOADER_FILESZ]; |
| char src_file[BIOS_LINKER_LOADER_FILESZ]; |
| uint32_t offset; |
| uint8_t size; |
| } pointer; |
| |
| /* |
| * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by |
| * @cksum_start and @cksum_length fields, |
| * and then add the value at @cksum.offset. |
| * Checksum simply sums -X for each byte X in the range |
| * using 8-bit math. |
| */ |
| struct { |
| char file[BIOS_LINKER_LOADER_FILESZ]; |
| uint32_t offset; |
| uint32_t start; |
| uint32_t length; |
| } cksum; |
| |
| /* padding */ |
| char pad[124]; |
| }; |
| } __packed; |
| typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry; |
| |
| enum { |
| BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1, |
| BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2, |
| BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3, |
| }; |
| |
| enum { |
| BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1, |
| BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2, |
| }; |
| |
| unsigned long fw_cfg_acpi_tables(unsigned long start) |
| { |
| FWCfgFile f; |
| BiosLinkerLoaderEntry *s; |
| unsigned long *addrs, current; |
| uint8_t *ptr; |
| int i, j, src, dst, max; |
| |
| if (fw_cfg_check_file(&f, "etc/table-loader")) |
| return 0; |
| |
| printk(BIOS_DEBUG, "QEMU: found ACPI tables in fw_cfg.\n"); |
| |
| max = f.size / sizeof(*s); |
| s = malloc(f.size); |
| addrs = malloc(max * sizeof(*addrs)); |
| fw_cfg_get(f.select, s, f.size); |
| |
| current = start; |
| for (i = 0; i < max && s[i].command != 0; i++) { |
| void *cksum_data; |
| uint32_t cksum; |
| uint32_t addr4; |
| uint64_t addr8; |
| switch (s[i].command) { |
| case BIOS_LINKER_LOADER_COMMAND_ALLOCATE: |
| current = ALIGN(current, s[i].alloc.align); |
| if (fw_cfg_check_file(&f, s[i].alloc.file)) |
| goto err; |
| |
| printk(BIOS_DEBUG, "QEMU: loading \"%s\" to 0x%lx (len %d)\n", |
| s[i].alloc.file, current, f.size); |
| fw_cfg_get(f.select, (void *)current, f.size); |
| addrs[i] = current; |
| current += f.size; |
| break; |
| |
| case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER: |
| src = -1; dst = -1; |
| for (j = 0; j < i; j++) { |
| if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE) |
| continue; |
| if (strcmp(s[j].alloc.file, s[i].pointer.dest_file) == 0) |
| dst = j; |
| if (strcmp(s[j].alloc.file, s[i].pointer.src_file) == 0) |
| src = j; |
| } |
| if (src == -1 || dst == -1) |
| goto err; |
| |
| switch (s[i].pointer.size) { |
| case 4: |
| ptr = (uint8_t *)addrs[dst]; |
| ptr += s[i].pointer.offset; |
| addr4 = read_le32(ptr); |
| addr4 += addrs[src]; |
| write_le32(ptr, addr4); |
| break; |
| |
| case 8: |
| ptr = (uint8_t *)addrs[dst]; |
| ptr += s[i].pointer.offset; |
| addr8 = read_le64(ptr); |
| addr8 += addrs[src]; |
| write_le64(ptr, addr8); |
| break; |
| |
| default: |
| /* |
| * Should not happen. ACPI knows 1 and 2 byte ptrs |
| * too, but we are operating with 32bit offsets which |
| * would simply not fit in there ... |
| */ |
| printk(BIOS_DEBUG, "QEMU: acpi: unimplemented ptr size %d\n", |
| s[i].pointer.size); |
| goto err; |
| } |
| break; |
| |
| case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM: |
| dst = -1; |
| for (j = 0; j < i; j++) { |
| if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE) |
| continue; |
| if (strcmp(s[j].alloc.file, s[i].cksum.file) == 0) |
| dst = j; |
| } |
| if (dst == -1) |
| goto err; |
| |
| ptr = (uint8_t *)(addrs[dst] + s[i].cksum.offset); |
| cksum_data = (void *)(addrs[dst] + s[i].cksum.start); |
| cksum = acpi_checksum(cksum_data, s[i].cksum.length); |
| write_le8(ptr, cksum); |
| break; |
| |
| default: |
| printk(BIOS_DEBUG, "QEMU: acpi: unknown script cmd 0x%x @ %p\n", |
| s[i].command, s+i); |
| goto err; |
| }; |
| } |
| |
| printk(BIOS_DEBUG, "QEMU: loaded ACPI tables from fw_cfg.\n"); |
| free(s); |
| free(addrs); |
| return ALIGN(current, 16); |
| |
| err: |
| printk(BIOS_DEBUG, "QEMU: loading ACPI tables from fw_cfg failed.\n"); |
| free(s); |
| free(addrs); |
| return 0; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| /* pick up smbios information from fw_cfg */ |
| |
| static const char *type1_manufacturer; |
| static const char *type1_product_name; |
| static const char *type1_version; |
| static const char *type1_serial_number; |
| static const char *type1_family; |
| static u8 type1_uuid[16]; |
| |
| static void fw_cfg_smbios_init(void) |
| { |
| static int done = 0; |
| uint16_t i, count = 0; |
| FwCfgSmbios entry; |
| char *buf; |
| |
| if (done) |
| return; |
| done = 1; |
| |
| fw_cfg_get(FW_CFG_SMBIOS_ENTRIES, &count, sizeof(count)); |
| for (i = 0; i < count; i++) { |
| insb(FW_CFG_PORT_DATA, &entry, sizeof(entry)); |
| buf = malloc(entry.length - sizeof(entry)); |
| insb(FW_CFG_PORT_DATA, buf, entry.length - sizeof(entry)); |
| if (entry.headertype == SMBIOS_FIELD_ENTRY && |
| entry.tabletype == 1) { |
| switch (entry.fieldoffset) { |
| case offsetof(struct smbios_type1, manufacturer): |
| type1_manufacturer = strdup(buf); |
| break; |
| case offsetof(struct smbios_type1, product_name): |
| type1_product_name = strdup(buf); |
| break; |
| case offsetof(struct smbios_type1, version): |
| type1_version = strdup(buf); |
| break; |
| case offsetof(struct smbios_type1, serial_number): |
| type1_serial_number = strdup(buf); |
| break; |
| case offsetof(struct smbios_type1, family): |
| type1_family = strdup(buf); |
| break; |
| case offsetof(struct smbios_type1, uuid): |
| memcpy(type1_uuid, buf, 16); |
| break; |
| } |
| } |
| free(buf); |
| } |
| } |
| |
| static unsigned long smbios_next(unsigned long current) |
| { |
| struct smbios_type0 *t0; |
| int l, count = 0; |
| char *s; |
| |
| t0 = (void*)current; |
| current += t0->length; |
| for (;;) { |
| s = (void*)current; |
| l = strlen(s); |
| if (!l) |
| return current + (count ? 1 : 2); |
| current += l + 1; |
| count++; |
| } |
| } |
| |
| /* |
| * Starting with version 2.1 qemu provides a full set of smbios tables |
| * for the virtual hardware emulated, except type 0 (bios information). |
| * |
| * What we are going to do here is find the type0 table, keep it, and |
| * override everything else generated by coreboot with the qemu smbios |
| * tables. |
| * |
| * It's a bit hackish, but qemu is a special case (compared to real |
| * hardware) and this way we don't need special qemu support in the |
| * generic smbios code. |
| */ |
| unsigned long fw_cfg_smbios_tables(int *handle, unsigned long *current) |
| { |
| FWCfgFile f; |
| struct smbios_type0 *t0; |
| unsigned long start, end; |
| int ret, i, count = 1; |
| char *str; |
| |
| if (fw_cfg_check_file(&f, "etc/smbios/smbios-tables")) |
| return 0; |
| |
| printk(BIOS_DEBUG, "QEMU: found smbios tables in fw_cfg (len %d).\n", f.size); |
| |
| /* |
| * Search backwards for "coreboot" (first string in type0 table, |
| * see src/arch/x86/boot/smbios.c), then find type0 table. |
| */ |
| for (i = 0; i < 16384; i++) { |
| str = (char*)(*current - i); |
| if (strcmp(str, "coreboot") == 0) |
| break; |
| } |
| if (i == 16384) |
| return 0; |
| i += sizeof(struct smbios_type0) - 2; |
| t0 = (struct smbios_type0*)(*current - i); |
| if (t0->type != SMBIOS_BIOS_INFORMATION || t0->handle != 0) |
| return 0; |
| printk(BIOS_DEBUG, "QEMU: coreboot type0 table found at 0x%lx.\n", |
| *current - i); |
| start = smbios_next(*current - i); |
| |
| /* |
| * Fetch smbios tables from qemu, go find the end marker. |
| * We'll exclude the end marker as coreboot will add one. |
| */ |
| printk(BIOS_DEBUG, "QEMU: loading smbios tables to 0x%lx\n", start); |
| fw_cfg_get(f.select, (void *)start, f.size); |
| end = start; |
| do { |
| t0 = (struct smbios_type0*)end; |
| if (t0->type == SMBIOS_END_OF_TABLE) |
| break; |
| end = smbios_next(end); |
| count++; |
| } while (end < start + f.size); |
| |
| /* final fixups. */ |
| ret = end - *current; |
| *current = end; |
| *handle = count; |
| return ret; |
| } |
| |
| const char *smbios_mainboard_manufacturer(void) |
| { |
| fw_cfg_smbios_init(); |
| return type1_manufacturer ?: CONFIG_MAINBOARD_SMBIOS_MANUFACTURER; |
| } |
| |
| const char *smbios_mainboard_product_name(void) |
| { |
| fw_cfg_smbios_init(); |
| return type1_product_name ?: CONFIG_MAINBOARD_SMBIOS_PRODUCT_NAME; |
| } |
| |
| const char *smbios_mainboard_version(void) |
| { |
| fw_cfg_smbios_init(); |
| return type1_version ?: CONFIG_MAINBOARD_VERSION; |
| } |
| |
| const char *smbios_mainboard_serial_number(void) |
| { |
| fw_cfg_smbios_init(); |
| return type1_serial_number ?: CONFIG_MAINBOARD_SERIAL_NUMBER; |
| } |
| |
| void smbios_system_set_uuid(u8 *uuid) |
| { |
| fw_cfg_smbios_init(); |
| memcpy(uuid, type1_uuid, 16); |
| } |
| |
| /* |
| * Configure DMA setup |
| */ |
| |
| static void fw_cfg_dma(int control, void *buf, int len) |
| { |
| volatile FwCfgDmaAccess dma; |
| uintptr_t dma_desc_addr; |
| uint32_t dma_desc_addr_hi, dma_desc_addr_lo; |
| |
| dma.control = be32_to_cpu(control); |
| dma.length = be32_to_cpu(len); |
| dma.address = be64_to_cpu((uintptr_t)buf); |
| |
| dma_desc_addr = (uintptr_t)&dma; |
| dma_desc_addr_lo = (uint32_t)(dma_desc_addr & 0xFFFFFFFFU); |
| dma_desc_addr_hi = sizeof(uintptr_t) > sizeof(uint32_t) |
| ? (uint32_t)(dma_desc_addr >> 32) : 0; |
| |
| // Skip writing high half if unnecessary. |
| if (dma_desc_addr_hi) |
| outl(be32_to_cpu(dma_desc_addr_hi), FW_CFG_DMA_ADDR_HIGH); |
| outl(be32_to_cpu(dma_desc_addr_lo), FW_CFG_DMA_ADDR_LOW); |
| |
| while (be32_to_cpu(dma.control) & ~FW_CFG_DMA_CTL_ERROR) |
| ; |
| } |