| /* TI PRU disassemble routines | 
 |    Copyright (C) 2014-2024 Free Software Foundation, Inc. | 
 |    Contributed by Dimitar Dimitrov <dimitar@dinux.eu> | 
 |  | 
 |    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 "disassemble.h" | 
 | #include "opcode/pru.h" | 
 | #include "libiberty.h" | 
 | #include <string.h> | 
 | #include <assert.h> | 
 |  | 
 | /* No symbol table is available when this code runs out in an embedded | 
 |    system as when it is used for disassembler support in a monitor.  */ | 
 | #if !defined (EMBEDDED_ENV) | 
 | #define SYMTAB_AVAILABLE 1 | 
 | #include "elf-bfd.h" | 
 | #include "elf/pru.h" | 
 | #endif | 
 |  | 
 | /* Length of PRU instruction in bytes.  */ | 
 | #define INSNLEN 4 | 
 |  | 
 | /* Return a pointer to an pru_opcode struct for a given instruction | 
 |    opcode, or NULL if there is an error.  */ | 
 | const struct pru_opcode * | 
 | pru_find_opcode (unsigned long opcode) | 
 | { | 
 |   const struct pru_opcode *p; | 
 |   const struct pru_opcode *op = NULL; | 
 |   const struct pru_opcode *pseudo_op = NULL; | 
 |  | 
 |   for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) | 
 |     { | 
 |       if ((p->mask & opcode) == p->match) | 
 | 	{ | 
 | 	  if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) | 
 | 	    pseudo_op = p; | 
 | 	  else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) | 
 | 	    /* ignore - should be caught with regular patterns */; | 
 | 	  else | 
 | 	    op = p; | 
 | 	} | 
 |     } | 
 |  | 
 |   return pseudo_op ? pseudo_op : op; | 
 | } | 
 |  | 
 | /* There are 32 regular registers, each with 8 possible subfield selectors.  */ | 
 | #define NUMREGNAMES (32 * 8) | 
 |  | 
 | static void | 
 | pru_print_insn_arg_reg (unsigned int r, unsigned int sel, | 
 | 			disassemble_info *info) | 
 | { | 
 |   unsigned int i = r * RSEL_NUM_ITEMS + sel; | 
 |   assert (i < (unsigned int)pru_num_regs); | 
 |   assert (i < NUMREGNAMES); | 
 |   (*info->fprintf_func) (info->stream, "%s", pru_regs[i].name); | 
 | } | 
 |  | 
 | /* The function pru_print_insn_arg uses the character pointed | 
 |    to by ARGPTR to determine how it print the next token or separator | 
 |    character in the arguments to an instruction.  */ | 
 | static int | 
 | pru_print_insn_arg (const char *argptr, | 
 | 		      unsigned long opcode, bfd_vma address, | 
 | 		      disassemble_info *info) | 
 | { | 
 |   long offs = 0; | 
 |   unsigned long i = 0; | 
 |   unsigned long io = 0; | 
 |  | 
 |   switch (*argptr) | 
 |     { | 
 |     case ',': | 
 |       (*info->fprintf_func) (info->stream, "%c ", *argptr); | 
 |       break; | 
 |     case 'd': | 
 |       pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), | 
 | 			      GET_INSN_FIELD (RDSEL, opcode), | 
 | 			      info); | 
 |       break; | 
 |     case 'D': | 
 |       /* The first 4 values for RDB and RSEL are the same, so we | 
 | 	 can reuse some code.  */ | 
 |       pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), | 
 | 			      GET_INSN_FIELD (RDB, opcode), | 
 | 			      info); | 
 |       break; | 
 |     case 's': | 
 |       pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), | 
 | 			      GET_INSN_FIELD (RS1SEL, opcode), | 
 | 			      info); | 
 |       break; | 
 |     case 'S': | 
 |       pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), | 
 | 			      RSEL_31_0, | 
 | 			      info); | 
 |       break; | 
 |     case 'b': | 
 |       io = GET_INSN_FIELD (IO, opcode); | 
 |  | 
 |       if (io) | 
 | 	{ | 
 | 	  i = GET_INSN_FIELD (IMM8, opcode); | 
 | 	  (*info->fprintf_func) (info->stream, "%ld", i); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
 | 				GET_INSN_FIELD (RS2SEL, opcode), | 
 | 				info); | 
 | 	} | 
 |       break; | 
 |     case 'B': | 
 |       io = GET_INSN_FIELD (IO, opcode); | 
 |  | 
 |       if (io) | 
 | 	{ | 
 | 	  i = GET_INSN_FIELD (IMM8, opcode) + 1; | 
 | 	  (*info->fprintf_func) (info->stream, "%ld", i); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
 | 				GET_INSN_FIELD (RS2SEL, opcode), | 
 | 				info); | 
 | 	} | 
 |       break; | 
 |     case 'j': | 
 |       io = GET_INSN_FIELD (IO, opcode); | 
 |  | 
 |       if (io) | 
 | 	{ | 
 | 	  /* For the sake of pretty-printing, dump text addresses with | 
 | 	     their "virtual" offset that we use for distinguishing | 
 | 	     PMEM vs DMEM. This is needed for printing the correct text | 
 | 	     labels.  */ | 
 | 	  bfd_vma text_offset = address & ~0x3fffff; | 
 | 	  i = GET_INSN_FIELD (IMM16, opcode) * 4; | 
 | 	  (*info->print_address_func) (i + text_offset, info); | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), | 
 | 				GET_INSN_FIELD (RS2SEL, opcode), | 
 | 				info); | 
 | 	} | 
 |       break; | 
 |     case 'W': | 
 |       i = GET_INSN_FIELD (IMM16, opcode); | 
 |       (*info->fprintf_func) (info->stream, "%ld", i); | 
 |       break; | 
 |     case 'o': | 
 |       offs = GET_BROFF_SIGNED (opcode) * 4; | 
 |       (*info->print_address_func) (address + offs, info); | 
 |       break; | 
 |     case 'O': | 
 |       offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; | 
 |       (*info->print_address_func) (address + offs, info); | 
 |       break; | 
 |     case 'l': | 
 |       i = GET_BURSTLEN (opcode); | 
 |       if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) | 
 | 	(*info->fprintf_func) (info->stream, "%ld", i + 1); | 
 |       else | 
 | 	{ | 
 | 	  i -= LSSBBO_BYTECOUNT_R0_BITS7_0; | 
 | 	  (*info->fprintf_func) (info->stream, "r0.b%ld", i); | 
 | 	} | 
 |       break; | 
 |     case 'n': | 
 |       i = GET_INSN_FIELD (XFR_LENGTH, opcode); | 
 |       if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) | 
 | 	(*info->fprintf_func) (info->stream, "%ld", i + 1); | 
 |       else | 
 | 	{ | 
 | 	  i -= LSSBBO_BYTECOUNT_R0_BITS7_0; | 
 | 	  (*info->fprintf_func) (info->stream, "r0.b%ld", i); | 
 | 	} | 
 |       break; | 
 |     case 'c': | 
 |       i = GET_INSN_FIELD (CB, opcode); | 
 |       (*info->fprintf_func) (info->stream, "%ld", i); | 
 |       break; | 
 |     case 'w': | 
 |       i = GET_INSN_FIELD (WAKEONSTATUS, opcode); | 
 |       (*info->fprintf_func) (info->stream, "%ld", i); | 
 |       break; | 
 |     case 'x': | 
 |       i = GET_INSN_FIELD (XFR_WBA, opcode); | 
 |       (*info->fprintf_func) (info->stream, "%ld", i); | 
 |       break; | 
 |     default: | 
 |       (*info->fprintf_func) (info->stream, "unknown"); | 
 |       break; | 
 |     } | 
 |   return 0; | 
 | } | 
 |  | 
 | /* pru_disassemble does all the work of disassembling a PRU | 
 |    instruction opcode.  */ | 
 | static int | 
 | pru_disassemble (bfd_vma address, unsigned long opcode, | 
 | 		   disassemble_info *info) | 
 | { | 
 |   const struct pru_opcode *op; | 
 |  | 
 |   info->bytes_per_line = INSNLEN; | 
 |   info->bytes_per_chunk = INSNLEN; | 
 |   info->display_endian = info->endian; | 
 |   info->insn_info_valid = 1; | 
 |   info->branch_delay_insns = 0; | 
 |   info->data_size = 0; | 
 |   info->insn_type = dis_nonbranch; | 
 |   info->target = 0; | 
 |   info->target2 = 0; | 
 |  | 
 |   /* Find the major opcode and use this to disassemble | 
 |      the instruction and its arguments.  */ | 
 |   op = pru_find_opcode (opcode); | 
 |  | 
 |   if (op != NULL) | 
 |     { | 
 |       (*info->fprintf_func) (info->stream, "%s", op->name); | 
 |  | 
 |       const char *argstr = op->args; | 
 |       if (argstr != NULL && *argstr != '\0') | 
 | 	{ | 
 | 	  (*info->fprintf_func) (info->stream, "\t"); | 
 | 	  while (*argstr != '\0') | 
 | 	    { | 
 | 	      pru_print_insn_arg (argstr, opcode, address, info); | 
 | 	      ++argstr; | 
 | 	    } | 
 | 	} | 
 |     } | 
 |   else | 
 |     { | 
 |       /* Handle undefined instructions.  */ | 
 |       info->insn_type = dis_noninsn; | 
 |       (*info->fprintf_func) (info->stream, "0x%lx", opcode); | 
 |     } | 
 |   /* Tell the caller how far to advance the program counter.  */ | 
 |   return INSNLEN; | 
 | } | 
 |  | 
 |  | 
 | /* print_insn_pru is the main disassemble function for PRU.  */ | 
 | int | 
 | print_insn_pru (bfd_vma address, disassemble_info *info) | 
 | { | 
 |   bfd_byte buffer[INSNLEN]; | 
 |   int status; | 
 |  | 
 |   status = (*info->read_memory_func) (address, buffer, INSNLEN, info); | 
 |   if (status == 0) | 
 |     { | 
 |       unsigned long insn; | 
 |       insn = (unsigned long) bfd_getl32 (buffer); | 
 |       status = pru_disassemble (address, insn, info); | 
 |     } | 
 |   else | 
 |     { | 
 |       (*info->memory_error_func) (status, address, info); | 
 |       status = -1; | 
 |     } | 
 |   return status; | 
 | } |