[PATCH v4 3/5] smbios: add support for dynamic generation of Type 16 table

Raymond Mao raymondmaoca at gmail.com
Fri Nov 14 18:06:00 CET 2025


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 <raymondmaoca at gmail.com>
---
Changes in v4:
- Initial patch.

 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



More information about the U-Boot mailing list