LIBJEDEC_SPD(3JEDEC) 3JEDEC LIBJEDEC_SPD(3JEDEC)

libjedec_spdparse DIMM SPD data

library “libjedec”

#include <libjedec.h>

nvlist_t *
libjedec_spd(const uint8_t *buf, size_t buflen, spd_error_t *errp);

The () function parses a binary payload of SPD (serial presence detect) data and transforms it into a nvlist_t that can be used by callers to inspect the data.

SPD data is defined by JEDEC and used in various DDR DRAM standards. This information describes everything from the size and organization of the DIMM and its dies, timing information, electrical properties, manufacturing data, and more. A DRAM module (the thing we plug into a computer) is made up of a number of different components. For example, a DDR5 module (which is plugged into a slot) contains not only a number of different DRAM dies, but also power controllers, registers, clock drivers, and more. The SPD data describes all of this information.

There are two major properties of SPD information that determine which keys are present in it:

  1. The DDR version that the device implements. Examples of the version information include DDR4, LPDDR5, DDR3, and many others.
  2. The module's type. Examples of this include whether it is an RDIMM (registered DIMM), UDIMM (unregistered DIMM), soldered down, etc.

While each DDR version has different SPD information and the module's type may further modify parts of it, the nvlist_t that is returned attempts to normalize this information as much as possible. Each key that is returned is defined in <libjedec.h> as a macro. The key space is dot (‘.’) delineated. The following key spaces are currently used:

“meta”
This contains information about the SPD ROM itself, the encoding revision, the DRAM standard in use, the type of module, etc. This section also contains all of the CRC values and information that affects how the rest of data is parsed (such as whether or not the package is monolithic).
“dram”
This section contains information that is specific to the SDRAM dies that are present. This includes addressing information such as the number of rows, columns, banks, and bank groups that are on each die. It also includes information such as the supported voltages of the dies themselves and then describes a lot of the different timing properties such as the minimum times that a controller must wait between certain actions.
“ddr4, “ddr5””
This section contains information that is specific to a given DDR standard and cannot otherwise share a common definition. This section has a few additional subdivisions of information to cover items related to the module's type or are otherwise logically grouped together such as refresh management. For example, the library uses namespaces such as “ddr4.rdimm” and “ddr5.lrdimm” for properties that would be specific to DDR4 RDIMMs and DDR5 LRDIMMs respectively.
“module”
This section relates to information about the physical module which includes information such as its physical dimensions, the types of components that are present on it, signal mapping, and related.
“mfg”
This section contains information about the module manufacturing. This is where things like the module's serial number, the manufacturer, the part number, and related can be found. Generally this information is found in a different part of the SPD ROM and therefore may not always be present.
“errors”
This section is an embedded nvlist_t that contains keys for each value that couldn't be parsed. For more information, see the Parsing Errors section for more information.

The library attempts to use a fixed number of data types when performing conversions and follows the following guidelines:

  • By default, integer values use a uint32_t to try and make things more uniform where possible and to allow for future changes. A uint64_t is used when the types contain data that could naturally overflow a uint32_t such as when counting the number of bits, bytes, or measuring time (which is normalized to picoseconds).

    All integers are denoted as such explicitly with their size: either ‘uint32_t’ or ‘uint64_t’. Some banks of keys all have the same type and are denoted as such in the introductory block comment. Use nvlist_lookup_uint32(3NVPAIR) or nvlist_lookup_uint64(3NVPAIR) to retrieve these keys.

  • Strings, which are stored as traditional char * values should only contain printable characters.

    All strings are denoted as such by using the comment ‘string’. Use nvlist_lookup_string(3NVPAIR) to retrieve these keys.

  • There are many values which represent enumerations. The raw type is stored as a uint32_t. In general, these enumerations, unless otherwise indicated should not be assumed to have the identical values of a given specification. This is done because the values are not always uniform from specification to specification, except in rare cases. Not all DDR specifications can result in the same set of enumeration values.

    For example, consider the spd_temp_type_t which is an enumeration that describes the kind of temperature monitoring device present. The enumeration contains values from DDR3, DDR4, and DDR5; however, each standard has its own defined type of SPD temperature sensor.

    All enumerations are denoted as such by using the comment ‘uint32_t (enum)’. Use nvlist_lookup_uint32(3NVPAIR) to retrieve these keys.

  • Several items are used to indicate a fact, but do not have any actual data associated with them. Their mere presence is sufficient to indicate a unique property of the DIMM. Examples of this include if the package is non-monolithic, the DIMM has asymmetrical ranks, etc.

    All such items are denoted as such by using the comment ‘key’. Use nvlist_lookup_boolean(3NVPAIR) to retrieve these keys.

  • A few types use arrays of uint32_t values to denote information. Some of the keys have a fixed number of entries that are expected, while others are variable and will depend on the parsed data. For example, a JEDEC ID is always encoded as two uint32_t values, the continuation and the underlying ID itself. Other values, such as the number of supported voltages or CAS latencies will vary.

    All fixed size arrays denote their size by using the comment ‘uint32_t [4]’, where the number four is replaced with the actual size. All variable sized arrays elide the number and are denoted by the comment ‘uint32_t []’. Use nvlist_lookup_uint32_array(3NVPAIR) to retrieve these keys.

There are a number of different issues that can arise when trying to parse the SPD data that a device returns. The library attempts to parse as much as it can and breaks errors into two large categories:

  1. Errors which prevent us from doing anything at all, which are noted in the ERRORS section. The main items that relate to data (as opposed to issues like running out of memory) include when we encounter a DDR specification that we don't support or that the data has an encoding version we don't understand. This can also occur if there's not enough data for us to figure out what kind of SPD information is encoded.
  2. Errors that mean we cannot parse a specific piece of SPD data. This is by far the most common form of error that we encounter and these are the entries that make up the “errors” section of the returned nvlist_t that was mentioned earlier.

The “errors” section is a nested nvlist_t that can be retrieved by using the SPD_KEY_ERRS key. This keys in this nvlist use the same name as the normal data keys. For example, if we were unable to properly parse the manufacturer's JEDEC ID, then the key SPD_KEY_MFG_MOD_MFG_ID (“mfg.module-mfg-id”) would not be present in the top-level nvlist_t and would instead be in the error nvlist_t.

Each item present in the error nvlist_t is itself a nvlist_t which contains the following keys:

The error code, a uint32_t, indicates a class of issue that occurred and its values are defined by the spd_error_kind_t enumeration. The following errors are defined:
SPD_ERROR_NO_XLATE
This indicates that the library did not know how to interpret a specific binary value. For example, if a given particular field was using what we believed to be a reserved value then we would set this error.
SPD_ERROR_UNPRINT
This indicates that we encountered a non-ASCII or otherwise unprintable character that was not allowed in the SPD string character set.
SPD_ERROR_NO_DATA
This indicates that there was no data for a given key. For example, this would be a string that consisted solely of space characters which are used to represent padding.
SPD_ERROR_INTERNAL
This indicates that something went wrong inside the library that was unexpected.
SPD_ERROR_BAD_DATA
This indicates that we've encountered something strange about the data in question. For example, this would be used for an invalid CRC or if the combination of values would cause an underflow in a calculation.
This is an error message that is intended for a person to understand and contains more specific information than the code above.

Finally, there is one last top-level key that we will set if we find that the set of data that we had was incomplete. Rather than tag every single item in the “errors” nvlist_t, we instead insert the key SPD_KEY_INCOMPLETE on the top-level nvlist. The key is a uint32_t that contains the starting offset of the that we were unable to parse, which may be less than the total amount of data that was provided. For example, if we had 100 bytes of data, but there was a 20 byte key starting at byte 90, the this key would have a value of 90 as that's what we were unable to parse.

Upon successful completion, the libjedec_spd() function returns an allocated nvlist_t that contains the parsed SPD data. There may be individual SPD fields that were unparsable which will be found in the SPD_KEY_ERRS field of the nvlist still. Otherwise NULL is returned and errp is set with a value that indicates why the function was unable to parse any data.

Printing SPD DRAM and Module Type

The following example shows how to parse the SPD information and print out the type of DRAM and module's type. This example assumes that one has already obtained the raw SPD file from somewhere and just prints the raw values.

#include <stdio.h>
#include <stdint.h>
#include <libjedec.h>

void
dump_dram_module_type(const uint8_t *buf, size_t len)
{
	nvlist_t *nvl;
	spd_error_t err;
	uint32_t ddr, mod;

	nvl = libjedec_spd(buf, len, &err);
	if (nvl == NULL) {
		(void) fprintf(stderr, "failed to parse SPD data: 0x%x\n",
		    err);
		return;
	}

	if (nvlist_lookup_pairs(nvl, 0,
	    SPD_KEY_DRAM_TYPE, DATA_TYPE_UINT32, &ddr,
	    SPD_KEY_MOD_TYPE, DATA_TYPE_UINT32, &mod,
	    NULL) != 0) {
		nvlist_free(nvl);
		(void) fprintf(stderr, "failed to look up keys\n");
		return;
	}

	nvlist_free(nvl);
	(void) printf("Found DDR type 0x%x, module type 0x%x\n", ddr,
	    mod);
}

Upon returning from the libjedec_spd() function, the errp pointer will be set to one of the following values:

This indicates that the system was able to parse SPD data into a valid nvlist_t and return it to the caller. This code should never appear when the function fails and returns NULL. Callers must still cehck the contents of the SPD_KEY_ERRS nvlist.
This indicates that the system ran out of memory while trying to construct the nvlist_t to return.
This indicates that not enough SPD data was present as indicated by buf and buflen for the given type of SPD information and therefore we were unable to successfully parse basic information.
This indicates that the type of SPD data present, e.g. DDR4 SDRAM, LPDDR3, etc., that was found is not currently supported by the library.
This indicates that while the library is familiar with the specific type of SPD data present, the encoding level of the data (similar to a major version) is unsupported by the library.

libjedec(3LIB), nvlist_lookup_boolean(3NVPAIR), nvlist_lookup_string(3NVPAIR), nvlist_lookup_uint32(3NVPAIR), nvlist_lookup_uint32_array(3NVPAIR), nvlist_lookup_uint64(3NVPAIR)

Serial Presence Detect (SPD), General Standard, 21-C, JEDEC Solid State Technology Association.

DDR5 Serial Presence Detect (SPD) Contents, JESD400-5A.01, JEDEC Solid State Technology Association, January 2023.

September 22, 2023 OmniOS