[prev in list] [next in list] [prev in thread] [next in thread]
List: linux-i2c
Subject: Re: Preliminary support for DDR3 in decode-dimms (fwd)
From: Paul Goyette <paul () whooppee ! com>
Date: 2008-11-25 12:10:02
Message-ID: Pine.NEB.4.64.0811250408050.29070 () quicky ! whooppee ! com
[Download RAW message or body]
On Tue, 25 Nov 2008, Jean Delvare wrote:
> Hi Paul,
>
> On Sun, 23 Nov 2008 07:04:41 -0800 (PST), Paul Goyette wrote:
>> The attached diffs provide some preliminary decode of the DDR3 SPD...
>> This work is based on the recently-published Appendix K to JESD21-C
>> (see http://www.jedec.org/download/search/4_01_02_11R18.pdf).
>>
>> Warning: I'm no perl expert. The code I've written is probably ugly to
>> most of the readers of this list. But it does work!
>
> Can you please provide a unified diff (as generated by diff -u)? Other
> diff formats are simply too fragile and/or too difficult for humans to
> read.
Sure. See attached.
This version includes some additional decoding of bytes 60-76 for both
unregistered and registered DIMMs, and module physical dimensions.
----------------------------------------------------------------------
| Paul Goyette | PGP DSS Key fingerprint: | E-mail addresses: |
| Customer Service | FA29 0E3B 35AF E8AE 6651 | paul@whooppee.com |
| Network Engineer | 0786 F758 55DE 53BA 7731 | pgoyette@juniper.net |
----------------------------------------------------------------------
["DECODE_DIMMS.diff" (TEXT/PLAIN)]
--- decode-dimms.orig 2008-05-23 07:18:37.000000000 -0700
+++ decode-dimms 2008-11-25 04:02:44.000000000 -0800
@@ -408,7 +408,7 @@
sub tns($) # print a time in ns
{
- return sprintf("%3.2f ns", $_[0]);
+ return sprintf("%.3f ns", $_[0]);
}
# Parameter: bytes 0-63
@@ -870,6 +870,251 @@
($byte & 0x80 ? " - Self Refresh" : "");
}
+sub ddr3_manufacturer(@)
+{
+ my @info = @_;
+ my @mfg_bytes = (0, 0, 0, 0, 0, 0, 0, 0);
+ my $i;
+ my $count = $info[0] & 127;
+ # We'll just ignore the high-order odd-parity bit in the count byte
+ for ($i = 0; $i < $count; $i++) {
+ $mfg_bytes[$i] = 0x7f;
+ }
+ $mfg_bytes[$count] = $info[1];
+ (my $mfg, my $extra) = manufacturer(@mfg_bytes);
+ return $mfg;
+}
+
+# Parameter: bytes 0-127
+sub decode_ddr3_sdram($)
+{
+ my $bytes = shift;
+ my $l;
+ my $temp;
+ my $ctime;
+
+ my @module_types = ("Undefined", "RDIMM", "UDIMM", "SO-DIMM",
+ "Micro-DIMM", "Mini-RDIMM", "Mini-UDIMM");
+
+ printl "Module Type", ($bytes->[3] <= $#module_types) ?
+ $module_types[$bytes->[3]] :
+ sprint("Reserved (0x%.2X)", $bytes->[3]);
+
+# speed
+ prints "Memory Characteristics";
+
+ $l = "Fine time base";
+ my $dividend = ($bytes->[9] >> 4) & 15;
+ my $divisor = $bytes->[9] & 15;
+ printl $l, sprintf("%.3f", $dividend / $divisor) . " ps";
+
+ $l = "Medium time base";
+ $dividend = $bytes->[10];
+ $divisor = $bytes->[11];
+ my $mtb = $dividend / $divisor;
+ printl $l, tns($mtb);
+
+ $l = "Maximum module speed";
+ $ctime = $bytes->[12] * $mtb;
+ my $ddrclk = 2 * (1000 / $ctime);
+ my $tbits = 1 << (($bytes->[8] & 7) + 3);
+ my $pcclk = int ($ddrclk * $tbits / 8);
+ $ddrclk = int ($ddrclk);
+ printl $l, "${ddrclk}MHz (PC3-${pcclk})";
+
+# Size computation
+
+ my $cap = ($bytes->[4] & 15) + 28;
+ $cap += ($bytes->[8] & 7) + 3;
+ $cap -= ($bytes->[7] & 7) + 2;
+ $cap -= 20 + 3;
+ my $k = (($bytes->[7] >> 3) & 31) + 1;
+ printl "Size", ((1 << $cap) * $k) . " MB";
+
+ printl "Banks x Rows x Columns x Bits",
+ join(' x ', 1 << ((($bytes->[4] >> 4) & 7) + 3),
+ ((($bytes->[5] >> 3) & 31) + 12),
+ ( ($bytes->[5] & 7) + 9),
+ ( 1 << (($bytes->[8] & 7) + 3)) );
+ printl "Ranks", $k;
+
+ printl "SDRAM Device Width", (1 << (($bytes->[7] & 7) + 2))." bits";
+
+ my $taa;
+ my $trcd;
+ my $trp;
+ my $tras;
+
+ $taa = int($bytes->[16] / $bytes->[12]);
+ $trcd = int($bytes->[18] / $bytes->[12]);
+ $trp = int($bytes->[20] / $bytes->[12]);
+ $tras = int((($bytes->[21] >> 4) * 256 + $bytes->[22]) / $bytes->[12]);
+
+ printl "tCL-tRCD-tRP-tRAS", join("-", $taa, $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)) {
+ $highestCAS = $ii + 4;
+ $cas{$highestCAS}++;
+ }
+ }
+ printl "Supported CAS Latencies (tCL)", cas_latencies(keys %cas);
+
+# more timing information
+ prints "Timing Parameters" ;
+
+ printl "Minimum Write Recovery time (tWR)", tns($bytes->[17] * $mtb);
+ printl "Minimum Row Active to Row Active Delay (tRRD)",
+ tns($bytes->[19] * $mtb);
+ printl "Minimum Active to Auto-Refresh Delay (tRC)",
+ tns((((($bytes->[21] >> 4) & 15) << 8) + $bytes->[23]) * $mtb);
+ printl "Minimum Recovery Delay (tRFC)",
+ tns((($bytes->[25] << 8) + $bytes->[24]) * $mtb);
+ printl "Minimum Write to Read CMD Delay (tWTR)",
+ tns($bytes->[26] * $mtb);
+ printl "Minimum Read to Pre-charge CMD Delay (tRTP)",
+ tns($bytes->[27] * $mtb);
+ printl "Minimum Four Activate Window Delay (tFAW)",
+ tns(((($bytes->[28] & 15) << 8) + $bytes->[29]) * $mtb);
+
+# miscellaneous stuff
+ prints "Optional Features";
+
+ my $volts = "1.5V";
+ if ($bytes->[6] & 1) {
+ $volts .= " tolerant";
+ }
+ if ($bytes->[6] & 2) {
+ $volts .= ", 1.35V ";
+ }
+ if ($bytes->[6] & 4) {
+ $volts .= ", 1.2X V";
+ }
+ printl "Operable voltages", $volts;
+ printl "RZQ/6 supported?", ($bytes->[30] & 1) ? "Yes" : "No";
+ printl "RZQ/7 supported?", ($bytes->[30] & 2) ? "Yes" : "No";
+ printl "DLL-Off Mode supported?", ($bytes->[30] & 128) ? "Yes" : "No";
+ printl "Operating temperature range", sprintf "0-%dC",
+ ($bytes->[31] & 1) ? 95 : 85;
+ printl "Refresh Rate in extended temp range",
+ ($bytes->[31] & 2) ? "2X" : "1X";
+ printl "Auto Self-Refresh?", ($bytes->[31] & 4) ? "Yes" : "No";
+ printl "On-Die Thermal Sensor readout?",
+ ($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) {
+
+ prints "Physical Characteristics";
+ printl "Module Height (mm)", ($bytes->[60] & 31) + 15;
+ printl "Module Thickness (mm)", sprintf("%d front, %d 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";
+
+ my @alphabet = ("A", "B", "C", "D", "E", "F", "G", "H", "J",
+ "K", "L", "M", "N", "P", "R", "T", "U", "V",
+ "W", "Y" );
+
+ my $ref = $bytes->[62] & 31;
+ my $ref_card;
+ if ($ref == 31) {
+ $ref_card = "ZZ";
+ } else {
+ if ($bytes->[62] & 128) {
+ $ref += 31;
+ }
+ if ($ref < $#alphabet) {
+ $ref_card = $alphabet[$ref];
+ } else {
+ my $ref1 = int($ref / $#alphabet);
+ $ref -= $#alphabet * $ref1;
+ $ref_card = $alphabet[$ref1] . $alphabet[$ref];
+ }
+ }
+ printl "Module Reference Card", $ref_card;
+ }
+ if ($bytes->[3] == 1 || $bytes->[3] == 5) {
+ prints "Registered DIMM";
+
+ my @rows = ("Undefined", 1, 2, 4);
+ printl "# DRAM Rows", $rows[($bytes->[63] >> 2) & 3];
+ printl "# Registers", $rows[$bytes->[63] & 3];
+ my @m_data = ($bytes->[65], $bytes->[66]);
+ printl "Register manufacturer", ddr3_manufacturer(@m_data[0..1]);
+ 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]);
+ }
+ }
+
+ prints "Manufacturer Data";
+
+ my @m_data = ($bytes->[117], $bytes->[118]);
+ printl "Module Manufacturer", ddr3_manufacturer(@m_data[0..1]);
+
+ @m_data = ($bytes->[148], $bytes->[149]);
+ printl "DRAM Manufacturer Code", ddr3_manufacturer(@m_data[0..1]);
+
+ $l = "Manufacturing Location";
+ $temp = (chr($bytes->[8]) =~ m/^[\w\d]$/) ? chr($bytes->[8])
+ : sprintf("0x%.2X", $bytes->[8]);
+ printl $l, $temp;
+
+ $l = "Part Number";
+ $temp = "";
+ for (my $i = 128; $i <= 145; $i++) {
+ $temp .= chr($bytes->[$i]);
+ };
+ printl $l, $temp;
+
+ $l = "Revision";
+ $temp = sprintf("0x%02X%02X\n", $bytes->[146], $bytes->[147]);
+ printl $l, $temp;
+
+ $l = "Manufacturing Date";
+ # In theory the year and week are in BCD format, but
+ # this is not always true in practice :(
+ if (($bytes->[120] & 0xf0) <= 0x90
+ && ($bytes->[120] & 0x0f) <= 0x09
+ && ($bytes->[121] & 0xf0) <= 0x90
+ && ($bytes->[121] & 0x0f) <= 0x09) {
+ # Note that this heuristic will break in year 2080
+ $temp = sprintf("%d%02X-W%02X\n",
+ $bytes->[120] >= 0x80 ? 19 : 20,
+ $bytes->[120], $bytes->[121]);
+ } else {
+ $temp = sprintf("0x%02X%02X\n", $bytes->[120], $bytes->[121]);
+ }
+ printl $l, $temp;
+
+ $l = "Assembly Serial Number";
+ $temp = sprintf("0x%02X%02X%02X%02X\n", $bytes->[122], $bytes->[123],
+ $bytes->[124], $bytes->[125]);
+ printl $l, $temp;
+}
+
# Parameter: bytes 0-63
sub decode_ddr2_sdram($)
{
@@ -1061,6 +1306,7 @@
"DDR2 SDRAM" => \&decode_ddr2_sdram,
"Direct Rambus" => \&decode_direct_rambus,
"Rambus" => \&decode_rambus,
+ "DDR3 SDRAM" => \&decode_ddr3_sdram,
);
# Parameter: bytes 64-127
@@ -1179,6 +1425,32 @@
return @bytes;
}
+sub readfullspd($$) # reads all bytes from SPD-EEPROM
+{
+ my ($size, $dimm_i) = @_;
+ my @bytes;
+ if ($use_hexdump) {
+ @bytes = read_hexdump($dimm_i);
+ return @bytes[0..$size];
+ } elsif ($use_sysfs) {
+ # Kernel 2.6 with sysfs
+ sysopen(HANDLE, "/sys/bus/i2c/drivers/eeprom/$dimm_i/eeprom", O_RDONLY)
+ or die "Cannot open /sys/bus/i2c/drivers/eeprom/$dimm_i/eeprom";
+ binmode HANDLE;
+ sysseek(HANDLE, 0, SEEK_SET);
+ sysread(HANDLE, my $eeprom, $size);
+ close HANDLE;
+ @bytes = unpack(sprintf("C%d", $size), $eeprom);
+ } else {
+ # Kernel 2.4 with procfs
+ for my $i (0 .. $size/16) {
+ my $hexoff = sprintf('%02x', $i * 16);
+ push @bytes, split(" ", `cat /proc/sys/dev/sensors/$dimm_i/$hexoff`);
+ }
+ }
+ return @bytes;
+}
+
# Parse command-line
foreach (@ARGV) {
if ($_ eq '-h' || $_ eq '--help') {
@@ -1271,9 +1543,6 @@
$dimm_checksum += $bytes[$_] foreach (0 .. 62);
$dimm_checksum &= 0xff;
- next unless $bytes[63] == $dimm_checksum || $opt_igncheck;
- $dimm_count++;
-
print "<b><u>" if $opt_html;
printl2 "\n\nDecoding EEPROM",
$use_hexdump ? $dimm_list[$i] : ($use_sysfs ?
@@ -1292,32 +1561,87 @@
# Decode first 3 bytes (0-2)
prints "SPD EEPROM Information";
- my $l = "EEPROM Checksum of bytes 0-62";
- printl $l, ($bytes[63] == $dimm_checksum ?
- sprintf("OK (0x%.2X)", $bytes[63]):
- sprintf("Bad\n(found 0x%.2X, calculated 0x%.2X)\n",
- $bytes[63], $dimm_checksum));
-
# Simple heuristic to detect Rambus
- my $is_rambus = $bytes[0] < 4;
+ my $spdsize;
+ my $written;
+ my @used = ( 0, 128, 176, 256);
+ my @sizes = ( "Undefined", 256);
+ my $is_rambus = ($bytes[0] < 4 && $bytes[0] >= 0);
+
+ if ($is_rambus || $bytes[2] <= 8) {
+ my $l = "EEPROM Checksum of bytes 0-62";
+ printl $l, ($bytes[63] == $dimm_checksum ?
+ sprintf("OK (0x%.2X)", $bytes[63]):
+ sprintf("Bad\n(found 0x%.2X, calculated 0x%.2X)\n",
+ $bytes[63], $dimm_checksum));
+ next unless $bytes[63] == $dimm_checksum ||
+ $opt_igncheck;
+ $dimm_count++;
+ if ($is_rambus) {
+ $spdsize = 0;
+ $written = 0;
+ } else {
+ $written = $bytes[0];
+ if ($bytes[1] <= 14) {
+ $spdsize = 1 << $bytes[1];
+ } else { $spdsize = "ERROR!"; }
+ }
+ } else {
+ if ((($bytes[0] >> 4) & 7) <= $#sizes) {
+ $spdsize = $sizes[($bytes[0] >> 4) & 7];
+ } else { $spdsize = sprintf("Reserved (0x%X)",
+ ($bytes[0] >> 4) & 7); }
+
+ if (($bytes[0] & 15) <= $#used) {
+ $written = $used[$bytes[0] & 15];
+ } else { $written = sprintf("Reserved (0x%X)",
+ $bytes[0] & 15); }
+
+ @bytes = readfullspd($written, $dimm_list[$i]);
+ my $dimm_crc = 0;
+ my $crc_cover = $bytes[0] & 0x80 ? 116 : 125;
+ my $crc_ptr = 0;
+ my $crc_bit;
+ while ($crc_ptr <= $crc_cover) {
+ $dimm_crc = $dimm_crc ^ ($bytes[$crc_ptr] << 8);
+ for ($crc_bit = 0; $crc_bit < 8; $crc_bit++) {
+ if ($dimm_crc & 0x8000) {
+ $dimm_crc = ($dimm_crc << 1) ^
+ 0x1021;
+ } else {
+ $dimm_crc = $dimm_crc << 1
+ }
+ }
+ $crc_ptr++;
+ }
+ $dimm_crc = $dimm_crc & 0xffff;
+
+ my $l = "EEPROM CRC of bytes 0-" .
+ sprintf("%d", $crc_cover);
+ my $crc_calc = $bytes[127] << 8 | $bytes[126];
+ printl $l, ($dimm_crc == $crc_calc)?
+ sprintf("OK (0x%.4X)", $dimm_crc):
+ sprintf("Bad\n(found 0x%.4X, calculated 0x%.4X)\n",
+ $crc_calc, $dimm_crc);
+ next unless $crc_calc == $dimm_crc || $opt_igncheck;
+ $dimm_count++;
+ }
+
my $temp;
if ($is_rambus) {
if ($bytes[0] == 1) { $temp = "0.7"; }
elsif ($bytes[0] == 2) { $temp = "1.0"; }
- elsif ($bytes[0] == 0 || $bytes[0] == 255) { $temp = "Invalid"; }
- else { $temp = "Reserved"; }
+ elsif ($bytes[0] == 0 || $bytes[0] == 255) {
+ $temp = "Invalid";
+ } else { $temp = "Reserved"; }
printl "SPD Revision", $temp;
- } else {
- printl "# of bytes written to SDRAM EEPROM",
- $bytes[0];
+ }
+ if ($spdsize != 0) {
+ printl "# of bytes written to SDRAM EEPROM", $written;
}
- $l = "Total number of bytes in EEPROM";
- if ($bytes[1] <= 14) {
- printl $l, 2**$bytes[1];
- } elsif ($bytes[1] == 0) {
- printl $l, "RFU";
- } else { printl $l, "ERROR!"; }
+ my $l = "Total number of bytes in EEPROM";
+ printl $l, $spdsize;
$l = "Fundamental Memory type";
my $type = "Unknown";
@@ -1325,14 +1649,14 @@
if ($bytes[2] == 1) { $type = "Direct Rambus"; }
elsif ($bytes[2] == 17) { $type = "Rambus"; }
} else {
- if ($bytes[2] == 1) { $type = "FPM DRAM"; }
- elsif ($bytes[2] == 2) { $type = "EDO"; }
- elsif ($bytes[2] == 3) { $type = "Pipelined Nibble"; }
- elsif ($bytes[2] == 4) { $type = "SDR SDRAM"; }
- elsif ($bytes[2] == 5) { $type = "Multiplexed ROM"; }
- elsif ($bytes[2] == 6) { $type = "DDR SGRAM"; }
- elsif ($bytes[2] == 7) { $type = "DDR SDRAM"; }
- elsif ($bytes[2] == 8) { $type = "DDR2 SDRAM"; }
+ my @types = ("Reserved", "FPM DRAM", "EDO",
+ "Pipelined Nibble", "SDR DRAM",
+ "Multiplexed ROM", "DDR SGRAM", "DDR SDRAM",
+ "DDR2 SDRAM", "FB_DIMM", "FB-DIMM PROBE",
+ "DDR3 SDRAM");
+ if ($bytes[2] <= $#types) {
+ $type = $types[$bytes[2]];
+ } else { $type = $types[0]; }
}
printl $l, $type;
@@ -1340,10 +1664,19 @@
$decode_callback{$type}->(\@bytes)
if exists $decode_callback{$type};
+ # DDR3 Manufacturer info is already decoded
+ # (It's NOT common!)
+
+ next if ($type eq "DDR3 SDRAM");
+
# Decode next 35 bytes (64-98, common to all memory types)
prints "Manufacturing Information";
- @bytes = readspd64(64, $dimm_list[$i]);
+ if ($#bytes == 63) {
+ @bytes = readspd64(64, $dimm_list[$i]);
+ } else {
+ @bytes = @bytes[64..$#bytes];
+ }
$l = "Manufacturer";
# $extra is a reference to an array containing up to
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic