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

import optparse, sys
import Tac, SmbusUtil
import email

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 _getSillyPlxAddressFormat(port, addr, read=True):
   if read:
      byte0 = 0x04
   else:
      byte0 = 0x03
   byte1 = (int(port,0) >> 1) & 0x7
   byte2 = (( int(addr,0) >> 10 ) & 0x3) | (0xf << 2) | ((int(port,0) & 0x1) << 7)
   byte3 = (int(addr,0) >> 2) & 0xff
    
   return ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3)

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)] )

def getPrefdl():
   return file( "/etc/prefdl" ).read()

def getPlatform():
   prefdl = getPrefdl()
   prefdlParams = email.message_from_string( prefdl )
   sid = prefdlParams.get( 'SID', '' )
   
   if "Oak" in sid:
      return "Sequoia"
   elif "EaglePeak" in sid or "OldFaithful" in sid \
      or "GreatFountain" in sid:
      return "Yosemite"
   else:
      print "Error, unknown/unsupported platform"
      return None
   
def decodePath( path ):
   if path.startswith( 'scd' ):
      return path
      
   platform = getPlatform()
   
   # scd path map 
   scdMap = { 'supfabplx' : 'scd/0/3/0x38',
              'suplineplx' : 'scd/0/3/0x3a', }
              
   accelId = 0
   busId = 0

   if platform == "Yosemite":
      devId = 63
   elif platform == "Sequoia":
      devId = 56
   else:
      return None

   # populate the map table, like following:
   #   'Linecard/1' : 'scd/1/0/63',
   #   'Linecard/2' : 'scd/2/0/63',
   #   'Linecard/3' : 'scd/1/1/63',
   #   'Linecard/4' : 'scd/2/1/63',
   #   ...
   #   'FabricCard/1 : 'scd/3/0/63'
   #   'FabricCard/2 : 'scd/3/1/63'
   #   'FabricCard/3 : 'scd/3/2/63'
   #   ...
   #   check aid/169 and aid/148 for hw specs

   maxLinecard = 16
   for i in range( 1, maxLinecard + 1 ):
      linecard = "linecard/%d" % i 
      scd = "scd/%d/%d/%d" % ( accelId + 1, busId, devId )
      scdMap[ linecard ] = scd

      accelId = ~accelId & 0x1
      if i % 2 == 0:
         busId += 1
   
   maxFabric = 8
   for i in range( 1, maxFabric + 1 ):
      fabric = "fabriccard/%d" % i 
      scd = "scd/3/%d/63" % ( i - 1 )
      scdMap[ fabric ] = scd

   try:
      ret = scdMap[ path.lower() ]
   except KeyError:
      ret = None
      
   return ret


usage = """
plx [options] {read|write} <devicepath> port address [value]
  <devicepath> is in one of following formats:
  Format 1:
     /<name>/[cardNumber]
     Valid names (case insensitive) are:
         SupFabPlx  (without card number)
         SupLinePlx (without card number)
         Linecard/<1-16>
         FabricCard/<1-8>
  
  or Format 2:
     /scd/<accelId>/<busId>/<deviceId>
     
     accelId and busId are zero-based, so the legal values are 0-3 and 0-15.

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

 e.g.,
     On Eagle Peak, one can use following path:
     /SupFabPlx
     /SupLinePlx
     /Linecard/1
     ...
     or
     /scd/0/3/0x3a is the path to the supervisor linecard PLX switch and
     /scd/0/3/0x38 is the path to the supervisor fabriccard PLX switch
     /scd/1/0/63   is the path to the first linecard PLX switch
     ...

 --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.

 Note:
    On Oak, SupFabPlx will point to plx 8749, which also connects to linecards.
"""

parser = optparse.OptionParser(usage=usage)
parser.add_option( "--smbusAddrSpacing", action="store",
                    default='0x100', dest="smbusAddrSpacing",
                    help="Specify accelerator address offsets." )
( options, args ) = parser.parse_args()
if len( args ) < 2:
   parser.error( "Too few arguments" )
   
op = args[0]
if op not in ( "read", "write"):
   parser.error( "Unknown argument:" + args[0] )

count = 1
if op == "write":
   if len( args ) != 5:
      parser.error( "Wrong # of arguments for write" )
   (op,path,port,addr,data) = args
   addr = _getSillyPlxAddressFormat(port, addr, False)
   data = dataStrToCharList( data )
   if len(data) != 4:
      parser.error( "data must be 4 bytes long exactly" )
elif op == "read":
   if len( args ) != 4:
       parser.error( "Wrong number of arguments for read" )
   (op,path,port,addr) = args
   addr = _getSillyPlxAddressFormat(port, addr, True)

if not path.startswith('/'):
   parser.error( "Device path must begin with a /" )
path = path[1:]

scdPath = decodePath( path )

if scdPath is None:
   parser.error( "Unknown device: /%s"  % path )

path = scdPath.split( '/' )

# Path is to an smbus device
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]
addrSize = 4
deviceId = strToInt(deviceId)

def doReadByteString( addr, size ):
      result = helper.readByteList( addr, size, size )
      for n in result:
         print "%02x" %n,
      print "\n"

factory = SmbusUtil.Factory()
helper = factory.device( accelId, busId, deviceId, addrSize, 'delay0', 
                         smbusAddrSpacing=options.smbusAddrSpacing )

try:
   if op == "read":
      doReadByteString( addr, 4 )
   elif op == "write":
      helper.write( addr, data, len( data ) )
      print "Wrote", len( data ), "bytes: ", data, "at", addr
except SmbusUtil.SmbusFailure:
   print "Smbus transaction failed"
   sys.exit(1)
