|  | /* Disassemble Xilinx microblaze instructions. | 
|  |  | 
|  | Copyright (C) 2009-2024 Free Software Foundation, Inc. | 
|  |  | 
|  | 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" | 
|  | #define STATIC_TABLE | 
|  | #define DEFINE_TABLE | 
|  |  | 
|  | #include "disassemble.h" | 
|  | #include <strings.h> | 
|  | #include "microblaze-opc.h" | 
|  | #include "microblaze-dis.h" | 
|  |  | 
|  | #define get_field_rd(buf, instr)   get_field (buf, instr, RD_MASK, RD_LOW) | 
|  | #define get_field_r1(buf, instr)   get_field (buf, instr, RA_MASK, RA_LOW) | 
|  | #define get_field_r2(buf, instr)   get_field (buf, instr, RB_MASK, RB_LOW) | 
|  | #define get_int_field_imm(instr)   ((instr & IMM_MASK) >> IMM_LOW) | 
|  | #define get_int_field_r1(instr)    ((instr & RA_MASK) >> RA_LOW) | 
|  |  | 
|  | #define NUM_STRBUFS 4 | 
|  | #define STRBUF_SIZE 25 | 
|  |  | 
|  | struct string_buf | 
|  | { | 
|  | unsigned int which; | 
|  | char str[NUM_STRBUFS][STRBUF_SIZE]; | 
|  | }; | 
|  |  | 
|  | static inline char * | 
|  | strbuf (struct string_buf *buf) | 
|  | { | 
|  | #ifdef ENABLE_CHECKING | 
|  | if (buf->which >= NUM_STRBUFS) | 
|  | abort (); | 
|  | #endif | 
|  | return buf->str[buf->which++]; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field (struct string_buf *buf, long instr, long mask, unsigned short low) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%s%d", register_prefix, (int)((instr & mask) >> low)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_imm (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%d", (short)((instr & IMM_MASK) >> IMM_LOW)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_imm5 (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%d", (short)((instr & IMM5_MASK) >> IMM_LOW)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_imm5_mbar (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%d", (short)((instr & IMM5_MBAR_MASK) >> IMM_MBAR)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_immw (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | if (instr & 0x00004000) | 
|  | sprintf (p, "%d", (short)(((instr & IMM5_WIDTH_MASK) | 
|  | >> IMM_WIDTH_LOW))); /* bsefi */ | 
|  | else | 
|  | sprintf (p, "%d", (short)(((instr & IMM5_WIDTH_MASK) >> | 
|  | IMM_WIDTH_LOW) - ((instr & IMM5_MASK) >> | 
|  | IMM_LOW) + 1)); /* bsifi */ | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_rfsl (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%s%d", fsl_register_prefix, | 
|  | (short)((instr & RFSL_MASK) >> IMM_LOW)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_imm15 (struct string_buf *buf, long instr) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  |  | 
|  | sprintf (p, "%d", (short)((instr & IMM15_MASK) >> IMM_LOW)); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static char * | 
|  | get_field_special (struct string_buf *buf, long instr, | 
|  | const struct op_code_struct *op) | 
|  | { | 
|  | char *p = strbuf (buf); | 
|  | char *spr; | 
|  |  | 
|  | switch ((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask)) | 
|  | { | 
|  | case REG_MSR_MASK : | 
|  | spr = "msr"; | 
|  | break; | 
|  | case REG_PC_MASK : | 
|  | spr = "pc"; | 
|  | break; | 
|  | case REG_EAR_MASK : | 
|  | spr = "ear"; | 
|  | break; | 
|  | case REG_ESR_MASK : | 
|  | spr = "esr"; | 
|  | break; | 
|  | case REG_FSR_MASK : | 
|  | spr = "fsr"; | 
|  | break; | 
|  | case REG_BTR_MASK : | 
|  | spr = "btr"; | 
|  | break; | 
|  | case REG_EDR_MASK : | 
|  | spr = "edr"; | 
|  | break; | 
|  | case REG_PID_MASK : | 
|  | spr = "pid"; | 
|  | break; | 
|  | case REG_ZPR_MASK : | 
|  | spr = "zpr"; | 
|  | break; | 
|  | case REG_TLBX_MASK : | 
|  | spr = "tlbx"; | 
|  | break; | 
|  | case REG_TLBLO_MASK : | 
|  | spr = "tlblo"; | 
|  | break; | 
|  | case REG_TLBHI_MASK : | 
|  | spr = "tlbhi"; | 
|  | break; | 
|  | case REG_TLBSX_MASK : | 
|  | spr = "tlbsx"; | 
|  | break; | 
|  | case REG_SHR_MASK : | 
|  | spr = "shr"; | 
|  | break; | 
|  | case REG_SLR_MASK : | 
|  | spr = "slr"; | 
|  | break; | 
|  | default : | 
|  | if (((((instr & IMM_MASK) >> IMM_LOW) ^ op->immval_mask) & 0xE000) | 
|  | == REG_PVR_MASK) | 
|  | { | 
|  | sprintf (p, "%spvr%d", register_prefix, | 
|  | (unsigned short)(((instr & IMM_MASK) >> IMM_LOW) | 
|  | ^ op->immval_mask) ^ REG_PVR_MASK); | 
|  | return p; | 
|  | } | 
|  | else | 
|  | spr = "pc"; | 
|  | break; | 
|  | } | 
|  |  | 
|  | sprintf (p, "%s%s", register_prefix, spr); | 
|  | return p; | 
|  | } | 
|  |  | 
|  | static unsigned long | 
|  | read_insn_microblaze (bfd_vma memaddr, | 
|  | struct disassemble_info *info, | 
|  | const struct op_code_struct **opr) | 
|  | { | 
|  | unsigned char       ibytes[4]; | 
|  | int                 status; | 
|  | const struct op_code_struct *op; | 
|  | unsigned long inst; | 
|  |  | 
|  | status = info->read_memory_func (memaddr, ibytes, 4, info); | 
|  |  | 
|  | if (status != 0) | 
|  | { | 
|  | info->memory_error_func (status, memaddr, info); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (info->endian == BFD_ENDIAN_BIG) | 
|  | inst = (((unsigned) ibytes[0] << 24) | (ibytes[1] << 16) | 
|  | | (ibytes[2] << 8) | ibytes[3]); | 
|  | else if (info->endian == BFD_ENDIAN_LITTLE) | 
|  | inst = (((unsigned) ibytes[3] << 24) | (ibytes[2] << 16) | 
|  | | (ibytes[1] << 8) | ibytes[0]); | 
|  | else | 
|  | abort (); | 
|  |  | 
|  | /* Just a linear search of the table.  */ | 
|  | for (op = microblaze_opcodes; op->name != 0; op ++) | 
|  | if (op->bit_sequence == (inst & op->opcode_mask)) | 
|  | break; | 
|  |  | 
|  | *opr = op; | 
|  | return inst; | 
|  | } | 
|  |  | 
|  |  | 
|  | int | 
|  | print_insn_microblaze (bfd_vma memaddr, struct disassemble_info * info) | 
|  | { | 
|  | fprintf_ftype print_func = info->fprintf_func; | 
|  | void *stream = info->stream; | 
|  | unsigned long inst, prev_inst; | 
|  | const struct op_code_struct *op, *pop; | 
|  | int immval = 0; | 
|  | bool immfound = false; | 
|  | static bfd_vma prev_insn_addr = -1;	/* Init the prev insn addr.  */ | 
|  | static int prev_insn_vma = -1;	/* Init the prev insn vma.  */ | 
|  | int curr_insn_vma = info->buffer_vma; | 
|  | struct string_buf buf; | 
|  |  | 
|  | buf.which = 0; | 
|  | info->bytes_per_chunk = 4; | 
|  |  | 
|  | inst = read_insn_microblaze (memaddr, info, &op); | 
|  | if (inst == 0) | 
|  | return -1; | 
|  |  | 
|  | if (prev_insn_vma == curr_insn_vma) | 
|  | { | 
|  | if (memaddr-(info->bytes_per_chunk) == prev_insn_addr) | 
|  | { | 
|  | prev_inst = read_insn_microblaze (prev_insn_addr, info, &pop); | 
|  | if (prev_inst == 0) | 
|  | return -1; | 
|  | if (pop->instr == imm) | 
|  | { | 
|  | immval = (get_int_field_imm (prev_inst) << 16) & 0xffff0000; | 
|  | immfound = true; | 
|  | } | 
|  | else | 
|  | { | 
|  | immval = 0; | 
|  | immfound = false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Make curr insn as prev insn.  */ | 
|  | prev_insn_addr = memaddr; | 
|  | prev_insn_vma = curr_insn_vma; | 
|  |  | 
|  | if (op->name == NULL) | 
|  | print_func (stream, ".long 0x%04x", (unsigned int) inst); | 
|  | else | 
|  | { | 
|  | print_func (stream, "%s", op->name); | 
|  |  | 
|  | switch (op->inst_type) | 
|  | { | 
|  | case INST_TYPE_RD_R1_R2: | 
|  | print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst), | 
|  | get_field_r1 (&buf, inst), get_field_r2 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RD_R1_IMM: | 
|  | print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst), | 
|  | get_field_r1 (&buf, inst), get_field_imm (&buf, inst)); | 
|  | if (info->print_address_func && get_int_field_r1 (inst) == 0 | 
|  | && info->symbol_at_address_func) | 
|  | { | 
|  | if (immfound) | 
|  | immval |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | else | 
|  | { | 
|  | immval = get_int_field_imm (inst); | 
|  | if (immval & 0x8000) | 
|  | immval |= 0xFFFF0000; | 
|  | } | 
|  | if (immval > 0 && info->symbol_at_address_func (immval, info)) | 
|  | { | 
|  | print_func (stream, "\t// "); | 
|  | info->print_address_func (immval, info); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case INST_TYPE_RD_R1_IMM5: | 
|  | print_func (stream, "\t%s, %s, %s", get_field_rd (&buf, inst), | 
|  | get_field_r1 (&buf, inst), get_field_imm5 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RD_RFSL: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_rfsl (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R1_RFSL: | 
|  | print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst), | 
|  | get_field_rfsl (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RD_SPECIAL: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_special (&buf, inst, op)); | 
|  | break; | 
|  | case INST_TYPE_SPECIAL_R1: | 
|  | print_func (stream, "\t%s, %s", get_field_special (&buf, inst, op), | 
|  | get_field_r1 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RD_R1: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_r1 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R1_R2: | 
|  | print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst), | 
|  | get_field_r2 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R1_IMM: | 
|  | print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst), | 
|  | get_field_imm (&buf, inst)); | 
|  | /* The non-pc relative instructions are returns, which shouldn't | 
|  | have a label printed.  */ | 
|  | if (info->print_address_func && op->inst_offset_type == INST_PC_OFFSET | 
|  | && info->symbol_at_address_func) | 
|  | { | 
|  | if (immfound) | 
|  | immval |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | else | 
|  | { | 
|  | immval = get_int_field_imm (inst); | 
|  | if (immval & 0x8000) | 
|  | immval |= 0xFFFF0000; | 
|  | } | 
|  | immval += memaddr; | 
|  | if (immval > 0 && info->symbol_at_address_func (immval, info)) | 
|  | { | 
|  | print_func (stream, "\t// "); | 
|  | info->print_address_func (immval, info); | 
|  | } | 
|  | else | 
|  | { | 
|  | print_func (stream, "\t\t// "); | 
|  | print_func (stream, "%x", immval); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case INST_TYPE_RD_IMM: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_imm (&buf, inst)); | 
|  | if (info->print_address_func && info->symbol_at_address_func) | 
|  | { | 
|  | if (immfound) | 
|  | immval |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | else | 
|  | { | 
|  | immval = get_int_field_imm (inst); | 
|  | if (immval & 0x8000) | 
|  | immval |= 0xFFFF0000; | 
|  | } | 
|  | if (op->inst_offset_type == INST_PC_OFFSET) | 
|  | immval += (int) memaddr; | 
|  | if (info->symbol_at_address_func (immval, info)) | 
|  | { | 
|  | print_func (stream, "\t// "); | 
|  | info->print_address_func (immval, info); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case INST_TYPE_IMM: | 
|  | print_func (stream, "\t%s", get_field_imm (&buf, inst)); | 
|  | if (info->print_address_func && info->symbol_at_address_func | 
|  | && op->instr != imm) | 
|  | { | 
|  | if (immfound) | 
|  | immval |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | else | 
|  | { | 
|  | immval = get_int_field_imm (inst); | 
|  | if (immval & 0x8000) | 
|  | immval |= 0xFFFF0000; | 
|  | } | 
|  | if (op->inst_offset_type == INST_PC_OFFSET) | 
|  | immval += (int) memaddr; | 
|  | if (immval > 0 && info->symbol_at_address_func (immval, info)) | 
|  | { | 
|  | print_func (stream, "\t// "); | 
|  | info->print_address_func (immval, info); | 
|  | } | 
|  | else if (op->inst_offset_type == INST_PC_OFFSET) | 
|  | { | 
|  | print_func (stream, "\t\t// "); | 
|  | print_func (stream, "%x", immval); | 
|  | } | 
|  | } | 
|  | break; | 
|  | case INST_TYPE_RD_R2: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_r2 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R2: | 
|  | print_func (stream, "\t%s", get_field_r2 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R1: | 
|  | print_func (stream, "\t%s", get_field_r1 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_R1_R2_SPECIAL: | 
|  | print_func (stream, "\t%s, %s", get_field_r1 (&buf, inst), | 
|  | get_field_r2 (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RD_IMM15: | 
|  | print_func (stream, "\t%s, %s", get_field_rd (&buf, inst), | 
|  | get_field_imm15 (&buf, inst)); | 
|  | break; | 
|  | /* For mbar insn.  */ | 
|  | case INST_TYPE_IMM5: | 
|  | print_func (stream, "\t%s", get_field_imm5_mbar (&buf, inst)); | 
|  | break; | 
|  | /* For mbar 16 or sleep insn.  */ | 
|  | case INST_TYPE_NONE: | 
|  | break; | 
|  | /* For bit field insns.  */ | 
|  | case INST_TYPE_RD_R1_IMMW_IMMS: | 
|  | print_func (stream, "\t%s, %s, %s, %s", | 
|  | get_field_rd (&buf, inst), | 
|  | get_field_r1 (&buf, inst), | 
|  | get_field_immw (&buf, inst), | 
|  | get_field_imm5 (&buf, inst)); | 
|  | break; | 
|  | /* For tuqula instruction */ | 
|  | case INST_TYPE_RD: | 
|  | print_func (stream, "\t%s", get_field_rd (&buf, inst)); | 
|  | break; | 
|  | case INST_TYPE_RFSL: | 
|  | print_func (stream, "\t%s", get_field_rfsl (&buf, inst)); | 
|  | break; | 
|  | default: | 
|  | /* If the disassembler lags the instruction set.  */ | 
|  | print_func (stream, "\tundecoded operands, inst is 0x%04x", | 
|  | (unsigned int) inst); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Say how many bytes we consumed.  */ | 
|  | return 4; | 
|  | } | 
|  |  | 
|  | enum microblaze_instr | 
|  | get_insn_microblaze (long inst, | 
|  | bool *isunsignedimm, | 
|  | enum microblaze_instr_type *insn_type, | 
|  | short *delay_slots) | 
|  | { | 
|  | const struct op_code_struct *op; | 
|  | *isunsignedimm = false; | 
|  |  | 
|  | /* Just a linear search of the table.  */ | 
|  | for (op = microblaze_opcodes; op->name != 0; op ++) | 
|  | if (op->bit_sequence == (inst & op->opcode_mask)) | 
|  | break; | 
|  |  | 
|  | if (op->name == 0) | 
|  | return invalid_inst; | 
|  | else | 
|  | { | 
|  | *isunsignedimm = (op->inst_type == INST_TYPE_RD_R1_UNSIGNED_IMM); | 
|  | *insn_type = op->instr_type; | 
|  | *delay_slots = op->delay_slots; | 
|  | return op->instr; | 
|  | } | 
|  | } | 
|  |  | 
|  | enum microblaze_instr | 
|  | microblaze_decode_insn (long insn, int *rd, int *ra, int *rb, int *immed) | 
|  | { | 
|  | enum microblaze_instr op; | 
|  | bool t1; | 
|  | enum microblaze_instr_type t2; | 
|  | short t3; | 
|  |  | 
|  | op = get_insn_microblaze (insn, &t1, &t2, &t3); | 
|  | *rd = (insn & RD_MASK) >> RD_LOW; | 
|  | *ra = (insn & RA_MASK) >> RA_LOW; | 
|  | *rb = (insn & RB_MASK) >> RB_LOW; | 
|  | t3 = (insn & IMM_MASK) >> IMM_LOW; | 
|  | *immed = (int) t3; | 
|  | return (op); | 
|  | } | 
|  |  | 
|  | unsigned long | 
|  | microblaze_get_target_address (long inst, bool immfound, int immval, | 
|  | long pcval, long r1val, long r2val, | 
|  | bool *targetvalid, | 
|  | bool *unconditionalbranch) | 
|  | { | 
|  | const struct op_code_struct *op; | 
|  | long targetaddr = 0; | 
|  |  | 
|  | *unconditionalbranch = false; | 
|  | /* Just a linear search of the table.  */ | 
|  | for (op = microblaze_opcodes; op->name != 0; op ++) | 
|  | if (op->bit_sequence == (inst & op->opcode_mask)) | 
|  | break; | 
|  |  | 
|  | if (op->name == 0) | 
|  | { | 
|  | *targetvalid = false; | 
|  | } | 
|  | else if (op->instr_type == branch_inst) | 
|  | { | 
|  | switch (op->inst_type) | 
|  | { | 
|  | case INST_TYPE_R2: | 
|  | *unconditionalbranch = true; | 
|  | /* Fall through.  */ | 
|  | case INST_TYPE_RD_R2: | 
|  | case INST_TYPE_R1_R2: | 
|  | targetaddr = r2val; | 
|  | *targetvalid = true; | 
|  | if (op->inst_offset_type == INST_PC_OFFSET) | 
|  | targetaddr += pcval; | 
|  | break; | 
|  | case INST_TYPE_IMM: | 
|  | *unconditionalbranch = true; | 
|  | /* Fall through.  */ | 
|  | case INST_TYPE_RD_IMM: | 
|  | case INST_TYPE_R1_IMM: | 
|  | if (immfound) | 
|  | { | 
|  | targetaddr = (immval << 16) & 0xffff0000; | 
|  | targetaddr |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | } | 
|  | else | 
|  | { | 
|  | targetaddr = get_int_field_imm (inst); | 
|  | if (targetaddr & 0x8000) | 
|  | targetaddr |= 0xFFFF0000; | 
|  | } | 
|  | if (op->inst_offset_type == INST_PC_OFFSET) | 
|  | targetaddr += pcval; | 
|  | *targetvalid = true; | 
|  | break; | 
|  | default: | 
|  | *targetvalid = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | else if (op->instr_type == return_inst) | 
|  | { | 
|  | if (immfound) | 
|  | { | 
|  | targetaddr = (immval << 16) & 0xffff0000; | 
|  | targetaddr |= (get_int_field_imm (inst) & 0x0000ffff); | 
|  | } | 
|  | else | 
|  | { | 
|  | targetaddr = get_int_field_imm (inst); | 
|  | if (targetaddr & 0x8000) | 
|  | targetaddr |= 0xFFFF0000; | 
|  | } | 
|  | targetaddr += r1val; | 
|  | *targetvalid = true; | 
|  | } | 
|  | else | 
|  | *targetvalid = false; | 
|  | return targetaddr; | 
|  | } |