/src/binutils-gdb/opcodes/pru-dis.c
Line | Count | Source |
1 | | /* TI PRU disassemble routines |
2 | | Copyright (C) 2014-2026 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 | 268k | #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 | 67.0k | { |
45 | 67.0k | const struct pru_opcode *p; |
46 | 67.0k | const struct pru_opcode *op = NULL; |
47 | 67.0k | const struct pru_opcode *pseudo_op = NULL; |
48 | | |
49 | 3.82M | for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) |
50 | 3.75M | { |
51 | 3.75M | if ((p->mask & opcode) == p->match) |
52 | 59.2k | { |
53 | 59.2k | if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) |
54 | 197 | pseudo_op = p; |
55 | 59.0k | else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) |
56 | 1.37k | /* ignore - should be caught with regular patterns */; |
57 | 57.6k | else |
58 | 57.6k | op = p; |
59 | 59.2k | } |
60 | 3.75M | } |
61 | | |
62 | 67.0k | return pseudo_op ? pseudo_op : op; |
63 | 67.0k | } |
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_indreg (unsigned int r, unsigned int sel, |
70 | | unsigned int mode, |
71 | | disassemble_info *info) |
72 | 109k | { |
73 | 109k | const char *fmtstr; |
74 | 109k | unsigned int i = r * RSEL_NUM_ITEMS + sel; |
75 | 109k | assert (i < (unsigned int)pru_num_regs); |
76 | 109k | assert (i < NUMREGNAMES); |
77 | | |
78 | 109k | switch (mode) |
79 | 109k | { |
80 | 108k | case MVI_OP_MODE_DIRECT: fmtstr = "%s"; break; |
81 | 378 | case MVI_OP_MODE_INDIRECT: fmtstr = "*%s"; break; |
82 | 189 | case MVI_OP_MODE_INDIRECT_POSTINC: fmtstr = "*%s++"; break; |
83 | 61 | case MVI_OP_MODE_INDIRECT_PREDEC: fmtstr = "*--%s"; break; |
84 | 0 | default: fmtstr = "<invalid>%s"; break; |
85 | 109k | } |
86 | 109k | (*info->fprintf_func) (info->stream, fmtstr, pru_regs[i].name); |
87 | 109k | } |
88 | | |
89 | | static void |
90 | | pru_print_insn_arg_reg (unsigned int r, unsigned int sel, |
91 | | disassemble_info *info) |
92 | 108k | { |
93 | 108k | pru_print_insn_arg_indreg (r, sel, MVI_OP_MODE_DIRECT, info); |
94 | 108k | } |
95 | | |
96 | | /* The function pru_print_insn_arg uses the character pointed |
97 | | to by ARGPTR to determine how it print the next token or separator |
98 | | character in the arguments to an instruction. */ |
99 | | static int |
100 | | pru_print_insn_arg (const char *argptr, |
101 | | unsigned long opcode, bfd_vma address, |
102 | | disassemble_info *info) |
103 | 285k | { |
104 | 285k | long offs = 0; |
105 | 285k | unsigned long i = 0; |
106 | 285k | unsigned long io = 0; |
107 | | |
108 | 285k | switch (*argptr) |
109 | 285k | { |
110 | 114k | case ',': |
111 | 114k | (*info->fprintf_func) (info->stream, "%c ", *argptr); |
112 | 114k | break; |
113 | 21.9k | case 'd': |
114 | 21.9k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
115 | 21.9k | GET_INSN_FIELD (RDSEL, opcode), |
116 | 21.9k | info); |
117 | 21.9k | break; |
118 | 15.6k | case 'D': |
119 | | /* The first 4 values for RDB and RSEL are the same, so we |
120 | | can reuse some code. */ |
121 | 15.6k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
122 | 15.6k | GET_INSN_FIELD (RDB, opcode), |
123 | 15.6k | info); |
124 | 15.6k | break; |
125 | 32.3k | case 's': |
126 | 32.3k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
127 | 32.3k | GET_INSN_FIELD (RS1SEL, opcode), |
128 | 32.3k | info); |
129 | 32.3k | break; |
130 | 11.6k | case 'S': |
131 | 11.6k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
132 | 11.6k | RSEL_31_0, |
133 | 11.6k | info); |
134 | 11.6k | break; |
135 | 47.1k | case 'b': |
136 | 47.1k | io = GET_INSN_FIELD (IO, opcode); |
137 | | |
138 | 47.1k | if (io) |
139 | 21.8k | { |
140 | 21.8k | i = GET_INSN_FIELD (IMM8, opcode); |
141 | 21.8k | (*info->fprintf_func) (info->stream, "%ld", i); |
142 | 21.8k | } |
143 | 25.3k | else |
144 | 25.3k | { |
145 | 25.3k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
146 | 25.3k | GET_INSN_FIELD (RS2SEL, opcode), |
147 | 25.3k | info); |
148 | 25.3k | } |
149 | 47.1k | break; |
150 | 1.50k | case 'B': |
151 | 1.50k | io = GET_INSN_FIELD (IO, opcode); |
152 | | |
153 | 1.50k | if (io) |
154 | 754 | { |
155 | 754 | i = GET_INSN_FIELD (IMM8, opcode) + 1; |
156 | 754 | (*info->fprintf_func) (info->stream, "%ld", i); |
157 | 754 | } |
158 | 754 | else |
159 | 754 | { |
160 | 754 | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
161 | 754 | GET_INSN_FIELD (RS2SEL, opcode), |
162 | 754 | info); |
163 | 754 | } |
164 | 1.50k | break; |
165 | 1.58k | case 'j': |
166 | 1.58k | io = GET_INSN_FIELD (IO, opcode); |
167 | | |
168 | 1.58k | if (io) |
169 | 543 | { |
170 | | /* For the sake of pretty-printing, dump text addresses with |
171 | | their "virtual" offset that we use for distinguishing |
172 | | PMEM vs DMEM. This is needed for printing the correct text |
173 | | labels. */ |
174 | 543 | bfd_vma text_offset = address & ~0x3fffff; |
175 | 543 | i = GET_INSN_FIELD (IMM16, opcode) * 4; |
176 | 543 | (*info->print_address_func) (i + text_offset, info); |
177 | 543 | } |
178 | 1.04k | else |
179 | 1.04k | { |
180 | 1.04k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
181 | 1.04k | GET_INSN_FIELD (RS2SEL, opcode), |
182 | 1.04k | info); |
183 | 1.04k | } |
184 | 1.58k | break; |
185 | 1.37k | case 'W': |
186 | 1.37k | i = GET_INSN_FIELD (IMM16, opcode); |
187 | 1.37k | (*info->fprintf_func) (info->stream, "%ld", i); |
188 | 1.37k | break; |
189 | 13.1k | case 'o': |
190 | 13.1k | offs = GET_BROFF_SIGNED (opcode) * 4; |
191 | 13.1k | (*info->print_address_func) (address + offs, info); |
192 | 13.1k | break; |
193 | 1.50k | case 'O': |
194 | 1.50k | offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; |
195 | 1.50k | (*info->print_address_func) (address + offs, info); |
196 | 1.50k | break; |
197 | 14.9k | case 'l': |
198 | 14.9k | i = GET_BURSTLEN (opcode); |
199 | 14.9k | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
200 | 9.64k | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
201 | 5.30k | else |
202 | 5.30k | { |
203 | 5.30k | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
204 | 5.30k | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
205 | 5.30k | } |
206 | 14.9k | break; |
207 | 487 | case 'm': |
208 | 487 | pru_print_insn_arg_indreg (GET_INSN_FIELD (RD, opcode), |
209 | 487 | GET_INSN_FIELD (RDSEL, opcode), |
210 | 487 | GET_INSN_FIELD (MVI_RD_MODE, opcode), |
211 | 487 | info); |
212 | 487 | break; |
213 | 487 | case 'M': |
214 | 487 | pru_print_insn_arg_indreg (GET_INSN_FIELD (RS1, opcode), |
215 | 487 | GET_INSN_FIELD (RS1SEL, opcode), |
216 | 487 | GET_INSN_FIELD (MVI_RS1_MODE, opcode), |
217 | 487 | info); |
218 | 487 | break; |
219 | 728 | case 'n': |
220 | 728 | i = GET_INSN_FIELD (XFR_LENGTH, opcode); |
221 | 728 | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
222 | 493 | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
223 | 235 | else |
224 | 235 | { |
225 | 235 | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
226 | 235 | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
227 | 235 | } |
228 | 728 | break; |
229 | 3.33k | case 'c': |
230 | 3.33k | i = GET_INSN_FIELD (CB, opcode); |
231 | 3.33k | (*info->fprintf_func) (info->stream, "%ld", i); |
232 | 3.33k | break; |
233 | 765 | case 't': |
234 | 765 | i = GET_INSN_FIELD (TSKMGR_MODE, opcode); |
235 | 765 | (*info->fprintf_func) (info->stream, "%ld", i); |
236 | 765 | break; |
237 | 1.95k | case 'w': |
238 | 1.95k | i = GET_INSN_FIELD (WAKEONSTATUS, opcode); |
239 | 1.95k | (*info->fprintf_func) (info->stream, "%ld", i); |
240 | 1.95k | break; |
241 | 724 | case 'x': |
242 | 724 | i = GET_INSN_FIELD (XFR_WBA, opcode); |
243 | 724 | (*info->fprintf_func) (info->stream, "%ld", i); |
244 | 724 | break; |
245 | 0 | default: |
246 | 0 | (*info->fprintf_func) (info->stream, "unknown"); |
247 | 0 | break; |
248 | 285k | } |
249 | 285k | return 0; |
250 | 285k | } |
251 | | |
252 | | /* pru_disassemble does all the work of disassembling a PRU |
253 | | instruction opcode. */ |
254 | | static int |
255 | | pru_disassemble (bfd_vma address, unsigned long opcode, |
256 | | disassemble_info *info) |
257 | 67.0k | { |
258 | 67.0k | const struct pru_opcode *op; |
259 | | |
260 | 67.0k | info->bytes_per_line = INSNLEN; |
261 | 67.0k | info->bytes_per_chunk = INSNLEN; |
262 | 67.0k | info->display_endian = info->endian; |
263 | 67.0k | info->insn_info_valid = 1; |
264 | 67.0k | info->branch_delay_insns = 0; |
265 | 67.0k | info->data_size = 0; |
266 | 67.0k | info->insn_type = dis_nonbranch; |
267 | 67.0k | info->target = 0; |
268 | 67.0k | info->target2 = 0; |
269 | | |
270 | | /* Find the major opcode and use this to disassemble |
271 | | the instruction and its arguments. */ |
272 | 67.0k | op = pru_find_opcode (opcode); |
273 | | |
274 | 67.0k | if (op != NULL) |
275 | 57.6k | { |
276 | 57.6k | (*info->fprintf_func) (info->stream, "%s", op->name); |
277 | | |
278 | 57.6k | const char *argstr = op->args; |
279 | 57.6k | if (argstr != NULL && *argstr != '\0') |
280 | 56.9k | { |
281 | 56.9k | (*info->fprintf_func) (info->stream, "\t"); |
282 | 342k | while (*argstr != '\0') |
283 | 285k | { |
284 | 285k | pru_print_insn_arg (argstr, opcode, address, info); |
285 | 285k | ++argstr; |
286 | 285k | } |
287 | 56.9k | } |
288 | 57.6k | } |
289 | 9.45k | else |
290 | 9.45k | { |
291 | | /* Handle undefined instructions. */ |
292 | 9.45k | info->insn_type = dis_noninsn; |
293 | 9.45k | (*info->fprintf_func) (info->stream, "0x%lx", opcode); |
294 | 9.45k | } |
295 | | /* Tell the caller how far to advance the program counter. */ |
296 | 67.0k | return INSNLEN; |
297 | 67.0k | } |
298 | | |
299 | | |
300 | | /* print_insn_pru is the main disassemble function for PRU. */ |
301 | | int |
302 | | print_insn_pru (bfd_vma address, disassemble_info *info) |
303 | 67.2k | { |
304 | 67.2k | bfd_byte buffer[INSNLEN]; |
305 | 67.2k | int status; |
306 | | |
307 | 67.2k | status = (*info->read_memory_func) (address, buffer, INSNLEN, info); |
308 | 67.2k | if (status == 0) |
309 | 67.0k | { |
310 | 67.0k | unsigned long insn; |
311 | 67.0k | insn = (unsigned long) bfd_getl32 (buffer); |
312 | 67.0k | status = pru_disassemble (address, insn, info); |
313 | 67.0k | } |
314 | 114 | else |
315 | 114 | { |
316 | 114 | (*info->memory_error_func) (status, address, info); |
317 | 114 | status = -1; |
318 | 114 | } |
319 | 67.2k | return status; |
320 | 67.2k | } |