qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]