| /* Disassemble D10V instructions. | 
 |    Copyright (C) 1996-2023 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 program; if not, write to the Free Software | 
 |    Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, | 
 |    MA 02110-1301, USA.  */ | 
 |  | 
 | #include "sysdep.h" | 
 | #include <stdio.h> | 
 | #include "opcode/d10v.h" | 
 | #include "disassemble.h" | 
 |  | 
 | /* The PC wraps at 18 bits, except for the segment number, | 
 |    so use this mask to keep the parts we want.  */ | 
 | #define PC_MASK	0x0303FFFF | 
 |  | 
 | static void | 
 | print_operand (struct d10v_operand *oper, | 
 | 	       unsigned long insn, | 
 | 	       struct d10v_opcode *op, | 
 | 	       bfd_vma memaddr, | 
 | 	       struct disassemble_info *info) | 
 | { | 
 |   int num, shift; | 
 |  | 
 |   if (oper->flags == OPERAND_ATMINUS) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "@-"); | 
 |       return; | 
 |     } | 
 |   if (oper->flags == OPERAND_MINUS) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "-"); | 
 |       return; | 
 |     } | 
 |   if (oper->flags == OPERAND_PLUS) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "+"); | 
 |       return; | 
 |     } | 
 |   if (oper->flags == OPERAND_ATSIGN) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "@"); | 
 |       return; | 
 |     } | 
 |   if (oper->flags == OPERAND_ATPAR) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "@("); | 
 |       return; | 
 |     } | 
 |  | 
 |   shift = oper->shift; | 
 |  | 
 |   /* The LONG_L format shifts registers over by 15.  */ | 
 |   if (op->format == LONG_L && (oper->flags & OPERAND_REG)) | 
 |     shift += 15; | 
 |  | 
 |   num = (insn >> shift) & (0x7FFFFFFF >> (31 - oper->bits)); | 
 |  | 
 |   if (oper->flags & OPERAND_REG) | 
 |     { | 
 |       int i; | 
 |       int match = 0; | 
 |  | 
 |       num += (oper->flags | 
 | 	      & (OPERAND_GPR | OPERAND_FFLAG | OPERAND_CFLAG | OPERAND_CONTROL)); | 
 |       if (oper->flags & (OPERAND_ACC0 | OPERAND_ACC1)) | 
 | 	num += num ? OPERAND_ACC1 : OPERAND_ACC0; | 
 |       for (i = 0; i < d10v_reg_name_cnt (); i++) | 
 | 	{ | 
 | 	  if (num == (d10v_predefined_registers[i].value & ~ OPERAND_SP)) | 
 | 	    { | 
 | 	      if (d10v_predefined_registers[i].pname) | 
 | 		(*info->fprintf_func) (info->stream, "%s", | 
 | 				       d10v_predefined_registers[i].pname); | 
 | 	      else | 
 | 		(*info->fprintf_func) (info->stream, "%s", | 
 | 				       d10v_predefined_registers[i].name); | 
 | 	      match = 1; | 
 | 	      break; | 
 | 	    } | 
 | 	} | 
 |       if (match == 0) | 
 | 	{ | 
 | 	  /* This would only get executed if a register was not in the | 
 | 	     register table.  */ | 
 | 	  if (oper->flags & (OPERAND_ACC0 | OPERAND_ACC1)) | 
 | 	    (*info->fprintf_func) (info->stream, "a"); | 
 | 	  else if (oper->flags & OPERAND_CONTROL) | 
 | 	    (*info->fprintf_func) (info->stream, "cr"); | 
 | 	  else if (oper->flags & OPERAND_REG) | 
 | 	    (*info->fprintf_func) (info->stream, "r"); | 
 | 	  (*info->fprintf_func) (info->stream, "%d", num & REGISTER_MASK); | 
 | 	} | 
 |     } | 
 |   else | 
 |     { | 
 |       /* Addresses are right-shifted by 2.  */ | 
 |       if (oper->flags & OPERAND_ADDR) | 
 | 	{ | 
 | 	  long max; | 
 | 	  int neg = 0; | 
 |  | 
 | 	  max = (1 << (oper->bits - 1)); | 
 | 	  if (num & max) | 
 | 	    { | 
 | 	      num = -num & ((1 << oper->bits) - 1); | 
 | 	      neg = 1; | 
 | 	    } | 
 | 	  num = num << 2; | 
 | 	  if (info->flags & INSN_HAS_RELOC) | 
 | 	    (*info->print_address_func) (num & PC_MASK, info); | 
 | 	  else | 
 | 	    { | 
 | 	      if (neg) | 
 | 		(*info->print_address_func) ((memaddr - num) & PC_MASK, info); | 
 | 	      else | 
 | 		(*info->print_address_func) ((memaddr + num) & PC_MASK, info); | 
 | 	    } | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  if (oper->flags & OPERAND_SIGNED) | 
 | 	    { | 
 | 	      int max = (1 << (oper->bits - 1)); | 
 | 	      if (num & max) | 
 | 		{ | 
 | 		  num = -num & ((1 << oper->bits) - 1); | 
 | 		  (*info->fprintf_func) (info->stream, "-"); | 
 | 		} | 
 | 	    } | 
 | 	  (*info->fprintf_func) (info->stream, "0x%x", num); | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | static void | 
 | dis_long (unsigned long insn, | 
 | 	  bfd_vma memaddr, | 
 | 	  struct disassemble_info *info) | 
 | { | 
 |   int i; | 
 |   struct d10v_opcode *op = (struct d10v_opcode *) d10v_opcodes; | 
 |   struct d10v_operand *oper; | 
 |   int need_paren = 0; | 
 |   int match = 0; | 
 |  | 
 |   while (op->name) | 
 |     { | 
 |       if ((op->format & LONG_OPCODE) | 
 | 	  && ((op->mask & insn) == (unsigned long) op->opcode)) | 
 | 	{ | 
 | 	  match = 1; | 
 | 	  (*info->fprintf_func) (info->stream, "%s\t", op->name); | 
 |  | 
 | 	  for (i = 0; op->operands[i]; i++) | 
 | 	    { | 
 | 	      oper = (struct d10v_operand *) &d10v_operands[op->operands[i]]; | 
 | 	      if (oper->flags == OPERAND_ATPAR) | 
 | 		need_paren = 1; | 
 | 	      print_operand (oper, insn, op, memaddr, info); | 
 | 	      if (op->operands[i + 1] && oper->bits | 
 | 		  && d10v_operands[op->operands[i + 1]].flags != OPERAND_PLUS | 
 | 		  && d10v_operands[op->operands[i + 1]].flags != OPERAND_MINUS) | 
 | 		(*info->fprintf_func) (info->stream, ", "); | 
 | 	    } | 
 | 	  break; | 
 | 	} | 
 |       op++; | 
 |     } | 
 |  | 
 |   if (!match) | 
 |     (*info->fprintf_func) (info->stream, ".long\t0x%08lx", insn); | 
 |  | 
 |   if (need_paren) | 
 |     (*info->fprintf_func) (info->stream, ")"); | 
 | } | 
 |  | 
 | static void | 
 | dis_2_short (unsigned long insn, | 
 | 	     bfd_vma memaddr, | 
 | 	     struct disassemble_info *info, | 
 | 	     int order) | 
 | { | 
 |   int i, j; | 
 |   unsigned int ins[2]; | 
 |   struct d10v_opcode *op; | 
 |   int match, num_match = 0; | 
 |   struct d10v_operand *oper; | 
 |   int need_paren = 0; | 
 |  | 
 |   ins[0] = (insn & 0x3FFFFFFF) >> 15; | 
 |   ins[1] = insn & 0x00007FFF; | 
 |  | 
 |   for (j = 0; j < 2; j++) | 
 |     { | 
 |       op = (struct d10v_opcode *) d10v_opcodes; | 
 |       match = 0; | 
 |       while (op->name) | 
 | 	{ | 
 | 	  if ((op->format & SHORT_OPCODE) | 
 | 	      && ((((unsigned int) op->mask) & ins[j]) | 
 | 		  == (unsigned int) op->opcode)) | 
 | 	    { | 
 | 	      (*info->fprintf_func) (info->stream, "%s\t", op->name); | 
 | 	      for (i = 0; op->operands[i]; i++) | 
 | 		{ | 
 | 		  oper = (struct d10v_operand *) &d10v_operands[op->operands[i]]; | 
 | 		  if (oper->flags == OPERAND_ATPAR) | 
 | 		    need_paren = 1; | 
 | 		  print_operand (oper, ins[j], op, memaddr, info); | 
 | 		  if (op->operands[i + 1] && oper->bits | 
 | 		      && d10v_operands[op->operands[i + 1]].flags != OPERAND_PLUS | 
 | 		      && d10v_operands[op->operands[i + 1]].flags != OPERAND_MINUS) | 
 | 		    (*info->fprintf_func) (info->stream, ", "); | 
 | 		} | 
 | 	      match = 1; | 
 | 	      num_match++; | 
 | 	      break; | 
 | 	    } | 
 | 	  op++; | 
 | 	} | 
 |       if (!match) | 
 | 	(*info->fprintf_func) (info->stream, "unknown"); | 
 |  | 
 |       switch (order) | 
 | 	{ | 
 | 	case 0: | 
 | 	  (*info->fprintf_func) (info->stream, "\t->\t"); | 
 | 	  order = -1; | 
 | 	  break; | 
 | 	case 1: | 
 | 	  (*info->fprintf_func) (info->stream, "\t<-\t"); | 
 | 	  order = -1; | 
 | 	  break; | 
 | 	case 2: | 
 | 	  (*info->fprintf_func) (info->stream, "\t||\t"); | 
 | 	  order = -1; | 
 | 	  break; | 
 | 	default: | 
 | 	  break; | 
 | 	} | 
 |     } | 
 |  | 
 |   if (num_match == 0) | 
 |     (*info->fprintf_func) (info->stream, ".long\t0x%08lx", insn); | 
 |  | 
 |   if (need_paren) | 
 |     (*info->fprintf_func) (info->stream, ")"); | 
 | } | 
 |  | 
 | int | 
 | print_insn_d10v (bfd_vma memaddr, struct disassemble_info *info) | 
 | { | 
 |   int status; | 
 |   bfd_byte buffer[4]; | 
 |   unsigned long insn; | 
 |  | 
 |   status = (*info->read_memory_func) (memaddr, buffer, 4, info); | 
 |   if (status != 0) | 
 |     { | 
 |       (*info->memory_error_func) (status, memaddr, info); | 
 |       return -1; | 
 |     } | 
 |   insn = bfd_getb32 (buffer); | 
 |  | 
 |   status = insn & FM11; | 
 |   switch (status) | 
 |     { | 
 |     case 0: | 
 |       dis_2_short (insn, memaddr, info, 2); | 
 |       break; | 
 |     case FM01: | 
 |       dis_2_short (insn, memaddr, info, 0); | 
 |       break; | 
 |     case FM10: | 
 |       dis_2_short (insn, memaddr, info, 1); | 
 |       break; | 
 |     case FM11: | 
 |       dis_long (insn, memaddr, info); | 
 |       break; | 
 |     } | 
 |   return 4; | 
 | } |