Extension modules
The specification leaves the structure types from 128 to 255 to the vendors of the platforms, and nothing keeps two vendors from giving the same number to different structures. The structure types of a vendor are therefore brought by a module of their own, which is enabled when the data comes from a platform of that vendor.
1. Modules
A module, dmi_module_t, is a list of the specifications of the structure
types it brings, which are written the same way the standard ones are, see
Describing a structure:
const dmi_module_t dmi_dell_module =
{
.code = "dell",
.name = "Dell extensions",
.entities = (const dmi_entity_spec_t *[]){
&dmi_dell_bios_flags_spec,
&dmi_dell_revisions_spec,
...
nullptr
}
};
The modules of the library are built in, and listed in
dmi_builtin_modules, so that they are linked into a static build as well:
acer, ami, apple, cisco, dell, hpe, intel, intel-rsd, lenovo
and sun. The tool
lists them with opendmi modules, and opendmi types -m dell lists the types
a module brings.
2. Enabling a module
A module is enabled for the platforms it lists, as the context is opened,
when the context has DMI_CONTEXT_FLAG_AUTO_MODULES, which the tool always
gives it unless told otherwise by its --no-auto-modules option:
const dmi_module_t dmi_dell_module =
{
...
.platforms = DMI_PLATFORMS({
{ .firmware_vendor = DMI_VENDOR_DELL },
DMI_PLATFORM_NULL
})
};
A program enables a module explicitly before the data is opened, e.g. when
the data does not tell the vendor, and the tool does with its -m option:
dmi_context_t *context = dmi_create(DMI_CONTEXT_FLAG_LINK);
if ((context == nullptr) ||
!dmi_add_extension(context, dmi_module_find("dell")) ||
!dmi_open(context, nullptr)) {
dmi_destroy(context);
return 1;
}
Enabling a module maps the types it brings to their specifications in the
context. A type is mapped to one specification at a time, so a module which
brings a type another enabled module already has, or the same module enabled
twice, fails with DMI_ERROR_MODULE_CONFLICT. Modules enabled explicitly
take precedence: a module of the platform whose types conflict with them is
skipped. Structures of the types no enabled module brings are left
undecoded, with their headers and strings only.
Modules enabled for the platform stay enabled until the context is closed, while the ones enabled explicitly stay for the next data to be opened.
3. Platform
The platform the data comes from, dmi_platform_t, is told as the context is
opened, before the rest of the table is decoded: the vendor of the firmware
from the platform firmware information (type 0), the vendor and the product
name of the system from the system information (type 1), the vendor of the
baseboard from the baseboard information (type 2), and the vendor of the
processors from the processor information (type 4). The vendors are told
apart, since each of them adds structures of its own: the firmware of AMI,
e.g., is found on the boards of many vendors. The vendor of the firmware
tells the family and the generation of the product by the detect handler
of its dmi_vendor_spec_t, since the structures of a vendor often depend on
them. HP and HPE, e.g., give the same
type numbers to different structures in their workstations and their
servers, and to different layouts in different generations of the servers.
A module lists the platforms it is enabled for by conditions on the vendors and the family, whose members left out are satisfied by any platform:
.platforms = DMI_PLATFORMS({
{ .firmware_vendor = DMI_VENDOR_HP, .family = DMI_HPE_FAMILY_SERVER },
{ .firmware_vendor = DMI_VENDOR_HPE, .family = DMI_HPE_FAMILY_SERVER },
DMI_PLATFORM_NULL
})
and a specification the generations it applies to, which the vendor numbers so that a later generation has a greater number:
.params = {
.generations = { .minimum = DMI_HPE_GEN9 },
...
}
Both bounds are included, and a zero bound leaves the range open on its side. A module may bring several specifications of the same type, whose ranges do not overlap, and the one of the generation of the platform is chosen. A bounded range is never chosen while the generation is unknown, so that no structure is decoded by the layout of another generation. The rest of the specifications leave the range zeroed, and apply to all generations.
The platform is available through dmi_get_platform, and belongs to the
context. A program sets another one when the data does not tell it, or to
decode structures without opening any data: it creates a platform with
dmi_platform_create, which owns the strings set with
dmi_platform_set_product and dmi_platform_set_family, and gives it to the
context with dmi_set_platform, which keeps a copy of it:
dmi_platform_t *platform = dmi_platform_create(context);
platform->firmware_vendor = DMI_VENDOR_HPE;
platform->generation = DMI_HPE_GEN10;
dmi_platform_set_family(platform, DMI_HPE_FAMILY_SERVER);
dmi_set_platform(context, platform);
dmi_platform_destroy(platform);
4. Structures of any platform
Some structures are defined neither by the vendor of the platform nor by the
specification, but by a third party whose code the firmware of many vendors
includes, e.g. the Intel reference code. Their module is enabled for the
platforms of any vendor, by a condition on the vendor of the processors
rather than on the vendor of the firmware. Vendors give the same type numbers
to structures of their own, so the module has DMI_MODULE_FLAG_YIELD, and its specifications give
way to the specifications of the other enabled modules of the same types,
rather than conflicting with them:
const dmi_module_t dmi_intel_module =
{
.code = "intel",
.name = "Intel extensions",
.entities = ...,
.flags = DMI_MODULE_FLAG_YIELD,
.platforms = DMI_PLATFORMS({
{ .processor_vendor = DMI_VENDOR_INTEL },
DMI_PLATFORM_NULL
})
};
The structures of Intel Rack Scale Design are only found on the platforms
built to it, at type numbers of vendor structures, so their module,
intel-rsd, is enabled explicitly.
5. Relocations
Some vendors place structures defined by others at type numbers of their own, since the original numbers are taken by their own structures. Dell, e.g., places the structures of the Intel reference code 16 types down. The module of such a vendor lists the relocations, which apply while it is enabled, to the specifications of the other enabled modules:
.relocations = DMI_RELOCATIONS({
{ &dmi_intel_fvi_spec, 205 },
{}
})
A vendor which places a structure at a type number or another, e.g. on platforms of different generations, relocates its specification more than once, and the structure is decoded at every one of the type numbers:
.relocations = DMI_RELOCATIONS({
{ &dmi_intel_fvi_spec, 205 },
{ &dmi_intel_fvi_spec, 237 },
{}
})
A vendor whose platforms never carry a structure, and give its type number to
a structure the module of the vendor does not describe, relocates it to
DMI_TYPE_ID_INVALID, so that the type is left undecoded rather than decoded
by a wrong layout:
.relocations = DMI_RELOCATIONS({
{ &dmi_intel_fvi_spec, DMI_TYPE_ID_INVALID },
{}
})
A relocated structure is decoded by its original specification, since its
data is laid out as that specification describes it. dmi_entity_type_id
gives the type number found in the table, while dmi_entity_type gives the
structure type of the specification, which dmi_entity_info and
dmi_entity_data check, so that the code which handles the structure does
not depend on where the vendor places it.
6. Signatures
Some tables hold structures of different layouts at the same type number, since two vendors whose code the firmware includes give it to structures of their own. Lenovo tables, e.g., hold both Intel vPro information and Lenovo ThinkVantage Technologies enablement at type 131. Neither the type number nor the platform tells them apart, so their specifications tell the content of the structures they describe by a signature, which is declared rather than checked by code of their own:
.params = {
.signature = DMI_SIGNATURE({
.offset = 0x38,
.bytes = "vPro",
.size = 4
})
}
A signature may name bytes at an offset, the length of the formatted area,
the text of a string, and that the structure carries no strings at all, and
a structure matches it if it meets all of the conditions it names. A type
number is mapped to one specification without a signature at most, and to a
few with signatures, up to DMI_TYPE_CANDIDATES in all. As a structure is
decoded, the specifications with signatures are tried in the order they are
mapped, and the one without a signature is taken for the structures no
signature matches, which are left undecoded if there is none. The fields of
a signature are kept in the decoded structure, so that it is written back
with it.
The specifications told apart by signatures decode into different structure
types, e.g. DMI_TYPE(intel_vpro) and DMI_TYPE(lenovo_tvt), so asking for
the information of one of them never gives the other, see
Structure types.
7. Groups
Firmware often lists the structures of a kind by a group association
(type 14) of a well-known name, e.g. the Intel reference code lists its
firmware version information by the Firmware Version Info group. A module
names such groups along with the specifications of their members:
.groups = DMI_GROUPS({
{ "Firmware Version Info", &dmi_intel_fvi_spec },
{}
})
Groups are not used to tell the structures, which their type numbers and
signatures do, but to check them: the group-assoc.member lint rule reports
the members of such a group which are not of the structure type of the
specification, whatever the layout they are decoded by. A report tells either a
structure the vendor places at a type number no module knows of, which calls
for a relocation, or a group the firmware lists a structure in by mistake.
8. Modules of a program
A program brings structure types of its own by a module of its own, which it
registers once, before the contexts which use it are created. The library
links the registered modules through their next member, so a module is not
constant:
static dmi_module_t example_module =
{
.code = "example",
.name = "Example extensions",
.entities = (const dmi_entity_spec_t *[]){
&example_spec,
nullptr
}
};
if (!dmi_module_register(&example_module))
return 1;
A registered module is found by dmi_module_find and listed by
dmi_module_next along with the built-in ones, and is enabled the same way.
The library does not copy it, so it stays valid for as long as the program
runs. Registration is not thread-safe, and belongs to the start of the
program.