Commit | Line | Data |
---|---|---|
11146849 | 1 | /* TI PRU disassemble routines |
250d07de | 2 | Copyright (C) 2014-2021 Free Software Foundation, Inc. |
11146849 DD |
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" | |
88c1242d | 23 | #include "disassemble.h" |
11146849 DD |
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 | } |