/* xgate-dis.c -- Freescale XGATE disassembly
   Copyright (C) 2009-2014 Free Software Foundation, Inc.
   Written by Sean Keys (skeys@ipdatasys.com)

   This file is part of the GNU opcodes library.

   This library is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   It is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
   License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "sysdep.h"
#include <assert.h>
#include "dis-asm.h"
#include "opintl.h"
#include "libiberty.h"
#include "ansidecl.h"
#include "opcode/xgate.h"

#define XGATE_TWO_BYTES      0x02
#define XGATE_NINE_BITS      0x1FF
#define XGATE_TEN_BITS       0x3FF
#define XGATE_NINE_SIGNBIT   0x100
#define XGATE_TEN_SIGNBIT    0x200

/* Structures.  */
struct decodeInfo
{
  unsigned int operMask;
  unsigned int operMasksRegisterBits;
  struct xgate_opcode *opcodePTR;
};

/* Prototypes for local functions.  */
static int print_insn (bfd_vma, struct disassemble_info *);
static int read_memory (bfd_vma, bfd_byte*, int, struct disassemble_info *);
static int ripBits (unsigned int *, int,
		    struct xgate_opcode *, unsigned int);
static int macro_search (char *, char *);
static struct decodeInfo * find_match (unsigned int);

/* Statics.  */
static struct decodeInfo *decodeTable;
static int initialized;
static char previousOpName[10];
static unsigned int perviousBin;

/* Disassemble one instruction at address 'memaddr'.  Returns the number
   of bytes used by that instruction.  */

static int
print_insn (bfd_vma memaddr, struct disassemble_info* info)
{
  int status;
  unsigned int raw_code;
  char *s = 0;
  long bytesRead = 0;
  int i = 0;
  struct xgate_opcode *opcodePTR = (struct xgate_opcode*) xgate_opcodes;
  struct decodeInfo *decodeTablePTR = 0;
  struct decodeInfo *decodePTR = 0;
  unsigned int operandRegisterBits = 0;
  signed int relAddr = 0;
  signed int operandOne = 0;
  signed int operandTwo = 0;
  bfd_byte buffer[4];
  bfd_vma absAddress;

  unsigned int operMaskReg = 0;
  /* Initialize our array of opcode masks and check them against our constant
     table.  */
  if (!initialized)
    {
      decodeTable = xmalloc (sizeof (struct decodeInfo) * xgate_num_opcodes);
      for (i = 0, decodeTablePTR = decodeTable; i < xgate_num_opcodes;
          i++, decodeTablePTR++, opcodePTR++)
        {
          unsigned int bin = 0;
          unsigned int mask = 0;
          for (s = opcodePTR->format; *s; s++)
            {
              bin <<= 1;
              mask <<= 1;
              operandRegisterBits <<= 1;
              bin |= (*s == '1');
              mask |= (*s == '0' || *s == '1');
              operandRegisterBits |= (*s == 'r');
            }
          /* Asserting will uncover inconsistencies in our table.  */
          assert ((s - opcodePTR->format) == 16 || (s - opcodePTR->format) == 32);
          assert (opcodePTR->bin_opcode == bin);

          decodeTablePTR->operMask = mask;
          decodeTablePTR->operMasksRegisterBits = operandRegisterBits;
          decodeTablePTR->opcodePTR = opcodePTR;
        }
      initialized = 1;
    }

  /* Read 16 bits.  */
  bytesRead += XGATE_TWO_BYTES;
  status = read_memory (memaddr, buffer, XGATE_TWO_BYTES, info);
  if (status == 0)
    {
      raw_code = buffer[0];
      raw_code <<= 8;
      raw_code += buffer[1];

      decodePTR = find_match (raw_code);
      if (decodePTR)
        {
          operMaskReg = decodePTR->operMasksRegisterBits;
          (*info->fprintf_func)(info->stream, "%s", decodePTR->opcodePTR->name);

          /* First we compare the shorthand format of the constraints. If we
	      still are unable to pinpoint the operands
	      we analyze the opcodes constraint string.  */
          if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_MON_R_C))
        	{
        	  (*info->fprintf_func)(info->stream, " R%x, CCR",
        		  (raw_code >> 8) & 0x7);
        	}
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_MON_C_R))
            {
        	  (*info->fprintf_func)(info->stream, " CCR, R%x",
        	      (raw_code >> 8) & 0x7);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_MON_R_P))
            {
        	  (*info->fprintf_func)(info->stream, " R%x, PC",
        	      (raw_code >> 8) & 0x7);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_TRI))
            {
                  (*info->fprintf_func)(info->stream, " R%x, R%x, R%x",
                      (raw_code >> 8) & 0x7, (raw_code >> 5) & 0x7,
                      (raw_code >> 2) & 0x7);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_IDR))
            {
                  if (raw_code & 0x01)
                    {
                      (*info->fprintf_func)(info->stream, " R%x, (R%x, R%x+)",
                          (raw_code >> 8) & 0x7, (raw_code >> 5) & 0x7,
                          (raw_code >> 2) & 0x7);
                    }
                   else if (raw_code & 0x02)
                          {
                            (*info->fprintf_func)(info->stream, " R%x, (R%x, -R%x)",
                                (raw_code >> 8) & 0x7, (raw_code >> 5) & 0x7,
                                (raw_code >> 2) & 0x7);
                          }
                   else
                     {
                       (*info->fprintf_func)(info->stream, " R%x, (R%x, R%x)",
                           (raw_code >> 8) & 0x7, (raw_code >> 5) & 0x7,
                           (raw_code >> 2) & 0x7);
                     }
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_DYA))
            {
        	  operandOne = ripBits (&operMaskReg, 3, opcodePTR, raw_code);
        	  operandTwo = ripBits (&operMaskReg, 3, opcodePTR, raw_code);
        	 ( *info->fprintf_func)(info->stream, " R%x, R%x", operandOne,
        	      operandTwo);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_IDO5))
            {
        	  (*info->fprintf_func)(info->stream, " R%x, (R%x, #0x%x)",
        	      (raw_code >> 8) & 0x7, (raw_code >> 5) & 0x7, raw_code & 0x1f);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_MON))
            {
        	  operandOne = ripBits (&operMaskReg, 3, decodePTR->opcodePTR,
        	     raw_code);
        	 (*info->fprintf_func)(info->stream, " R%x", operandOne);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_REL9))
            {
              /* If address is negative handle it accordingly.  */
              if (raw_code & XGATE_NINE_SIGNBIT)
                {
                  relAddr = XGATE_NINE_BITS >> 1; /* Clip sign bit.  */
                  relAddr = ~relAddr; /* Make signed.  */
                  relAddr |= (raw_code & 0xFF) + 1; /* Apply our value.  */
                  relAddr <<= 1; /* Multiply by two as per processor docs.  */
                }
              else
                {
                  relAddr = raw_code & 0xff;
                  relAddr = (relAddr << 1) + 2;
                }
             (*info->fprintf_func)(info->stream, " *%d", relAddr);
             (*info->fprintf_func)(info->stream, "  Abs* 0x");
             (*info->print_address_func)(memaddr + relAddr, info);
           }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_REL10))
            {
              /* If address is negative handle it accordingly.  */
              if (raw_code & XGATE_TEN_SIGNBIT)
                {
                  relAddr = XGATE_TEN_BITS >> 1; /* Clip sign bit.  */
                  relAddr = ~relAddr; /* Make signed.  */
                  relAddr |= (raw_code & 0x1FF) + 1; /* Apply our value.  */
                  relAddr <<= 1; /* Multiply by two as per processor docs.  */
                }
              else
                {
                  relAddr = raw_code & 0x1FF;
                  relAddr = (relAddr << 1) + 2;
                }
              (*info->fprintf_func)(info->stream, " *%d", relAddr);
              (*info->fprintf_func)(info->stream, "  Abs* 0x");
              (*info->print_address_func)(memaddr + relAddr, info);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_IMM4))
            {
              (*info->fprintf_func)(info->stream, " R%x, #0x%02x",
              (raw_code >> 8) & 0x7, (raw_code >> 4) & 0xF);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_IMM8))
            {
              if (macro_search (decodePTR->opcodePTR->name, previousOpName) &&
                 previousOpName[0])
               {
                 absAddress = (0xFF & raw_code) << 8;
                 absAddress |= perviousBin & 0xFF;
                 (*info->fprintf_func)(info->stream, " R%x, #0x%02x Abs* 0x",
                     (raw_code >> 8) & 0x7, raw_code & 0xff);
                 (*info->print_address_func)(absAddress, info);
                 previousOpName[0] = 0;
               }
              else
               {
                 strcpy (previousOpName, decodePTR->opcodePTR->name);
                 (*info->fprintf_func)(info->stream, " R%x, #0x%02x",
                     (raw_code >> 8) & 0x7, raw_code & 0xff);
               }
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_IMM3))
            {
        	  (*info->fprintf_func)(info->stream, " #0x%x",
        	     (raw_code >> 8) & 0x7);
            }
          else if (!strcmp (decodePTR->opcodePTR->constraints, XGATE_OP_INH))
            {
        	  //
            }
          else
            {
              (*info->fprintf_func)(info->stream, " unhandled mode %s",
                opcodePTR->constraints);
            }
          perviousBin = raw_code;
        }
      else
        {
          (*info->fprintf_func)(info->stream,
				" unable to find opcode match #0%x", raw_code);
        }
    }
  return bytesRead;
}

int
print_insn_xgate (bfd_vma memaddr, struct disassemble_info* info)
{
  return print_insn (memaddr, info);
}

static int
read_memory (bfd_vma memaddr, bfd_byte* buffer, int size,
    struct disassemble_info* info)
{
  int status;
  status = (*info->read_memory_func) (memaddr, buffer, size, info);
  if (status != 0)
    {
      (*info->memory_error_func) (status, memaddr, info);
      return -1;
    }
  return 0;
}

static int
ripBits (unsigned int *operandBitsRemaining,
	 int numBitsRequested,
	 struct xgate_opcode *opcodePTR,
	 unsigned int memory)
{
  unsigned int currentBit;
  int operand;
  int numBitsFound;

  for (operand = 0, numBitsFound = 0, currentBit = 1
	 << ((opcodePTR->size * 8) - 1);
       (numBitsFound < numBitsRequested) && currentBit; currentBit >>= 1)
    {
      if (currentBit & *operandBitsRemaining)
	{
	  *operandBitsRemaining &= ~(currentBit); /* Consume the current bit.  */
	  operand <<= 1; /* Make room for our next bit.  */
	  numBitsFound++;
	  operand |= (currentBit & memory) > 0;
	}
    }
  return operand;
}

static int
macro_search (char *currentName, char *lastName)
{
  int i;
  int length = 0;
  char *where;

  for (i = 0; i < xgate_num_opcodes; i++)
    {
      where = strstr (xgate_opcodes[i].constraints, lastName);

      if (where)
        {
          length = strlen (where);
        }
      if (length)
        {
          where = strstr (xgate_opcodes[i].constraints, currentName);
          if (where)
            {
              length = strlen (where);
              return 1;
            }
        }
    }
  return 0;
}

static struct decodeInfo *
find_match (unsigned int raw_code)
{
  struct decodeInfo *decodeTablePTR = 0;
  int i;

  for (i = 0, decodeTablePTR = decodeTable; i < xgate_num_opcodes;
      i++, decodeTablePTR++)
    {
      if ((raw_code & decodeTablePTR->operMask)
          == decodeTablePTR->opcodePTR->bin_opcode)
        {
          /* Make sure we didn't run into a macro or alias.  */
          if (decodeTablePTR->opcodePTR->cycles_min != 0)
            {
              return decodeTablePTR;
              break;
            }
          else
	    continue;
        }
    }
  return 0;
}