#!/usr/bin/env python
# Copyright (c) 2006,2011 Arastra, Inc.  All rights reserved.
# Arastra, Inc. Confidential and Proprietary.

import optparse, sys
import Tac, SmbusUtil, BusUtilCommon

AhamConstants = Tac.Type( "Hardware::AhamConstants" )

def strToInt( s ):
   try:                   
      i = int( s )
   except ValueError:
      if s.startswith( "0b" ) or s.startswith( "0B" ):
         i = int( s[2:], 2 )
      else:
         i = int( s, 16 )
   return i

def dataStrToCharList( s ):
   result = []
   length = len( s )
   if (length==0) or ((length % 2) != 0):
      parser.error( "Data string of wrong length" )
   start = 0
   if( s[1]=='x' or s[1]=='X' ): start = 2
   for n in range( start, length, 2 ):
      try:
         i = int( s[n : n+2], 16 )
      except ValueError:
         parser.error( "Wrong data string" )
      result.append( chr( i ) )
   return result

def binary( value, bytes=4 ):
   """
String rep of an integer in binary, similar to 'hex'
   Turns 0xc7 into 1100 0111
The optional bytes parameter indicates how many bytes wide the value is."""
   return "".join( [((value>>n &1) and "1" or "0") + ((n % 4 == 0) and " " or "")
                    for n in xrange(bytes*8-1,-1,-1)] )

usage = """
smbus [<options>] {read8|read16} <devicepath> address [count]
smbus [<options>] {write8|write16} <devicepath> address data
smbus [<options>] reads <devicepath> address size
smbus [<options>] writes <devicepath> address data
smbus [<options>] processCall <devicepath> address writeData readSize
smbus [<options>] send <devicepath> address
smbus [<options>] reset /scd/<accelId>
  <devicepath> is in this form:
     /scd/<accelId>/<busId>/<deviceId>[L[L]]
     /sb/<busId>/<deviceId>
  e.g.,
     /scd/3/2/80L is the path to the supervisor seeprom
  If the deviceId is suffixed with "l" or "L" then 16-bit addresses are
  used when accessing the device.
  If the deviceId is suffixed with "ll" or "LL" then 32-bit addresses are
  used when accessing the device.

  accelId and busId are zero-based, so the legal values are 0-3 and 0-15.

  A 'bulk' read can be done by specifying a non-zero count for the
  read.

  A single read prints its result in hex, binary,and ascii

  All numeric arguments can be specified in decimal, hex (prefixed by
  '0x'), or binary (prefixed by '0b').

  The reset command is only applicable to scd based accelerators. Similarly,
  the --agent, --raw, --timeout, and --delay options only affect scd reads and
  do not have an effect on the smbuses accessed through the South Bridge (sb).

  --string
      For 'writes' argument:
         By default, input data is treated as a string of 2-character hex numbers.
         The corresponding ascii characters are what is actually written to the
         device.

         If -s is specified with 'writes' argument, input data is considered as a
         string of ASCII characters and is written as such.

      For 'reads' argument:
         By default, each byte of data read from the device is displayed as a
         2-character HEX number.

         if -s is specified, the data read from device is treated as ASCII
         numbers and the corresponding ASCII characters are printed

  --input <filename> can be used with 'writes' option. If '-' is specified as
      filename then stdin is used. This is an alternative to specifying the
      data in the args. In this case, the data is read from the specified file.

  --maxChunkSize
      This must be used with 'writes'. When specified, if the given data size is
      greater then the maxChunkSize, it is split into multiple chunks and
      written to the specified device - one chunk at a time

  --smbusAddrSpacing
      By default, smbus accelerators are assumed to have base addresses that
      are 0x100 apart.  To change the spacing between accelerator address
      spaces, specify this argument.  

      An argument of 'schooner' for this option specifies the discontiguous address
      mapping used by Schooner (accelerators 8-15 have addresses starting at
      0x18000; otherwise spacing is 0x80 apart).

  --smbusBaseAddr <address> can be used to specify smbus base address.
      Default is 0x8000

  --pciAddr <address> can be used with raw mode to specify the PCI address of
      the SCD on platforms where there are multiple Scds.

  Examples:
   1) To read IDEEPROM on the Mezzanine on Silverado:
         smbus -s reads /scd/0/6/0x50 0x0 0xff
   2) To write prefdl to IDEEPROM on the Mezzanine on Silverado:
         /usr/bin/smbus -i /tmp/prefdl -s -m 16 writes /scd/0/6/0x50 0x0
               or
         cat /tmp/prefdl | /tmp/smbus -i - -s -m 16 writes /scd/0/6/0x50 0x0
      Here, /tmp/prefdl has the encoded prefdl for Mezzanine
   3) To read the first AOM EEPROM in a ThreeBrothers in slot Linecard9:
         smbus read8 /scd/0/0/0x53l 0x0 0xff -s -l 9
"""

timeoutDefault = 10
parser = optparse.OptionParser(usage=usage)
parser.add_option( "-v", "--verbose", action="store_true",
                   help="enable verbose output" )
parser.add_option( "-a", "--agent", action="store_true",
                   help="force access to hardware via Smbus agent",
                   dest="agent", default=False )
parser.add_option( "-r", "--raw", action="store_true",
                   help="force direct access to hardware",
                   dest="raw", default=False )
parser.add_option( "-o", "--smbusAddrSpacing", action="store",
                   default='0x100', dest="smbusAddrSpacing",
                   help="Specify accelerator address offsets." )
parser.add_option( "", "--smbusBaseAddr", action="store",
                   default='0x8000', dest="smbusBaseAddr",
                   help="Specify smbus base address." )
parser.add_option( "-s", "--string", action="store_true",
                   help="Output result as a string to stdout" )
parser.add_option( "-t", "--timeout", action="store",
                   help="max seconds to wait for the agent to respond " \
                        "(default=%d)" %( timeoutDefault ),
                   default=timeoutDefault )
parser.add_option( "-w", "--width", action="store", type="int",
                   default=16,
                   help="Bytes to show per line in bulk mode" )
parser.add_option( "", "--delay", action="store",
                   default='0', choices=['0','1','10','50'],
                   help="Ms to delay between transactions (default 50)" )
parser.add_option( "-b", "--busTimeout", action="store",
                   default='1000', choices=['0','100','250','1000'],
                   help="Smbus transaction timeout in ms (default 1000)" )
parser.add_option( "-c", "--writeNoStopReadCurrent", action="store_true",
                   default=False,
                   help="enable write no stop read current" )
parser.add_option( "-i", "--inputFile", action="store",
                   default=False,
                   help="Specify input file for data to write." )
parser.add_option( "-m", "--maxChunkSize", action="store",
                   default=False,
                   help="Specify maxChunkSize for each write." )
parser.add_option( "-l", "--linecard", action="store",
                   type="int", default=None,
                   help="Specify the linecard on which the device exists." )
parser.add_option( "", "--pciAddr", action="store",
                   default=None,
                   help="Specify the PCI address of the Scd device." )
parser.add_option( "-p", "--pec", action="store_true",
                   help="Send PEC on write or check PEC on read." )

( options, args ) = parser.parse_args()
if len( args ) < 2:
   parser.error( "Too few arguments" )

if not args[1].startswith( "/sb" ):   
   agentPresent = BusUtilCommon.agentAddr( 'Smbus' )
   if( agentPresent and options.raw ):
      parser.error( "Kill Smbus agent before asking for raw" )
   if( (not agentPresent) and options.agent ):
      parser.error( "Smbus agent is not running" )
   
options.timeout = int( options.timeout )

op = args[0]
if op not in ( "read8", "write8", "read16", "write16", "reads", "writes", 
               "processCall", "send", "reset" ):
   parser.error( "Unknown argument:" + args[0] )

count = 1
if op.startswith( "write" ):
   if len( args ) < 3:
      parser.error( "Wrong # of arguments for write" )
   op = args[ 0 ]
   path = args[ 1 ]
   addr = args[ 2 ]
   if options.inputFile:
      assert op == "writes"
      if options.inputFile == "-":
         fp = sys.stdin
      else:
         fp = file( options.inputFile, "r" )
      data = fp.read().strip()
   else:
      # -i not specified. 4th argument must be data
      if len( args ) != 4:
         parser.error( "Wrong # of arguments for write" )
      data = args[ 3 ]
   if op == "writes":
      # Data as a list of bytes
      if options.string:
         data = list( data )
      else:
         data = dataStrToCharList( data )
   else:
      # Data as a single word
      data = strToInt( data )
elif op.startswith( "read" ) or op == "send":
   (op,path,addr) = args[0:3]
   if(len(args) > 3)  or (op == "reads"):
      if len( args ) == 3:
         # Block read
         count = AhamConstants.blockReadCount
      elif len( args ) == 4:
         count = strToInt(args[3])
      else:
         parser.error( "Wrong number of arguments for read" )
elif op == "processCall":
   if len( args ) != 5:
      parser.error( "Wrong # of arguments for processCall" )
   path, addr = args[1:3]
   if options.string:
      data = list( args[3] )
   else:
      data = dataStrToCharList( args[3] )
   count = strToInt( args[4] )
else: # reset
   if not args[1].startswith( '/scd' ):
      parser.error( "Cannot use reset with non-scd buses" )
   if len(args) != 2:
      parser.error( "Too many arguments for reset" )
   (op,path) = args

if not path.startswith('/'):
   parser.error( "Device path must begin with a /" )
path = path[1:]
path = path.split( '/' )
if not ( path[0] == 'scd' or path[0] == 'sb' ):
   parser.error( "Unknown device: %s"  % path[0] )
if op == "reset":
   # Path is to an smbus accelerator
   if len( path ) != 2:
      parser.error( "Device path error: should be /scd/<accelId>" )
   if path[0] == 'scd':
      accelId = strToInt( path[1] )
      busId = 0
      deviceId = 0
      addrSize = 1
      addr = 0
   else:
      parser.error( "Unknown device:" + path[0] )
else:
   # Path is to an smbus device, now mux on scd and sb
   if path[0] == 'scd':
      if len( path ) != 4:
         parser.error( "Device path error: should be " \
                       "/scd/<accelId>/<busId>/<deviceId>" )
      accelId = strToInt(path[1])
      busId = strToInt(path[2])
      deviceId = path[3]
      if deviceId[-2] in "Ll" and deviceId[-1] in "Ll":
         deviceId = deviceId[:-2]
         addrSize = 4
      elif deviceId[-1] in "Ll":
         deviceId = deviceId[:-1]
         addrSize = 2
      else:
         addrSize = 1
      deviceId = strToInt(deviceId)
   elif path[0] == 'sb':
      if len( path ) != 3:
         parser.error( "Device path error: should be /sb/<busId>/<deviceId>" )
      busId = strToInt(path[1])
      deviceId = path[2]
      if deviceId[-2] in "Ll" and deviceId[-1] in "Ll":
         deviceId = deviceId[:-2]
         addrSize = 4
      elif deviceId[-1] in "Ll":
         deviceId = deviceId[:-1]
         addrSize = 2
      else:
         addrSize = 1
      deviceId = strToInt(deviceId)
   else:
      assert False

silent = options.string
verbose = options.verbose and not silent

addr = strToInt(addr)
width = options.width

def ascii( value, size ):
   if size == 1:
      return chr( value )
   elif size == 2:
      return chr( value & 0xff ) + chr( (value >> 8) & 0xff )

def doRead( func, addr, count, size, pec=False ):
   if count == 1:
      result = func( addr, pec=pec )
      a = ascii( result, size )
      if options.string:
         print a
      else:
         print hex( result ), binary( result, size ), ascii( result, size )
   else:
      result = helper.read( addr, count, accessSize=size, pec=pec )
      # NOTE - result is a list of bytes
      from Hexdump import hexdump
      resultStr = "".join( [ascii(i,1) for i in result] )
      if options.string:
         print resultStr
      else:
         print hexdump( resultStr, firstAddr=addr, rowLen=width )

def doReadByteString( addr, size, stringOp, pec=False ):
   if size == AhamConstants.blockReadCount:
      accessSize = 1
   else:
      accessSize = size
   result = helper.readByteList( addr, size, accessSize, pec=pec )
   if stringOp:
      for n in result:
         # This does not print the space/newline after each character
         sys.stdout.write( chr( n ) )
   else:
      for n in result:
         print "%02x" %n,
   print "\n"

# Once again, mux on scd and sb
if path[0] == 'scd':
   factory = SmbusUtil.Factory()
   delayMs = { '0': 'delay0', '1': 'delay1ms',
               '10': 'delay10ms', '50': 'delay50ms' }[ options.delay ]
   busTimeout = { '0': 'busTimeout100us', '100': 'busTimeout100ms',
                  '250': 'busTimeout250ms', '1000': 'busTimeout1000ms' 
                  }[ options.busTimeout ]
   # Modular systems need to specify which smbus agent to talk to,
   # which is identified by linecard number. Fixed systems should
   # pass the default None.
   helper = factory.device( accelId, busId, deviceId, addrSize,
                            readDelayMs=delayMs, writeDelayMs=delayMs,
                            busTimeout=busTimeout,
                            writeNoStopReadCurrent=options.writeNoStopReadCurrent,
                            smbusAddrSpacing=options.smbusAddrSpacing,
                            smbusAgentId=options.linecard,
                            pciAddress=options.pciAddr,
                            smbusBaseAddr=options.smbusBaseAddr )
elif path[0] == 'sb':
   factory = SmbusUtil.Factory( factoryType='sb' )
   helper = factory.device( busId, deviceId )
else:
   assert False

if op == "reset":
   if agentPresent:
      parser.error( "reset operation is supported in raw mode only" )
   helper.reset()
   sys.exit( 0 )

try:
   if op == "read8":
      doRead( helper.read8, addr, count, 1, pec=options.pec )
   elif op == "read16":
      doRead( helper.read16, addr, count, 2, pec=options.pec )
   elif op == "reads":
      doReadByteString( addr, count, options.string, pec=options.pec )
   elif op == "write8":
      helper.write8( addr, data, pec=options.pec )
      print "wrote", data, "at", addr
   elif op == "write16":
      helper.write16( addr, data, pec=options.pec )
      print "wrote", data, "at", addr
   elif op == "writes":
      if options.maxChunkSize:
         length = len( data )
         maxChunkSize = int( options.maxChunkSize )
         while length > 0:
            writeLength = min( length, maxChunkSize )
            helper.write( addr, data[ :writeLength ], writeLength, pec=options.pec )
            print "Wrote", writeLength, "bytes: ", data[ :writeLength ], "at", addr
            length = length - writeLength
            addr = addr + writeLength
            data = data[ writeLength: ]
      else:
         helper.write( addr, data, len( data ), pec=options.pec )
   elif op == "send":
      helper.sendByte( addr, pec=options.pec )
   elif op == "processCall":
      result = helper.processCall( addr, data, count )
      print "process call at %s: wrote %s; read following data" % ( addr, data )
      if options.string:
         for n in result:
            sys.stdout.write( chr( n ) )
      else:
         for n in result:
            print "%02x" % n,
      print "\n"

except SmbusUtil.SmbusFailure as e:
   smbusStr = "/" + "/".join( path ) + " 0x%x" % addr
   if "data" in vars():
      smbusStr += " 0x%x" % data
   print "Smbus transaction failed (%s), exception = %s" % ( smbusStr, str( e ) )
   sys.exit(1)

