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 # modified by Christian Zuckschwerdt # modified by Burkart Lingner -# Copyright (C) 2005-2011 Jean Delvare +# Copyright (C) 2005-2013 Jean Delvare # # 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)