#!/usr/bin/env python3
# Copyright (c) 2007, 2011 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.

# pylint: disable=consider-using-f-string

import sys, errno, struct
import Pci

def field( value, hibit, lobit ):
   return ( value >> lobit ) & ( ( 1 << (hibit - lobit + 1) ) - 1 )

print( "AMD Model 0F CPU Settings" )
print( "=========================" )
print()

dev = Pci.Device( Pci.Address( slot=0x18, function=2 ) )

try:
   print( "Memory controller at PCI address {} (id {})".format( dev.address(),
      dev.id() ) )
except OSError as e:
   if e.errno != errno.ENOENT: raise # pylint: disable=multiple-statements
   print( "No memory controller found at PCI address %s" % dev.address() )
   sys.exit( 1 )

config = dev.config()
dramCsBaseAddr0 = config.read32( 0x40 )
dramCsBaseAddr1 = config.read32( 0x44 )
dramCsBaseAddr2 = config.read32( 0x48 )
dramCsBaseAddr3 = config.read32( 0x4c )
dramCsBaseAddr4 = config.read32( 0x50 )
dramCsBaseAddr5 = config.read32( 0x54 )
dramCsBaseAddr6 = config.read32( 0x58 )
dramCsBaseAddr7 = config.read32( 0x5c )
dramCsAddrMapping = config.read32( 0x80 )
dramTimingLow = config.read32( 0x88 )
dramTimingHigh = config.read32( 0x8c )
dramConfigurationLow = config.read32( 0x90 )
dramConfigurationHigh = config.read32( 0x94 )
config.write32( 0x98, 0x00 )
outputDrvCompControlA = config.read32( 0x9c )
config.write32( 0x98, 0x20 )
outputDrvCompControlB = config.read32( 0x9c )
config.write32( 0x98, 0x04 )
addrTimingControlA = config.read32( 0x9c )
config.write32( 0x98, 0x24 )
addrTimingControlB = config.read32( 0x9c )

try:
   f = open( "/dev/cpu/0/msr" ) # pylint: disable=consider-using-with
   f.seek( 0xc0010042 )
   fidvid = struct.unpack( "<Q", f.read( 8 ) )[0]
   f.close()
except OSError as e:
   if e.errno != errno.ENOENT: raise # pylint: disable=multiple-statements
   print( "Could not read CPU FID/VID from MSR; try 'modprobe msr'" )
   sys.exit( 1 )
print( "CPU FID 0x{:02x} VID 0x{:02x}".format( field( fidvid,
      5, 0 ), field( fidvid, 37,
   32 ) ) )
print()

s = dict() # pylint: disable=use-dict-literal

s["CPU Core Clock Frequency (based on FID)"] = (
   "%d MHz" % ((field(fidvid, 5, 0) // 2 + 4) * 200) )

s["CPU Voltage (based on VID)"] = (
   "%0.3f V" % (1.550 - field(fidvid, 37, 32) * 0.025) )

for reg, cs in ( (dramCsBaseAddr0, "CS0"),
                 (dramCsBaseAddr1, "CS1"),
                 (dramCsBaseAddr2, "CS2"),
                 (dramCsBaseAddr3, "CS3"),
                 (dramCsBaseAddr4, "CS4"),
                 (dramCsBaseAddr5, "CS5"),
                 (dramCsBaseAddr6, "CS6"),
                 (dramCsBaseAddr7, "CS7") ):
   s["Chip-Select Bank Enable (CSEnable) (%s)" % cs] = (
      "%d" % field(reg, 0, 0) )

for f, cs in ( (0, "CS1/0"),
               (4, "CS3/2"),
               (8, "CS5/4"),
               (12, "CS7/6") ):
   s["DIMM size (%s)" % cs] = (
      { (0x0, 0x0): "128 MB (256 Mb x16)",
        (0x0, 0x1): "256 MB (256 Mb x8, 512 Mb x16)",
        (0x0, 0x2): "512 MB (512 Mb x8)",
        (0x0, 0x3): "512 MB (256 Mb x4)",
        (0x0, 0x4): "512 MB (1 Gb x16)",
        (0x0, 0x5): "1 GB (1 Gb x8, 2Gb x16)",
        (0x0, 0x6): "1 GB (512 Mb, x4)",
        (0x0, 0x7): "2 GB (2 Gb x8, 4 Gb x16)",
        (0x0, 0x8): "2 GB (1 Gb x4)",
        (0x0, 0x9): "4 GB (2 Gb x4)",
        (0x0, 0xa): "4 GB (4 Gb x8)",
        (0x0, 0xb): "8 GB (4 Gb x4)",
        (0x1, 0x0): "256 MB (256 Mb x16)",
        (0x1, 0x1): "512 MB (256 Mb x8, 512 Mb x16)",
        (0x1, 0x2): "1 GB (512 Mb x8)",
        (0x1, 0x3): "1 GB (256 Mb x4)",
        (0x1, 0x4): "1 GB (1 Gb x16)",
        (0x1, 0x5): "2 GB (1 Gb x8, 2Gb x16)",
        (0x1, 0x6): "2 GB (512 Mb, x4)",
        (0x1, 0x7): "4 GB (2 Gb x8, 4 Gb x16)",
        (0x1, 0x8): "4 GB (1 Gb x4)",
        (0x1, 0x9): "8 GB (2 Gb x4)",
        (0x1, 0xa): "8 GB (4 Gb x8)",
        (0x1, 0xb): "16 GB (4 Gb x4)"
        }.get( (field(dramConfigurationLow, 11, 11),
                field(dramCsAddrMapping, f+3, f)) ) )

s["CAS# Latency (TCL)"] = (
   "CL=%d" % (field(dramTimingLow, 2, 0) + 1) )

s["RAS# Active to CAS# read/write Delay (Trcd)"] = (
   "%d Clocks" % (field(dramTimingLow, 5, 4) + 3) )

s["Row Precharge Time (Trp)"] = (
   "%d Clocks" % (field(dramTimingLow, 9, 8) + 3) )

s["Read to Precharge Time (Trtp)"] = (
   { (0x0, 0x0): "4 Clocks",
     (0x0, 0x1): "2 Clocks",
     (0x1, 0x0): "5 Clocks",
     (0x1, 0x1): "3 Clocks" }.get( (field(dramTimingLow, 11, 11),
                                    field(dramConfigurationLow, 10, 10)) ) )

s["Minimum RAS# Active Time (Tras)"] = (
   "%d bus clocks" % (field(dramTimingLow, 15, 12) + 3) )

s["Row Cycle Time (Trc)"] = (
   "%d bus clocks" % (field(dramTimingLow, 19, 16) + 11) )

s["Write Recovery Time (Twr)"] = (
   "%d bus clocks" % (field(dramTimingLow, 21, 20) + 3) )

s["Active-to-active (RAS#-to-RAS#) Delay (Trrd)"] = (
   "%d bus clocks" % (field(dramTimingLow, 23, 22) + 3) )

s["MEMCLK disable (MemClkDis)"] = (
   "0x%02x" % field(dramTimingLow, 31, 24) )

s["Read-to-Write Turnaround for Data, DQS Contention (TrwtTO)"] = (
   "%d bus clocks" % (field(dramTimingHigh, 6, 4) + 2) )

s["Internal DRAM Write-to-Read Command Delay (Twtr)"] = (
   "%d bus clocks" % field(dramTimingHigh, 9, 8) )

s["Write to Read DIMM Termination Turnaround (Twrrd)"] = (
   "%d bus clocks" % field(dramTimingHigh, 11, 10) )

s["Write to Write Timing (Twrwr)"] = (
   "%d bus clocks (%d idle cycles on the bus)" % (field(dramTimingHigh, 13, 12) + 1,
                                                  field(dramTimingHigh, 13, 12)) )

s["Read to Read Timing (Trdrd)"] = (
   "%d bus clocks (%d idle cycles on the bus)" % (field(dramTimingHigh, 15, 14) + 2,
                                                  field(dramTimingHigh, 15, 14) + 1))

s["Refresh Rate (Tref)"] = (
   { 0x0: "Undefined behavior",
     0x1: "Reserved",
     0x2: "Refresh interval of 7.8 microseconds",
     0x3: "Refresh interval of 3.9 microseconds"
     }.get( field(dramTimingHigh, 17, 16) ) )

for i in range(0, 4):
   s["Auto-Refresh Row Cycle Time for Logical DIMM%d (Trfc%d)" % (i, i)] = (
      { 0x0: "75 ns (all speeds, 256 Mbit)",
        0x1: "105 ns (all speeds, 512 Mbit)",
        0x2: "127.5 ns (all speeds, 1 Gbit)",
        0x3: "195 ns (all speeds, 2 Gbit)",
        0x4: "327.5 ns (all speeds, 4 Gbit)"
        }.get( field(dramTimingHigh, 22 + i*3, 20 + i*3) ) )

s["DRAM Termination (DramTerm)"] = (
   { 0x0: "On die termination disabled",
     0x1: "75 ohms",
     0x2: "150 ohms",
     0x3: "50 ohms" }.get( field(dramConfigurationLow, 5, 4) ) )

s["DRAM Drivers Weak Mode (DramDrvWeak)"] = (
   { 0x0: "Normal drive strength mode",
     0x1: "Weak drive strength mode" }.get( field(dramConfigurationLow, 7, 7) ) )

s["Parity Enable (ParEn)"] = (
   "%d" % field(dramConfigurationLow, 8, 8) )

s["Faster Self Refresh Rate Enable (SelfRefRateEn)"] = (
   "%d" % field(dramConfigurationLow, 9, 9) )
   
s["DRAM Burst Length Set for 32 Bytes (BurstLength32)"] = (
   { 0x0: "64-byte mode",
     0x1: "32-byte mode" }.get( field(dramConfigurationLow, 10, 10) ) )

s["Width of DRAM Interface (Width128)"] = (
   { 0x0: "64 bits wide",
     0x1: "128 bits wide" }.get( field(dramConfigurationLow, 11, 11) ) )

for i in range(0, 4):
   s["X4 DIMM%d (X4Dimm%d)" % (i, i)] = (
      { 0x0: "DIMM is not x4",
        0x1: "x4 DIMM present"
        }.get( field(dramConfigurationLow, 12+i, 12+i ) ) )

s["Unbuffered DIMMs (UnBuffDimm)"] = (
   { 0x0: "Buffered DIMMs",
     0x1: "Unbuffered DIMMs" }.get( field(dramConfigurationLow, 16, 16) ) )

s["DIMM ECC Enable (DimmEccEn)"] = (
   "%d" % field(dramConfigurationLow, 19, 19) )

s["Memory Clock Frequency (MemClkFreq)"] = (
   { 0x0: "200 MHz",
     0x1: "266 MHz",
     0x2: "333 MHz",
     0x3: "400 MHz" }.get( field(dramConfigurationHigh, 2, 0) ) )

s["Maximum Asynchronous Latency (MaxAsyncLat)"] = (
   "%d ns" % field(dramConfigurationHigh, 7, 4) )

s["Read DQS Enable (RDqsEn)"] = (
   { 0x0: "DM pins function as data mask pins",
     0x1: "DM pins function as read DQS pins"
     }.get( field(dramConfigurationHigh, 12, 12) ) )

s["Disable the DRAM Interface (DisDramInterface)"] = (
   { 0x0: "Enabled",
     0x1: "Disabled" }.get( field(dramConfigurationHigh, 14, 14) ) )

s["Power Down Mode Enable (PowerDownEn)"] = (
   { 0x0: "Disabled",
     0x1: "Enabled" }.get( field(dramConfigurationHigh, 15, 15) ) )

s["Power Down Mode (PowerDownMode)"] = (
   { 0x0: "Channel CKE control",
     0x1: "Chip Select CKE control" }.get( field(dramConfigurationHigh, 16, 16) ) )

s["Four Rank SO-DIMM (FourRankSODimm)"] = (
   "%d" % field(dramConfigurationHigh, 17, 17) )

s["Four Rank Registered DIMM (FourRankRDimm)"] = (
   "%d" % field(dramConfigurationHigh, 18, 18) )

s["Slow Access Mode (2T Mode) (SlowAccessMode)"] = (
   { 0x0: "1T Mode",
     0x1: "2T Mode" }.get( field(dramConfigurationHigh, 20, 20) ) )

s["Bank Swizzle Mode (BankSwizzleMode)"] = (
   { 0x0: "Disabled",
     0x1: "Enabled" }.get( field(dramConfigurationHigh, 22, 22) ) )

s["DRAM Controller Queue Bypass Maximum (DcqBypassMax)"] = (
   "The oldest request may be bypassed no more than %d times" %
   field(dramConfigurationHigh, 27, 24) )

s["Four Bank Activate Window (FourActWindow)"] = (
   "%d MEMCLK cycles" % (field(dramConfigurationHigh, 31, 28) + 7) )

for reg, ch in ( (outputDrvCompControlA, "A"),
                 (outputDrvCompControlB, "B") ):
   s["CKE Drive Strength (CkeDrvStren) (channel %s)" % ch] = (
      { 0x0: "1.0x",
        0x1: "1.25x",
        0x2: "1.5x",
        0x3: "2.0x" }.get( field(reg, 1, 0) ) )

   s["CS/ODT Drive Strength (CsOdtDrvStren) (channel %s)" % ch] = (
      { 0x0: "1.0x",
        0x1: "1.25x",
        0x2: "1.5x",
        0x3: "2.0x" }.get( field(reg, 5, 4) ) )

   s["Address/Command Drive Strength (AddrCmdDrvStren) (channel %s)" % ch] = (
      { 0x0: "1.0x",
        0x1: "1.25x",
        0x2: "1.5x",
        0x3: "2.0x" }.get( field(reg, 9, 8) ) )

   s["MEMCLK Drive Strength (ClkDrvStren) (channel %s)" % ch] = (
      { 0x0: "0.75x",
        0x1: "1.0x",
        0x2: "1.25x",
        0x3: "1.5x" }.get( field(reg, 13, 12) ) )

   s["Data Drive Strength (DataDrvStren) (channel %s)" % ch] = (
      { 0x0: "0.75x",
        0x1: "1.0x",
        0x2: "1.25x",
        0x3: "1.5x" }.get( field(reg, 17, 16) ) )

   s["DQS Drive Strength (DqsDrvStren) (channel %s)" % ch] = (
      { 0x0: "0.75x",
        0x1: "1.0x",
        0x2: "1.25x",
        0x3: "1.5x" }.get( field(reg, 21, 20) ) )

   s["Processor On-die Termination (ProcOdt) (channel %s)" % ch] = (
      { 0x0: "300 ohms +/- 20%",
        0x1: "150 ohms +/- 20%",
        0x2: "75 ohms +/- 20%" }.get( field(reg, 29, 28) ) )

for reg, ch in ( (addrTimingControlA, "A"),
                 (addrTimingControlB, "B") ):
   s["CKE Fine Delay (CkeFineDelay) (channel %s)" % ch] = (
      "%d/64 MEMCLK delay" % field(reg, 4, 0) )

   s["CKE Setup Time (CkeSetupTime) (channel %s)" % ch] = (
      { 0x0: "1/2 MEMCLK",
        0x1: "1 MEMCLK" }.get( field(reg, 5, 5) ) )

   s["CS/ODT Fine Delay (CsOdtFineDelay) (channel %s)" % ch] = (
      "%d/64 MEMCLK delay" % field(reg, 12, 8) )

   s["CS/ODT Setup Time (CsOdtSetup) (channel %s)" % ch] = (
      { 0x0: "1/2 MEMCLK",
        0x1: "1 MEMCLK" }.get( field(reg, 13, 13) ) )

   s["Address/Command Fine Delay (AddrCmdFineDelay) (channel %s)" % ch] = (
      "%d/64 MEMCLK delay" % field(reg, 20, 16) )

   s["Address/Command Setup Time (AddrCmdSetup) (channel %s)" % ch] = (
      { 0x0: "1/2 MEMCLK",
        0x1: "1 MEMCLK" }.get( field(reg, 21, 21) ) )

   s["Address Timing Control DLL Phases (AtcDllMaxPhases) (channel %s)" % ch] = (
      { 0x0: "32 phases per bit-time",
        0x1: "64 phases per bit-time" }.get( field(reg, 28, 28) ) )

s["DRAM Interface Speed (based on FID)"] = (
   { (0x0, 0x0): "160.00 MHz",
     (0x0, 0x1): "160.00 MHz",
     (0x0, 0x2): "160.00 MHz",
     (0x0, 0x3): "160.00 MHz",
     (0x1, 0x0): "180.00 MHz",
     (0x1, 0x1): "180.00 MHz",
     (0x1, 0x2): "180.00 MHz",
     (0x1, 0x3): "180.00 MHz",
     (0x2, 0x0): "200.00 MHz",
     (0x2, 0x1): "200.00 MHz",
     (0x2, 0x2): "200.00 MHz",
     (0x2, 0x3): "200.00 MHz",
     (0x3, 0x0): "183.33 MHz",
     (0x3, 0x1): "220.00 MHz",
     (0x3, 0x2): "220.00 MHz",
     (0x3, 0x3): "220.00 MHz",
     (0x4, 0x0): "200.00 MHz",
     (0x4, 0x1): "240.00 MHz",
     (0x4, 0x2): "240.00 MHz",
     (0x4, 0x3): "240.00 MHz",
     (0x5, 0x0): "185.71 MHz",
     (0x5, 0x1): "260.00 MHz",
     (0x5, 0x2): "260.00 MHz",
     (0x5, 0x3): "260.00 MHz",
     (0x6, 0x0): "200.00 MHz",
     (0x6, 0x1): "233.33 MHz",
     (0x6, 0x2): "280.00 MHz",
     (0x6, 0x3): "280.00 MHz",
     (0x7, 0x0): "187.50 MHz",
     (0x7, 0x1): "250.00 MHz",
     (0x7, 0x2): "300.00 MHz",
     (0x7, 0x3): "300.00 MHz",
     (0x8, 0x0): "200.00 MHz",
     (0x8, 0x1): "266.67 MHz",
     (0x8, 0x2): "320.00 MHz",
     (0x8, 0x3): "320.00 MHz",
     (0x9, 0x0): "188.89 MHz",
     (0x9, 0x1): "242.86 MHz",
     (0x9, 0x2): "283.33 MHz",
     (0x9, 0x3): "340.00 MHz",
     (0xa, 0x0): "200.00 MHz",
     (0xa, 0x1): "257.14 MHz",
     (0xa, 0x2): "300.00 MHz",
     (0xa, 0x3): "360.00 MHz",
     (0xb, 0x0): "190.00 MHz",
     (0xb, 0x1): "237.50 MHz",
     (0xb, 0x2): "316.67 MHz",
     (0xb, 0x3): "380.00 MHz",
     (0xc, 0x0): "200.00 MHz",
     (0xc, 0x1): "250.00 MHz",
     (0xc, 0x2): "333.33 MHz",
     (0xc, 0x3): "400.00 MHz",
     (0xd, 0x0): "190.90 MHz",
     (0xd, 0x1): "262.50 MHz",
     (0xd, 0x2): "300.00 MHz",
     (0xd, 0x3): "350.00 MHz",
     (0xe, 0x0): "200.00 MHz",
     (0xe, 0x1): "244.44 MHz",
     (0xe, 0x2): "314.29 MHz",
     (0xe, 0x3): "366.67 MHz",
     (0xf, 0x0): "191.67 MHz",
     (0xf, 0x1): "255.56 MHz",
     (0xf, 0x2): "328.57 MHz",
     (0xf, 0x3): "383.33 MHz",
     (0x10, 0x0): "200.00 MHz",
     (0x10, 0x1): "266.67 MHz",
     (0x10, 0x2): "300.00 MHz",
     (0x10, 0x3): "400.00 MHz",
     (0x11, 0x0): "192.30 MHz",
     (0x11, 0x1): "250.00 MHz",
     (0x11, 0x2): "312.50 MHz",
     (0x11, 0x3): "357.14 MHz",
     (0x12, 0x0): "200.00 MHz",
     (0x12, 0x1): "260.00 MHz",
     (0x12, 0x2): "325.00 MHz",
     (0x12, 0x3): "371.43 MHz",
     (0x13, 0x0): "192.86 MHz",
     (0x13, 0x1): "245.46 MHz",
     (0x13, 0x2): "300.00 MHz",
     (0x13, 0x3): "385.71 MHz",
     (0x14, 0x0): "200.00 MHz",
     (0x14, 0x1): "254.55 MHz",
     (0x14, 0x2): "311.11 MHz",
     (0x14, 0x3): "400.00 MHz",
     (0x15, 0x0): "193.33 MHz",
     (0x15, 0x1): "263.64 MHz",
     (0x15, 0x2): "322.22 MHz",
     (0x15, 0x3): "362.50 MHz",
     (0x16, 0x0): "200.00 MHz",
     (0x16, 0x1): "250.00 MHz",
     (0x16, 0x2): "333.33 MHz",
     (0x16, 0x3): "375.00 MHz",
     (0x17, 0x0): "193.75 MHz",
     (0x17, 0x1): "258.33 MHz",
     (0x17, 0x2): "310.00 MHz",
     (0x17, 0x3): "387.50 MHz"
     }.get( (field(fidvid, 5, 0), field(dramConfigurationHigh, 2, 0)) ) )

for x, y in sorted( s.items() ):
   print( x + ":" )
   print( "  " + ( y or "N/A" ) )
