#!/usr/bin/env python
#
# 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 Pci
import Schan
import schan_accel
import SchanMsgLib

defaultArgTypes = ( 'bar', 'hver', 'cmcgen', 'cmc', )
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
   '0xb34' : 'CMICm2', # Helix4
   '0xb85' : 'CMICm1', # Trident
   '0xb86' : 'CMICm1', # Trident2Plus
   '0xb75' : 'CMICm1', # Titan2
   '0xb87' : 'CMICx', # Trident3
   '0xb37' : 'CMICx', # T3X3
   '0xb77' : 'CMICx', # T3X5
   '0xb96' : 'CMICm3', # Tomahawk/TomahawkPlus/Titanhawk
   '0xb97' : 'CMICm3', # Tomahawk2
   '0xb98' : 'CMICx', # Tomahawk3
}

cmicGenerationToArgumentsMap = {
   'CMICm1' : ArgumentDefaults( bar=0, hver=3, cmcgen='m', cmc=1 ),
   'CMICm2' : ArgumentDefaults( bar=2, hver=3, cmcgen='m', cmc=1 ),
   'CMICm3' : ArgumentDefaults( bar=2, hver=4, cmcgen='m', cmc=1 ),
   'CMICx' : ArgumentDefaults( bar=2, hver=4, cmcgen='x', cmc=2 ),
}

def maybeApplyDefaultArgsForChip( parsedArgs ):
   '''Choose default values for hver, bar, cmc, and cmcgen based on the chip
      type when possible. Use the CMICm1 defaults when no values are found for
      the chip type.'''
   bcmId = chipUnit( parsedArgs.pcidev )
   cmicGeneration = deviceToCmicGenerationMap.get( bcmId )
   if cmicGeneration is not None:
      defaults = cmicGenerationToArgumentsMap[ cmicGeneration ]
   else:
      defaults = cmicGenerationToArgumentsMap[ 'CMICm1' ]

   for arg in defaultArgTypes:
      if getattr( parsedArgs, arg ) 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( '{:#08x}'.format( value ) for value in data )

#
# Read commands
#
CMIC_SCHAN_WORDS=22

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 xrange( count ):
      req.readcmd.address = address + offset
      ack = schan.op( req, 2, CMIC_SCHAN_WORDS )
      printReadResponse( ack, hver, msw )

def readReg( schan, args ):
   req = SchanMsgLib.readRegister( args.hver, args.acc_type, args.dst_blk,
                                   args.address )
   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 )
   readOp( schan, args.hver, args.msw, req, args.address, args.count )


#
# Write commands
#

def writeOp( schan, hver, req, address, count, words ):
   for offset in xrange( 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 ):
   print "response %u:" % ack.genresp_v2.response.type,
   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 xrange( 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 ),
                        help="S-Channel message header version (default: auto)" )
   parser.add_argument( "--cmcgen", choices=( "m", "x" ),
                        help="CMIC Generation (default: auto)" )

   parser.add_argument( "pcidev", metavar="pci-device-id",
                        help="PCI device ID of the chip" )

   # 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" )

   # 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=[ cmdParser ] )
   subParsers.add_parser( "readmem", parents=[ cmdParser ] )
   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 = Schan.Schan( args.pcidev, args.bar, args.cmc, args.hver, args.cmcgen )

   # 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()
