1/*
2 * Copyright (c) 2019 Vasily Khoruzhick <anarsoul@gmail.com>
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, sub license,
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
12 * next paragraph) shall be included in all copies or substantial portions
13 * of the 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 NON-INFRINGEMENT. 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
21 * DEALINGS IN THE SOFTWARE.
22 *
23 */
24
25#include "util/ralloc.h"
26
27#include <err.h>
28#include <stdio.h>
29#include <string.h>
30#include <stdint.h>
31
32#include "ir/pp/codegen.h"
33#include "ir/gp/codegen.h"
34
35static void
36print_usage(void)
37{
38   printf("Usage: lima_disasm [OPTIONS]... FILE\n");
39   printf("    --help            - show this message\n");
40}
41
42typedef struct __attribute__((__packed__)) {
43   char name[4];
44   uint32_t size;
45} mbs_chunk;
46
47/* Parses an MBS1 file. MBS1 is used for Mali-400 and earlier which only support
48 * GLES2, as opposed to MBS2 which is used by later Mali gens, and contains
49 * the entire inferface between the compiler and the (blob) driver. It's
50 * produced by the offline compiler as well as glGetProgramBinary(). The
51 * format is documented at
52 * https://web.archive.org/web/20171026141029/http://limadriver.org/MBS+File+Format/
53 * and consists of a bunch of nested "chunks" where each chunk has a
54 * 4-character tag followed by a 32-bit size, then the contents of the chunk.
55 * The chunks are nested as follows:
56 *
57 * - MBS1
58 *   - optional CFRA (fragment shader)
59 *     - core version (uint32_t, Mali-200 vs Mali-400)
60 *     - FSTA (Fragment STAck information)
61 *     - FDIS (if Fragment shader contains a DIScard instruction)
62 *     - FBUU (information on color/depth reads/writes)
63 *     - SUNI (uniform symbol table)
64 *     - SVAR (varying symbol table)
65 *     - DBIN (the actual code)
66 *   - optional CVER (vertex shader)
67 *     - core version (uint32_t, GP2 vs Mali-400)
68 *     - FINS (# of instruction and attrib_prefetch)
69 *     - SUNI (uniform table)
70 *     - SATT (attribute table)
71 *     - SVAR (varying table)
72 *     - DBIN (the actual code)
73 *
74 * This routine just finds the DBIN chunk and returns the binary assuming
75 * there's only the fragment or vertex shader. We don't bother to parse the
76 * other stuff yet.
77 */
78static uint32_t *
79extract_shader_binary(char *filename, uint32_t *size, bool *is_frag)
80{
81   mbs_chunk chunk;
82
83   if (!filename || !size || !is_frag)
84      return NULL;
85
86   FILE *in = fopen(filename, "rb");
87   if (!in)
88      return NULL;
89
90   if (!fread(&chunk, sizeof(chunk), 1, in)) {
91      printf("Failed to read MBS1 segment\n");
92      return NULL;
93   }
94
95   if (strncmp(chunk.name, "MBS1", 4)) {
96      printf("File is not MBS\n");
97      return NULL;
98   }
99
100   if (!fread(&chunk, sizeof(chunk), 1, in)) {
101      printf("Failed to read shader segment\n");
102      return NULL;
103   }
104
105   if (!strncmp(chunk.name, "CFRA", 4)) {
106      *is_frag = true;
107   } else if (!strncmp(chunk.name, "CVER", 4)) {
108      *is_frag = false;
109   } else {
110      printf("Unsupported shader type\n");
111      return NULL;
112   }
113
114   /* Skip version */
115   fseek(in, 4, SEEK_CUR);
116
117   /* Skip the other chunks and find the DBIN chunk. */
118   do {
119      if (!fread(&chunk, sizeof(chunk), 1, in)) {
120         printf("Failed to read segment\n");
121         return NULL;
122      }
123      if (!strncmp(chunk.name, "DBIN", 4))
124         break;
125      fseek(in, chunk.size, SEEK_CUR);
126   } while (!feof(in));
127
128   if (feof(in)) {
129      printf("CBIN segment not found!\n");
130      return NULL;
131   }
132
133   *size = chunk.size;
134
135   uint32_t *bin = ralloc_size(NULL, chunk.size);
136   if (!bin) {
137      printf("Failed to allocate shader binary\n");
138      return NULL;
139   }
140
141   if (!fread(bin, chunk.size, 1, in)) {
142      printf("Failed to read shader binary\n");
143      ralloc_free(bin);
144      bin = NULL;
145   }
146
147   return bin;
148}
149
150int
151main(int argc, char **argv)
152{
153   int n;
154   bool is_frag = true;
155
156   if (argc < 2) {
157      print_usage();
158      return 1;
159   }
160
161   for (n = 1; n < argc; n++) {
162      if (!strcmp(argv[n], "--help")) {
163         print_usage();
164         return 1;
165      }
166   }
167
168   char *filename = NULL;
169   filename = argv[argc - 1];
170
171   uint32_t size = 0;
172   uint32_t *prog = extract_shader_binary(filename, &size, &is_frag);
173   if (!prog) {
174      printf("Failed to parse mbs!\n");
175      return -1;
176   }
177
178   if (is_frag) {
179      assert((size & 0x3) == 0);
180      size >>= 2;
181      uint32_t *bin = prog;
182      uint32_t offset = 0;
183      do {
184         ppir_codegen_ctrl *ctrl = (ppir_codegen_ctrl *)bin;
185         printf("@%6d: ", offset);
186         ppir_disassemble_instr(bin, offset, stdout);
187         bin += ctrl->count;
188         offset += ctrl->count;
189         size -= ctrl->count;
190      } while (size);
191   } else {
192      gpir_disassemble_program((gpir_codegen_instr *)prog, size / (sizeof(gpir_codegen_instr)), stdout);
193   }
194
195   ralloc_free(prog);
196
197   return 0;
198}
199
200