/src/binutils-gdb/opcodes/pru-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* TI PRU disassemble routines |
2 | | Copyright (C) 2014-2025 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 | 95.0k | #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 | 23.7k | { |
45 | 23.7k | const struct pru_opcode *p; |
46 | 23.7k | const struct pru_opcode *op = NULL; |
47 | 23.7k | const struct pru_opcode *pseudo_op = NULL; |
48 | | |
49 | 1.25M | for (p = pru_opcodes; p < &pru_opcodes[NUMOPCODES]; p++) |
50 | 1.23M | { |
51 | 1.23M | if ((p->mask & opcode) == p->match) |
52 | 20.7k | { |
53 | 20.7k | if ((p->pinfo & PRU_INSN_MACRO) == PRU_INSN_MACRO) |
54 | 91 | pseudo_op = p; |
55 | 20.7k | else if ((p->pinfo & PRU_INSN_LDI32) == PRU_INSN_LDI32) |
56 | 287 | /* ignore - should be caught with regular patterns */; |
57 | 20.4k | else |
58 | 20.4k | op = p; |
59 | 20.7k | } |
60 | 1.23M | } |
61 | | |
62 | 23.7k | return pseudo_op ? pseudo_op : op; |
63 | 23.7k | } |
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 | 37.9k | { |
72 | 37.9k | unsigned int i = r * RSEL_NUM_ITEMS + sel; |
73 | 37.9k | assert (i < (unsigned int)pru_num_regs); |
74 | 37.9k | assert (i < NUMREGNAMES); |
75 | 37.9k | (*info->fprintf_func) (info->stream, "%s", pru_regs[i].name); |
76 | 37.9k | } |
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 | 103k | { |
86 | 103k | long offs = 0; |
87 | 103k | unsigned long i = 0; |
88 | 103k | unsigned long io = 0; |
89 | | |
90 | 103k | switch (*argptr) |
91 | 103k | { |
92 | 41.6k | case ',': |
93 | 41.6k | (*info->fprintf_func) (info->stream, "%c ", *argptr); |
94 | 41.6k | break; |
95 | 6.54k | case 'd': |
96 | 6.54k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
97 | 6.54k | GET_INSN_FIELD (RDSEL, opcode), |
98 | 6.54k | info); |
99 | 6.54k | break; |
100 | 7.07k | case 'D': |
101 | | /* The first 4 values for RDB and RSEL are the same, so we |
102 | | can reuse some code. */ |
103 | 7.07k | pru_print_insn_arg_reg (GET_INSN_FIELD (RD, opcode), |
104 | 7.07k | GET_INSN_FIELD (RDB, opcode), |
105 | 7.07k | info); |
106 | 7.07k | break; |
107 | 9.97k | case 's': |
108 | 9.97k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
109 | 9.97k | GET_INSN_FIELD (RS1SEL, opcode), |
110 | 9.97k | info); |
111 | 9.97k | break; |
112 | 4.87k | case 'S': |
113 | 4.87k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS1, opcode), |
114 | 4.87k | RSEL_31_0, |
115 | 4.87k | info); |
116 | 4.87k | break; |
117 | 16.4k | case 'b': |
118 | 16.4k | io = GET_INSN_FIELD (IO, opcode); |
119 | | |
120 | 16.4k | if (io) |
121 | 7.72k | { |
122 | 7.72k | i = GET_INSN_FIELD (IMM8, opcode); |
123 | 7.72k | (*info->fprintf_func) (info->stream, "%ld", i); |
124 | 7.72k | } |
125 | 8.72k | else |
126 | 8.72k | { |
127 | 8.72k | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
128 | 8.72k | GET_INSN_FIELD (RS2SEL, opcode), |
129 | 8.72k | info); |
130 | 8.72k | } |
131 | 16.4k | break; |
132 | 736 | case 'B': |
133 | 736 | io = GET_INSN_FIELD (IO, opcode); |
134 | | |
135 | 736 | if (io) |
136 | 346 | { |
137 | 346 | i = GET_INSN_FIELD (IMM8, opcode) + 1; |
138 | 346 | (*info->fprintf_func) (info->stream, "%ld", i); |
139 | 346 | } |
140 | 390 | else |
141 | 390 | { |
142 | 390 | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
143 | 390 | GET_INSN_FIELD (RS2SEL, opcode), |
144 | 390 | info); |
145 | 390 | } |
146 | 736 | break; |
147 | 1.05k | case 'j': |
148 | 1.05k | io = GET_INSN_FIELD (IO, opcode); |
149 | | |
150 | 1.05k | if (io) |
151 | 684 | { |
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 | 684 | bfd_vma text_offset = address & ~0x3fffff; |
157 | 684 | i = GET_INSN_FIELD (IMM16, opcode) * 4; |
158 | 684 | (*info->print_address_func) (i + text_offset, info); |
159 | 684 | } |
160 | 373 | else |
161 | 373 | { |
162 | 373 | pru_print_insn_arg_reg (GET_INSN_FIELD (RS2, opcode), |
163 | 373 | GET_INSN_FIELD (RS2SEL, opcode), |
164 | 373 | info); |
165 | 373 | } |
166 | 1.05k | break; |
167 | 287 | case 'W': |
168 | 287 | i = GET_INSN_FIELD (IMM16, opcode); |
169 | 287 | (*info->fprintf_func) (info->stream, "%ld", i); |
170 | 287 | break; |
171 | 4.46k | case 'o': |
172 | 4.46k | offs = GET_BROFF_SIGNED (opcode) * 4; |
173 | 4.46k | (*info->print_address_func) (address + offs, info); |
174 | 4.46k | break; |
175 | 736 | case 'O': |
176 | 736 | offs = GET_INSN_FIELD (LOOP_JMPOFFS, opcode) * 4; |
177 | 736 | (*info->print_address_func) (address + offs, info); |
178 | 736 | break; |
179 | 6.50k | case 'l': |
180 | 6.50k | i = GET_BURSTLEN (opcode); |
181 | 6.50k | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
182 | 4.70k | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
183 | 1.80k | else |
184 | 1.80k | { |
185 | 1.80k | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
186 | 1.80k | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
187 | 1.80k | } |
188 | 6.50k | break; |
189 | 561 | case 'n': |
190 | 561 | i = GET_INSN_FIELD (XFR_LENGTH, opcode); |
191 | 561 | if (i < LSSBBO_BYTECOUNT_R0_BITS7_0) |
192 | 481 | (*info->fprintf_func) (info->stream, "%ld", i + 1); |
193 | 80 | else |
194 | 80 | { |
195 | 80 | i -= LSSBBO_BYTECOUNT_R0_BITS7_0; |
196 | 80 | (*info->fprintf_func) (info->stream, "r0.b%ld", i); |
197 | 80 | } |
198 | 561 | break; |
199 | 1.63k | case 'c': |
200 | 1.63k | i = GET_INSN_FIELD (CB, opcode); |
201 | 1.63k | (*info->fprintf_func) (info->stream, "%ld", i); |
202 | 1.63k | break; |
203 | 329 | case 'w': |
204 | 329 | i = GET_INSN_FIELD (WAKEONSTATUS, opcode); |
205 | 329 | (*info->fprintf_func) (info->stream, "%ld", i); |
206 | 329 | break; |
207 | 561 | case 'x': |
208 | 561 | i = GET_INSN_FIELD (XFR_WBA, opcode); |
209 | 561 | (*info->fprintf_func) (info->stream, "%ld", i); |
210 | 561 | break; |
211 | 0 | default: |
212 | 0 | (*info->fprintf_func) (info->stream, "unknown"); |
213 | 0 | break; |
214 | 103k | } |
215 | 103k | return 0; |
216 | 103k | } |
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 | 23.7k | { |
224 | 23.7k | const struct pru_opcode *op; |
225 | | |
226 | 23.7k | info->bytes_per_line = INSNLEN; |
227 | 23.7k | info->bytes_per_chunk = INSNLEN; |
228 | 23.7k | info->display_endian = info->endian; |
229 | 23.7k | info->insn_info_valid = 1; |
230 | 23.7k | info->branch_delay_insns = 0; |
231 | 23.7k | info->data_size = 0; |
232 | 23.7k | info->insn_type = dis_nonbranch; |
233 | 23.7k | info->target = 0; |
234 | 23.7k | info->target2 = 0; |
235 | | |
236 | | /* Find the major opcode and use this to disassemble |
237 | | the instruction and its arguments. */ |
238 | 23.7k | op = pru_find_opcode (opcode); |
239 | | |
240 | 23.7k | if (op != NULL) |
241 | 20.4k | { |
242 | 20.4k | (*info->fprintf_func) (info->stream, "%s", op->name); |
243 | | |
244 | 20.4k | const char *argstr = op->args; |
245 | 20.4k | if (argstr != NULL && *argstr != '\0') |
246 | 20.1k | { |
247 | 20.1k | (*info->fprintf_func) (info->stream, "\t"); |
248 | 123k | while (*argstr != '\0') |
249 | 103k | { |
250 | 103k | pru_print_insn_arg (argstr, opcode, address, info); |
251 | 103k | ++argstr; |
252 | 103k | } |
253 | 20.1k | } |
254 | 20.4k | } |
255 | 3.32k | else |
256 | 3.32k | { |
257 | | /* Handle undefined instructions. */ |
258 | 3.32k | info->insn_type = dis_noninsn; |
259 | 3.32k | (*info->fprintf_func) (info->stream, "0x%lx", opcode); |
260 | 3.32k | } |
261 | | /* Tell the caller how far to advance the program counter. */ |
262 | 23.7k | return INSNLEN; |
263 | 23.7k | } |
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 | 23.8k | { |
270 | 23.8k | bfd_byte buffer[INSNLEN]; |
271 | 23.8k | int status; |
272 | | |
273 | 23.8k | status = (*info->read_memory_func) (address, buffer, INSNLEN, info); |
274 | 23.8k | if (status == 0) |
275 | 23.7k | { |
276 | 23.7k | unsigned long insn; |
277 | 23.7k | insn = (unsigned long) bfd_getl32 (buffer); |
278 | 23.7k | status = pru_disassemble (address, insn, info); |
279 | 23.7k | } |
280 | 102 | else |
281 | 102 | { |
282 | 102 | (*info->memory_error_func) (status, address, info); |
283 | 102 | status = -1; |
284 | 102 | } |
285 | 23.8k | return status; |
286 | 23.8k | } |