[PATCH i2c-tools v4 1/8] decode-dimms: Implement DDR5 checksum parsing

Stephen Horvath <[email protected]> Thu, 23 Jul 2026 13:40:12 +0000
Newsgroups org.kernel.vger.linux-i2c
Message-ID <[email protected]>
The CRC for the DDR5 SPD is located at bytes 510-511 of the SPD data.
There is now functionality to read upto byte 512 and calculate the CRC16
if byte 2 indicates DDR5 memory and byte 0 indicates at least 512 bytes.

This patch is based off of a patch by Guenter Roeck, linked below.

Link: https://lore.kernel.org/linux-hwmon/[email protected]/
Cc: Guenter Roeck <[email protected]>
Signed-off-by: Stephen Horvath <[email protected]>
---
 eeprom/decode-dimms | 31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/eeprom/decode-dimms b/eeprom/decode-dimms
index 7682345..149178f 100755
--- a/eeprom/decode-dimms
+++ b/eeprom/decode-dimms
@@ -22,7 +22,8 @@
 # the legacy eeprom driver (in the kernel tree since v2.6.0). For kernels
 # older than 2.6.0, the eeprom driver can be found in the lm-sensors 2
 # package. For DDR4, you need the ee1004 driver (in the kernel tree since
-# kernel v4.20).
+# kernel v4.20). For DDR5, you need the spd5118 driver (in the kernel tree
+# since v6.11).
 #
 # References:
 # PC SDRAM Serial Presence
@@ -2402,7 +2403,12 @@ sub spd_sizes($)
 	my $bytes = shift;
 	my $type = $bytes->[2];
 
-	if ($type == 12 || $type == 14 || $type == 16 || $type == 17) {
+	if ($type == 18 || $type == 19 || $type == 20 || $type == 21) {
+		# DDR5
+		my $spd_len = 2 ** (7 + (($bytes->[0] >> 4) & 7));
+		my $used = $spd_len;
+		return ($spd_len, $used);
+	} elsif ($type == 12 || $type == 14 || $type == 16 || $type == 17) {
 		# DDR4
 		my $spd_len = 256 * (($bytes->[0] >> 4) & 7);
 		my $used = 128 * ($bytes->[0] & 15);
@@ -2511,10 +2517,16 @@ sub calculate_crc($$$)
 sub check_crc($)
 {
 	my $bytes = shift;
+	my $is_ddr5 = ($bytes->[0] & 0x70) == 0x30;
 	my $crc_cover = $bytes->[0] & 0x80 ? 116 : 125;
+	my $crc_start = 126;
+	if ($is_ddr5) {
+	    $crc_cover = 509;
+	    $crc_start = 510;
+	}
 	my $crc = calculate_crc($bytes, 0, $crc_cover + 1);
 
-	my $dimm_crc = ($bytes->[127] << 8) | $bytes->[126];
+	my $dimm_crc = ($bytes->[$crc_start + 1] << 8) | $bytes->[$crc_start];
 	return ("EEPROM CRC of bytes 0-$crc_cover",
 		($dimm_crc == $crc) ? 1 : 0,
 		sprintf("0x%04X", $dimm_crc),
@@ -2617,7 +2629,8 @@ sub get_dimm_list
 	if ($use_sysfs) {
 		@drivers = ('eeprom',
 			    'at24',
-			    'ee1004');	# DDR4
+			    'ee1004',	# DDR4
+			    'spd5118');	# DDR5
 	} else {
 		@drivers = ('eeprom');
 		$dir = '/proc/sys/dev/sensors';
@@ -2642,7 +2655,8 @@ sub get_dimm_list
 				next unless defined $attr &&
 					    ($attr eq "eeprom" ||
 					     $attr eq "spd" ||
-					     $attr eq "ee1004");	# DDR4
+					     $attr eq "ee1004" ||	# DDR4
+					     $attr eq "spd5118");	# DDR5
 			} else {
 				next unless $file =~ /^eeprom-/;
 			}
@@ -2654,7 +2668,7 @@ sub get_dimm_list
 	}
 
 	if (!$opened) {
-		print STDERR "No EEPROM found, try loading the eeprom, at24 or ee1004 module\n";
+		print STDERR "No EEPROM found, try loading the eeprom, at24, ee1004 or spd5118 module\n";
 		exit;
 	}
 
@@ -2684,6 +2698,11 @@ for my $i (0 .. $#dimm) {
 		 $dimm[$i]->{chk_spd}, $dimm[$i]->{chk_calc}) =
 			checksum(\@bytes);
 	} else {
+		# Check for DDR5 protocol type 18, 19, 20, 21 and size >= 512
+		if ($bytes[2] >= 18 && $bytes[2] <= 21 && ($bytes[0] & 0x70) >= 0x30) {
+			# DDR5's checksum is at 510-511
+			push(@bytes, readspd(@bytes, 512, $dimm[$i]->{file}));
+		}
 		($dimm[$i]->{chk_label}, $dimm[$i]->{chk_valid},
 		 $dimm[$i]->{chk_spd}, $dimm[$i]->{chk_calc}) =
 			check_crc(\@bytes);

-- 
2.53.0