[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [QEMU v5 PATCH 09/18] SMBIOS: Build full smbios memory tabl
From: |
Gabriel L. Somlo |
Subject: |
[Qemu-devel] [QEMU v5 PATCH 09/18] SMBIOS: Build full smbios memory tables (type 16, 17, 19, and 20) |
Date: |
Fri, 11 Apr 2014 12:11:49 -0400 |
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 <address@hidden>
---
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.9.0
- [Qemu-devel] [QEMU v5 PATCH 00/18] SMBIOS: build full tables in QEMU, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 01/18] SMBIOS: Rename smbios_set_type1_defaults() for more general use, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 02/18] SMBIOS: Use macro to set smbios defaults, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 03/18] SMBIOS: Use bitmaps to check for smbios table collisions, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 04/18] SMBIOS: Add code to build full smbios tables; build type 2 table, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 05/18] SMBIOS: Build full tables for types 0 and 1, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 06/18] SMBIOS: Remove unused code for passing individual fields to bios, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 07/18] SMBIOS: Build full type 3 table, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 09/18] SMBIOS: Build full smbios memory tables (type 16, 17, 19, and 20),
Gabriel L. Somlo <=
- [Qemu-devel] [QEMU v5 PATCH 10/18] SMBIOS: Build full tables for type 32 and 127, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 08/18] SMBIOS: Build full type 4 tables, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 11/18] SMBIOS: Update all table definitions to smbios spec v2.3, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 13/18] SMBIOS: Stop including type 20 tables, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 12/18] SMBIOS: Remove SeaBIOS compatibility quirks, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 14/18] SMBIOS: Use e820 memory map to generate type 19 tables, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 15/18] SMBIOS: Update type 3 definition to smbios spec v2.7, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 18/18] SMBIOS: Generate complete smbios tables, including entry point, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 16/18] SMBIOS: Update type 4 definition to smbios spec v2.6, Gabriel L. Somlo, 2014/04/11
- [Qemu-devel] [QEMU v5 PATCH 17/18] SMBIOS: Update memory table types (16, 17, and 19) to smbios spec v2.8, Gabriel L. Somlo, 2014/04/11