/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 | 152k | #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 | 38.0k | { |
45 | 38.0k | const struct pru_opcode *p; |
46 | 38.0k | const struct pru_opcode *op = NULL; |
47 | 38.0k | const struct pru_opcode *pseudo_op = NULL; |
48 | | |
49 | 2.16M | for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) |
50 | 2.12M | { |
51 | 2.12M | if ((p->mask & opcode) == p->match) |
52 | 32.4k | { |
53 | 32.4k | if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) |
54 | 127 | pseudo_op = p; |
55 | 32.2k | else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) |
56 | 257 | /* ignore - should be caught with regular patterns */; |
57 | 32.0k | else |
58 | 32.0k | op = p; |
59 | 32.4k | } |
60 | 2.12M | } |
61 | | |
62 | 38.0k | return pseudo_op ? pseudo_op : op; |
63 | 38.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 | 63.1k | { |
73 | 63.1k | const char *fmtstr; |
74 | 63.1k | unsigned int i = r * RSEL_NUM_ITEMS + sel; |
75 | 63.1k | assert (i < (unsigned int)pru_num_regs); |
76 | 63.1k | assert (i < NUMREGNAMES); |
77 | | |
78 | 63.1k | switch (mode) |
79 | 63.1k | { |
80 | 62.9k | case MVI_OP_MODE_DIRECT: fmtstr = "%s"; break; |
81 | 68 | case MVI_OP_MODE_INDIRECT: fmtstr = "*%s"; break; |
82 | 37 | case MVI_OP_MODE_INDIRECT_POSTINC: fmtstr = "*%s++"; break; |
83 | 66 | case MVI_OP_MODE_INDIRECT_PREDEC: fmtstr = "*--%s"; break; |
84 | 0 | default: fmtstr = "<invalid>%s"; break; |
85 | 63.1k | } |
86 | 63.1k | (*info->fprintf_func) (info->stream, fmtstr, pru_regs[i].name); |
87 | 63.1k | } |
88 | | |
89 | | static void |
90 | | pru_print_insn_arg_reg (unsigned int r, unsigned int sel, |
91 | | disassemble_info *info) |
92 | 62.8k | { |
93 | 62.8k | pru_print_insn_arg_indreg (r, sel, MVI_OP_MODE_DIRECT, info); |
94 | 62.8k | } |
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 | 167k | { |
104 | 167k | long offs = 0; |
105 | 167k | unsigned long i = 0; |
106 | 167k | unsigned long io = 0; |
107 | | |
108 | 167k | switch (*argptr) |
109 | 167k | { |
110 | 67.9k | case ',': |
111 | 67.9k | (*info->fprintf_func) (info->stream, "%c ", *argptr); |
112 | 67.9k | break; |
113 | 10.9k | case 'd': |
114 | 10.9k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
115 | 10.9k | GET_INSN_FIELD (RDSEL, opcode), |
116 | 10.9k | info); |
117 | 10.9k | break; |
118 | 10.9k | case 'D': |
119 | | /* The first 4 values for RDB and RSEL are the same, so we |
120 | | can reuse some code. */ |
121 | 10.9k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
122 | 10.9k | GET_INSN_FIELD (RDB, opcode), |
123 | 10.9k | info); |
124 | 10.9k | break; |
125 | 16.8k | case 's': |
126 | 16.8k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
127 | 16.8k | GET_INSN_FIELD (RS1SEL, opcode), |
128 | 16.8k | info); |
129 | 16.8k | break; |
130 | 8.23k | case 'S': |
131 | 8.23k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
132 | 8.23k | RSEL_31_0, |
133 | 8.23k | info); |
134 | 8.23k | break; |
135 | 27.4k | case 'b': |
136 | 27.4k | io = GET_INSN_FIELD (IO, opcode); |
137 | | |
138 | 27.4k | if (io) |
139 | 12.7k | { |
140 | 12.7k | i = GET_INSN_FIELD (IMM8, opcode); |
141 | 12.7k | (*info->fprintf_func) (info->stream, "%ld", i); |
142 | 12.7k | } |
143 | 14.7k | else |
144 | 14.7k | { |
145 | 14.7k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
146 | 14.7k | GET_INSN_FIELD (RS2SEL, opcode), |
147 | 14.7k | info); |
148 | 14.7k | } |
149 | 27.4k | break; |
150 | 762 | case 'B': |
151 | 762 | io = GET_INSN_FIELD (IO, opcode); |
152 | | |
153 | 762 | if (io) |
154 | 257 | { |
155 | 257 | i = GET_INSN_FIELD (IMM8, opcode) + 1; |
156 | 257 | (*info->fprintf_func) (info->stream, "%ld", i); |
157 | 257 | } |
158 | 505 | else |
159 | 505 | { |
160 | 505 | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
161 | 505 | GET_INSN_FIELD (RS2SEL, opcode), |
162 | 505 | info); |
163 | 505 | } |
164 | 762 | break; |
165 | 1.41k | case 'j': |
166 | 1.41k | io = GET_INSN_FIELD (IO, opcode); |
167 | | |
168 | 1.41k | if (io) |
169 | 675 | { |
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 | 675 | bfd_vma text_offset = address & ~0x3fffff; |
175 | 675 | i = GET_INSN_FIELD (IMM16, opcode) * 4; |
176 | 675 | (*info->print_address_func) (i + text_offset, info); |
177 | 675 | } |
178 | 739 | else |
179 | 739 | { |
180 | 739 | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
181 | 739 | GET_INSN_FIELD (RS2SEL, opcode), |
182 | 739 | info); |
183 | 739 | } |
184 | 1.41k | break; |
185 | 257 | case 'W': |
186 | 257 | i = GET_INSN_FIELD (IMM16, opcode); |
187 | 257 | (*info->fprintf_func) (info->stream, "%ld", i); |
188 | 257 | break; |
189 | 7.31k | case 'o': |
190 | 7.31k | offs = GET_BROFF_SIGNED (opcode) * 4; |
191 | 7.31k | (*info->print_address_func) (address + offs, info); |
192 | 7.31k | break; |
193 | 762 | case 'O': |
194 | 762 | offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; |
195 | 762 | (*info->print_address_func) (address + offs, info); |
196 | 762 | break; |
197 | 10.6k | case 'l': |
198 | 10.6k | i = GET_BURSTLEN (opcode); |
199 | 10.6k | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
200 | 5.94k | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
201 | 4.72k | else |
202 | 4.72k | { |
203 | 4.72k | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
204 | 4.72k | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
205 | 4.72k | } |
206 | 10.6k | break; |
207 | 128 | case 'm': |
208 | 128 | pru_print_insn_arg_indreg (GET_INSN_FIELD (RD, opcode), |
209 | 128 | GET_INSN_FIELD (RDSEL, opcode), |
210 | 128 | GET_INSN_FIELD (MVI_RD_MODE, opcode), |
211 | 128 | info); |
212 | 128 | break; |
213 | 128 | case 'M': |
214 | 128 | pru_print_insn_arg_indreg (GET_INSN_FIELD (RS1, opcode), |
215 | 128 | GET_INSN_FIELD (RS1SEL, opcode), |
216 | 128 | GET_INSN_FIELD (MVI_RS1_MODE, opcode), |
217 | 128 | info); |
218 | 128 | break; |
219 | 311 | case 'n': |
220 | 311 | i = GET_INSN_FIELD (XFR_LENGTH, opcode); |
221 | 311 | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
222 | 223 | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
223 | 88 | else |
224 | 88 | { |
225 | 88 | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
226 | 88 | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
227 | 88 | } |
228 | 311 | break; |
229 | 2.43k | case 'c': |
230 | 2.43k | i = GET_INSN_FIELD (CB, opcode); |
231 | 2.43k | (*info->fprintf_func) (info->stream, "%ld", i); |
232 | 2.43k | break; |
233 | 522 | case 't': |
234 | 522 | i = GET_INSN_FIELD (TSKMGR_MODE, opcode); |
235 | 522 | (*info->fprintf_func) (info->stream, "%ld", i); |
236 | 522 | break; |
237 | 336 | case 'w': |
238 | 336 | i = GET_INSN_FIELD (WAKEONSTATUS, opcode); |
239 | 336 | (*info->fprintf_func) (info->stream, "%ld", i); |
240 | 336 | break; |
241 | 309 | case 'x': |
242 | 309 | i = GET_INSN_FIELD (XFR_WBA, opcode); |
243 | 309 | (*info->fprintf_func) (info->stream, "%ld", i); |
244 | 309 | break; |
245 | 0 | default: |
246 | 0 | (*info->fprintf_func) (info->stream, "unknown"); |
247 | 0 | break; |
248 | 167k | } |
249 | 167k | return 0; |
250 | 167k | } |
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 | 38.0k | { |
258 | 38.0k | const struct pru_opcode *op; |
259 | | |
260 | 38.0k | info->bytes_per_line = INSNLEN; |
261 | 38.0k | info->bytes_per_chunk = INSNLEN; |
262 | 38.0k | info->display_endian = info->endian; |
263 | 38.0k | info->insn_info_valid = 1; |
264 | 38.0k | info->branch_delay_insns = 0; |
265 | 38.0k | info->data_size = 0; |
266 | 38.0k | info->insn_type = dis_nonbranch; |
267 | 38.0k | info->target = 0; |
268 | 38.0k | info->target2 = 0; |
269 | | |
270 | | /* Find the major opcode and use this to disassemble |
271 | | the instruction and its arguments. */ |
272 | 38.0k | op = pru_find_opcode (opcode); |
273 | | |
274 | 38.0k | if (op != NULL) |
275 | 32.0k | { |
276 | 32.0k | (*info->fprintf_func) (info->stream, "%s", op->name); |
277 | | |
278 | 32.0k | const char *argstr = op->args; |
279 | 32.0k | if (argstr != NULL && *argstr != '\0') |
280 | 31.7k | { |
281 | 31.7k | (*info->fprintf_func) (info->stream, "\t"); |
282 | 199k | while (*argstr != '\0') |
283 | 167k | { |
284 | 167k | pru_print_insn_arg (argstr, opcode, address, info); |
285 | 167k | ++argstr; |
286 | 167k | } |
287 | 31.7k | } |
288 | 32.0k | } |
289 | 6.00k | else |
290 | 6.00k | { |
291 | | /* Handle undefined instructions. */ |
292 | 6.00k | info->insn_type = dis_noninsn; |
293 | 6.00k | (*info->fprintf_func) (info->stream, "0x%lx", opcode); |
294 | 6.00k | } |
295 | | /* Tell the caller how far to advance the program counter. */ |
296 | 38.0k | return INSNLEN; |
297 | 38.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 | 38.0k | { |
304 | 38.0k | bfd_byte buffer[INSNLEN]; |
305 | 38.0k | int status; |
306 | | |
307 | 38.0k | status = (*info->read_memory_func) (address, buffer, INSNLEN, info); |
308 | 38.0k | if (status == 0) |
309 | 38.0k | { |
310 | 38.0k | unsigned long insn; |
311 | 38.0k | insn = (unsigned long) bfd_getl32 (buffer); |
312 | 38.0k | status = pru_disassemble (address, insn, info); |
313 | 38.0k | } |
314 | 57 | else |
315 | 57 | { |
316 | 57 | (*info->memory_error_func) (status, address, info); |
317 | 57 | status = -1; |
318 | 57 | } |
319 | 38.0k | return status; |
320 | 38.0k | } |