
/*
 *      National Semiconductor Corporation CR16 processor module for IDA.
 *      Copyright (c) 2002-2006 Konstantin Norvatoff, <konnor@bk.ru>
 *      Freeware.
 */

#include "cr16.hpp"

//----------------------------------------------------------------------
static uchar Rproc(uchar code)
{
  switch ( code )
  {
    case 0x1:
      return rPSR;
    case 0x3:
      return rINTBASE;
    case 0x4:
      return rINTBASEH;
    case 0x5:
      return rCFG;
    case 0x7:
      return rDSR;
    case 0x9:
      return rDCR;
    case 0xB:
      return rISP;
    case 0xD:
      return rCARL;
    case 0xE:
      return rCARH;
  }
  return 0;
}

//----------------------------------------------------------------------
// immediate operands
static void SetImmData(op_t &op, int32 code, int bits)
{
  // extend sign
  if ( code & (1 << bits) )
    code -= 1 << (bits + 1);
  op.type = o_imm;
  // always in the second byte
  op.offb = 1;
  // data size
  op.dtype = bits > 8 ? (bits > 16 ? dt_dword : dt_word) : dt_byte;
  // value
  op.addr = op.value = code;
}

//----------------------------------------------------------------------
// register operand
static void SetReg(op_t &op, uchar reg_n)
{
  op.type  = o_reg;
  op.reg   = reg_n;
  op.dtype = dt_word;
}


//----------------------------------------------------------------------
// relative jump
static void SetRelative(op_t &op, int32 disp, int bits, const insn_t &insn)
{
  op.type  = o_near;
  op.dtype = dt_word;
  op.offb  = 0;
  // sign extend
  if ( disp & (1 << bits) )
    disp -= 1 << (bits + 1);
  op.addr = insn.ip + disp;
}

//----------------------------------------------------------------------
static ushort GetWord(insn_t &insn)
{
  ushort wrd = insn.get_next_byte();
  wrd |= ((ushort) insn.get_next_byte()) << 8;
  return wrd;
}

//----------------------------------------------------------------------
// store/load operands
static void SetSL(insn_t &insn, op_t &op, ushort code)
{
  op.reg = rR0 + ((code >> 1) & 0x0F);
  op.dtype = (code & 0x2000) ? dt_word : dt_byte;
  if ( code & 1 )
  {
    if ( code & 0x1000 )
    {
      if ( code & 0x800 )
      {
        if ( (code & 0x1F) == 0x1F )
        {
          // absolute addr
          op.type = o_mem;
          op.addr = op.value = GetWord(insn) | (((uint32) code & 0x600) << 11);
        }
        else
        {                       // reg pair
          op.type = o_displ;
          op.addr = op.value = GetWord(insn) | (((uint32) code & 0x600) << 11);
          op.specflag1 |= URR_PAIR;
        }
      }
      else
      {                         // reg base
        op.type = o_displ;
        op.addr = op.value = GetWord(insn) | (((uint32) code & 0x600) << 11);
      }
    }
    else
    {                           // Offset
      op.type = o_displ;
      op.addr = op.value = ((code >> 8) & 0x1E) | 1;
    }
  }
  else
  {
    op.type = o_displ;
    op.addr = op.value = (code >> 8) & 0x1E;
  }
}

//----------------------------------------------------------------------
#define EXTOPS uint16(-2)
static const uint16 Ops[16] =
{
  CR16_addb,  CR16_addub, EXTOPS,    CR16_mulb,
  CR16_ashub, CR16_lshb,  CR16_xorb, CR16_cmpb,
  CR16_andb,  CR16_addcb, CR16_br,   CR16_tbit,
  CR16_movb,  CR16_subcb, CR16_orb,  CR16_subb,
};

static const uint16 ExtOps[16] =
{
  CR16_cbitb, CR16_sbitb, CR16_tbitb, CR16_storb,
};

// extended instructions
// register-relative with no displacement:
// 54 3 2109 8   76     5  4321        d
// 01 i 0010 bs1 ex-op bs0 bit-num/Imm 1
// register-relative with 16-bit displacement:
// 54 3 2109 8   76     5  4321        d
// 00 i 0010 bs1 ex-op bs0 bit-num/Imm 1
// 18-bit absolute memory:
// 54 3 2109 8   76     5  4321        d
// 00 i 0010 bs1 ex-op bs0 bit-num/Imm 0
static void SetExtOp(insn_t &insn, ushort code)
{
  if ( code & 1 )
  {
    // Register-relative
    insn.Op2.reg   = rR0 + ((code >> 5) & 9);
    insn.Op2.type  = o_displ;
    insn.Op2.dtype = (code & 0x2000) ? dt_word : dt_byte;
    if ( (code >> 14) & 1 )
    {
      // no displacement
      insn.Op2.addr = 0;
    }
    else
    {
      insn.Op2.addr = GetWord(insn);
    }
  }
  else
  {
    // 18-bit absolute memory
    insn.Op2.type = o_mem;
    insn.Op2.dtype = (code & 0x2000) ? dt_word : dt_byte;
    int adext = ((code >> 7) & 2) | ((code >> 5) & 1);
    insn.Op2.addr = GetWord(insn) | (adext<<16);
  }
  insn.Op1.type = o_imm;
  insn.Op1.value = (code >> 1) & 0xF;
}

//----------------------------------------------------------------------
// analyzer
int idaapi CR16_ana(insn_t *_insn)
{
  if ( _insn == nullptr )
    return 0;
  insn_t &insn = *_insn;
  if ( insn.ip & 1 )
    return 0;

  // get instruction word
  ushort code = GetWord(insn);

  uchar WordFlg = (code >> 13) & 1;
  uchar OpCode = (code >> 9) & 0x0F;
  uchar Oper1 = (code >> 5) & 0x0F;
  uchar Oper2 = (code >> 1) & 0x0F;


  switch ( (code >> 14) & 3 )
  {
    // register-register op and special OP
    case 0x01:
      if ( code & 1 )
      {
        // 01xxxxxxxxxxxxx1
        insn.itype = Ops[OpCode];
        switch ( insn.itype )
        {
          case 0:
            return 0;
          case EXTOPS:
            {
              int exop = (Oper1 >> 1) & 3;
              insn.itype = ExtOps[exop] + WordFlg;
              SetExtOp(insn, code);
            }
            break;
            // branch's
          case CR16_br:
            if ( WordFlg )
            {
              insn.itype = CR16_jal;
              SetReg(insn.Op1, rR0 + Oper1);
              SetReg(insn.Op2, rR0 + Oper2);
            }
            else
            {
              insn.itype = CR16_jeq + Oper1;
              SetReg(insn.Op1, rR0 + Oper2);
            }
            break;
            // Special tbit
          case CR16_tbit:
            if ( WordFlg == 0 )
              return 0;
            insn.itype--;
            [[fallthrough]];
            // all other cmds
          default:             // fix word operations
            if ( WordFlg )
              insn.itype++;
            // Setup register OP
            SetReg(insn.Op2, rR0 + Oper1);
            // Setup register OP
            SetReg(insn.Op1, rR0 + Oper2);
            break;
        }
      }
      else
      {                         // 01xxxxxxxxxxxxx0
        if ( WordFlg )
        {
          // 011xxxxxxxxxxxx0
          static const uchar SCmd[16] =
          {
            CR16_mulsb, CR16_mulsw, CR16_movd, CR16_movd,
            CR16_movxb, CR16_movzb, CR16_push, CR16_seq,
            CR16_lpr,   CR16_spr,   CR16_beq,  CR16_bal,
            CR16_retx,  CR16_excp,  CR16_di,   CR16_wait
          };
          insn.itype = SCmd[OpCode];
          switch ( insn.itype )
          {
            case 0:
              return 0;

            case CR16_beq:
              {
                // 01 1 1010    cond   d16,d19-d17 0
                insn.itype = CR16_beq + Oper1;
                int disp = GetWord(insn);
                disp |= (Oper2 & 8) << (16-3);
                disp |= (Oper2 & 7) << 17;
                SetRelative(insn.Op1, disp, 20, insn);
              }
              break;

            case CR16_push:
              {
                static const uchar PQ[4] =
                {
                  CR16_push,   CR16_pop,
                  CR16_popret, CR16_popret
                };
                insn.itype = PQ[Oper1 >> 2];
                SetReg(insn.Op2, rR0 + Oper2);
                SetImmData(insn.Op1, (Oper1 & 3) + 1, 4);
                break;
              }

            case CR16_mulsw:
              SetReg(insn.Op2, rR0 + Oper1);
              SetReg(insn.Op1, rR0 + Oper2);
              insn.Op2.specflag1 |= URR_PAIR;
              break;

            case CR16_movd:
              SetReg(insn.Op2, rR0 + Oper2);
              insn.Op2.specflag1 |= URR_PAIR;
              // !!!! ADD HIIIII ?!?!?!?
              SetImmData(insn.Op1, GetWord(insn), 20);
              break;
            case CR16_excp:
              if ( Oper1 != 0x0F )
                return 0;
              SetImmData(insn.Op1, Oper2, 4);
              break;

            case CR16_retx:
              if ( Oper1 != 0x0F )
                return 0;
              if ( Oper2 != 0x0F )
                return 0;
              break;

            case CR16_wait:
              if ( Oper1 == 0x0F )
              {
                if ( Oper2 == 0x0F )
                  break;
                if ( Oper2 == 0x03 )
                {
                  insn.itype = CR16_eiwait;
                  break;
                }
              }
              if ( (code & 0x19E) == 0x84 )
              {
                insn.itype = CR16_storm;
                SetImmData(insn.Op1, (Oper2 & 3) + 1, 8);
                break;
              }
              if ( (code & 0x19E) == 0x04 )
              {
                insn.itype = CR16_loadm;
                SetImmData(insn.Op1, (Oper2 & 3) + 1, 8);
                break;
              }
              if ( (Oper2 & 0x6) == 0 )
              {
                insn.itype = CR16_muluw;
                SetReg(insn.Op2, rR0 + Oper1);
                SetReg(insn.Op1, rR0 + Oper2);
                insn.Op2.specflag1 |= URR_PAIR;
                break;
              }

              return 0;

            case CR16_di:
              if ( Oper2 != 0x0F )
                return 0;
              switch ( Oper1 )
              {
                case 0x0F:
                  insn.itype = CR16_ei;
                  break;
                case 0x0E:
                  break;
                default:
                  return 0;
              }
              break;

            case CR16_seq:
              SetReg(insn.Op1, rR0 + Oper2);
              if ( Oper1 > 0x0D )
                return 0;
              insn.itype = CR16_seq + Oper1;
              break;

            case CR16_lpr:
              SetReg(insn.Op1, rR0 + Oper2);
              Oper1 = Rproc(Oper1);
              if ( Oper1 == 0 )
                return 0;
              SetReg(insn.Op2, Oper1);
              break;

            case CR16_spr:
              SetReg(insn.Op2, rR0 + Oper2);
              Oper1 = Rproc(Oper1);
              if ( Oper1 == 0 )
                return 0;
              SetReg(insn.Op1, Oper1);
              break;

            case CR16_bal:
              {
                // 01 1 1011 lnk-pair  d16,d19-d17 0
                SetReg(insn.Op1, rR0 + Oper1);
                insn.Op1.specflag1 |= URR_PAIR;
                int disp = GetWord(insn);
                disp |= (Oper2 & 8) << (16-3);
                disp |= (Oper2 & 7) << 17;
                SetRelative(insn.Op2, disp, 20, insn);
              }
              break;

            default:
              SetReg(insn.Op2, rR0 + Oper1);
              SetReg(insn.Op1, rR0 + Oper2);
              break;
          }
        }
        else
        {                       // jump's
          // 010xxxxxxxxxxxx0
          insn.itype = CR16_beq + Oper1;
          SetRelative(insn.Op1, (code & 0x1E) | (OpCode << 5), 8, insn);
        }
      }
      break;

      // short immediate-register (two word)
    case 0x00:
      insn.itype = Ops[OpCode];
      switch ( insn.itype )
      {
        case 0:
          return 0;
          // branch's
        case CR16_br:
          if ( code & 1 )
          {
            static const uchar BQ[4] =
            {
              CR16_beq0b, CR16_beq1b,
              CR16_bne0b, CR16_bne1b
            };
            insn.itype = BQ[(Oper1 >> 1) & 3];
            if ( WordFlg )
              insn.itype++;
            SetReg(insn.Op1, rR0 + (Oper1 & 0x9));
            SetRelative(insn.Op1, code & 0x1E, 5, insn);
          }
          else if ( WordFlg )
          {
            insn.itype = CR16_bal;
            SetReg(insn.Op1, rR0 + Oper1);
            if ( (code & 0x0F) == 0x0E )
            {
              SetRelative(insn.Op2,
                          GetWord(insn) | (((uint32) code & 0x10) << 12), 16, insn);
              insn.Op2.addr = insn.Op2.value = insn.Op2.addr & 0x1FFFF;
            }
            else
              SetRelative(insn.Op2, code & 0x1F, 4, insn);
          }
          else
          {
            insn.itype = CR16_beq + Oper1;
            if ( (code & 0x0F) == 0x0E )
            {
              SetRelative(insn.Op1,
                          GetWord(insn) | (((uint32) code & 0x10) << 12), 16, insn);
              insn.Op1.addr = insn.Op1.value = insn.Op2.addr & 0x1FFFF;
            }
            else
            {
              SetRelative(insn.Op1, code & 0x1F, 4, insn);
            }
          }
          break;

        case EXTOPS:
          {
            // 54 3 2109 8   76     5  4321        d
            // 00 i 0010 bs1 ex-op bs0 bit-num/Imm d
            int exop = (Oper1 >> 1) & 3;
            insn.itype = ExtOps[exop] + WordFlg;
            SetExtOp(insn, code);
          }
          break;

          // Special tbit
        case CR16_tbit:
          if ( WordFlg == 0 )
          {
            // jcond large format
            // 00 0 1011 cond target-pair 1
            // jal large format
            // 00 0 1011 link-pair target-pair 0
            if ( code & 1 )
            {
              insn.itype = CR16_jeq + Oper1;
              SetReg(insn.Op1, rR0 + Oper2);
              insn.Op1.specflag1 |= URR_PAIR;
            }
            else
            {
              insn.itype = CR16_jal;
              SetReg(insn.Op1, rR0 + Oper1);
              insn.Op1.specflag1 |= URR_PAIR;
              SetReg(insn.Op2, rR0 + Oper2);
              insn.Op2.specflag1 |= URR_PAIR;
            }
            break;
          }
          insn.itype--;
          [[fallthrough]];

          // all other cmds
        default:
          if ( code == 0x200 )
          {
            insn.itype = CR16_nop;
            break;
          }
          if ( WordFlg ) // fix word operations
            insn.itype++;
          // Setup register OP
          SetReg(insn.Op2, rR0 + Oper1);
          // Setup immediate
          if ( (code & 0x1F) == 0x11 )
            SetImmData(insn.Op1, GetWord(insn), 15);
          else
            SetImmData(insn.Op1, code & 0x1F, 4);
          break;
      }
      break;

      // LOADi
    case 0x02:
      insn.itype = WordFlg ? CR16_loadw : CR16_loadb;
      SetReg(insn.Op2, rR0 + Oper1);
      SetSL(insn, insn.Op1, code);
      break;
      // STORi
    case 0x3:
      insn.itype = WordFlg ? CR16_storw : CR16_storb;
      SetReg(insn.Op1, rR0 + Oper1);
      SetSL(insn, insn.Op2, code);
      break;
  }
  return insn.size;
}
