Build full smbios tables representing the system RAM: - type 16 (physical memory array): represents the entire system RAM; - type 17 (memory device) tables: one per virtual DIMM; - type 19 (memory array mapped address): represent major RAM areas (currently one for below-4G memory, and, if applicable, one for above-4G memory); - type 20 (memory device mapped address): mappings between type 19 areas and type 17 DIMMs; These tables will be made available to the bios via fw_cfg.
This patch also thoroughly documents the current memory table layout. Signed-off-by: Gabriel Somlo <so...@cmu.edu> --- hw/i386/pc_piix.c | 3 +- hw/i386/pc_q35.c | 3 +- hw/i386/smbios.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++- include/hw/i386/smbios.h | 13 ++- 4 files changed, 264 insertions(+), 11 deletions(-) diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index 8513de0..db075eb 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -146,7 +146,8 @@ static void pc_init1(QEMUMachineInitArgs *args, if (smbios_defaults) { /* These values are guest ABI, do not change */ smbios_set_defaults("QEMU", "Standard PC (i440FX + PIIX, 1996)", - args->machine->name); + args->machine->name, + below_4g_mem_size, above_4g_mem_size); } /* allocate ram and load rom/bios */ diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index eacec53..3aaac7a 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -133,7 +133,8 @@ static void pc_q35_init(QEMUMachineInitArgs *args) if (smbios_defaults) { /* These values are guest ABI, do not change */ smbios_set_defaults("QEMU", "Standard PC (Q35 + ICH9, 2009)", - args->machine->name); + args->machine->name, + below_4g_mem_size, above_4g_mem_size); } /* allocate ram and load rom/bios */ diff --git a/hw/i386/smbios.c b/hw/i386/smbios.c index 5b80021..6510ff3 100644 --- a/hw/i386/smbios.c +++ b/hw/i386/smbios.c @@ -43,6 +43,7 @@ static int smbios_type4_count = 0; static bool smbios_immutable; static bool smbios_have_defaults; static uint32_t smbios_cpuid_version, smbios_cpuid_features; /* for type 4 */ +static ram_addr_t smbios_below_4g_ram, smbios_above_4g_ram; /* for type 19 */ static DECLARE_BITMAP(have_binfile_bitmap, SMBIOS_MAX_TYPE+1); static DECLARE_BITMAP(have_fields_bitmap, SMBIOS_MAX_TYPE+1); @@ -70,6 +71,10 @@ static struct { const char *sock_pfx, *manufacturer, *version, *serial, *asset, *part; } type4; +static struct { + const char *loc_pfx, *bank, *manufacturer, *serial, *asset, *part; +} type17; + static QemuOptsList qemu_smbios_opts = { .name = "smbios", .head = QTAILQ_HEAD_INITIALIZER(qemu_smbios_opts.head), @@ -244,6 +249,39 @@ static const QemuOptDesc qemu_smbios_type4_opts[] = { { /* end of list */ } }; +static const QemuOptDesc qemu_smbios_type17_opts[] = { + { + .name = "type", + .type = QEMU_OPT_NUMBER, + .help = "SMBIOS element type", + },{ + .name = "loc_pfx", + .type = QEMU_OPT_STRING, + .help = "device locator string prefix", + },{ + .name = "bank", + .type = QEMU_OPT_STRING, + .help = "bank locator string", + },{ + .name = "manufacturer", + .type = QEMU_OPT_STRING, + .help = "manufacturer name", + },{ + .name = "serial", + .type = QEMU_OPT_STRING, + .help = "serial number", + },{ + .name = "asset", + .type = QEMU_OPT_STRING, + .help = "asset tag number", + },{ + .name = "part", + .type = QEMU_OPT_STRING, + .help = "part number", + }, + { /* end of list */ } +}; + static void smbios_register_config(void) { qemu_add_opts(&qemu_smbios_opts); @@ -466,6 +504,117 @@ static void smbios_build_type_4_table(unsigned instance) smbios_type4_count++; } +#define ONE_KB ((ram_addr_t)1 << 10) +#define ONE_MB ((ram_addr_t)1 << 20) +#define ONE_GB ((ram_addr_t)1 << 30) + +static void smbios_build_type_16_table(unsigned dimm_cnt) +{ + ram_addr_t ram_size_kb = ram_size >> 10; + + SMBIOS_BUILD_TABLE_PRE(16, 0x1000, true); /* required */ + + t->location = 0x01; /* Other */ + t->use = 0x03; /* System memory */ + t->error_correction = 0x06; /* Multi-bit ECC (for Microsoft, per SeaBIOS) */ + /* if ram_size < 2T, use value in Kilobytes; 0x80000000 == 2T and over */ + t->maximum_capacity = (ram_size_kb < 0x80000000) ? ram_size_kb : 0x80000000; + if (t->maximum_capacity == 0x80000000) { + /* TODO: support smbios v2.7 extended capacity */ + fprintf(stderr, "qemu: warning: SMBIOS v2.7+ required for " + "ram_size >= 2T (%ld)\n", ram_size); + } + t->memory_error_information_handle = 0xFFFE; /* Not provided */ + t->number_of_memory_devices = dimm_cnt; + + SMBIOS_BUILD_TABLE_POST; +} + +static void smbios_build_type_17_table(unsigned instance, ram_addr_t size) +{ + char loc_str[128]; + ram_addr_t size_mb; + + SMBIOS_BUILD_TABLE_PRE(17, 0x1100 + instance, true); /* required */ + + t->physical_memory_array_handle = 0x1000; /* Type 16 (Phys. Mem. Array) */ + t->memory_error_information_handle = 0; /* SeaBIOS, should be 0xFFFE(N/A) */ + t->total_width = 64; /* hardcoded in SeaBIOS */ + t->data_width = 64; /* hardcoded in SeaBIOS */ + size_mb = QEMU_ALIGN_UP(size, ONE_MB) / ONE_MB; + if (size_mb < 0x7FFF) { + t->size = size_mb; + } else { + t->size = 0x7FFF; + /* TODO: support smbios v2.7 extended capacity */ + fprintf(stderr, "qemu: warning: SMBIOS v2.7+ required for " + "DIMM size >= 0x7FFF Mbytes (0x%lx)\n", size_mb); + } + t->form_factor = 0x09; /* DIMM */ + t->device_set = 0; /* Not in a set */ + snprintf(loc_str, sizeof(loc_str), "%s %d", type17.loc_pfx, instance); + SMBIOS_TABLE_SET_STR(17, device_locator_str, loc_str); + SMBIOS_TABLE_SET_STR(17, bank_locator_str, type17.bank); + t->memory_type = 0x07; /* RAM */ + t->type_detail = 0; /* hardcoded in SeaBIOS */ + + SMBIOS_BUILD_TABLE_POST; +} + +static void smbios_build_type_19_table(unsigned instance, + ram_addr_t start, ram_addr_t size) +{ + ram_addr_t end, start_kb, end_kb; + + SMBIOS_BUILD_TABLE_PRE(19, 0x1300 + instance, true); /* required */ + + end = start + size - 1; + assert(end > start); + start_kb = start / ONE_KB; + end_kb = end / ONE_KB; + if (start_kb >= UINT32_MAX || end_kb >= UINT32_MAX) { + t->starting_address = t->ending_address = UINT32_MAX; + fprintf(stderr, "qemu: warning: SMBIOS v2.7+ required for " + "type19(start=%lx, size=%lx)\n", start, size); + } else { + t->starting_address = start_kb; + t->ending_address = end_kb; + } + t->memory_array_handle = 0x1000; /* Type 16 (Phys. Mem. Array) */ + t->partition_width = 1; /* One device per row */ + + SMBIOS_BUILD_TABLE_POST; +} + +static void smbios_build_type_20_table(unsigned instance, + unsigned dev_hndl, unsigned array_hndl, + ram_addr_t start, ram_addr_t size) +{ + ram_addr_t end, start_kb, end_kb; + + SMBIOS_BUILD_TABLE_PRE(20, 0x1400 + instance, true); /* required */ + + end = start + size - 1; + assert(end > start); + start_kb = start / ONE_KB; + end_kb = end / ONE_KB; + if (start_kb >= UINT32_MAX || end_kb >= UINT32_MAX) { + t->starting_address = t->ending_address = UINT32_MAX; + fprintf(stderr, "qemu: warning: SMBIOS v2.7+ required for " + "type20(start=%lx, size=%lx)\n", start, size); + } else { + t->starting_address = start_kb; + t->ending_address = end_kb; + } + t->memory_device_handle = 0x1100 + dev_hndl; /* Type 17 (Memory Device) */ + t->memory_array_mapped_address_handle = 0x1300 + array_hndl; /* Type 19 */ + t->partition_row_position = 1; /* One device per row, always first pos. */ + t->interleave_position = 0; /* Not interleaved */ + t->interleaved_data_depth = 0; /* Not interleaved */ + + SMBIOS_BUILD_TABLE_POST; +} + #define SMBIOS_SET_DEFAULT(field, value) \ if (!field) { \ field = value; \ @@ -478,10 +627,17 @@ void smbios_set_cpuid(uint32_t version, uint32_t features) } void smbios_set_defaults(const char *manufacturer, - const char *product, const char *version) + const char *product, const char *version, + ram_addr_t below_4g_mem_size, + ram_addr_t above_4g_mem_size) { const char *manufacturer_compat = "Bochs"; /* SeaBIOS compatibility */ smbios_have_defaults = true; + + assert(ram_size == below_4g_mem_size + above_4g_mem_size); + smbios_below_4g_ram = below_4g_mem_size; + smbios_above_4g_ram = above_4g_mem_size; + SMBIOS_SET_DEFAULT(type0.vendor, manufacturer); SMBIOS_SET_DEFAULT(type0.version, version); SMBIOS_SET_DEFAULT(type0.date, "01/01/2014"); @@ -500,11 +656,12 @@ void smbios_set_defaults(const char *manufacturer, /* not set in SeaBIOS SMBIOS_SET_DEFAULT(type4.version, version); */ + SMBIOS_SET_DEFAULT(type17.loc_pfx, "DIMM"); } uint8_t *smbios_get_table(size_t *length) { - unsigned i; + unsigned i, dimm_cnt; if (!smbios_immutable) { smbios_build_type_0_table(); @@ -515,6 +672,88 @@ uint8_t *smbios_get_table(size_t *length) /* count CPUs starting with 1, to minimize diff vs. SeaBIOS */ smbios_build_type_4_table(i + 1); } + + /* SeaBIOS expects tables compliant to smbios v2.4; + * As such, we currently support ram_size up to 2T + * (relevant to type 16), and DIMM sizes up to 16G + * (for type 17). + * + * One type 16 (physical memory array) table is created + * to represent the entire given ram_size, which is then + * split into type 17 (memory device) DMIMMs of 16G, with + * the last DIMM covering the sub-16G remainder + * (ram_size % 16G). + * + * Up to two type 19 (memory array mapped address) tables + * are created: the first one covers below-4G memory, and + * the second, if applicable, covers above-4g memory. + * + * Tables of type 20 (memory device mapped address) are + * created as necessary, to connect type 17 DIMMs to + * type 19 memory areas. + * + * The following figure illustrates how many instances of + * each type are generated: + * + * ------- ------- + * | T17 | | T17 | + * | <=16G | | <=16G | ... + * | 1100h |<----+ | 1101h | + * ------- | ------- + * ^ | ^ + * | | | + * ---+--- ---+--- ---+--- + * | T20 | | T20 | | T20 | + * | <4G | | 4G+ | | <=16G | ... + * | 1400h | | 1401h | | 1402h | + * ---+--- ---+--- ---+--- + * | | | + * v v v + * ------- -------------------...-- + * | T19 | | T19 | + * | <4G | | 4G and up | + * | 1300h | | 1301h | + * ------- -------------------...-- + * + * With under 4G of memory, a single DIMM and a single + * below-4G memory area are linked together by a single + * type 20 device mapped address. + * + * With over 4G (but less than 16G) of memory, we still + * require only one DIMM, but create two memory areas, + * one representing the below_4g_ram, and the other one + * for above_4g_ram. Two type 20 device mapped address + * tables link our DIMM to the below_4g and above_4g + * areas, respectively. + * + * With over 16G of memory, we create additional DIMMs, and + * additional type 20 device mapped address tables to link + * each such additional DIMM to the above_4g_ram area. + */ + +#define MAX_DIMM_SZ (16 * ONE_GB) +#define GET_DIMM_SZ ((i < dimm_cnt - 1) ? MAX_DIMM_SZ : ram_size % MAX_DIMM_SZ) + + dimm_cnt = QEMU_ALIGN_UP(ram_size, MAX_DIMM_SZ) / MAX_DIMM_SZ; + smbios_build_type_16_table(dimm_cnt); + for (i = 0; i < dimm_cnt; i++) { + smbios_build_type_17_table(i, GET_DIMM_SZ); + } + smbios_build_type_19_table(0, 0, smbios_below_4g_ram); + smbios_build_type_20_table(0, 0, 0, 0, smbios_below_4g_ram); + if (smbios_above_4g_ram) { + ram_addr_t start = 4 * ONE_GB, size; + smbios_build_type_19_table(1, start, smbios_above_4g_ram); + for (i = 0; i < dimm_cnt; i++) { + size = GET_DIMM_SZ; + if (i == 0) { /* below-4G portion of DIMM 0 already mapped */ + size -= smbios_below_4g_ram; + } + smbios_build_type_20_table(i + 1, i, 1, start, size); + start += size; + } + } + smbios_validate_table(); smbios_immutable = true; } @@ -685,6 +924,19 @@ void smbios_entry_add(QemuOpts *opts) save_opt(&type4.asset, opts, "asset"); save_opt(&type4.part, opts, "part"); return; + case 17: + qemu_opts_validate(opts, qemu_smbios_type17_opts, &local_err); + if (local_err) { + error_report("%s", error_get_pretty(local_err)); + exit(1); + } + save_opt(&type17.loc_pfx, opts, "loc_pfx"); + save_opt(&type17.bank, opts, "bank"); + save_opt(&type17.manufacturer, opts, "manufacturer"); + save_opt(&type17.serial, opts, "serial"); + save_opt(&type17.asset, opts, "asset"); + save_opt(&type17.part, opts, "part"); + return; default: error_report("Don't know how to build fields for SMBIOS type %ld", type); diff --git a/include/hw/i386/smbios.h b/include/hw/i386/smbios.h index af5ee01..2a0d384 100644 --- a/include/hw/i386/smbios.h +++ b/include/hw/i386/smbios.h @@ -20,7 +20,9 @@ void smbios_entry_add(QemuOpts *opts); void smbios_set_cpuid(uint32_t version, uint32_t features); void smbios_set_defaults(const char *manufacturer, - const char *product, const char *version); + const char *product, const char *version, + ram_addr_t below_4g_mem_size, + ram_addr_t above_4g_mem_size); uint8_t *smbios_get_table(size_t *length); /* @@ -118,9 +120,7 @@ struct smbios_type_4 { uint16_t l3_cache_handle; } QEMU_PACKED; -/* SMBIOS type 16 - Physical Memory Array - * Associated with one type 17 (Memory Device). - */ +/* SMBIOS type 16 - Physical Memory Array */ struct smbios_type_16 { struct smbios_structure_header header; uint8_t location; @@ -130,9 +130,8 @@ struct smbios_type_16 { uint16_t memory_error_information_handle; uint16_t number_of_memory_devices; } QEMU_PACKED; -/* SMBIOS type 17 - Memory Device - * Associated with one type 19 - */ + +/* SMBIOS type 17 - Memory Device */ struct smbios_type_17 { struct smbios_structure_header header; uint16_t physical_memory_array_handle; -- 1.8.5.3