#!/usr/bin/env python3
#
# Copyright (c) 2013 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.
#
# Broadcom S-Channel raw access

import argparse
from collections import namedtuple
import sys

import Pci
from Sbuspio import Sbuspio
from Schan import Schan
import schan_accel
import SchanMsgLib

defaultArgTypes = ( 'bar', 'hver', 'cmcgen', 'cmc', 'engine', 'sbuswords' )
ArgumentDefaults = namedtuple( 'SchanChipArguments', defaultArgTypes )

deviceToCmicGenerationMap = {
   '0x846' : 'CMICm1', # Arad Fixed
   '0x865' : 'CMICm1', # Arad Modular
   '0x866' : 'CMICm1', # AradPlus
   '0x837' : 'CMICm3', # QumranMx
   '0x847' : 'CMICm3', # QumranAx
   '0x867' : 'CMICm3', # Jericho
   '0x868' : 'CMICm3', # JerichoPlus
   '0x869' : 'CMICx', # Jericho2
   '0x880' : 'CMICx', # Jericho2c
   '0x882' : 'CMICx', # Qumran2c-2t
   '0x884' : 'CMICxv4', # Jericho2cPlus
   '0x885' : 'CMICxv4', # Jericho2cPlus
   '0x848' : 'CMICx', # Qumran2a
   '0x886' : 'CMICr7', # Jericho3
   '0x887' : 'CMICr7', # Qumran3d
   '0x889' : 'CMICr7', # Jericho3ai
   '0xb34' : 'CMICm2', # Helix4
   '0xb85' : 'CMICm1', # Trident
   '0xb86' : 'CMICm1', # Trident2Plus
   '0xb75' : 'CMICm1', # Titan2
   '0xb87' : 'CMICx', # Trident3
   '0xb27' : 'CMICx', # T3X2 (BCM56274, BCM56275)
   '0xb57' : 'CMICx', # T3X2 (BCM56575)
   '0xb37' : 'CMICx', # T3X3
   '0xb47' : 'CMICx', # T3X4
   '0xb77' : 'CMICx', # T3X5
   '0xb69' : 'CMICxv4', # T4X7
   '0xb78' : 'CMICxv4', # T4X9
   '0xb88' : 'CMICxv4', # T4X11
   '0xb89' : 'CMICxv4', # T4X11C
   '0xb96' : 'CMICm3', # Tomahawk/TomahawkPlus/Titanhawk
   '0xb97' : 'CMICm3', # Tomahawk2
   '0xb98' : 'CMICx', # Tomahawk3
   '0xb99' : 'CMICxv4', # Tomahawk4
   '0xf90' : 'CMICr6', # Tomahawk5
   '0xb07' : 'CMICx', # Firelight
}

cmicGenerationToArgumentsMap = {
   'CMICm1' : ArgumentDefaults( bar=0, hver=3, cmcgen='m', cmc=1, engine=Schan,
                                sbuswords=22 ),
   'CMICm2' : ArgumentDefaults( bar=2, hver=3, cmcgen='m', cmc=1, engine=Schan,
                                sbuswords=22 ),
   'CMICm3' : ArgumentDefaults( bar=2, hver=4, cmcgen='m', cmc=1, engine=Schan,
                                sbuswords=22 ),
   'CMICx' : ArgumentDefaults( bar=2, hver=4, cmcgen='x', cmc=2, engine=Schan,
                               sbuswords=22 ),
   'CMICxv4' : ArgumentDefaults( bar=2, hver=4, cmcgen='x', cmc=2, engine=Schan,
                                 sbuswords=32 ),
   'CMICr6' : ArgumentDefaults( bar=2, hver=6, cmcgen='r', cmc=0, engine=Sbuspio,
                                sbuswords=32 ),
   'CMICr7' : ArgumentDefaults( bar=2, hver=7, cmcgen='r', cmc=0, engine=Sbuspio,
                                sbuswords=32 ),
}

def maybeApplyDefaultArgsForChip( parsedArgs ):
   '''Choose default values for hver, bar, cmc, and cmcgen based on the chip
      type when possible. Use the defaults for the latest generation when no values
      are found for the chip type, because we assume that it's a new device which we
      have not explicitly listed yet.'''
   if parsedArgs.pcidev == 'amba':
      # Only Wolfhound3Plus is on AXI bus right now
      # and it uses CMICx
      cmicGeneration = 'CMICx'
   else:
      bcmId = chipUnit( parsedArgs.pcidev )
      cmicGeneration = deviceToCmicGenerationMap.get( bcmId )
   if cmicGeneration is not None:
      defaults = cmicGenerationToArgumentsMap[ cmicGeneration ]
   else:
      latestGen = list( cmicGenerationToArgumentsMap )[ -1 ]
      if not parsedArgs.cmcgen:
         print( f"Unknown device {bcmId}, defaulting to {latestGen}",
                file=sys.stderr )
      defaults = cmicGenerationToArgumentsMap[ latestGen ]

   for arg in defaultArgTypes:
      if getattr( parsedArgs, arg, None ) is None:
         defaultValue = getattr( defaults, arg )
         setattr( parsedArgs, arg, defaultValue )
   return parsedArgs

def chipUnit( pci ):
   pciDeviceId = Pci.Device( pci ).id()
   assert pciDeviceId is not None, "Couldn't find PCI device."
   # The last digit of the unit code doesn't affect the CMIC generation.
   return hex( pciDeviceId.device )[ :5 ]

def printData( data, msw ):
   if not data:
      print( "no data" )
   else:
      data = list( reversed( data ) ) if msw else data
      print( ' '.join( f'{value:#08x}' for value in data ) )

#
# Read commands
#
def printReadResponse( ack, hver, msw ):
   ack_data_byte_len = schan_accel.schan_header_data_byte_len( ack.header, hver )
   words = ack_data_byte_len // 4
   printData( ack.readresp.data[ : words ], msw )

def readOp( schan, hver, msw, req, address, count ):
   for offset in range( count ):
      req.readcmd.address = address + offset
      ack = schan.op( req, 2, schan.maxWords )
      printReadResponse( ack, hver, msw )

def readReg( schan, args ):
   req = SchanMsgLib.readRegister( args.hver, args.acc_type, args.dst_blk,
                                   args.address, args.num_words * 4 )
   readOp( schan, args.hver, args.msw, req, args.address, args.count )

def readMem( schan, args ):
   req = SchanMsgLib.readMemory( args.hver, args.acc_type, args.dst_blk,
                                 args.address, args.num_words * 4 )
   readOp( schan, args.hver, args.msw, req, args.address, args.count )


#
# Write commands
#

def writeOp( schan, hver, req, address, count, words ):
   for offset in range( count ):
      req.writecmd.address = address + offset
      schan.op( req, 2 + len( words ), 1 )

def writeReg( schan, args, words ):
   req = SchanMsgLib.writeRegister( args.hver, args.acc_type, args.dst_blk,
                                    args.address, words )
   writeOp( schan, args.hver, req, args.address, args.count, words )

def writeMem( schan, args, words ):
   req = SchanMsgLib.writeMemory( args.hver, args.acc_type, args.dst_blk,
                                  args.address, words )
   writeOp( schan, args.hver, req, args.address, args.count, words )


#
# Table commands
#

def printTableResponse( ack, hver, msw ):
   # pylint: disable-next=consider-using-f-string
   print( "response %u:" % ack.genresp_v2.response.type, end=' ' )
   ack_data_byte_len = schan_accel.schan_header_data_byte_len( ack.header, hver )
   nw = ( ack_data_byte_len - 4 ) // 4
   printData( ack.genresp_v2.data[ : nw ], msw )

def tableOp( schan, op, hver, acc_type, dst_blk, msw, address, count, words ):
   req = op( hver, acc_type, dst_blk, address, words )

   for offset in range( count ):
      req.gencmd.address = address + offset
      nw = len( words ) + 2
      ack = schan.op( req, nw, nw )
      printTableResponse( ack, hver, msw )

def tableInsert( schan, args, words ):
   tableOp( schan, SchanMsgLib.tableInsert, args.hver, args.acc_type, args.dst_blk,
            args.msw, args.address, args.count, words )

def tableDelete( schan, args, words ):
   tableOp( schan, SchanMsgLib.tableDelete, args.hver, args.acc_type, args.dst_blk,
            args.msw, args.address, args.count, words )

def tableLookup( schan, args, words ):
   tableOp( schan, SchanMsgLib.tableLookup, args.hver, args.acc_type, args.dst_blk,
            args.msw, args.address, args.count, words )


#
# Argument parsing
#

description = """
Provides raw Broadcom S-Channel access.

To view arguments for each subcommand, use:
   bash# schan <pci-device-id> <command> --help
e.g.
   bash# schan 01:00.0 readreg --help

Note: Argument defaults for bar, hver, cmc, and cmcgen are chosen
dynamically based on the chipType. Pass these explicitly to opt out.
"""

examples = """
examples:
   - Read the ENHANCED_HASHING_CONTROL_2 register on Calpella (Trident3),
     overriding the default arguments for bar, hver, and cmcgen:
   bash# schan --bar 2 --hver 4 01:00.0 --cmcgen=x readreg 9 1 0x82001300

   - Use default arguments to read RQP_PRP_DEBUG_COUNTERSr on Lyonsville (Jericho2):
   bash# schan 01:00.0 readreg 21 1 0x0138
"""


def parseArgs():
   def integer( value ):
      return int( value, 0 )

   # Top-level parser
   parser = argparse.ArgumentParser( description=description, epilog=examples,
                  formatter_class=argparse.RawDescriptionHelpFormatter )

   parser.add_argument( "--bar", type=int,
                        help="Device CMIC BAR number (0-2) (default: auto)" )
   parser.add_argument( "--cmc", type=int,
                        help="CMICM CMC# (0-2) or CMICX POOL# (0-4) (default: auto)"
                      )
   parser.add_argument( "--hver", type=int, choices=( 3, 4, 6, 7 ),
                        help="S-Channel message header version (default: auto)" )
   parser.add_argument( "--cmcgen", choices=( "m", "x", "r" ),
                        help="CMIC Generation (default: auto)" )

   parser.add_argument( "pcidev", metavar="pci-device-id",
               help="PCI device ID of the chip or 'amba' keyword for AXI bus" )

   # Common arguments for all subcommands
   cmdParser = argparse.ArgumentParser( add_help=False )
   cmdParser.add_argument( "-n", type=int, dest="count",
      help="Number of entries to read or write (default: %(default)s)",
      default=1 )
   cmdParser.add_argument( "--msw", action="store_true",
      help="Use most-significant-word-first data format (default: %(default)s)",
      default=False )
   cmdParser.add_argument( "acc_type", type=integer, help="Access type" )
   cmdParser.add_argument( "dst_blk", type=integer, help="Destination block ID" )
   cmdParser.add_argument( "address", type=integer, help="Address to access" )

   readCmdParser = argparse.ArgumentParser( add_help=False, parents=[ cmdParser ] )
   readCmdParser.add_argument( "--nw", type=int, dest="num_words",
                               help="Force a number of words to read", default=0 )

   # Common arguments for write and table subcommands
   wordsCmdParser = argparse.ArgumentParser( add_help=False, parents=[ cmdParser ] )
   wordsCmdParser.add_argument( "words", metavar="word", type=integer, nargs='+',
                                help="Words of data to use" )

   # Add sub-parsers for each command
   subParsers = parser.add_subparsers( help='S-Channel command to execute',
                                       dest='command' )

   subParsers.add_parser( "readreg", parents=[ readCmdParser ] )
   subParsers.add_parser( "readmem", parents=[ readCmdParser ] )
   subParsers.add_parser( "writereg", parents=[ wordsCmdParser ] )
   subParsers.add_parser( "writemem", parents=[ wordsCmdParser ] )
   subParsers.add_parser( "tableinsert", parents=[ wordsCmdParser ] )
   subParsers.add_parser( "tabledelete", parents=[ wordsCmdParser ] )
   subParsers.add_parser( "tablelookup", parents=[ wordsCmdParser ] )

   return parser.parse_args()


def main():
   cliArgs = parseArgs()
   args = maybeApplyDefaultArgsForChip( cliArgs )

   schan = args.engine( args.pcidev, args.bar, args.cmc, args.hver, args.cmcgen,
                        args.sbuswords )

   # reverse words when in most-significant-word-first mode
   if 'words' in args:
      words = list( reversed( args.words ) if args.msw else args.words )

   if args.command == 'readreg':
      readReg( schan, args )
   elif args.command == 'readmem':
      readMem( schan, args )
   elif args.command == 'writereg':
      writeReg( schan, args, words )
   elif args.command == 'writemem':
      writeMem( schan, args, words )
   elif args.command == 'tableinsert':
      tableInsert( schan, args, words )
   elif args.command == 'tabledelete':
      tableDelete( schan, args, words )
   elif args.command == 'tablelookup':
      tableLookup( schan, args, words )


if __name__ == "__main__":
   main()
