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

# pylint: disable=consider-using-f-string
# pylint: disable=redefined-outer-name

import optparse, sys # pylint: disable=deprecated-module
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 dataStrToBytes( 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 # pylint: disable=multiple-statements
   for n in range( start, length, 2 ):
      try:
         i = int( s[n : n+2], 16 )
      except ValueError:
         parser.error( "Wrong data string" )
      result.append( i ); # pylint: disable=unnecessary-semicolon
   return bytes( 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
    
   # pylint: disable-next=superfluous-parens
   return ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3)

def binary( value, bytes=4 ): # pylint: disable=redefined-builtin
   """
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 range( bytes*8-1,-1,-1 ) ] )

def getPrefdl():
   return open( "/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"
   elif  sid.startswith( "OtterLake" ) or sid.startswith( "HorseshoeLake" ):
      return "Denali"
   else:
      print( "Error, unknown/unsupported platform", sid )
      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
   elif platform == "Denali":
      devId = 0x38
   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 = dataStrToBytes( data )
   if len( data ) != 4:
      parser.error( "data must be 4 bytes long exactly" )
elif op == "read":
   if len( args ) != 4:
       # pylint: disable-next=bad-indentation
       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 ):
      # pylint: disable-next=bad-indentation
      result = helper.readByteList( addr, size, size )
      for n in result: # pylint: disable=bad-indentation
         print( "%02x" %n, end=' ' ) # pylint: disable=bad-indentation
      print( "\n" ) # pylint: disable=bad-indentation

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)
