Describing a structure
Every structure type the library knows is described by a specification, a
dmi_entity_spec_t, which says everything the library needs to know of the
type: its number and its names, the versions it belongs to, how its bytes are
laid out, what its values mean, and the rules its data is checked against.
Decoding, encoding, linking, the output of the tool in every format and the
lint rules of the values all work from it, so a type described once is
handled everywhere.
A structure type is made of three parts:
-
The decoded structure, a C structure in a public header, e.g.
dmi_cache_tin<opendmi/entity/cache.h>, which a program reads. -
The layout, a list of fields, which says how the bytes of the structure become the members of the decoded structure, and back.
-
The attributes, a list of them, which say what the members mean: their names, units, limits and values.
Fields describe bytes and attributes describe values, and the two meet in the members of the decoded structure. The cache information structure is used as the example throughout this chapter.
1. The specification
The specification of the cache information structure, in
libopendmi/src/entity/cache.c, starts with what the type is:
const dmi_entity_spec_t dmi_cache_spec =
{
.code = "cache",
.name = "Cache information",
.type = DMI_TYPE(cache),
.params = {
.minimum_version = DMI_VERSION(2, 0, 0),
.required_from = DMI_VERSION(2, 3, 0),
.minimum_length = 0x0F,
.decoded_length = sizeof(dmi_cache_t)
},
.fields = ...,
.attributes = ...,
.lint_rules = ...
};
code-
Name of the type in the data the tool exports and in the options which select the types, e.g.
opendmi show -t cache. Codes are never translated. name-
Name of the type as the output shows it, which is translated into the locale.
type-
Structure type the specification decodes the structures into, see Structure types.
params-
Versions of the specification the type belongs to: the one which defined it, the ones from which the table is required to have a structure of it or is expected to, whether the table may have only one of it, the minimum length a structure of it has, and the size of the decoded structure, which the library allocates before decoding.
fields,attributes,lint_rules-
The layout, the attributes and the rules, described below.
handlers-
Functions which do what the fields and the attributes cannot say, described at the end of the chapter.
The specifications of the standard types are listed in the library, and the ones of the vendors are brought by their modules, see Extension modules.
2. Structure types
A structure type tells what the information a structure is decoded into is,
which is one C type, e.g. dmi_cache_t. Types are objects rather than
numbers: dmi_type_t constants, declared by <opendmi/types.h> for the
standard types and by the headers of the modules for the vendor ones, and
named by DMI_TYPE(), e.g. DMI_TYPE(cache) for dmi_type_cache:
const dmi_type_t dmi_type_cache = { .id = DMI_TYPE_ID_CACHE };
The number a type carries, id, is the one its specification gives the
structures, and DMI_TYPE_ID() names the numbers, e.g.
DMI_TYPE_ID(CACHE). Numbers do not tell the types, and the objects do:
-
Several specifications may decode the structures of one type, e.g. the layouts of different generations of a vendor, which all refer to it. The code handling the structures asks for the type, whatever layout the table holds.
-
Several types may share a number, e.g. Intel vPro information and Lenovo ThinkVantage Technologies enablement, which are both of type 131, and asking for one of them never gives the other.
-
A structure of a type may be found at another number, where a vendor places it, and is still of its type.
So everything asking for a structure of a type takes the type object:
dmi_entity_info, dmi_entity_data, the lookups of the registry and the
types the handles refer to. Numbers are for what the table itself holds: the
number a structure is found at, which dmi_entity_type_id gives, the types
the options of the tool select, and the structures no specification
describes, which dmi_registry_lookup_first_id finds.
3. Fields
The layout of a structure is a list of fields, in the order the structure holds them, from the byte after the header on. A field names the member of the decoded structure it goes into and the bytes it occupies:
.fields = DMI_FIELDS({
DMI_FIELD_STRING(dmi_cache_t, socket_designator),
// One word holding the configuration of the cache
DMI_FIELD_BITS(dmi_cache_t, level, 3,
.decode = dmi_cache_decode_level,
.encode = dmi_cache_encode_level),
DMI_FIELD_BITS(dmi_cache_t, socketed, 1),
DMI_FIELD_BITS_SKIP(1),
DMI_FIELD_BITS(dmi_cache_t, location, 2),
DMI_FIELD_BITS(dmi_cache_t, enabled, 1),
DMI_FIELD_BITS(dmi_cache_t, mode, 2),
DMI_FIELD_PAD(dmi_word_t),
DMI_FIELD(dmi_cache_t, maximum_size, dmi_word_t,
.decode = dmi_cache_decode_size,
.encode = dmi_cache_encode_size),
DMI_FIELD(dmi_cache_t, installed_size, dmi_word_t,
.decode = dmi_cache_decode_size,
.encode = dmi_cache_encode_size),
DMI_FIELD(dmi_cache_t, supported_sram, dmi_word_t),
DMI_FIELD(dmi_cache_t, current_sram, dmi_word_t),
DMI_FIELD_GROUP(.since = DMI_VERSION(2, 1, 0)),
DMI_FIELD(dmi_cache_t, speed, dmi_byte_t),
DMI_FIELD(dmi_cache_t, error_correction, dmi_byte_t),
DMI_FIELD(dmi_cache_t, type, dmi_byte_t),
DMI_FIELD(dmi_cache_t, associativity, dmi_byte_t),
DMI_FIELD_GROUP(.since = DMI_VERSION(3, 1, 0)),
DMI_FIELD_EXTENDED(dmi_cache_t, maximum_size, dmi_dword_t,
.when_raw = 0xFFFFu,
.decode = dmi_cache_decode_size_ex,
.encode = dmi_cache_encode_size_ex),
DMI_FIELD_EXTENDED(dmi_cache_t, installed_size, dmi_dword_t,
.when_raw = 0xFFFFu,
.decode = dmi_cache_decode_size_ex,
.encode = dmi_cache_encode_size_ex),
{}
}),
The type of the data a field is written with, e.g. dmi_word_t, gives the
number of the bytes it occupies, which may be fewer than the member holds: a
member is made wider than its field when the value standing for "unknown"
would otherwise collide with a value a device may really have. The kinds of
the fields are:
| Field | What it describes |
|---|---|
|
An integer, read into the member. |
|
The number of a string, read into a pointer to the string. |
|
A binary-coded decimal, read into the number it spells. |
|
Sixteen bytes of a UUID, read into a |
|
Bytes taken as they are, which the member refers to in place. |
|
Ranges of the bits of a unit several members share, from the least significant bit up, the bits the specification reserves between them, and the ones left over at the end of the unit. |
|
An array, preceded by the number of its elements, whose elements are described by fields of their own, or by one field for the arrays of plain values. |
|
A fixed number of elements in a row, held in place in an array member of the decoded structure, whose number of elements it takes. The elements are described the way the ones of an array are, e.g. the six firmware status registers of an interface of the Intel Management Engine. |
|
A wider field which a later version adds for a value the plain one is too
narrow for. Both name the same member, and the plain one holding the value
of |
|
Data which feeds several members at once, whose handlers are given the whole decoded structure. |
|
A member the data does not carry, set before anything is read, e.g. a handle standing for no reference until the data gives one. |
|
Bytes the structure holds and nothing reads. |
|
The beginning of the fields a version of the specification adds, which occupies no bytes. |
|
The offset the fields after it begin at, which occupies no bytes and is checked while decoding. |
3.1. Versions
Every version of the specification adds fields at the end of a structure, and
a structure is allowed to end at the beginning of any version, since the
firmware writes the structure of the version it knows. DMI_FIELD_GROUP
marks the fields of a version with since: a cache information structure of
SMBIOS 2.0 ends before the speed, and one of 2.1 before the extended sizes.
The version the data has been read up to becomes the level of the structure,
which the output shows, and the members of the fields the data ends before
are left zero, or are given the value the field declares as absent. A
structure which ends in the middle of the fields of a version is decoded up
to where it ends, and is marked as incomplete.
Vendor structures grow the same way without versions, and a group of their
fields tells whether the structure holds it with present, a bool member
the decoder sets once every field of the group has been read:
DMI_FIELD_GROUP(.present = dmi_member(dmi_hpe_trusted_module_t, has_chip)),
DMI_FIELD(dmi_hpe_trusted_module_t, chip, dmi_byte_t),
The attributes of the fields of the group use the member to tell the values from the zeros of the fields the structure is too short for, so no handler has to count the length of the structure, and encoding writes the fields of no group the structure does not hold: the canonical mode ends the structure there, and the preserve mode keeps the source bytes of the group.
A version may also give a meaning to the reserved bits of a field the
structure already holds, which says nothing about its length: the version the
entry point declares decides it instead. Such ranges of bits declare the
version they are defined from with from, and a range the version widens is
declared twice, up to the version with before and from it with from. The
attributes byte of a memory device is read this way, since SMBIOS 3.10 widens
the rank it holds to five bits and adds two flags next to it:
DMI_FIELD_BITS(dmi_memory_device_t, rank, 4, .before = DMI_VERSION(3, 10, 0)),
DMI_FIELD_BITS(dmi_memory_device_t, rank, 5, .from = DMI_VERSION(3, 10, 0)),
DMI_FIELD_BITS(dmi_memory_device_t, is_disabled, 1, .from = DMI_VERSION(3, 10, 0)),
DMI_FIELD_BITS(dmi_memory_device_t, is_unmapped, 1, .from = DMI_VERSION(3, 10, 0)),
DMI_FIELD_PAD(dmi_byte_t),
Ranges of bits the version of the table does not define are left to the padding, which keeps the bits the source data has in the preserve mode of the encoder, and a range defined from a later version than the fields the data holds raises the level of the structure to it.
DMI_FIELD_OFFSET states where a part of the structure begins, the way the
tables of the specification number the offsets. It says nothing about where
the data may end, and is checked as the structure is decoded, so that a field
lost from the layout, or added to it twice, is caught where it happens rather
than in the values of everything after it.
3.2. Handlers of the fields
A field whose value is not the number the data holds has a pair of handlers:
decode makes the value of the member from the data, and encode makes the
data from the value, which undoes it. The level of the cache is counted from
zero in the data and from one everywhere else:
bool dmi_cache_decode_level(
const dmi_field_t *field,
const dmi_field_data_t *data,
void *value)
{
return dmi_field_set(field, value, data->number + 1);
}
bool dmi_cache_encode_level(
const dmi_field_t *field,
const void *value,
dmi_field_data_t *data)
{
uintmax_t level = dmi_field_get(field, value);
data->number = (level > 0) ? level - 1 : 0;
return true;
}
Handlers deal with values rather than with bytes: the engine reads the bytes,
resolves the string a string field refers to, and writes them back.
dmi_field_get and dmi_field_set read and write a member of whatever width
the field names. A handler depends on the data of its own field alone, and
whatever combines several fields is derived once all of them have been read,
see Handlers of the structure. This is what lets the encoder tell whether
the data it has already decodes into the value the structure holds, and keep
it.
4. Attributes
The attributes of a structure say what its members mean, one attribute per member:
.attributes = DMI_ATTRIBUTES({
DMI_ATTRIBUTE(dmi_cache_t, level, INTEGER, {
.code = "level",
.name = "Cache level",
.minimum = dmi_value_ptr((unsigned short)1),
.maximum = dmi_value_ptr((unsigned short)8),
}),
DMI_ATTRIBUTE(dmi_cache_t, installed_size, SIZE, {
.code = "installed-size",
.name = "Installed cache size"
}),
DMI_ATTRIBUTE(dmi_cache_t, speed, INTEGER, {
.code = "speed",
.name = "Cache speed",
.unit = DMI_UNIT_NANOSECOND,
.level = DMI_VERSION(2, 1, 0)
}),
DMI_ATTRIBUTE(dmi_cache_t, type, ENUM, {
.code = "type",
.name = "System cache type",
.unspec = dmi_value_ptr(DMI_CACHE_TYPE_UNSPEC),
.unknown = dmi_value_ptr(DMI_CACHE_TYPE_UNKNOWN),
.values = &dmi_cache_type_names,
.level = DMI_VERSION(2, 1, 0)
}),
...
{}
}),
The type of an attribute says how its value is read and shown:
| Type | Value |
|---|---|
|
A string, |
|
A flag, shown with the names of |
|
A number of the |
|
A number of bytes, shown in the unit which suits it. |
|
An address, shown in hexadecimal. |
|
One of the values of an enumeration, named by |
|
A set of the bits of a flag word, named by |
|
Values of those types. |
|
Bytes taken as they are, which the flags may say to show as a MAC or an IP address. |
|
The handle of another structure. |
|
A nested structure, described by attributes of its own. |
|
A value whose type depends on another member, described by one of several attributes. |
The parameters of an attribute are:
code,name-
The name of the value in the exported data, which is unique within its structure, and the one the output shows, which is translated.
unit,scale,flags-
How the value is shown: its unit of measurement, the digits after the point, and whether it is signed, hexadecimal, a binary-coded decimal or an address.
unspec,unknown-
The values which stand for "unspecified" and "unknown" rather than for themselves, which the output shows by those words, and which the lint rules of the values leave out.
values-
The names of the values of an enumeration or of the bits of a set, see Name sets.
minimum,maximum-
The limits the specification sets for the value, which the lint rule
value.rangechecks. level-
The version of the specification the value appears in, so that the output leaves out the values a structure of an older version does not have.
targets,link-
For a handle: the types of the structures it may refer to, and the member the referenced structure is put into once the table is linked.
attrs,variants-
The attributes of a nested structure, and the variants of a variant value.
An array of values is described by DMI_ATTRIBUTE_ARRAY, which names the
member holding the number of the elements along with the array, and a vector
by DMI_ATTRIBUTE_VECTOR, whose number of elements is the one of the array
member. Both are shown the same way, and dmi_attribute_is_array and
dmi_attribute_get_elements tell the code which walks the attributes where
the elements are. The processor
information structure refers to its caches this way:
DMI_ATTRIBUTE(dmi_processor_t, l1_cache_handle, HANDLE, {
.code = "l1-cache-handle",
.name = "L1 Cache handle",
.targets = dmi_types(DMI_TYPE(cache)),
.level = DMI_VERSION(2, 1, 0),
.link = dmi_member(dmi_processor_t, l1_cache)
}),
Linking the table resolves the handle held in l1_cache_handle into the
cache information structure it refers to, which goes into l1_cache, and
reports a handle which refers to no structure, or to a structure of another
type. The lint rules link.dangling and link.wrong-type check the same
attribute.
Attributes are what the tool is built on: opendmi show shows the values of
the attributes, and opendmi export writes them under their codes. A member
which has no attribute is known to the program which reads the structure, and
to nothing else.
5. Handlers of the structure
Some structures cannot be described by fields alone, and their specification
gives handlers, in handlers, which do what the fields and the attributes
cannot:
decode,encode-
Decode and encode the structure by hand, in place of the fields, see Decoders and encoders. A few types are decoded this way, e.g. the additional information, whose entries declare lengths of their own, and the management controller host interface, whose data is read again according to its type once it has been read.
derive-
Fills in the members which are computed from the others rather than read, once the fields have been read, e.g. whether a checksum the structure carries matches. It never writes a member a field decodes into: those are what the structure is encoded from.
link-
Resolves what the attributes cannot say by themselves, once the references they declare have been resolved, e.g. the references back from the referenced structures, or the handles whose type another member names.
cleanup-
Frees what the handlers of the specification have allocated beyond the decoded structure itself, e.g. the arrays a decoding or deriving handler fills in. The arrays the fields declare and the arrays of the linked structures belong to the library, which frees them on its own, so most specifications need no cleanup handler.