/* Disassembly routines for TMS320C54X architecture
  Copyright (C) 1999-2024 Free Software Foundation, Inc.
  Contributed by Timothy Wall ([email protected])

  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 file; see the file COPYING.  If not, write to the
  Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
  MA 02110-1301, USA.  */

#include "sysdep.h"
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include "disassemble.h"
#include "opcode/tic54x.h"
#include "coff/tic54x.h"

static int has_lkaddr (unsigned short, const insn_template *);
static int get_insn_size (unsigned short, const insn_template *);
static int print_instruction (disassemble_info *, bfd_vma,
                             unsigned short, const char *,
                             const enum optype [], int, int);
static int print_parallel_instruction (disassemble_info *, bfd_vma,
                                      unsigned short,
                                      const insn_template *, int);
static int sprint_dual_address (disassemble_info *,char [],
                               unsigned short);
static int sprint_indirect_address (disassemble_info *,char [],
                                   unsigned short);
static int sprint_direct_address (disassemble_info *,char [],
                                 unsigned short);
static int sprint_mmr (disassemble_info *,char [],int);
static int sprint_condition (disassemble_info *,char *,unsigned short);
static int sprint_cc2 (disassemble_info *,char *,unsigned short);

int
print_insn_tic54x (bfd_vma memaddr, disassemble_info *info)
{
 bfd_byte opbuf[2];
 unsigned short opcode;
 int status, size;
 const insn_template* tm;

 status = (*info->read_memory_func) (memaddr, opbuf, 2, info);
 if (status != 0)
 {
   (*info->memory_error_func) (status, memaddr, info);
   return -1;
 }

 opcode = bfd_getl16 (opbuf);
 tm = tic54x_get_insn (info, memaddr, opcode, &size);

 info->bytes_per_line = 2;
 info->bytes_per_chunk = 2;
 info->octets_per_byte = 2;
 info->display_endian = BFD_ENDIAN_LITTLE;

 if (tm->flags & FL_PAR)
 {
   if (!print_parallel_instruction (info, memaddr, opcode, tm, size))
     return -1;
 }
 else
 {
   if (!print_instruction (info, memaddr, opcode,
                           (char *) tm->name,
                           tm->operand_types,
                           size, (tm->flags & FL_EXT)))
     return -1;
 }

 return size * 2;
}

static int
has_lkaddr (unsigned short memdata, const insn_template *tm)
{
 return (IS_LKADDR (memdata)
         && (OPTYPE (tm->operand_types[0]) == OP_Smem
             || OPTYPE (tm->operand_types[1]) == OP_Smem
             || OPTYPE (tm->operand_types[2]) == OP_Smem
             || OPTYPE (tm->operand_types[1]) == OP_Sind
             || OPTYPE (tm->operand_types[0]) == OP_Lmem
             || OPTYPE (tm->operand_types[1]) == OP_Lmem));
}

/* always returns 1 (whether an insn template was found) since we provide an
  "unknown instruction" template */
const insn_template*
tic54x_get_insn (disassemble_info *info, bfd_vma addr,
                unsigned short memdata, int *size)
{
 const insn_template *tm = NULL;

 for (tm = tic54x_optab; tm->name; tm++)
 {
   if (tm->opcode == (memdata & tm->mask))
   {
     /* a few opcodes span two words */
     if (tm->flags & FL_EXT)
       {
         /* if lk addressing is used, the second half of the opcode gets
            pushed one word later */
         bfd_byte opbuf[2];
         bfd_vma addr2 = addr + 1 + has_lkaddr (memdata, tm);
         int status = (*info->read_memory_func) (addr2, opbuf, 2, info);
         /* FIXME handle errors.  */
         if (status == 0)
           {
             unsigned short data2 = bfd_getl16 (opbuf);
             if (tm->opcode2 == (data2 & tm->mask2))
               {
                 if (size) *size = get_insn_size (memdata, tm);
                 return tm;
               }
           }
       }
     else
       {
         if (size) *size = get_insn_size (memdata, tm);
         return tm;
       }
   }
 }
 for (tm = (insn_template *) tic54x_paroptab; tm->name; tm++)
 {
   if (tm->opcode == (memdata & tm->mask))
   {
     if (size) *size = get_insn_size (memdata, tm);
     return tm;
   }
 }

 if (size) *size = 1;
 return &tic54x_unknown_opcode;
}

static int
get_insn_size (unsigned short memdata, const insn_template *insn)
{
 int size;

 if (insn->flags & FL_PAR)
   {
     /* only non-parallel instructions support lk addressing */
     size = insn->words;
   }
 else
   {
     size = insn->words + has_lkaddr (memdata, insn);
   }

 return size;
}

int
print_instruction (disassemble_info *info,
                  bfd_vma memaddr,
                  unsigned short opcode,
                  const char *tm_name,
                  const enum optype tm_operands[],
                  int size,
                  int ext)
{
 static int n;
 /* string storage for multiple operands */
 char operand[4][64] = { {0},{0},{0},{0}, };
 bfd_byte buf[2];
 unsigned long opcode2 = 0;
 unsigned long lkaddr = 0;
 enum optype src = OP_None;
 enum optype dst = OP_None;
 int i, shift;
 char *comma = "";

 info->fprintf_func (info->stream, "%-7s", tm_name);

 if (size > 1)
   {
     int status = (*info->read_memory_func) (memaddr + 1, buf, 2, info);
     if (status != 0)
       return 0;
     lkaddr = opcode2 = bfd_getl16 (buf);
     if (size > 2)
       {
         status = (*info->read_memory_func) (memaddr + 2, buf, 2, info);
         if (status != 0)
           return 0;
         opcode2 = bfd_getl16 (buf);
       }
   }

 for (i = 0; i < MAX_OPERANDS && OPTYPE (tm_operands[i]) != OP_None; i++)
   {
     char *next_comma = ",";
     int optional = (tm_operands[i] & OPT) != 0;

     switch (OPTYPE (tm_operands[i]))
       {
       case OP_Xmem:
         sprint_dual_address (info, operand[i], XMEM (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_Ymem:
         sprint_dual_address (info, operand[i], YMEM (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_Smem:
       case OP_Sind:
       case OP_Lmem:
         info->fprintf_func (info->stream, "%s", comma);
         if (INDIRECT (opcode))
           {
             if (MOD (opcode) >= 12)
               {
                 bfd_vma addr = lkaddr;
                 int arf = ARF (opcode);
                 int mod = MOD (opcode);
                 if (mod == 15)
                     info->fprintf_func (info->stream, "*(");
                 else
                     info->fprintf_func (info->stream, "*%sar%d(",
                                         (mod == 13 || mod == 14 ? "+" : ""),
                                         arf);
                 (*(info->print_address_func)) ((bfd_vma) addr, info);
                 info->fprintf_func (info->stream, ")%s",
                                     mod == 14 ? "%" : "");
               }
             else
               {
                 sprint_indirect_address (info, operand[i], opcode);
                 info->fprintf_func (info->stream, "%s", operand[i]);
               }
           }
         else
         {
           /* FIXME -- use labels (print_address_func) */
           /* in order to do this, we need to guess what DP is */
           sprint_direct_address (info, operand[i], opcode);
           info->fprintf_func (info->stream, "%s", operand[i]);
         }
         break;
       case OP_dmad:
         info->fprintf_func (info->stream, "%s", comma);
         (*(info->print_address_func)) ((bfd_vma) opcode2, info);
         break;
       case OP_xpmad:
         /* upper 7 bits of address are in the opcode */
         opcode2 += ((unsigned long) opcode & 0x7F) << 16;
         /* fall through */
       case OP_pmad:
         info->fprintf_func (info->stream, "%s", comma);
         (*(info->print_address_func)) ((bfd_vma) opcode2, info);
         break;
       case OP_MMRX:
         sprint_mmr (info, operand[i], MMRX (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_MMRY:
         sprint_mmr (info, operand[i], MMRY (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_MMR:
         sprint_mmr (info, operand[i], MMR (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_PA:
         sprintf (operand[i], "pa%d", (unsigned) opcode2);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_SRC:
         src = SRC (ext ? opcode2 : opcode) ? OP_B : OP_A;
         sprintf (operand[i], (src == OP_B) ? "b" : "a");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_SRC1:
         src = SRC1 (ext ? opcode2 : opcode) ? OP_B : OP_A;
         sprintf (operand[i], (src == OP_B) ? "b" : "a");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_RND:
         dst = DST (opcode) ? OP_B : OP_A;
         sprintf (operand[i], (dst == OP_B) ? "a" : "b");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_DST:
         dst = DST (ext ? opcode2 : opcode) ? OP_B : OP_A;
         if (!optional || dst != src)
           {
             sprintf (operand[i], (dst == OP_B) ? "b" : "a");
             info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
           }
         else
           next_comma = comma;
         break;
       case OP_B:
         sprintf (operand[i], "b");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_A:
         sprintf (operand[i], "a");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_ARX:
         sprintf (operand[i], "ar%d", (int) ARX (opcode));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_SHIFT:
         shift = SHIFT (ext ? opcode2 : opcode);
         if (!optional || shift != 0)
           {
             sprintf (operand[i], "%d", shift);
             info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
           }
         else
           next_comma = comma;
         break;
       case OP_SHFT:
         shift = SHFT (opcode);
         if (!optional || shift != 0)
           {
             sprintf (operand[i], "%d", (unsigned) shift);
             info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
           }
         else
           next_comma = comma;
         break;
       case OP_lk:
         sprintf (operand[i], "#%d", (int) (short) opcode2);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_T:
         sprintf (operand[i], "t");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_TS:
         sprintf (operand[i], "ts");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_k8:
         sprintf (operand[i], "%d", (int) ((signed char) (opcode & 0xFF)));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_16:
         sprintf (operand[i], "16");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_ASM:
         sprintf (operand[i], "asm");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_BITC:
         sprintf (operand[i], "%d", (int) (opcode & 0xF));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_CC:
         /* put all CC operands in the same operand */
         sprint_condition (info, operand[i], opcode);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         i = MAX_OPERANDS;
         break;
       case OP_CC2:
         sprint_cc2 (info, operand[i], opcode);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_CC3:
       {
         const char *code[] = { "eq", "lt", "gt", "neq" };

         /* Do not use sprintf with only two parameters as a
            compiler warning could be generated in such conditions.  */
         sprintf (operand[i], "%s", code[CC3 (opcode)]);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       }
       case OP_123:
         {
           int code = (opcode >> 8) & 0x3;
           sprintf (operand[i], "%d", (code == 0) ? 1 : (code == 2) ? 2 : 3);
           info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
           break;
         }
       case OP_k5:
         sprintf (operand[i], "#%d", ((opcode & 0x1F) ^ 0x10) - 0x10);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_k8u:
         sprintf (operand[i], "#%d", (unsigned) (opcode & 0xFF));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_k3:
         sprintf (operand[i], "#%d", (int) (opcode & 0x7));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_lku:
         sprintf (operand[i], "#%d", (unsigned) opcode2);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_N:
         n = (opcode >> 9) & 0x1;
         sprintf (operand[i], "st%d", n);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_SBIT:
       {
         const char *status0[] = {
           "0", "1", "2", "3", "4", "5", "6", "7", "8",
           "ovb", "ova", "c", "tc", "13", "14", "15"
         };
         const char *status1[] = {
           "0", "1", "2", "3", "4",
           "cmpt", "frct", "c16", "sxm", "ovm", "10",
           "intm", "hm", "xf", "cpl", "braf"
         };
         sprintf (operand[i], "%s",
                  n ? status1[SBIT (opcode)] : status0[SBIT (opcode)]);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       }
       case OP_12:
         sprintf (operand[i], "%d", (int) ((opcode >> 9) & 1) + 1);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_TRN:
         sprintf (operand[i], "trn");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_DP:
         sprintf (operand[i], "dp");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_k9:
         /* FIXME-- this is DP, print the original address? */
         sprintf (operand[i], "#%d", (int) (opcode & 0x1FF));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_ARP:
         sprintf (operand[i], "arp");
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       case OP_031:
         sprintf (operand[i], "%d", (int) (opcode & 0x1F));
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       default:
         sprintf (operand[i], "??? (0x%x)", tm_operands[i]);
         info->fprintf_func (info->stream, "%s%s", comma, operand[i]);
         break;
       }
     comma = next_comma;
   }
 return 1;
}

static int
print_parallel_instruction (disassemble_info *info,
                           bfd_vma memaddr,
                           unsigned short opcode,
                           const insn_template *ptm,
                           int size)
{
 print_instruction (info, memaddr, opcode,
                    ptm->name, ptm->operand_types, size, 0);
 info->fprintf_func (info->stream, " || ");
 return print_instruction (info, memaddr, opcode,
                           ptm->parname, ptm->paroperand_types, size, 0);
}

static int
sprint_dual_address (disassemble_info *info ATTRIBUTE_UNUSED,
                    char buf[],
                    unsigned short code)
{
 const char *formats[] = {
   "*ar%d",
   "*ar%d-",
   "*ar%d+",
   "*ar%d+0%%",
 };
 return sprintf (buf, formats[XMOD (code)], XARX (code));
}

static int
sprint_indirect_address (disassemble_info *info ATTRIBUTE_UNUSED,
                        char buf[],
                        unsigned short opcode)
{
 const char *formats[] = {
   "*ar%d",
   "*ar%d-",
   "*ar%d+",
   "*+ar%d",
   "*ar%d-0B",
   "*ar%d-0",
   "*ar%d+0",
   "*ar%d+0B",
   "*ar%d-%%",
   "*ar%d-0%%",
   "*ar%d+%%",
   "*ar%d+0%%",
 };
 return sprintf (buf, formats[MOD (opcode)], ARF (opcode));
}

static int
sprint_direct_address (disassemble_info *info ATTRIBUTE_UNUSED,
                      char buf[],
                      unsigned short opcode)
{
 /* FIXME -- look up relocation if available */
 return sprintf (buf, "DP+0x%02x", (int) (opcode & 0x7F));
}

static int
sprint_mmr (disassemble_info *info ATTRIBUTE_UNUSED,
           char buf[],
           int mmr)
{
 const tic54x_symbol *reg = tic54x_mmregs;
 while (reg->name != NULL)
   {
     if (mmr == reg->value)
       {
         sprintf (buf, "%s", (reg + 1)->name);
         return 1;
       }
     ++reg;
   }
 sprintf (buf, "MMR(%d)", mmr); /* FIXME -- different targets.  */
 return 0;
}

static int
sprint_cc2 (disassemble_info *info ATTRIBUTE_UNUSED,
           char *buf,
           unsigned short opcode)
{
 const char *cc2[] = {
   "??", "??", "ageq", "alt", "aneq", "aeq", "agt", "aleq",
   "??", "??", "bgeq", "blt", "bneq", "beq", "bgt", "bleq",
 };
 return sprintf (buf, "%s", cc2[opcode & 0xF]);
}

static int
sprint_condition (disassemble_info *info ATTRIBUTE_UNUSED,
                 char *buf,
                 unsigned short opcode)
{
 char *start = buf;
 const char *cmp[] = {
     "??", "??", "geq", "lt", "neq", "eq", "gt", "leq"
 };
 if (opcode & 0x40)
   {
     char acc = (opcode & 0x8) ? 'b' : 'a';
     if (opcode & 0x7)
         buf += sprintf (buf, "%c%s%s", acc, cmp[(opcode & 0x7)],
                         (opcode & 0x20) ? ", " : "");
     if (opcode & 0x20)
         buf += sprintf (buf, "%c%s", acc, (opcode & 0x10) ? "ov" : "nov");
   }
 else if (opcode & 0x3F)
   {
     if (opcode & 0x30)
       buf += sprintf (buf, "%s%s",
                       ((opcode & 0x30) == 0x30) ? "tc" : "ntc",
                       (opcode & 0x0F) ? ", " : "");
     if (opcode & 0x0C)
       buf += sprintf (buf, "%s%s",
                       ((opcode & 0x0C) == 0x0C) ? "c" : "nc",
                       (opcode & 0x03) ? ", " : "");
     if (opcode & 0x03)
       buf += sprintf (buf, "%s",
                       ((opcode & 0x03) == 0x03) ? "bio" : "nbio");
   }
 else
   buf += sprintf (buf, "unc");

 return buf - start;
}