This commit implements SMBIOS Type 16 (Physical Memory Array)
generation with a hybrid approach supporting both:

1. Explicit definition via Device Tree 'smbios' node:
   Child node under '/smbios/smbios/memory-array' will be used to
   populate as individual Type 16 structure directly.
   - Properties follow SMBIOS field names with lowercase letters and
     hyphen-separated words (e.g., 'memory-error-correction',
     'maximum-capacity', 'extended-maximum-capacity', etc.).
   - This method supports precise platform-defined overrides and system
     descriptions.

2. Fallback to automatic DT-based discovery:
   If child node under '/smbios/smbios/memory-array' does not exist,
   the implementation will:
   - Scan all top-level 'memory@' nodes to populate Type 16 structure with
     inferred size and location data.
   - Scan nodes named or marked as 'memory-controller' and parse
     associated 'dimm@' subnodes (if present) to extract DIMM sizes and
     map them accordingly.

This dual-mode support enables flexible firmware SMBIOS reporting while
aligning with spec-compliant naming and runtime-detected memory topology.

Type 16 support is under GENERATE_SMBIOS_TABLE_VERBOSE to avoid
increasing rom size for those platforms which only require basic SMBIOS
support.

Signed-off-by: Raymond Mao <[email protected]>
---
Changes in v4:
- Initial patch.
Changes in v5:
- None.

 arch/arm/dts/smbios_generic.dtsi |   3 +
 cmd/smbios.c                     |  46 +++++
 drivers/sysinfo/smbios.c         |   5 +
 include/smbios.h                 |  18 ++
 include/smbios_def.h             |  29 ++++
 include/sysinfo.h                |   4 +
 lib/smbios.c                     | 282 +++++++++++++++++++++++++++++++
 7 files changed, 387 insertions(+)

diff --git a/arch/arm/dts/smbios_generic.dtsi b/arch/arm/dts/smbios_generic.dtsi
index 4463dade217..1a9adfaa409 100644
--- a/arch/arm/dts/smbios_generic.dtsi
+++ b/arch/arm/dts/smbios_generic.dtsi
@@ -80,6 +80,9 @@
 
                        system-slot {
                        };
+
+                       memory-array {
+                       };
                };
        };
 };
diff --git a/cmd/smbios.c b/cmd/smbios.c
index f9b62e66229..3f7dd21f92e 100644
--- a/cmd/smbios.c
+++ b/cmd/smbios.c
@@ -168,6 +168,32 @@ static const struct str_lookup_table slot_length_strings[] 
= {
        { SMBIOS_SYSSLOT_LENG_3_5INDRV, "3.5 inch drive form factor" },
 };
 
+static const struct str_lookup_table ma_location_strings[] = {
+       { SMBIOS_MA_LOCATION_OTHER,             "Other" },
+       { SMBIOS_MA_LOCATION_UNKNOWN,           "Unknown" },
+       { SMBIOS_MA_LOCATION_MOTHERBOARD,       "System board or motherboard" },
+};
+
+static const struct str_lookup_table ma_use_strings[] = {
+       { SMBIOS_MA_USE_OTHER,          "Other" },
+       { SMBIOS_MA_USE_UNKNOWN,        "Unknown" },
+       { SMBIOS_MA_USE_SYSTEM,         "System memory" },
+       { SMBIOS_MA_USE_VIDEO,          "Video memory" },
+       { SMBIOS_MA_USE_FLASH,          "Flash memory" },
+       { SMBIOS_MA_USE_NVRAM,          "Non-volatile RAM" },
+       { SMBIOS_MA_USE_CACHE,          "Cache memory" },
+};
+
+static const struct str_lookup_table ma_err_corr_strings[] = {
+       { SMBIOS_MA_ERRCORR_OTHER,      "Other" },
+       { SMBIOS_MA_ERRCORR_UNKNOWN,    "Unknown" },
+       { SMBIOS_MA_ERRCORR_NONE,       "None" },
+       { SMBIOS_MA_ERRCORR_PARITY,     "Parity" },
+       { SMBIOS_MA_ERRCORR_SBITECC,    "Single-bit ECC" },
+       { SMBIOS_MA_ERRCORR_MBITECC,    "Multi-bit ECC" },
+       { SMBIOS_MA_ERRCORR_CRC,        "CRC" },
+};
+
 /**
  * smbios_get_string() - get SMBIOS string from table
  *
@@ -514,6 +540,23 @@ static void smbios_print_type9(struct smbios_type9 *table)
        printf("\tSlot Height: 0x%04x\n", *addr);
 }
 
+static void smbios_print_type16(struct smbios_type16 *table)
+{
+       printf("Physical Memory Array:\n");
+       smbios_print_lookup_str(ma_location_strings, table->location,
+                               ARRAY_SIZE(ma_location_strings), "Location");
+       smbios_print_lookup_str(ma_use_strings, table->use,
+                               ARRAY_SIZE(ma_use_strings), "Use");
+       smbios_print_lookup_str(ma_err_corr_strings, table->mem_err_corr,
+                               ARRAY_SIZE(ma_err_corr_strings),
+                               "Memory Error Correction");
+       printf("\tMaximum Capacity: 0x%08x\n", table->max_cap);
+       printf("\tMemory Error Information Handle: 0x%04x\n",
+              table->mem_err_info_hdl);
+       printf("\tNumber of Memory Devices: 0x%04x\n", table->num_of_mem_dev);
+       printf("\tExtended Maximum Capacity: 0x%016llx\n", table->ext_max_cap);
+}
+
 static void smbios_print_type127(struct smbios_type127 *table)
 {
        printf("End Of Table\n");
@@ -596,6 +639,9 @@ static int do_smbios(struct cmd_tbl *cmdtp, int flag, int 
argc,
                case SMBIOS_SYSTEM_SLOTS:
                        smbios_print_type9((struct smbios_type9 *)pos);
                        break;
+               case SMBIOS_PHYS_MEMORY_ARRAY:
+                       smbios_print_type16((struct smbios_type16 *)pos);
+                       break;
                case SMBIOS_END_OF_TABLE:
                        smbios_print_type127((struct smbios_type127 *)pos);
                        break;
diff --git a/drivers/sysinfo/smbios.c b/drivers/sysinfo/smbios.c
index 99104274f72..ff5873c940e 100644
--- a/drivers/sysinfo/smbios.c
+++ b/drivers/sysinfo/smbios.c
@@ -24,6 +24,7 @@ struct sysinfo_plat_priv {
        struct smbios_type7 t7[SYSINFO_CACHE_LVL_MAX];
        u16 cache_handles[SYSINFO_CACHE_LVL_MAX];
        u8 cache_level;
+       u16 marray_handles[SYSINFO_MEM_HANDLE_MAX];
 };
 
 static void smbios_cache_info_dump(struct smbios_type7 *cache_info)
@@ -165,6 +166,10 @@ static int sysinfo_plat_get_data(struct udevice *dev, int 
id, void **buf,
                *buf = &priv->cache_handles[0];
                *size = sizeof(priv->cache_handles);
                break;
+       case SYSID_SM_MEMARRAY_HANDLE:
+               *buf = &priv->marray_handles[0];
+               *size = sizeof(priv->marray_handles);
+               break;
        default:
                return -EOPNOTSUPP;
        }
diff --git a/include/smbios.h b/include/smbios.h
index d885285ea41..16438c059c7 100644
--- a/include/smbios.h
+++ b/include/smbios.h
@@ -309,6 +309,24 @@ struct __packed smbios_type9 {
        char eos[SMBIOS_STRUCT_EOS_BYTES];
 };
 
+enum {
+       SMBIOS_MEM_NONE = 0,
+       SMBIOS_MEM_CUSTOM = 1,
+       SMBIOS_MEM_FDT_MEM_NODE = 2,
+       SMBIOS_MEM_FDT_MEMCON_NODE = 3
+};
+
+struct __packed smbios_type16 {
+       struct smbios_header hdr;
+       u8 location;
+       u8 use;
+       u8 mem_err_corr;
+       u32 max_cap;
+       u16 mem_err_info_hdl;
+       u16 num_of_mem_dev;
+       u64 ext_max_cap;
+       char eos[SMBIOS_STRUCT_EOS_BYTES];
+};
 struct __packed smbios_type32 {
        u8 type;
        u8 length;
diff --git a/include/smbios_def.h b/include/smbios_def.h
index ef9cb02ed25..c6850a5d6f5 100644
--- a/include/smbios_def.h
+++ b/include/smbios_def.h
@@ -280,4 +280,33 @@
 /* Slot segment group number */
 #define SMBIOS_SYSSLOT_SGGNUM_UND      0
 
+/* Physical Memory Array */
+
+/* Location */
+#define SMBIOS_MA_LOCATION_OTHER       1
+#define SMBIOS_MA_LOCATION_UNKNOWN     2
+#define SMBIOS_MA_LOCATION_MOTHERBOARD 3
+
+/* Use */
+#define SMBIOS_MA_USE_OTHER            1
+#define SMBIOS_MA_USE_UNKNOWN          2
+#define SMBIOS_MA_USE_SYSTEM           3
+#define SMBIOS_MA_USE_VIDEO            4
+#define SMBIOS_MA_USE_FLASH            5
+#define SMBIOS_MA_USE_NVRAM            6
+#define SMBIOS_MA_USE_CACHE            7
+
+/* Error Correction Type */
+#define SMBIOS_MA_ERRCORR_OTHER                1
+#define SMBIOS_MA_ERRCORR_UNKNOWN      2
+#define SMBIOS_MA_ERRCORR_NONE         3
+#define SMBIOS_MA_ERRCORR_PARITY       4
+#define SMBIOS_MA_ERRCORR_SBITECC      5
+#define SMBIOS_MA_ERRCORR_MBITECC      6
+#define SMBIOS_MA_ERRCORR_CRC          7
+
+/* Error Information Handle */
+#define SMBIOS_MA_ERRINFO_NONE         0xFFFE
+#define SMBIOS_MA_ERRINFO_NOERR                0xFFFF
+
 #endif /* _SMBIOS_DEF_H_ */
diff --git a/include/sysinfo.h b/include/sysinfo.h
index e87cf969fcd..54eb64a204a 100644
--- a/include/sysinfo.h
+++ b/include/sysinfo.h
@@ -12,6 +12,7 @@
 struct udevice;
 
 #define SYSINFO_CACHE_LVL_MAX 3
+#define SYSINFO_MEM_HANDLE_MAX 8
 
 /*
  * This uclass encapsulates hardware methods to gather information about a
@@ -149,6 +150,9 @@ enum sysinfo_id {
        SYSID_SM_CACHE_INFO_END =
                SYSID_SM_CACHE_INST_SIZE2 + SYSINFO_CACHE_LVL_MAX - 1,
 
+       /* Memory Array (Type 16) */
+       SYSID_SM_MEMARRAY_HANDLE,
+
        /* For show_board_info() */
        SYSID_BOARD_MODEL,
        SYSID_BOARD_MANUFACTURER,
diff --git a/lib/smbios.c b/lib/smbios.c
index caeb309294d..27c9c975cf2 100644
--- a/lib/smbios.c
+++ b/lib/smbios.c
@@ -290,6 +290,49 @@ static int smbios_get_val_si(struct smbios_ctx * 
__maybe_unused ctx,
        return val_def;
 }
 
+#if IS_ENABLED(CONFIG_GENERATE_SMBIOS_TABLE_VERBOSE)
+static u64 smbios_get_u64_si(struct smbios_ctx * __maybe_unused ctx,
+                            const char * __maybe_unused prop,
+                            int __maybe_unused sysinfo_id, u64 val_def)
+{
+       size_t len;
+       void *data;
+       const fdt32_t *prop_val;
+       int prop_len;
+       u64 val = 0;
+
+       if (!ctx->dev)
+               return val_def;
+
+       if (!sysinfo_get_data(ctx->dev, sysinfo_id, &data, &len))
+               return *((u64 *)data);
+
+       if (!IS_ENABLED(CONFIG_OF_CONTROL) || !prop || !ofnode_valid(ctx->node))
+               return val_def;
+
+       prop_val = ofnode_read_prop(ctx->node, prop, &prop_len);
+       if (!prop_val || prop_len < sizeof(fdt32_t) ||
+           prop_len % sizeof(fdt32_t)) {
+               /*
+                * If the node or property is not valid fallback and try the 
root
+                */
+               prop_val = ofnode_read_prop(ofnode_root(), prop, &prop_len);
+               if (!prop_val || prop_len < sizeof(fdt32_t) ||
+                   prop_len % sizeof(fdt32_t))
+                       return val_def;
+       }
+
+       /* 64-bit: <hi lo> or 32-bit */
+       if (prop_len >= sizeof(fdt32_t) * 2) {
+               val = ((u64)fdt32_to_cpu(prop_val[0]) << 32) |
+                    fdt32_to_cpu(prop_val[1]);
+       } else {
+               val = fdt32_to_cpu(prop_val[0]);
+       }
+       return val;
+}
+#endif
+
 /**
  * smbios_add_prop_si() - Add a property from the devicetree or sysinfo
  *
@@ -1151,6 +1194,244 @@ static int smbios_write_type9(ulong *current, int 
*handle,
        return len;
 }
 
+static u64 smbios_pop_size_from_memory_node(ofnode node)
+{
+       const fdt32_t *reg;
+       int len;
+       u64 size_bytes;
+
+       /* Read property 'reg' from the node */
+       reg = ofnode_read_prop(node, "reg", &len);
+       if (!reg || len < sizeof(fdt32_t) * 4 || len % sizeof(fdt32_t))
+               return 0;
+
+       /* Combine hi/lo for size (typically 64-bit) */
+       size_bytes = ((u64)fdt32_to_cpu(reg[2]) << 32) | fdt32_to_cpu(reg[3]);
+
+       return size_bytes;
+}
+
+static int
+smbios_write_type16_sum_memory_nodes(ulong *current, int handle,
+                                    struct smbios_ctx *ctx, u16 cnt, u64 size)
+{
+       struct smbios_type16 *t;
+       int len = sizeof(*t);
+       u8 *eos_addr;
+       void *hdl;
+       size_t hdl_size;
+
+       t = map_sysmem(*current, len);
+       memset(t, 0, len);
+
+       fill_smbios_header(t, SMBIOS_PHYS_MEMORY_ARRAY, len, handle);
+
+       /* eos is at the end of the structure */
+       eos_addr = (u8 *)t + len - sizeof(t->eos);
+       smbios_set_eos(ctx, eos_addr);
+
+       /* default attributes */
+       t->location = SMBIOS_MA_LOCATION_MOTHERBOARD;
+       t->use = SMBIOS_MA_USE_SYSTEM;
+       t->mem_err_corr = SMBIOS_MA_ERRCORR_UNKNOWN;
+       t->mem_err_info_hdl = SMBIOS_MA_ERRINFO_NONE;
+       t->num_of_mem_dev = cnt;
+
+       /* Use extended field */
+       t->max_cap = cpu_to_le32(0x80000000);
+       t->ext_max_cap = cpu_to_le64(size >> 10); /* In KB */
+
+       /* Save the memory array handles */
+       if (!sysinfo_get_data(ctx->dev, SYSID_SM_MEMARRAY_HANDLE, &hdl,
+                             &hdl_size) &&
+           hdl_size == SYSINFO_MEM_HANDLE_MAX * sizeof(u16))
+               *((u16 *)hdl) = handle;
+
+       len = t->hdr.length + smbios_string_table_len(ctx);
+       *current += len;
+       unmap_sysmem(t);
+
+       return len;
+}
+
+static void
+smbios_pop_type16_from_memcontroller_node(ofnode node, struct smbios_type16 *t)
+{
+       ofnode child;
+       int count = 0;
+       u64 total = 0;
+
+       /* default attributes */
+       t->location = SMBIOS_MA_LOCATION_MOTHERBOARD;
+       t->use = SMBIOS_MA_USE_SYSTEM;
+       t->mem_err_info_hdl = SMBIOS_MA_ERRINFO_NONE;
+
+       /* Check custom property 'ecc-enabled' */
+       if (ofnode_read_bool(node, "ecc-enabled"))
+               t->mem_err_corr = SMBIOS_MA_ERRCORR_SBITECC;
+       else
+               t->mem_err_corr = SMBIOS_MA_ERRCORR_UNKNOWN;
+
+       /* Read subnodes with 'size' property */
+       for (child = ofnode_first_subnode(node); ofnode_valid(child);
+            child = ofnode_next_subnode(child)) {
+               u64 sz = 0;
+               const fdt32_t *size;
+               int len;
+
+               size = ofnode_read_prop(child, "size", &len);
+               if (!size || len < sizeof(fdt32_t) || len % sizeof(fdt32_t))
+                       continue;
+
+               /* 64-bit size: <hi lo> or 32-bit size */
+               if (len >= sizeof(fdt32_t) * 2)
+                       sz = ((u64)fdt32_to_cpu(size[0]) << 32) |
+                            fdt32_to_cpu(size[1]);
+               else
+                       sz = fdt32_to_cpu(size[0]);
+
+               count++;
+               total += sz;
+       }
+
+       /*
+        * Number of memory devices associated with this array
+        * (i.e., how many Type17 entries link to this Type16 array)
+        */
+       t->num_of_mem_dev = count;
+
+       /* Use extended field */
+       t->max_cap = cpu_to_le32(0x80000000);
+       t->ext_max_cap = cpu_to_le64(total >> 10); /* In KB */
+}
+
+static void smbios_pop_type16_si(struct smbios_ctx *ctx,
+                                struct smbios_type16 *t)
+{
+       t->location = smbios_get_val_si(ctx, "location", SYSID_NONE,
+                                       SMBIOS_MA_LOCATION_UNKNOWN);
+       t->use = smbios_get_val_si(ctx, "use", SYSID_NONE,
+                                  SMBIOS_MA_USE_UNKNOWN);
+       t->mem_err_corr = smbios_get_val_si(ctx, "memory-error-correction", 
SYSID_NONE,
+                                           SMBIOS_MA_ERRCORR_UNKNOWN);
+       t->max_cap = smbios_get_val_si(ctx, "maximum-capacity", SYSID_NONE, 0);
+       t->mem_err_info_hdl = smbios_get_val_si(ctx, 
"memory-error-information-handle",
+                                               SYSID_NONE, 
SMBIOS_MA_ERRINFO_NONE);
+       t->num_of_mem_dev = smbios_get_val_si(ctx, "number-of-memory-devices", 
SYSID_NONE, 1);
+       t->ext_max_cap = smbios_get_u64_si(ctx, "extended-maximum-capacity", 
SYSID_NONE, 0);
+}
+
+static int smbios_write_type16_1array(ulong *current, int handle,
+                                     struct smbios_ctx *ctx, int idx,
+                                     int type)
+{
+       struct smbios_type16 *t;
+       int len = sizeof(*t);
+       u8 *eos_addr;
+       void *hdl;
+       size_t hdl_size;
+
+       t = map_sysmem(*current, len);
+       memset(t, 0, len);
+
+       fill_smbios_header(t, SMBIOS_PHYS_MEMORY_ARRAY, len, handle);
+
+       /* eos is at the end of the structure */
+       eos_addr = (u8 *)t + len - sizeof(t->eos);
+       smbios_set_eos(ctx, eos_addr);
+
+       if (type == SMBIOS_MEM_CUSTOM)
+               smbios_pop_type16_si(ctx, t);
+       else if (type == SMBIOS_MEM_FDT_MEMCON_NODE)
+               smbios_pop_type16_from_memcontroller_node(ctx->node, t);
+
+       /* Save the memory array handles */
+       if (!sysinfo_get_data(ctx->dev, SYSID_SM_MEMARRAY_HANDLE, &hdl,
+                             &hdl_size) &&
+           hdl_size == SYSINFO_MEM_HANDLE_MAX * sizeof(u16))
+               *((u16 *)hdl + idx) = handle;
+
+       len = t->hdr.length + smbios_string_table_len(ctx);
+       *current += len;
+       unmap_sysmem(t);
+
+       return len;
+}
+
+static int smbios_write_type16(ulong *current, int *handle,
+                              struct smbios_ctx *ctx)
+{
+       int len;
+       struct smbios_ctx ctx_bak;
+       ofnode child;
+       int idx;
+       u64 total = 0;
+       int count = 0;
+       int hdl_base = *handle;
+
+       if (!IS_ENABLED(CONFIG_OF_CONTROL))
+               return 0;       /* Error, return 0-length */
+
+       /* Step 1: Scan any subnode exists under 'memory-array' */
+       len = smbios_scan_subnodes(current, ctx, handle,
+                                  smbios_write_type16_1array,
+                                  SMBIOS_MEM_CUSTOM);
+       if (len)
+               return len;
+
+       /* Step 2: Scan 'memory' node from the entire FDT */
+       for (child = ofnode_first_subnode(ofnode_root());
+            ofnode_valid(child); child = ofnode_next_subnode(child)) {
+               const char *str;
+
+               /* Look up for 'device_type = "memory"' */
+               str = ofnode_read_string(child, "device_type");
+               if (str && !strcmp(str, "memory")) {
+                       count++;
+                       total += smbios_pop_size_from_memory_node(child);
+               }
+       }
+       /*
+        * Generate one type16 instance for all 'memory' nodes,
+        * use idx=0 implicitly
+        */
+       if (count)
+               len += smbios_write_type16_sum_memory_nodes(current, *handle,
+                                                           ctx, count, total);
+
+       /* Step 3: Scan 'memory-controller' node from the entire FDT */
+       /* idx starts from 1 */
+       memcpy(&ctx_bak, ctx, sizeof(ctx_bak));
+       for (idx = 1, child = ofnode_first_subnode(ofnode_root());
+            ofnode_valid(child); child = ofnode_next_subnode(child)) {
+               const char *compat;
+               const char *name;
+
+               /*
+                * Look up for node with name or property 'compatible'
+                * containing 'memory-controller'.
+                */
+               name = ofnode_get_name(child);
+               compat = ofnode_read_string(child, "compatible");
+               if ((!compat || !strstr(compat, "memory-controller")) &&
+                   (!name || !strstr(name, "memory-controller")))
+                       continue;
+
+               *handle = hdl_base + idx;
+               ctx->node = child;
+               /*
+                * Generate one type16 instance for each 'memory-controller'
+                * node, sum the 'size' of all subnodes.
+                */
+               len += smbios_write_type16_1array(current, *handle, ctx, idx,
+                                                 SMBIOS_MEM_FDT_MEMCON_NODE);
+               idx++;
+               memcpy(ctx, &ctx_bak, sizeof(*ctx));
+       }
+
+       return len;
+}
+
 #endif /* #if IS_ENABLED(CONFIG_GENERATE_SMBIOS_TABLE_VERBOSE) */
 
 static int smbios_write_type32(ulong *current, int *handle,
@@ -1199,6 +1480,7 @@ static struct smbios_write_method smbios_write_funcs[] = {
        { smbios_write_type4, "processor"},
 #if IS_ENABLED(CONFIG_GENERATE_SMBIOS_TABLE_VERBOSE)
        { smbios_write_type9, "system-slot"},
+       { smbios_write_type16, "memory-array"},
 #endif
        { smbios_write_type32, },
        { smbios_write_type127 },
-- 
2.25.1

Reply via email to