smbios: add support for dynamic generation of Type 16 table

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@gmail.com>
Tested-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
This commit is contained in:
Raymond Mao
2026-02-13 17:52:48 -05:00
committed by Tom Rini
parent 83b28b55d7
commit 374896158b
7 changed files with 387 additions and 0 deletions

View File

@@ -80,6 +80,9 @@
system-slot {
};
memory-array {
};
};
};
};

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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_ */

View File

@@ -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,

View File

@@ -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 },