Release 2.33.1
[external/binutils.git] / opcodes / pru-dis.c
1 /* TI PRU disassemble routines
2    Copyright (C) 2014-2019 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 }