1 /* TI PRU disassemble routines 2 Copyright (C) 2014-2024 Free Software Foundation, Inc. 3 Contributed by Dimitar Dimitrov <dimitar@dinux.eu> 4 5 This file is part of the GNU opcodes library. 6 7 This library is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 3, or (at your option) 10 any later version. 11 12 It is distributed in the hope that it will be useful, but WITHOUT 13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 14 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 15 License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this file; see the file COPYING. If not, write to the 19 Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, 20 MA 02110-1301, USA. */ 21 22 #include "sysdep.h" 23 #include "disassemble.h" 24 #include "opcode/pru.h" 25 #include "libiberty.h" 26 #include <string.h> 27 #include <assert.h> 28 29 /* No symbol table is available when this code runs out in an embedded 30 system as when it is used for disassembler support in a monitor. */ 31 #if !defined (EMBEDDED_ENV) 32 #define SYMTAB_AVAILABLE 1 33 #include "elf-bfd.h" 34 #include "elf/pru.h" 35 #endif 36 37 /* Length of PRU instruction in bytes. */ 38 #define INSNLEN 4 39 40 /* Return a pointer to an pru_opcode struct for a given instruction 41 opcode, or NULL if there is an error. */ 42 const struct pru_opcode * 43 pru_find_opcode (unsigned long opcode) 44 { 45 const struct pru_opcode *p; 46 const struct pru_opcode *op = NULL; 47 const struct pru_opcode *pseudo_op = NULL; 48 49 for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) 50 { 51 if ((p->mask & opcode) == p->match) 52 { 53 if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) 54 pseudo_op = p; 55 else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) 56 /* ignore - should be caught with regular patterns */; 57 else 58 op = p; 59 } 60 } 61 62 return pseudo_op ? pseudo_op : op; 63 } 64 65 /* There are 32 regular registers, each with 8 possible subfield selectors. */ 66 #define NUMREGNAMES (32 * 8) 67 68 static void 69 pru_print_insn_arg_reg (unsigned int r, unsigned int sel, 70 disassemble_info *info) 71 { 72 unsigned int i = r * RSEL_NUM_ITEMS + sel; 73 assert (i < (unsigned int)pru_num_regs); 74 assert (i < NUMREGNAMES); 75 (*info->fprintf_func) (info->stream, "%s", pru_regs[i].name); 76 } 77 78 /* The function pru_print_insn_arg uses the character pointed 79 to by ARGPTR to determine how it print the next token or separator 80 character in the arguments to an instruction. */ 81 static int 82 pru_print_insn_arg (const char *argptr, 83 unsigned long opcode, bfd_vma address, 84 disassemble_info *info) 85 { 86 long offs = 0; 87 unsigned long i = 0; 88 unsigned long io = 0; 89 90 switch (*argptr) 91 { 92 case ',': 93 (*info->fprintf_func) (info->stream, "%c ", *argptr); 94 break; 95 case 'd': 96 pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), 97 GET_INSN_FIELD (RDSEL, opcode), 98 info); 99 break; 100 case 'D': 101 /* The first 4 values for RDB and RSEL are the same, so we 102 can reuse some code. */ 103 pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), 104 GET_INSN_FIELD (RDB, opcode), 105 info); 106 break; 107 case 's': 108 pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), 109 GET_INSN_FIELD (RS1SEL, opcode), 110 info); 111 break; 112 case 'S': 113 pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), 114 RSEL_31_0, 115 info); 116 break; 117 case 'b': 118 io = GET_INSN_FIELD (IO, opcode); 119 120 if (io) 121 { 122 i = GET_INSN_FIELD (IMM8, opcode); 123 (*info->fprintf_func) (info->stream, "%ld", i); 124 } 125 else 126 { 127 pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), 128 GET_INSN_FIELD (RS2SEL, opcode), 129 info); 130 } 131 break; 132 case 'B': 133 io = GET_INSN_FIELD (IO, opcode); 134 135 if (io) 136 { 137 i = GET_INSN_FIELD (IMM8, opcode) + 1; 138 (*info->fprintf_func) (info->stream, "%ld", i); 139 } 140 else 141 { 142 pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), 143 GET_INSN_FIELD (RS2SEL, opcode), 144 info); 145 } 146 break; 147 case 'j': 148 io = GET_INSN_FIELD (IO, opcode); 149 150 if (io) 151 { 152 /* For the sake of pretty-printing, dump text addresses with 153 their "virtual" offset that we use for distinguishing 154 PMEM vs DMEM. This is needed for printing the correct text 155 labels. */ 156 bfd_vma text_offset = address & ~0x3fffff; 157 i = GET_INSN_FIELD (IMM16, opcode) * 4; 158 (*info->print_address_func) (i + text_offset, info); 159 } 160 else 161 { 162 pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), 163 GET_INSN_FIELD (RS2SEL, opcode), 164 info); 165 } 166 break; 167 case 'W': 168 i = GET_INSN_FIELD (IMM16, opcode); 169 (*info->fprintf_func) (info->stream, "%ld", i); 170 break; 171 case 'o': 172 offs = GET_BROFF_SIGNED (opcode) * 4; 173 (*info->print_address_func) (address + offs, info); 174 break; 175 case 'O': 176 offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; 177 (*info->print_address_func) (address + offs, info); 178 break; 179 case 'l': 180 i = GET_BURSTLEN (opcode); 181 if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) 182 (*info->fprintf_func) (info->stream, "%ld", i + 1); 183 else 184 { 185 i -= LSSBBO_BYTECOUNT_R0_BITS7_0; 186 (*info->fprintf_func) (info->stream, "r0.b%ld", i); 187 } 188 break; 189 case 'n': 190 i = GET_INSN_FIELD (XFR_LENGTH, opcode); 191 if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) 192 (*info->fprintf_func) (info->stream, "%ld", i + 1); 193 else 194 { 195 i -= LSSBBO_BYTECOUNT_R0_BITS7_0; 196 (*info->fprintf_func) (info->stream, "r0.b%ld", i); 197 } 198 break; 199 case 'c': 200 i = GET_INSN_FIELD (CB, opcode); 201 (*info->fprintf_func) (info->stream, "%ld", i); 202 break; 203 case 'w': 204 i = GET_INSN_FIELD (WAKEONSTATUS, opcode); 205 (*info->fprintf_func) (info->stream, "%ld", i); 206 break; 207 case 'x': 208 i = GET_INSN_FIELD (XFR_WBA, opcode); 209 (*info->fprintf_func) (info->stream, "%ld", i); 210 break; 211 default: 212 (*info->fprintf_func) (info->stream, "unknown"); 213 break; 214 } 215 return 0; 216 } 217 218 /* pru_disassemble does all the work of disassembling a PRU 219 instruction opcode. */ 220 static int 221 pru_disassemble (bfd_vma address, unsigned long opcode, 222 disassemble_info *info) 223 { 224 const struct pru_opcode *op; 225 226 info->bytes_per_line = INSNLEN; 227 info->bytes_per_chunk = INSNLEN; 228 info->display_endian = info->endian; 229 info->insn_info_valid = 1; 230 info->branch_delay_insns = 0; 231 info->data_size = 0; 232 info->insn_type = dis_nonbranch; 233 info->target = 0; 234 info->target2 = 0; 235 236 /* Find the major opcode and use this to disassemble 237 the instruction and its arguments. */ 238 op = pru_find_opcode (opcode); 239 240 if (op != NULL) 241 { 242 (*info->fprintf_func) (info->stream, "%s", op->name); 243 244 const char *argstr = op->args; 245 if (argstr != NULL && *argstr != '\0') 246 { 247 (*info->fprintf_func) (info->stream, "\t"); 248 while (*argstr != '\0') 249 { 250 pru_print_insn_arg (argstr, opcode, address, info); 251 ++argstr; 252 } 253 } 254 } 255 else 256 { 257 /* Handle undefined instructions. */ 258 info->insn_type = dis_noninsn; 259 (*info->fprintf_func) (info->stream, "0x%lx", opcode); 260 } 261 /* Tell the caller how far to advance the program counter. */ 262 return INSNLEN; 263 } 264 265 266 /* print_insn_pru is the main disassemble function for PRU. */ 267 int 268 print_insn_pru (bfd_vma address, disassemble_info *info) 269 { 270 bfd_byte buffer[INSNLEN]; 271 int status; 272 273 status = (*info->read_memory_func) (address, buffer, INSNLEN, info); 274 if (status == 0) 275 { 276 unsigned long insn; 277 insn = (unsigned long) bfd_getl32 (buffer); 278 status = pru_disassemble (address, insn, info); 279 } 280 else 281 { 282 (*info->memory_error_func) (status, address, info); 283 status = -1; 284 } 285 return status; 286 } 287