374 lines
13 KiB
Diff
374 lines
13 KiB
Diff
|
Subject: decode-dimms: Improve DDR3 support
|
||
|
Upstream: yes, r6132 to r6149
|
||
|
|
||
|
This is my hackweek 9 project:
|
||
|
https://github.com/SUSE/hackweek/wiki/DDR3-SPD-Information-Decoding
|
||
|
---
|
||
|
eeprom/decode-dimms | 271 +++++++++++++++++++++++++++++++++++++---------------
|
||
|
1 file changed, 195 insertions(+), 76 deletions(-)
|
||
|
|
||
|
--- a/eeprom/decode-dimms
|
||
|
+++ b/eeprom/decode-dimms
|
||
|
@@ -5,7 +5,7 @@
|
||
|
# Copyright 1998, 1999 Philip Edelbrock <phil@netroedge.com>
|
||
|
# modified by Christian Zuckschwerdt <zany@triq.net>
|
||
|
# modified by Burkart Lingner <burkart@bollchen.de>
|
||
|
-# Copyright (C) 2005-2011 Jean Delvare <khali@linux-fr.org>
|
||
|
+# Copyright (C) 2005-2013 Jean Delvare <khali@linux-fr.org>
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
@@ -1176,36 +1176,137 @@ sub decode_ddr2_sdram($)
|
||
|
printl("PLL Relock Time", $bytes->[46] . " us") if ($bytes->[46]);
|
||
|
}
|
||
|
|
||
|
+# Return combined time in ns
|
||
|
+sub ddr3_mtb_ftb($$$$)
|
||
|
+{
|
||
|
+ my ($byte1, $byte2, $mtb, $ftb) = @_;
|
||
|
+
|
||
|
+ # byte1 is unsigned in ns, but byte2 is signed in ps
|
||
|
+ $byte2 -= 0x100 if $byte2 & 0x80;
|
||
|
+
|
||
|
+ return $byte1 * $mtb + $byte2 * $ftb / 1000;
|
||
|
+}
|
||
|
+
|
||
|
+sub ddr3_reference_card($$)
|
||
|
+{
|
||
|
+ my ($rrc, $ext) = @_;
|
||
|
+ my $alphabet = "ABCDEFGHJKLMNPRTUVWY";
|
||
|
+ my $ref = $rrc & 0x1f;
|
||
|
+ my $revision = $ext >> 5;
|
||
|
+ my $ref_card;
|
||
|
+
|
||
|
+ return "ZZ" if $ref == 0x1f;
|
||
|
+ $ref += 0x1f if $rrc & 0x80;
|
||
|
+ $revision = (($rrc >> 5) & 0x03) if $revision == 0;
|
||
|
+
|
||
|
+ if ($ref < length($alphabet)) {
|
||
|
+ # One letter reference card
|
||
|
+ $ref_card = substr($alphabet, $ref, 1);
|
||
|
+ } else {
|
||
|
+ # Two letter reference card
|
||
|
+ my $ref1 = int($ref / (length($alphabet)));
|
||
|
+ $ref -= length($alphabet) * $ref1;
|
||
|
+ $ref_card = substr($alphabet, $ref1, 1) .
|
||
|
+ substr($alphabet, $ref, 1);
|
||
|
+ }
|
||
|
+
|
||
|
+ return "$ref_card revision $revision";
|
||
|
+}
|
||
|
+
|
||
|
+sub ddr3_revision_number($)
|
||
|
+{
|
||
|
+ my $h = $_[0] >> 4;
|
||
|
+ my $l = $_[0] & 0x0f;
|
||
|
+
|
||
|
+ # Decode as suggested by JEDEC Standard 21-C
|
||
|
+ return sprintf("%d", $l) if $h == 0;
|
||
|
+ return sprintf("%d.%d", $h, $l) if $h < 0xa;
|
||
|
+ return sprintf("%c%d", ord('A') + $h - 0xa, $l);
|
||
|
+}
|
||
|
+
|
||
|
+sub ddr3_device_type($)
|
||
|
+{
|
||
|
+ my $byte = shift;
|
||
|
+ my $type = $byte & 0x80 ? "Non-Standard" : "Standard Monolithic";
|
||
|
+ my $die_count = ($byte >> 4) & 0x07;
|
||
|
+ my $loading = ($byte >> 2) & 0x03;
|
||
|
+
|
||
|
+ if ($die_count == 1) {
|
||
|
+ $type .= "\nSingle die";
|
||
|
+ } elsif ($die_count == 2) {
|
||
|
+ $type .= "\n2 die";
|
||
|
+ } elsif ($die_count == 3) {
|
||
|
+ $type .= "\n4 die";
|
||
|
+ } elsif ($die_count == 4) {
|
||
|
+ $type .= "\n8 die";
|
||
|
+ }
|
||
|
+
|
||
|
+ if ($loading == 1) {
|
||
|
+ $type .= "\nMulti load stack";
|
||
|
+ } elsif ($loading == 2) {
|
||
|
+ $type .= "\nSingle load stack";
|
||
|
+ }
|
||
|
+
|
||
|
+ return $type;
|
||
|
+}
|
||
|
+
|
||
|
+use constant DDR3_UNBUFFERED => 1;
|
||
|
+use constant DDR3_REGISTERED => 2;
|
||
|
+use constant DDR3_CLOCKED => 3;
|
||
|
+use constant DDR3_LOAD_REDUCED => 4;
|
||
|
+
|
||
|
# Parameter: EEPROM bytes 0-127 (using 3-76)
|
||
|
sub decode_ddr3_sdram($)
|
||
|
{
|
||
|
my $bytes = shift;
|
||
|
my $temp;
|
||
|
my $ctime;
|
||
|
+ my ($ftb, $mtb);
|
||
|
+ my $ii;
|
||
|
|
||
|
- my @module_types = ("Undefined", "RDIMM", "UDIMM", "SO-DIMM",
|
||
|
- "Micro-DIMM", "Mini-RDIMM", "Mini-UDIMM",
|
||
|
- "Mini-CDIMM", "72b-SO-UDIMM", "72b-SO-RDIMM",
|
||
|
- "72b-SO-CDIMM", "LRDIMM", "16b-SO-DIMM",
|
||
|
- "32b-SO-DIMM");
|
||
|
+ my @module_types = (
|
||
|
+ { type => "Undefined", width => "Unknown" },
|
||
|
+ { type => "RDIMM", width => "133.35 mm", family => DDR3_REGISTERED },
|
||
|
+ { type => "UDIMM", width => "133.35 mm", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "Micro-DIMM", width => "TBD", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "Mini-RDIMM", width => "82.0 mm", family => DDR3_REGISTERED },
|
||
|
+ { type => "Mini-UDIMM", width => "82.0 mm", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "Mini-CDIMM", width => "67.6 mm", family => DDR3_CLOCKED },
|
||
|
+ { type => "72b-SO-UDIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "72b-SO-RDIMM", width => "67.6 mm", family => DDR3_REGISTERED },
|
||
|
+ { type => "72b-SO-CDIMM", width => "67.6 mm", family => DDR3_CLOCKED },
|
||
|
+ { type => "LRDIMM", width => "133.35 mm", family => DDR3_LOAD_REDUCED },
|
||
|
+ { type => "16b-SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
|
||
|
+ { type => "32b-SO-DIMM", width => "67.6 mm", family => DDR3_UNBUFFERED },
|
||
|
+ );
|
||
|
|
||
|
printl("Module Type", ($bytes->[3] <= $#module_types) ?
|
||
|
- $module_types[$bytes->[3]] :
|
||
|
+ $module_types[$bytes->[3]]->{type} :
|
||
|
sprintf("Reserved (0x%.2X)", $bytes->[3]));
|
||
|
|
||
|
+# time bases
|
||
|
+ if (($bytes->[9] & 0x0f) == 0 || $bytes->[11] == 0) {
|
||
|
+ print STDERR "Invalid time base divisor, can't decode\n";
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ $ftb = ($bytes->[9] >> 4) / ($bytes->[9] & 0x0f);
|
||
|
+ $mtb = $bytes->[10] / $bytes->[11];
|
||
|
+
|
||
|
# speed
|
||
|
prints("Memory Characteristics");
|
||
|
|
||
|
- my $dividend = ($bytes->[9] >> 4) & 15;
|
||
|
- my $divisor = $bytes->[9] & 15;
|
||
|
- printl("Fine time base", sprintf("%.3f", $dividend / $divisor) . " ps");
|
||
|
-
|
||
|
- $dividend = $bytes->[10];
|
||
|
- $divisor = $bytes->[11];
|
||
|
- my $mtb = $dividend / $divisor;
|
||
|
- printl("Medium time base", tns3($mtb));
|
||
|
+ $ctime = ddr3_mtb_ftb($bytes->[12], $bytes->[34], $mtb, $ftb);
|
||
|
+ # Starting with DDR3-1866, vendors may start approximating the
|
||
|
+ # minimum cycle time. Try to guess what they really meant so
|
||
|
+ # that the reported speed matches the standard.
|
||
|
+ for ($ii = 7; $ii < 15; $ii++) {
|
||
|
+ if ($ctime > 7.5/$ii - $ftb/1000 && $ctime < 7.5/$ii + $ftb/1000) {
|
||
|
+ $ctime = 7.5/$ii;
|
||
|
+ last;
|
||
|
+ }
|
||
|
+ }
|
||
|
|
||
|
- $ctime = $bytes->[12] * $mtb;
|
||
|
my $ddrclk = 2 * (1000 / $ctime);
|
||
|
my $tbits = 1 << (($bytes->[8] & 7) + 3);
|
||
|
my $pcclk = int ($ddrclk * $tbits / 8);
|
||
|
@@ -1237,17 +1338,16 @@ sub decode_ddr3_sdram($)
|
||
|
my $trp;
|
||
|
my $tras;
|
||
|
|
||
|
- $taa = ceil($bytes->[16] / $bytes->[12]);
|
||
|
- $trcd = ceil($bytes->[18] / $bytes->[12]);
|
||
|
- $trp = ceil($bytes->[20] / $bytes->[12]);
|
||
|
- $tras = ceil(((($bytes->[21] & 0x0f) << 8) + $bytes->[22]) / $bytes->[12]);
|
||
|
+ $taa = ddr3_mtb_ftb($bytes->[16], $bytes->[35], $mtb, $ftb);
|
||
|
+ $trcd = ddr3_mtb_ftb($bytes->[18], $bytes->[36], $mtb, $ftb);
|
||
|
+ $trp = ddr3_mtb_ftb($bytes->[20], $bytes->[37], $mtb, $ftb);
|
||
|
+ $tras = ((($bytes->[21] & 0x0f) << 8) + $bytes->[22]) * $mtb;
|
||
|
|
||
|
- printl("tCL-tRCD-tRP-tRAS", join("-", $taa, $trcd, $trp, $tras));
|
||
|
+ printl("tCL-tRCD-tRP-tRAS", ddr_core_timings(ceil($taa / $ctime), $ctime, $trcd, $trp, $tras));
|
||
|
|
||
|
# latencies
|
||
|
my $highestCAS = 0;
|
||
|
my %cas;
|
||
|
- my $ii;
|
||
|
my $cas_sup = ($bytes->[15] << 8) + $bytes->[14];
|
||
|
for ($ii = 0; $ii < 15; $ii++) {
|
||
|
if ($cas_sup & (1 << $ii)) {
|
||
|
@@ -1257,14 +1357,38 @@ sub decode_ddr3_sdram($)
|
||
|
}
|
||
|
printl("Supported CAS Latencies (tCL)", cas_latencies(keys %cas));
|
||
|
|
||
|
+# standard DDR3 speeds
|
||
|
+ prints("Timings at Standard Speeds");
|
||
|
+ foreach my $ctime_at_speed (7.5/8, 7.5/7, 1.25, 1.5, 1.875, 2.5) {
|
||
|
+ my $best_cas = 0;
|
||
|
+
|
||
|
+ # Find min CAS latency at this speed
|
||
|
+ for ($ii = 14; $ii >= 0; $ii--) {
|
||
|
+ next unless ($cas_sup & (1 << $ii));
|
||
|
+ if (ceil($taa / $ctime_at_speed) <= $ii + 4) {
|
||
|
+ $best_cas = $ii + 4;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ printl_cond($best_cas && $ctime_at_speed >= $ctime,
|
||
|
+ "tCL-tRCD-tRP-tRAS" . as_ddr(3, $ctime_at_speed),
|
||
|
+ ddr_core_timings($best_cas, $ctime_at_speed,
|
||
|
+ $trcd, $trp, $tras));
|
||
|
+ }
|
||
|
+
|
||
|
# more timing information
|
||
|
prints("Timing Parameters");
|
||
|
|
||
|
+ printl("Minimum Cycle Time (tCK)", tns3($ctime));
|
||
|
+ printl("Minimum CAS Latency Time (tAA)", tns3($taa));
|
||
|
printl("Minimum Write Recovery time (tWR)", tns3($bytes->[17] * $mtb));
|
||
|
+ printl("Minimum RAS# to CAS# Delay (tRCD)", tns3($trcd));
|
||
|
printl("Minimum Row Active to Row Active Delay (tRRD)",
|
||
|
tns3($bytes->[19] * $mtb));
|
||
|
+ printl("Minimum Row Precharge Delay (tRP)", tns3($trp));
|
||
|
+ printl("Minimum Active to Precharge Delay (tRAS)", tns3($tras));
|
||
|
printl("Minimum Active to Auto-Refresh Delay (tRC)",
|
||
|
- tns3((((($bytes->[21] >> 4) & 15) << 8) + $bytes->[23]) * $mtb));
|
||
|
+ tns3(ddr3_mtb_ftb((($bytes->[21] & 0xf0) << 4) + $bytes->[23], $bytes->[38], $mtb, $ftb)));
|
||
|
printl("Minimum Recovery Delay (tRFC)",
|
||
|
tns3((($bytes->[25] << 8) + $bytes->[24]) * $mtb));
|
||
|
printl("Minimum Write to Read CMD Delay (tWTR)",
|
||
|
@@ -1300,43 +1424,31 @@ sub decode_ddr3_sdram($)
|
||
|
($bytes->[31] & 8) ? "Yes" : "No");
|
||
|
printl("Partial Array Self-Refresh?",
|
||
|
($bytes->[31] & 128) ? "Yes" : "No");
|
||
|
- printl("Thermal Sensor Accuracy",
|
||
|
- ($bytes->[32] & 128) ? sprintf($bytes->[32] & 127) :
|
||
|
- "Not implemented");
|
||
|
- printl("SDRAM Device Type",
|
||
|
- ($bytes->[33] & 128) ? sprintf($bytes->[33] & 127) :
|
||
|
- "Standard Monolithic");
|
||
|
- if ($bytes->[3] >= 1 && $bytes->[3] <= 6) {
|
||
|
+ printl("Module Thermal Sensor",
|
||
|
+ ($bytes->[32] & 128) ? "Yes" : "No");
|
||
|
+ printl("SDRAM Device Type", ddr3_device_type($bytes->[33]));
|
||
|
+
|
||
|
+ # Following bytes are type-specific, so don't continue if type
|
||
|
+ # isn't known.
|
||
|
+ return if $bytes->[3] == 0 || $bytes->[3] > $#module_types;
|
||
|
|
||
|
+ if ($module_types[$bytes->[3]]->{family} == DDR3_UNBUFFERED ||
|
||
|
+ $module_types[$bytes->[3]]->{family} == DDR3_REGISTERED ||
|
||
|
+ $module_types[$bytes->[3]]->{family} == DDR3_CLOCKED ||
|
||
|
+ $module_types[$bytes->[3]]->{family} == DDR3_LOAD_REDUCED) {
|
||
|
prints("Physical Characteristics");
|
||
|
- printl("Module Height (mm)", ($bytes->[60] & 31) + 15);
|
||
|
- printl("Module Thickness (mm)", sprintf("%d front, %d back",
|
||
|
+ printl("Module Height", (($bytes->[60] & 31) + 15) . " mm");
|
||
|
+ printl("Module Thickness", sprintf("%d mm front, %d mm back",
|
||
|
($bytes->[61] & 15) + 1,
|
||
|
(($bytes->[61] >> 4) & 15) +1));
|
||
|
- printl("Module Width (mm)", ($bytes->[3] <= 2) ? 133.5 :
|
||
|
- ($bytes->[3] == 3) ? 67.6 : "TBD");
|
||
|
+ printl("Module Width", $module_types[$bytes->[3]]->{width});
|
||
|
+ printl("Module Reference Card", ddr3_reference_card($bytes->[62], $bytes->[60]));
|
||
|
|
||
|
- my $alphabet = "ABCDEFGHJKLMNPRTUVWY";
|
||
|
- my $ref = $bytes->[62] & 31;
|
||
|
- my $ref_card;
|
||
|
- if ($ref == 31) {
|
||
|
- $ref_card = "ZZ";
|
||
|
- } else {
|
||
|
- if ($bytes->[62] & 128) {
|
||
|
- $ref += 31;
|
||
|
- }
|
||
|
- if ($ref < length $alphabet) {
|
||
|
- $ref_card = substr $alphabet, $ref, 1;
|
||
|
- } else {
|
||
|
- my $ref1 = int($ref / (length $alphabet));
|
||
|
- $ref -= (length $alphabet) * $ref1;
|
||
|
- $ref_card = (substr $alphabet, $ref1, 1) .
|
||
|
- (substr $alphabet, $ref, 1);
|
||
|
- }
|
||
|
- }
|
||
|
- printl("Module Reference Card", $ref_card);
|
||
|
+ printl_cond($module_types[$bytes->[3]]->{family} == DDR3_UNBUFFERED,
|
||
|
+ "Rank 1 Mapping", $bytes->[63] & 0x01 ? "Mirrored" : "Standard");
|
||
|
}
|
||
|
- if ($bytes->[3] == 1 || $bytes->[3] == 5) {
|
||
|
+
|
||
|
+ if ($module_types[$bytes->[3]]->{family} == DDR3_REGISTERED) {
|
||
|
prints("Registered DIMM");
|
||
|
|
||
|
my @rows = ("Undefined", 1, 2, 4);
|
||
|
@@ -1347,17 +1459,27 @@ sub decode_ddr3_sdram($)
|
||
|
printl("Register device type",
|
||
|
(($bytes->[68] & 7) == 0) ? "SSTE32882" :
|
||
|
"Undefined");
|
||
|
- printl("Register revision", sprintf("0x%.2X", $bytes->[67]));
|
||
|
- printl("Heat spreader characteristics",
|
||
|
- ($bytes->[64] < 128) ? "Not incorporated" :
|
||
|
- sprintf("%.2X", ($bytes->[64] & 127)));
|
||
|
- my $regs;
|
||
|
- for (my $i = 0; $i < 8; $i++) {
|
||
|
- $regs = sprintf("SSTE32882 RC%d/RC%d",
|
||
|
- $i * 2, $i * 2 + 1);
|
||
|
- printl($regs, sprintf("%.2X", $bytes->[$i + 69]));
|
||
|
- }
|
||
|
+ printl_cond($bytes->[67] != 0xff,
|
||
|
+ "Register revision", ddr3_revision_number($bytes->[67]));
|
||
|
+ printl("Heat spreader", $bytes->[64] & 0x80 ? "Yes" : "No");
|
||
|
}
|
||
|
+
|
||
|
+ if ($module_types[$bytes->[3]]->{family} == DDR3_LOAD_REDUCED) {
|
||
|
+ prints("Load Reduced DIMM");
|
||
|
+
|
||
|
+ my @rows = ("Undefined", 1, 2, "Reserved");
|
||
|
+ printl("# DRAM Rows", $rows[($bytes->[63] >> 2) & 3]);
|
||
|
+ my @mirroring = ("None", "Odd ranks", "Reserved", "Reserved");
|
||
|
+ printl("Mirroring", $mirroring[$bytes->[63] & 3]);
|
||
|
+ printl("Rank Numbering", $bytes->[63] & 0x20 ? "Even only" : "Contiguous");
|
||
|
+ printl("Buffer Orientation", $bytes->[63] & 0x10 ? "Horizontal" : "Vertical");
|
||
|
+ printl("Register manufacturer",
|
||
|
+ manufacturer_ddr3($bytes->[65], $bytes->[66]));
|
||
|
+ printl_cond($bytes->[64] != 0xff,
|
||
|
+ "Buffer Revision", ddr3_revision_number($bytes->[64]));
|
||
|
+ printl("Heat spreader", $bytes->[63] & 0x80 ? "Yes" : "No");
|
||
|
+ }
|
||
|
+
|
||
|
}
|
||
|
|
||
|
# Parameter: EEPROM bytes 0-127 (using 4-5)
|
||
|
@@ -1458,26 +1580,23 @@ sub decode_ddr3_mfg_data($)
|
||
|
printl("Module Manufacturer",
|
||
|
manufacturer_ddr3($bytes->[117], $bytes->[118]));
|
||
|
|
||
|
- if (spd_written(@{$bytes}[148..149])) {
|
||
|
- printl("DRAM Manufacturer",
|
||
|
- manufacturer_ddr3($bytes->[148], $bytes->[149]));
|
||
|
- }
|
||
|
+ printl_cond(spd_written(@{$bytes}[148..149]),
|
||
|
+ "DRAM Manufacturer",
|
||
|
+ manufacturer_ddr3($bytes->[148], $bytes->[149]));
|
||
|
|
||
|
printl_mfg_location_code($bytes->[119]);
|
||
|
|
||
|
- if (spd_written(@{$bytes}[120..121])) {
|
||
|
- printl("Manufacturing Date",
|
||
|
- manufacture_date($bytes->[120], $bytes->[121]));
|
||
|
- }
|
||
|
+ printl_cond(spd_written(@{$bytes}[120..121]),
|
||
|
+ "Manufacturing Date",
|
||
|
+ manufacture_date($bytes->[120], $bytes->[121]));
|
||
|
|
||
|
printl_mfg_assembly_serial(@{$bytes}[122..125]);
|
||
|
|
||
|
printl("Part Number", part_number(@{$bytes}[128..145]));
|
||
|
|
||
|
- if (spd_written(@{$bytes}[146..147])) {
|
||
|
- printl("Revision Code",
|
||
|
- sprintf("0x%02X%02X", $bytes->[146], $bytes->[147]));
|
||
|
- }
|
||
|
+ printl_cond(spd_written(@{$bytes}[146..147]),
|
||
|
+ "Revision Code",
|
||
|
+ sprintf("0x%02X%02X", $bytes->[146], $bytes->[147]));
|
||
|
}
|
||
|
|
||
|
# Parameter: EEPROM bytes 0-127 (using 64-98)
|