1 /*
2  * Copyright © 2016 Broadcom
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 #include <string.h>
25 #include <stdio.h>
26 #include "util/ralloc.h"
27 
28 #include "broadcom/common/v3d_device_info.h"
29 #include "qpu_instr.h"
30 #include "qpu_disasm.h"
31 
32 struct disasm_state {
33         const struct v3d_device_info *devinfo;
34         char *string;
35         size_t offset;
36 };
37 
38 static void
append(struct disasm_state * disasm,const char * fmt,...)39 append(struct disasm_state *disasm, const char *fmt, ...)
40 {
41         va_list args;
42         va_start(args, fmt);
43         ralloc_vasprintf_rewrite_tail(&disasm->string,
44                                       &disasm->offset,
45                                       fmt, args);
46         va_end(args);
47 }
48 
49 static void
pad_to(struct disasm_state * disasm,int n)50 pad_to(struct disasm_state *disasm, int n)
51 {
52         /* FIXME: Do a single append somehow. */
53         while (disasm->offset < n)
54                 append(disasm, " ");
55 }
56 
57 
58 static void
v3d_qpu_disasm_raddr(struct disasm_state * disasm,const struct v3d_qpu_instr * instr,uint8_t mux)59 v3d_qpu_disasm_raddr(struct disasm_state *disasm,
60                      const struct v3d_qpu_instr *instr, uint8_t mux)
61 {
62         if (mux == V3D_QPU_MUX_A) {
63                 append(disasm, "rf%d", instr->raddr_a);
64         } else if (mux == V3D_QPU_MUX_B) {
65                 if (instr->sig.small_imm) {
66                         uint32_t val;
67                         MAYBE_UNUSED bool ok =
68                                 v3d_qpu_small_imm_unpack(disasm->devinfo,
69                                                          instr->raddr_b,
70                                                          &val);
71 
72                         if ((int)val >= -16 && (int)val <= 15)
73                                 append(disasm, "%d", val);
74                         else
75                                 append(disasm, "0x%08x", val);
76                         assert(ok);
77                 } else {
78                         append(disasm, "rf%d", instr->raddr_b);
79                 }
80         } else {
81                 append(disasm, "r%d", mux);
82         }
83 }
84 
85 static void
v3d_qpu_disasm_waddr(struct disasm_state * disasm,uint32_t waddr,bool magic)86 v3d_qpu_disasm_waddr(struct disasm_state *disasm, uint32_t waddr, bool magic)
87 {
88         if (!magic) {
89                 append(disasm, "rf%d", waddr);
90                 return;
91         }
92 
93         const char *name = v3d_qpu_magic_waddr_name(waddr);
94         if (name)
95                 append(disasm, "%s", name);
96         else
97                 append(disasm, "waddr UNKNOWN %d", waddr);
98 }
99 
100 static void
v3d_qpu_disasm_add(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)101 v3d_qpu_disasm_add(struct disasm_state *disasm,
102                    const struct v3d_qpu_instr *instr)
103 {
104         bool has_dst = v3d_qpu_add_op_has_dst(instr->alu.add.op);
105         int num_src = v3d_qpu_add_op_num_src(instr->alu.add.op);
106 
107         append(disasm, "%s", v3d_qpu_add_op_name(instr->alu.add.op));
108         if (!v3d_qpu_sig_writes_address(disasm->devinfo, &instr->sig))
109                 append(disasm, "%s", v3d_qpu_cond_name(instr->flags.ac));
110         append(disasm, "%s", v3d_qpu_pf_name(instr->flags.apf));
111         append(disasm, "%s", v3d_qpu_uf_name(instr->flags.auf));
112 
113         append(disasm, "  ");
114 
115         if (has_dst) {
116                 v3d_qpu_disasm_waddr(disasm, instr->alu.add.waddr,
117                                      instr->alu.add.magic_write);
118                 append(disasm, v3d_qpu_pack_name(instr->alu.add.output_pack));
119         }
120 
121         if (num_src >= 1) {
122                 if (has_dst)
123                         append(disasm, ", ");
124                 v3d_qpu_disasm_raddr(disasm, instr, instr->alu.add.a);
125                 append(disasm, "%s",
126                        v3d_qpu_unpack_name(instr->alu.add.a_unpack));
127         }
128 
129         if (num_src >= 2) {
130                 append(disasm, ", ");
131                 v3d_qpu_disasm_raddr(disasm, instr, instr->alu.add.b);
132                 append(disasm, "%s",
133                        v3d_qpu_unpack_name(instr->alu.add.b_unpack));
134         }
135 }
136 
137 static void
v3d_qpu_disasm_mul(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)138 v3d_qpu_disasm_mul(struct disasm_state *disasm,
139                    const struct v3d_qpu_instr *instr)
140 {
141         bool has_dst = v3d_qpu_mul_op_has_dst(instr->alu.mul.op);
142         int num_src = v3d_qpu_mul_op_num_src(instr->alu.mul.op);
143 
144         pad_to(disasm, 21);
145         append(disasm, "; ");
146 
147         append(disasm, "%s", v3d_qpu_mul_op_name(instr->alu.mul.op));
148         if (!v3d_qpu_sig_writes_address(disasm->devinfo, &instr->sig))
149                 append(disasm, "%s", v3d_qpu_cond_name(instr->flags.mc));
150         append(disasm, "%s", v3d_qpu_pf_name(instr->flags.mpf));
151         append(disasm, "%s", v3d_qpu_uf_name(instr->flags.muf));
152 
153         if (instr->alu.mul.op == V3D_QPU_M_NOP)
154                 return;
155 
156         append(disasm, "  ");
157 
158         if (has_dst) {
159                 v3d_qpu_disasm_waddr(disasm, instr->alu.mul.waddr,
160                                      instr->alu.mul.magic_write);
161                 append(disasm, v3d_qpu_pack_name(instr->alu.mul.output_pack));
162         }
163 
164         if (num_src >= 1) {
165                 if (has_dst)
166                         append(disasm, ", ");
167                 v3d_qpu_disasm_raddr(disasm, instr, instr->alu.mul.a);
168                 append(disasm, "%s",
169                        v3d_qpu_unpack_name(instr->alu.mul.a_unpack));
170         }
171 
172         if (num_src >= 2) {
173                 append(disasm, ", ");
174                 v3d_qpu_disasm_raddr(disasm, instr, instr->alu.mul.b);
175                 append(disasm, "%s",
176                        v3d_qpu_unpack_name(instr->alu.mul.b_unpack));
177         }
178 }
179 
180 static void
v3d_qpu_disasm_sig_addr(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)181 v3d_qpu_disasm_sig_addr(struct disasm_state *disasm,
182                         const struct v3d_qpu_instr *instr)
183 {
184         if (disasm->devinfo->ver < 41)
185                 return;
186 
187         if (!instr->sig_magic)
188                 append(disasm, ".rf%d", instr->sig_addr);
189         else {
190                 const char *name = v3d_qpu_magic_waddr_name(instr->sig_addr);
191                 if (name)
192                         append(disasm, ".%s", name);
193                 else
194                         append(disasm, ".UNKNOWN%d", instr->sig_addr);
195         }
196 }
197 
198 static void
v3d_qpu_disasm_sig(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)199 v3d_qpu_disasm_sig(struct disasm_state *disasm,
200                    const struct v3d_qpu_instr *instr)
201 {
202         const struct v3d_qpu_sig *sig = &instr->sig;
203 
204         if (!sig->thrsw &&
205             !sig->ldvary &&
206             !sig->ldvpm &&
207             !sig->ldtmu &&
208             !sig->ldunif &&
209             !sig->ldunifrf &&
210             !sig->ldunifa &&
211             !sig->ldunifarf &&
212             !sig->wrtmuc) {
213                 return;
214         }
215 
216         pad_to(disasm, 41);
217 
218         if (sig->thrsw)
219                 append(disasm, "; thrsw");
220         if (sig->ldvary) {
221                 append(disasm, "; ldvary");
222                 v3d_qpu_disasm_sig_addr(disasm, instr);
223         }
224         if (sig->ldvpm)
225                 append(disasm, "; ldvpm");
226         if (sig->ldtmu) {
227                 append(disasm, "; ldtmu");
228                 v3d_qpu_disasm_sig_addr(disasm, instr);
229         }
230         if (sig->ldtlb) {
231                 append(disasm, "; ldtlb");
232                 v3d_qpu_disasm_sig_addr(disasm, instr);
233         }
234         if (sig->ldtlbu) {
235                 append(disasm, "; ldtlbu");
236                 v3d_qpu_disasm_sig_addr(disasm, instr);
237         }
238         if (sig->ldunif)
239                 append(disasm, "; ldunif");
240         if (sig->ldunifrf) {
241                 append(disasm, "; ldunifrf");
242                 v3d_qpu_disasm_sig_addr(disasm, instr);
243         }
244         if (sig->ldunifa)
245                 append(disasm, "; ldunifa");
246         if (sig->ldunifarf) {
247                 append(disasm, "; ldunifarf");
248                 v3d_qpu_disasm_sig_addr(disasm, instr);
249         }
250         if (sig->wrtmuc)
251                 append(disasm, "; wrtmuc");
252 }
253 
254 static void
v3d_qpu_disasm_alu(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)255 v3d_qpu_disasm_alu(struct disasm_state *disasm,
256                    const struct v3d_qpu_instr *instr)
257 {
258         v3d_qpu_disasm_add(disasm, instr);
259         v3d_qpu_disasm_mul(disasm, instr);
260         v3d_qpu_disasm_sig(disasm, instr);
261 }
262 
263 static void
v3d_qpu_disasm_branch(struct disasm_state * disasm,const struct v3d_qpu_instr * instr)264 v3d_qpu_disasm_branch(struct disasm_state *disasm,
265                       const struct v3d_qpu_instr *instr)
266 {
267         append(disasm, "b");
268         if (instr->branch.ub)
269                 append(disasm, "u");
270         append(disasm, "%s", v3d_qpu_branch_cond_name(instr->branch.cond));
271         append(disasm, "%s", v3d_qpu_msfign_name(instr->branch.msfign));
272 
273         switch (instr->branch.bdi) {
274         case V3D_QPU_BRANCH_DEST_ABS:
275                 append(disasm, "  zero_addr+0x%08x", instr->branch.offset);
276                 break;
277 
278         case V3D_QPU_BRANCH_DEST_REL:
279                 append(disasm, "  %d", instr->branch.offset);
280                 break;
281 
282         case V3D_QPU_BRANCH_DEST_LINK_REG:
283                 append(disasm, "  lri");
284                 break;
285 
286         case V3D_QPU_BRANCH_DEST_REGFILE:
287                 append(disasm, "  rf%d", instr->branch.raddr_a);
288                 break;
289         }
290 
291         if (instr->branch.ub) {
292                 switch (instr->branch.bdu) {
293                 case V3D_QPU_BRANCH_DEST_ABS:
294                         append(disasm, ", a:unif");
295                         break;
296 
297                 case V3D_QPU_BRANCH_DEST_REL:
298                         append(disasm, ", r:unif");
299                         break;
300 
301                 case V3D_QPU_BRANCH_DEST_LINK_REG:
302                         append(disasm, ", lri");
303                         break;
304 
305                 case V3D_QPU_BRANCH_DEST_REGFILE:
306                         append(disasm, ", rf%d", instr->branch.raddr_a);
307                         break;
308                 }
309         }
310 }
311 
312 const char *
v3d_qpu_decode(const struct v3d_device_info * devinfo,const struct v3d_qpu_instr * instr)313 v3d_qpu_decode(const struct v3d_device_info *devinfo,
314                const struct v3d_qpu_instr *instr)
315 {
316         struct disasm_state disasm = {
317                 .string = rzalloc_size(NULL, 1),
318                 .offset = 0,
319                 .devinfo = devinfo,
320         };
321 
322         switch (instr->type) {
323         case V3D_QPU_INSTR_TYPE_ALU:
324                 v3d_qpu_disasm_alu(&disasm, instr);
325                 break;
326 
327         case V3D_QPU_INSTR_TYPE_BRANCH:
328                 v3d_qpu_disasm_branch(&disasm, instr);
329                 break;
330         }
331 
332         return disasm.string;
333 }
334 
335 /**
336  * Returns a string containing the disassembled representation of the QPU
337  * instruction.  It is the caller's responsibility to free the return value
338  * with ralloc_free().
339  */
340 const char *
v3d_qpu_disasm(const struct v3d_device_info * devinfo,uint64_t inst)341 v3d_qpu_disasm(const struct v3d_device_info *devinfo, uint64_t inst)
342 {
343         struct v3d_qpu_instr instr;
344         bool ok = v3d_qpu_instr_unpack(devinfo, inst, &instr);
345         assert(ok); (void)ok;
346 
347         return v3d_qpu_decode(devinfo, &instr);
348 }
349 
350 void
v3d_qpu_dump(const struct v3d_device_info * devinfo,const struct v3d_qpu_instr * instr)351 v3d_qpu_dump(const struct v3d_device_info *devinfo,
352              const struct v3d_qpu_instr *instr)
353 {
354         const char *decoded = v3d_qpu_decode(devinfo, instr);
355         fprintf(stderr, "%s", decoded);
356         ralloc_free((char *)decoded);
357 }
358