| /* Disassembly routines for TMS320C30 architecture |
| Copyright 1998, 1999, 2000, 2002, 2005 Free Software Foundation, Inc. |
| Contributed by Steven Haworth (steve@pm.cse.rmit.edu.au) |
| |
| This program 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 2 of the License, or |
| (at your option) any later version. |
| |
| This program 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 <errno.h> |
| #include <math.h> |
| #include "sysdep.h" |
| #include "dis-asm.h" |
| #include "opcode/tic30.h" |
| |
| #define NORMAL_INSN 1 |
| #define PARALLEL_INSN 2 |
| |
| /* Gets the type of instruction based on the top 2 or 3 bits of the |
| instruction word. */ |
| #define GET_TYPE(insn) (insn & 0x80000000 ? insn & 0xC0000000 : insn & 0xE0000000) |
| |
| /* Instruction types. */ |
| #define TWO_OPERAND_1 0x00000000 |
| #define TWO_OPERAND_2 0x40000000 |
| #define THREE_OPERAND 0x20000000 |
| #define PAR_STORE 0xC0000000 |
| #define MUL_ADDS 0x80000000 |
| #define BRANCHES 0x60000000 |
| |
| /* Specific instruction id bits. */ |
| #define NORMAL_IDEN 0x1F800000 |
| #define PAR_STORE_IDEN 0x3E000000 |
| #define MUL_ADD_IDEN 0x2C000000 |
| #define BR_IMM_IDEN 0x1F000000 |
| #define BR_COND_IDEN 0x1C3F0000 |
| |
| /* Addressing modes. */ |
| #define AM_REGISTER 0x00000000 |
| #define AM_DIRECT 0x00200000 |
| #define AM_INDIRECT 0x00400000 |
| #define AM_IMM 0x00600000 |
| |
| #define P_FIELD 0x03000000 |
| |
| #define REG_AR0 0x08 |
| #define LDP_INSN 0x08700000 |
| |
| /* TMS320C30 program counter for current instruction. */ |
| static unsigned int _pc; |
| |
| struct instruction |
| { |
| int type; |
| template *tm; |
| partemplate *ptm; |
| }; |
| |
| static int |
| get_tic30_instruction (unsigned long insn_word, struct instruction *insn) |
| { |
| switch (GET_TYPE (insn_word)) |
| { |
| case TWO_OPERAND_1: |
| case TWO_OPERAND_2: |
| case THREE_OPERAND: |
| insn->type = NORMAL_INSN; |
| { |
| template *current_optab = (template *) tic30_optab; |
| |
| for (; current_optab < tic30_optab_end; current_optab++) |
| { |
| if (GET_TYPE (current_optab->base_opcode) == GET_TYPE (insn_word)) |
| { |
| if (current_optab->operands == 0) |
| { |
| if (current_optab->base_opcode == insn_word) |
| { |
| insn->tm = current_optab; |
| break; |
| } |
| } |
| else if ((current_optab->base_opcode & NORMAL_IDEN) == (insn_word & NORMAL_IDEN)) |
| { |
| insn->tm = current_optab; |
| break; |
| } |
| } |
| } |
| } |
| break; |
| |
| case PAR_STORE: |
| insn->type = PARALLEL_INSN; |
| { |
| partemplate *current_optab = (partemplate *) tic30_paroptab; |
| |
| for (; current_optab < tic30_paroptab_end; current_optab++) |
| { |
| if (GET_TYPE (current_optab->base_opcode) == GET_TYPE (insn_word)) |
| { |
| if ((current_optab->base_opcode & PAR_STORE_IDEN) |
| == (insn_word & PAR_STORE_IDEN)) |
| { |
| insn->ptm = current_optab; |
| break; |
| } |
| } |
| } |
| } |
| break; |
| |
| case MUL_ADDS: |
| insn->type = PARALLEL_INSN; |
| { |
| partemplate *current_optab = (partemplate *) tic30_paroptab; |
| |
| for (; current_optab < tic30_paroptab_end; current_optab++) |
| { |
| if (GET_TYPE (current_optab->base_opcode) == GET_TYPE (insn_word)) |
| { |
| if ((current_optab->base_opcode & MUL_ADD_IDEN) |
| == (insn_word & MUL_ADD_IDEN)) |
| { |
| insn->ptm = current_optab; |
| break; |
| } |
| } |
| } |
| } |
| break; |
| |
| case BRANCHES: |
| insn->type = NORMAL_INSN; |
| { |
| template *current_optab = (template *) tic30_optab; |
| |
| for (; current_optab < tic30_optab_end; current_optab++) |
| { |
| if (GET_TYPE (current_optab->base_opcode) == GET_TYPE (insn_word)) |
| { |
| if (current_optab->operand_types[0] & Imm24) |
| { |
| if ((current_optab->base_opcode & BR_IMM_IDEN) |
| == (insn_word & BR_IMM_IDEN)) |
| { |
| insn->tm = current_optab; |
| break; |
| } |
| } |
| else if (current_optab->operands > 0) |
| { |
| if ((current_optab->base_opcode & BR_COND_IDEN) |
| == (insn_word & BR_COND_IDEN)) |
| { |
| insn->tm = current_optab; |
| break; |
| } |
| } |
| else |
| { |
| if ((current_optab->base_opcode & (BR_COND_IDEN | 0x00800000)) |
| == (insn_word & (BR_COND_IDEN | 0x00800000))) |
| { |
| insn->tm = current_optab; |
| break; |
| } |
| } |
| } |
| } |
| } |
| break; |
| default: |
| return 0; |
| } |
| return 1; |
| } |
| |
| static int |
| get_register_operand (unsigned char fragment, char *buffer) |
| { |
| const reg *current_reg = tic30_regtab; |
| |
| if (buffer == NULL) |
| return 0; |
| for (; current_reg < tic30_regtab_end; current_reg++) |
| { |
| if ((fragment & 0x1F) == current_reg->opcode) |
| { |
| strcpy (buffer, current_reg->name); |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static int |
| get_indirect_operand (unsigned short fragment, |
| int size, |
| char *buffer) |
| { |
| unsigned char mod; |
| unsigned arnum; |
| unsigned char disp; |
| |
| if (buffer == NULL) |
| return 0; |
| /* Determine which bits identify the sections of the indirect |
| operand based on the size in bytes. */ |
| switch (size) |
| { |
| case 1: |
| mod = (fragment & 0x00F8) >> 3; |
| arnum = (fragment & 0x0007); |
| disp = 0; |
| break; |
| case 2: |
| mod = (fragment & 0xF800) >> 11; |
| arnum = (fragment & 0x0700) >> 8; |
| disp = (fragment & 0x00FF); |
| break; |
| default: |
| return 0; |
| } |
| { |
| const ind_addr_type *current_ind = tic30_indaddr_tab; |
| |
| for (; current_ind < tic30_indaddrtab_end; current_ind++) |
| { |
| if (current_ind->modfield == mod) |
| { |
| if (current_ind->displacement == IMPLIED_DISP && size == 2) |
| continue; |
| |
| else |
| { |
| size_t i, len; |
| int bufcnt; |
| |
| len = strlen (current_ind->syntax); |
| for (i = 0, bufcnt = 0; i < len; i++, bufcnt++) |
| { |
| buffer[bufcnt] = current_ind->syntax[i]; |
| if (buffer[bufcnt - 1] == 'a' && buffer[bufcnt] == 'r') |
| buffer[++bufcnt] = arnum + '0'; |
| if (buffer[bufcnt] == '(' |
| && current_ind->displacement == DISP_REQUIRED) |
| { |
| sprintf (&buffer[bufcnt + 1], "%u", disp); |
| bufcnt += strlen (&buffer[bufcnt + 1]); |
| } |
| } |
| buffer[bufcnt + 1] = '\0'; |
| break; |
| } |
| } |
| } |
| } |
| return 1; |
| } |
| |
| static int |
| cnvt_tmsfloat_ieee (unsigned long tmsfloat, int size, float *ieeefloat) |
| { |
| unsigned long exp, sign, mant; |
| union |
| { |
| unsigned long l; |
| float f; |
| } val; |
| |
| if (size == 2) |
| { |
| if ((tmsfloat & 0x0000F000) == 0x00008000) |
| tmsfloat = 0x80000000; |
| else |
| { |
| tmsfloat <<= 16; |
| tmsfloat = (long) tmsfloat >> 4; |
| } |
| } |
| exp = tmsfloat & 0xFF000000; |
| if (exp == 0x80000000) |
| { |
| *ieeefloat = 0.0; |
| return 1; |
| } |
| exp += 0x7F000000; |
| sign = (tmsfloat & 0x00800000) << 8; |
| mant = tmsfloat & 0x007FFFFF; |
| if (exp == 0xFF000000) |
| { |
| if (mant == 0) |
| *ieeefloat = ERANGE; |
| #ifdef HUGE_VALF |
| if (sign == 0) |
| *ieeefloat = HUGE_VALF; |
| else |
| *ieeefloat = -HUGE_VALF; |
| #else |
| if (sign == 0) |
| *ieeefloat = 1.0 / 0.0; |
| else |
| *ieeefloat = -1.0 / 0.0; |
| #endif |
| return 1; |
| } |
| exp >>= 1; |
| if (sign) |
| { |
| mant = (~mant) & 0x007FFFFF; |
| mant += 1; |
| exp += mant & 0x00800000; |
| exp &= 0x7F800000; |
| mant &= 0x007FFFFF; |
| } |
| if (tmsfloat == 0x80000000) |
| sign = mant = exp = 0; |
| tmsfloat = sign | exp | mant; |
| val.l = tmsfloat; |
| *ieeefloat = val.f; |
| return 1; |
| } |
| |
| static int |
| print_two_operand (disassemble_info *info, |
| unsigned long insn_word, |
| struct instruction *insn) |
| { |
| char name[12]; |
| char operand[2][13] = |
| { |
| {0}, |
| {0} |
| }; |
| float f_number; |
| |
| if (insn->tm == NULL) |
| return 0; |
| strcpy (name, insn->tm->name); |
| if (insn->tm->opcode_modifier == AddressMode) |
| { |
| int src_op, dest_op; |
| /* Determine whether instruction is a store or a normal instruction. */ |
| if ((insn->tm->operand_types[1] & (Direct | Indirect)) |
| == (Direct | Indirect)) |
| { |
| src_op = 1; |
| dest_op = 0; |
| } |
| else |
| { |
| src_op = 0; |
| dest_op = 1; |
| } |
| /* Get the destination register. */ |
| if (insn->tm->operands == 2) |
| get_register_operand ((insn_word & 0x001F0000) >> 16, operand[dest_op]); |
| /* Get the source operand based on addressing mode. */ |
| switch (insn_word & AddressMode) |
| { |
| case AM_REGISTER: |
| /* Check for the NOP instruction before getting the operand. */ |
| if ((insn->tm->operand_types[0] & NotReq) == 0) |
| get_register_operand ((insn_word & 0x0000001F), operand[src_op]); |
| break; |
| case AM_DIRECT: |
| sprintf (operand[src_op], "@0x%lX", (insn_word & 0x0000FFFF)); |
| break; |
| case AM_INDIRECT: |
| get_indirect_operand ((insn_word & 0x0000FFFF), 2, operand[src_op]); |
| break; |
| case AM_IMM: |
| /* Get the value of the immediate operand based on variable type. */ |
| switch (insn->tm->imm_arg_type) |
| { |
| case Imm_Float: |
| cnvt_tmsfloat_ieee ((insn_word & 0x0000FFFF), 2, &f_number); |
| sprintf (operand[src_op], "%2.2f", f_number); |
| break; |
| case Imm_SInt: |
| sprintf (operand[src_op], "%d", (short) (insn_word & 0x0000FFFF)); |
| break; |
| case Imm_UInt: |
| sprintf (operand[src_op], "%lu", (insn_word & 0x0000FFFF)); |
| break; |
| default: |
| return 0; |
| } |
| /* Handle special case for LDP instruction. */ |
| if ((insn_word & 0xFFFFFF00) == LDP_INSN) |
| { |
| strcpy (name, "ldp"); |
| sprintf (operand[0], "0x%06lX", (insn_word & 0x000000FF) << 16); |
| operand[1][0] = '\0'; |
| } |
| } |
| } |
| /* Handle case for stack and rotate instructions. */ |
| else if (insn->tm->operands == 1) |
| { |
| if (insn->tm->opcode_modifier == StackOp) |
| get_register_operand ((insn_word & 0x001F0000) >> 16, operand[0]); |
| } |
| /* Output instruction to stream. */ |
| info->fprintf_func (info->stream, " %s %s%c%s", name, |
| operand[0][0] ? operand[0] : "", |
| operand[1][0] ? ',' : ' ', |
| operand[1][0] ? operand[1] : ""); |
| return 1; |
| } |
| |
| static int |
| print_three_operand (disassemble_info *info, |
| unsigned long insn_word, |
| struct instruction *insn) |
| { |
| char operand[3][13] = |
| { |
| {0}, |
| {0}, |
| {0} |
| }; |
| |
| if (insn->tm == NULL) |
| return 0; |
| switch (insn_word & AddressMode) |
| { |
| case AM_REGISTER: |
| get_register_operand ((insn_word & 0x000000FF), operand[0]); |
| get_register_operand ((insn_word & 0x0000FF00) >> 8, operand[1]); |
| break; |
| case AM_DIRECT: |
| get_register_operand ((insn_word & 0x000000FF), operand[0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1]); |
| break; |
| case AM_INDIRECT: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0]); |
| get_register_operand ((insn_word & 0x0000FF00) >> 8, operand[1]); |
| break; |
| case AM_IMM: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1]); |
| break; |
| default: |
| return 0; |
| } |
| if (insn->tm->operands == 3) |
| get_register_operand ((insn_word & 0x001F0000) >> 16, operand[2]); |
| info->fprintf_func (info->stream, " %s %s,%s%c%s", insn->tm->name, |
| operand[0], operand[1], |
| operand[2][0] ? ',' : ' ', |
| operand[2][0] ? operand[2] : ""); |
| return 1; |
| } |
| |
| static int |
| print_par_insn (disassemble_info *info, |
| unsigned long insn_word, |
| struct instruction *insn) |
| { |
| size_t i, len; |
| char *name1, *name2; |
| char operand[2][3][13] = |
| { |
| { |
| {0}, |
| {0}, |
| {0} |
| }, |
| { |
| {0}, |
| {0}, |
| {0} |
| } |
| }; |
| |
| if (insn->ptm == NULL) |
| return 0; |
| /* Parse out the names of each of the parallel instructions from the |
| q_insn1_insn2 format. */ |
| name1 = (char *) strdup (insn->ptm->name + 2); |
| name2 = ""; |
| len = strlen (name1); |
| for (i = 0; i < len; i++) |
| { |
| if (name1[i] == '_') |
| { |
| name2 = &name1[i + 1]; |
| name1[i] = '\0'; |
| break; |
| } |
| } |
| /* Get the operands of the instruction based on the operand order. */ |
| switch (insn->ptm->oporder) |
| { |
| case OO_4op1: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][1]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][0]); |
| get_register_operand ((insn_word >> 22) & 0x07, operand[0][1]); |
| break; |
| case OO_4op2: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][0]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[1][1]); |
| get_register_operand ((insn_word >> 22) & 0x07, operand[0][1]); |
| break; |
| case OO_4op3: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][1]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][1]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][0]); |
| get_register_operand ((insn_word >> 22) & 0x07, operand[0][0]); |
| break; |
| case OO_5op1: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][1]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][0]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[0][1]); |
| get_register_operand ((insn_word >> 22) & 0x07, operand[0][2]); |
| break; |
| case OO_5op2: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][1]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][1]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][0]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[0][0]); |
| get_register_operand ((insn_word >> 22) & 0x07, operand[0][2]); |
| break; |
| case OO_PField: |
| if (insn_word & 0x00800000) |
| get_register_operand (0x01, operand[0][2]); |
| else |
| get_register_operand (0x00, operand[0][2]); |
| if (insn_word & 0x00400000) |
| get_register_operand (0x03, operand[1][2]); |
| else |
| get_register_operand (0x02, operand[1][2]); |
| switch (insn_word & P_FIELD) |
| { |
| case 0x00000000: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[0][1]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[0][0]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][1]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[1][0]); |
| break; |
| case 0x01000000: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[1][0]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[0][0]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][1]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[0][1]); |
| break; |
| case 0x02000000: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[1][1]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[1][0]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[0][1]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[0][0]); |
| break; |
| case 0x03000000: |
| get_indirect_operand ((insn_word & 0x000000FF), 1, operand[1][1]); |
| get_indirect_operand ((insn_word & 0x0000FF00) >> 8, 1, operand[0][0]); |
| get_register_operand ((insn_word >> 16) & 0x07, operand[1][0]); |
| get_register_operand ((insn_word >> 19) & 0x07, operand[0][1]); |
| break; |
| } |
| break; |
| default: |
| return 0; |
| } |
| info->fprintf_func (info->stream, " %s %s,%s%c%s", name1, |
| operand[0][0], operand[0][1], |
| operand[0][2][0] ? ',' : ' ', |
| operand[0][2][0] ? operand[0][2] : ""); |
| info->fprintf_func (info->stream, "\n\t\t\t|| %s %s,%s%c%s", name2, |
| operand[1][0], operand[1][1], |
| operand[1][2][0] ? ',' : ' ', |
| operand[1][2][0] ? operand[1][2] : ""); |
| free (name1); |
| return 1; |
| } |
| |
| static int |
| print_branch (disassemble_info *info, |
| unsigned long insn_word, |
| struct instruction *insn) |
| { |
| char operand[2][13] = |
| { |
| {0}, |
| {0} |
| }; |
| unsigned long address; |
| int print_label = 0; |
| |
| if (insn->tm == NULL) |
| return 0; |
| /* Get the operands for 24-bit immediate jumps. */ |
| if (insn->tm->operand_types[0] & Imm24) |
| { |
| address = insn_word & 0x00FFFFFF; |
| sprintf (operand[0], "0x%lX", address); |
| print_label = 1; |
| } |
| /* Get the operand for the trap instruction. */ |
| else if (insn->tm->operand_types[0] & IVector) |
| { |
| address = insn_word & 0x0000001F; |
| sprintf (operand[0], "0x%lX", address); |
| } |
| else |
| { |
| address = insn_word & 0x0000FFFF; |
| /* Get the operands for the DB instructions. */ |
| if (insn->tm->operands == 2) |
| { |
| get_register_operand (((insn_word & 0x01C00000) >> 22) + REG_AR0, operand[0]); |
| if (insn_word & PCRel) |
| { |
| sprintf (operand[1], "%d", (short) address); |
| print_label = 1; |
| } |
| else |
| get_register_operand (insn_word & 0x0000001F, operand[1]); |
| } |
| /* Get the operands for the standard branches. */ |
| else if (insn->tm->operands == 1) |
| { |
| if (insn_word & PCRel) |
| { |
| address = (short) address; |
| sprintf (operand[0], "%ld", address); |
| print_label = 1; |
| } |
| else |
| get_register_operand (insn_word & 0x0000001F, operand[0]); |
| } |
| } |
| info->fprintf_func (info->stream, " %s %s%c%s", insn->tm->name, |
| operand[0][0] ? operand[0] : "", |
| operand[1][0] ? ',' : ' ', |
| operand[1][0] ? operand[1] : ""); |
| /* Print destination of branch in relation to current symbol. */ |
| if (print_label && info->symbols) |
| { |
| asymbol *sym = *info->symbols; |
| |
| if ((insn->tm->opcode_modifier == PCRel) && (insn_word & PCRel)) |
| { |
| address = (_pc + 1 + (short) address) - ((sym->section->vma + sym->value) / 4); |
| /* Check for delayed instruction, if so adjust destination. */ |
| if (insn_word & 0x00200000) |
| address += 2; |
| } |
| else |
| { |
| address -= ((sym->section->vma + sym->value) / 4); |
| } |
| if (address == 0) |
| info->fprintf_func (info->stream, " <%s>", sym->name); |
| else |
| info->fprintf_func (info->stream, " <%s %c %d>", sym->name, |
| ((short) address < 0) ? '-' : '+', |
| abs (address)); |
| } |
| return 1; |
| } |
| |
| int |
| print_insn_tic30 (bfd_vma pc, disassemble_info *info) |
| { |
| unsigned long insn_word; |
| struct instruction insn = { 0, NULL, NULL }; |
| bfd_vma bufaddr = pc - info->buffer_vma; |
| |
| /* Obtain the current instruction word from the buffer. */ |
| insn_word = (*(info->buffer + bufaddr) << 24) | (*(info->buffer + bufaddr + 1) << 16) | |
| (*(info->buffer + bufaddr + 2) << 8) | *(info->buffer + bufaddr + 3); |
| _pc = pc / 4; |
| /* Get the instruction refered to by the current instruction word |
| and print it out based on its type. */ |
| if (!get_tic30_instruction (insn_word, &insn)) |
| return -1; |
| switch (GET_TYPE (insn_word)) |
| { |
| case TWO_OPERAND_1: |
| case TWO_OPERAND_2: |
| if (!print_two_operand (info, insn_word, &insn)) |
| return -1; |
| break; |
| case THREE_OPERAND: |
| if (!print_three_operand (info, insn_word, &insn)) |
| return -1; |
| break; |
| case PAR_STORE: |
| case MUL_ADDS: |
| if (!print_par_insn (info, insn_word, &insn)) |
| return -1; |
| break; |
| case BRANCHES: |
| if (!print_branch (info, insn_word, &insn)) |
| return -1; |
| break; |
| } |
| return 4; |
| } |