• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021-2022 Alyssa Rosenzweig
3  * SPDX-License-Identifier: MIT
4  */
5 #include <assert.h>
6 #include <dlfcn.h>
7 #include <inttypes.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <unistd.h>
11 
12 #include <IOKit/IOKitLib.h>
13 #include <mach/mach.h>
14 
15 #include "util/compiler.h"
16 #include "util/u_hexdump.h"
17 #include "agx_iokit.h"
18 #include "decode.h"
19 #include "dyld_interpose.h"
20 #include "util.h"
21 
22 /*
23  * Wrap IOKit entrypoints to intercept communication between the AGX kernel
24  * extension and userspace clients. IOKit prototypes are public from the IOKit
25  * source release.
26  */
27 
28 mach_port_t metal_connection = 0;
29 
30 kern_return_t
wrap_Method(mach_port_t connection,uint32_t selector,const uint64_t * input,uint32_t inputCnt,const void * inputStruct,size_t inputStructCnt,uint64_t * output,uint32_t * outputCnt,void * outputStruct,size_t * outputStructCntP)31 wrap_Method(mach_port_t connection, uint32_t selector, const uint64_t *input,
32             uint32_t inputCnt, const void *inputStruct, size_t inputStructCnt,
33             uint64_t *output, uint32_t *outputCnt, void *outputStruct,
34             size_t *outputStructCntP)
35 {
36    /* Heuristic guess which connection is Metal, skip over I/O from everything
37     * else. This is technically wrong but it works in practice, and reduces the
38     * surface area we need to wrap.
39     */
40    if (selector == AGX_SELECTOR_SET_API) {
41       metal_connection = connection;
42    } else if (metal_connection != connection) {
43       return IOConnectCallMethod(connection, selector, input, inputCnt,
44                                  inputStruct, inputStructCnt, output, outputCnt,
45                                  outputStruct, outputStructCntP);
46    }
47 
48    printf("Selector %u, %X, %X\n", selector, connection, metal_connection);
49 
50    /* Check the arguments make sense */
51    assert((input != NULL) == (inputCnt != 0));
52    assert((inputStruct != NULL) == (inputStructCnt != 0));
53    assert((output != NULL) == (outputCnt != 0));
54    assert((outputStruct != NULL) == (outputStructCntP != 0));
55 
56    /* Dump inputs */
57    switch (selector) {
58    case AGX_SELECTOR_SET_API:
59       assert(input == NULL && output == NULL && outputStruct == NULL);
60       assert(inputStruct != NULL && inputStructCnt == 16);
61       assert(((uint8_t *)inputStruct)[15] == 0x0);
62 
63       printf("%X: SET_API(%s)\n", connection, (const char *)inputStruct);
64       break;
65 
66    case AGX_SELECTOR_ALLOCATE_MEM: {
67       const struct agx_allocate_resource_req *req = inputStruct;
68       struct agx_allocate_resource_req *req2 = (void *)inputStruct;
69       req2->mode = (req->mode & 0x800) | 0x430;
70 
71       bool suballocated = req->mode & 0x800;
72 
73       printf("Resource allocation:\n");
74       printf("  Mode: 0x%X%s\n", req->mode & ~0x800,
75              suballocated ? " (suballocated) " : "");
76       printf("  CPU fixed: 0x%" PRIx64 "\n", req->cpu_fixed);
77       printf("  CPU fixed (parent): 0x%" PRIx64 "\n", req->cpu_fixed_parent);
78       printf("  Size: 0x%X\n", req->size);
79       printf("  Flags: 0x%X\n", req->flags);
80 
81       if (suballocated) {
82          printf("  Parent: %u\n", req->parent);
83       } else {
84          assert(req->parent == 0);
85       }
86 
87       for (unsigned i = 0; i < ARRAY_SIZE(req->unk0); ++i) {
88          if (req->unk0[i])
89             printf("  UNK%u: 0x%X\n", 0 + i, req->unk0[i]);
90       }
91 
92       for (unsigned i = 0; i < ARRAY_SIZE(req->unk6); ++i) {
93          if (req->unk6[i])
94             printf("  UNK%u: 0x%X\n", 6 + i, req->unk6[i]);
95       }
96 
97       if (req->unk17)
98          printf("  UNK17: 0x%X\n", req->unk17);
99 
100       if (req->unk19)
101          printf("  UNK19: 0x%X\n", req->unk19);
102 
103       for (unsigned i = 0; i < ARRAY_SIZE(req->unk21); ++i) {
104          if (req->unk21[i])
105             printf("  UNK%u: 0x%X\n", 21 + i, req->unk21[i]);
106       }
107 
108       break;
109    }
110 
111    case AGX_SELECTOR_SUBMIT_COMMAND_BUFFERS:
112       assert(output == NULL && outputStruct == NULL);
113       assert(inputCnt == 1);
114 
115       printf("%X: SUBMIT_COMMAND_BUFFERS command queue id:%llx %p\n",
116              connection, input[0], inputStruct);
117 
118       const struct IOAccelCommandQueueSubmitArgs_Header *hdr = inputStruct;
119       const struct IOAccelCommandQueueSubmitArgs_Command *cmds =
120          (void *)(hdr + 1);
121 
122       for (unsigned i = 0; i < hdr->count; ++i) {
123          const struct IOAccelCommandQueueSubmitArgs_Command *req = &cmds[i];
124          agxdecode_cmdstream(req->command_buffer_shmem_id,
125                              req->segment_list_shmem_id, true);
126          if (getenv("ASAHI_DUMP"))
127             agxdecode_dump_mappings(req->segment_list_shmem_id);
128       }
129 
130       agxdecode_next_frame();
131       FALLTHROUGH;
132 
133    default:
134       printf("%X: call %s (out %p, %zu)", connection,
135              wrap_selector_name(selector), outputStructCntP,
136              outputStructCntP ? *outputStructCntP : 0);
137 
138       for (uint64_t u = 0; u < inputCnt; ++u)
139          printf(" %llx", input[u]);
140 
141       if (inputStructCnt) {
142          printf(", struct:\n");
143          u_hexdump(stdout, inputStruct, inputStructCnt, true);
144       } else {
145          printf("\n");
146       }
147 
148       break;
149    }
150 
151    /* Invoke the real method */
152    kern_return_t ret = IOConnectCallMethod(
153       connection, selector, input, inputCnt, inputStruct, inputStructCnt,
154       output, outputCnt, outputStruct, outputStructCntP);
155 
156    if (ret != 0)
157       printf("return %u\n", ret);
158 
159    /* Track allocations for later analysis (dumping, disassembly, etc) */
160    switch (selector) {
161    case AGX_SELECTOR_CREATE_SHMEM: {
162       assert(inputCnt == 2);
163       assert((*outputStructCntP) == 0x10);
164       uint64_t *inp = (uint64_t *)input;
165 
166       uint8_t type = inp[1];
167 
168       assert(type <= 2);
169       if (type == 2)
170          printf("(cmdbuf with error reporting)\n");
171 
172       uint64_t *ptr = (uint64_t *)outputStruct;
173       uint32_t *words = (uint32_t *)(ptr + 1);
174 
175       agxdecode_track_alloc(&(struct agx_bo){
176          .handle = words[1],
177          .ptr.cpu = (void *)*ptr,
178          .size = words[0],
179          .type = inp[1] ? AGX_ALLOC_CMDBUF : AGX_ALLOC_MEMMAP});
180 
181       break;
182    }
183 
184    case AGX_SELECTOR_ALLOCATE_MEM: {
185       assert((*outputStructCntP) == 0x50);
186       const struct agx_allocate_resource_req *req = inputStruct;
187       struct agx_allocate_resource_resp *resp = outputStruct;
188       if (resp->cpu && req->cpu_fixed)
189          assert(resp->cpu == req->cpu_fixed);
190       printf("Response:\n");
191       printf("  GPU VA: 0x%" PRIx64 "\n", resp->gpu_va);
192       printf("  CPU VA: 0x%" PRIx64 "\n", resp->cpu);
193       printf("  Handle: %u\n", resp->handle);
194       printf("  Root size: 0x%" PRIx64 "\n", resp->root_size);
195       printf("  Suballocation size: 0x%" PRIx64 "\n", resp->sub_size);
196       printf("  GUID: 0x%X\n", resp->guid);
197       for (unsigned i = 0; i < ARRAY_SIZE(resp->unk4); ++i) {
198          if (resp->unk4[i])
199             printf("  UNK%u: 0x%X\n", 4 + i, resp->unk4[i]);
200       }
201       for (unsigned i = 0; i < ARRAY_SIZE(resp->unk11); ++i) {
202          if (resp->unk11[i])
203             printf("  UNK%u: 0x%X\n", 11 + i, resp->unk11[i]);
204       }
205 
206       if (req->parent)
207          assert(resp->sub_size <= resp->root_size);
208       else
209          assert(resp->sub_size == resp->root_size);
210 
211       agxdecode_track_alloc(&(struct agx_bo){
212          .type = AGX_ALLOC_REGULAR,
213          .size = resp->sub_size,
214          .handle = resp->handle,
215          .ptr.gpu = resp->gpu_va,
216          .ptr.cpu = (void *)resp->cpu,
217       });
218 
219       break;
220    }
221 
222    case AGX_SELECTOR_FREE_MEM: {
223       assert(inputCnt == 1);
224       assert(inputStruct == NULL);
225       assert(output == NULL);
226       assert(outputStruct == NULL);
227 
228       agxdecode_track_free(
229          &(struct agx_bo){.type = AGX_ALLOC_REGULAR, .handle = input[0]});
230 
231       break;
232    }
233 
234    case AGX_SELECTOR_FREE_SHMEM: {
235       assert(inputCnt == 1);
236       assert(inputStruct == NULL);
237       assert(output == NULL);
238       assert(outputStruct == NULL);
239 
240       agxdecode_track_free(
241          &(struct agx_bo){.type = AGX_ALLOC_CMDBUF, .handle = input[0]});
242 
243       break;
244    }
245 
246    default:
247       /* Dump the outputs */
248       if (outputCnt) {
249          printf("%u scalars: ", *outputCnt);
250 
251          for (uint64_t u = 0; u < *outputCnt; ++u)
252             printf("%llx ", output[u]);
253 
254          printf("\n");
255       }
256 
257       if (outputStructCntP) {
258          printf(" struct\n");
259          u_hexdump(stdout, outputStruct, *outputStructCntP, true);
260 
261          if (selector == 2) {
262             /* Dump linked buffer as well */
263             void **o = outputStruct;
264             u_hexdump(stdout, *o, 64, true);
265          }
266       }
267 
268       printf("\n");
269       break;
270    }
271 
272    return ret;
273 }
274 
275 kern_return_t
wrap_AsyncMethod(mach_port_t connection,uint32_t selector,mach_port_t wakePort,uint64_t * reference,uint32_t referenceCnt,const uint64_t * input,uint32_t inputCnt,const void * inputStruct,size_t inputStructCnt,uint64_t * output,uint32_t * outputCnt,void * outputStruct,size_t * outputStructCntP)276 wrap_AsyncMethod(mach_port_t connection, uint32_t selector,
277                  mach_port_t wakePort, uint64_t *reference,
278                  uint32_t referenceCnt, const uint64_t *input,
279                  uint32_t inputCnt, const void *inputStruct,
280                  size_t inputStructCnt, uint64_t *output, uint32_t *outputCnt,
281                  void *outputStruct, size_t *outputStructCntP)
282 {
283    /* Check the arguments make sense */
284    assert((input != NULL) == (inputCnt != 0));
285    assert((inputStruct != NULL) == (inputStructCnt != 0));
286    assert((output != NULL) == (outputCnt != 0));
287    assert((outputStruct != NULL) == (outputStructCntP != 0));
288 
289    printf("%X: call %X, wake port %X (out %p, %zu)", connection, selector,
290           wakePort, outputStructCntP, outputStructCntP ? *outputStructCntP : 0);
291 
292    for (uint64_t u = 0; u < inputCnt; ++u)
293       printf(" %llx", input[u]);
294 
295    if (inputStructCnt) {
296       printf(", struct:\n");
297       u_hexdump(stdout, inputStruct, inputStructCnt, true);
298    } else {
299       printf("\n");
300    }
301 
302    printf(", references: ");
303    for (unsigned i = 0; i < referenceCnt; ++i)
304       printf(" %llx", reference[i]);
305    printf("\n");
306 
307    kern_return_t ret = IOConnectCallAsyncMethod(
308       connection, selector, wakePort, reference, referenceCnt, input, inputCnt,
309       inputStruct, inputStructCnt, output, outputCnt, outputStruct,
310       outputStructCntP);
311 
312    printf("return %u", ret);
313 
314    if (outputCnt) {
315       printf("%u scalars: ", *outputCnt);
316 
317       for (uint64_t u = 0; u < *outputCnt; ++u)
318          printf("%llx ", output[u]);
319 
320       printf("\n");
321    }
322 
323    if (outputStructCntP) {
324       printf(" struct\n");
325       u_hexdump(stdout, outputStruct, *outputStructCntP, true);
326 
327       if (selector == 2) {
328          /* Dump linked buffer as well */
329          void **o = outputStruct;
330          u_hexdump(stdout, *o, 64, true);
331       }
332    }
333 
334    printf("\n");
335    return ret;
336 }
337 
338 kern_return_t
wrap_StructMethod(mach_port_t connection,uint32_t selector,const void * inputStruct,size_t inputStructCnt,void * outputStruct,size_t * outputStructCntP)339 wrap_StructMethod(mach_port_t connection, uint32_t selector,
340                   const void *inputStruct, size_t inputStructCnt,
341                   void *outputStruct, size_t *outputStructCntP)
342 {
343    return wrap_Method(connection, selector, NULL, 0, inputStruct,
344                       inputStructCnt, NULL, NULL, outputStruct,
345                       outputStructCntP);
346 }
347 
348 kern_return_t
wrap_AsyncStructMethod(mach_port_t connection,uint32_t selector,mach_port_t wakePort,uint64_t * reference,uint32_t referenceCnt,const void * inputStruct,size_t inputStructCnt,void * outputStruct,size_t * outputStructCnt)349 wrap_AsyncStructMethod(mach_port_t connection, uint32_t selector,
350                        mach_port_t wakePort, uint64_t *reference,
351                        uint32_t referenceCnt, const void *inputStruct,
352                        size_t inputStructCnt, void *outputStruct,
353                        size_t *outputStructCnt)
354 {
355    return wrap_AsyncMethod(connection, selector, wakePort, reference,
356                            referenceCnt, NULL, 0, inputStruct, inputStructCnt,
357                            NULL, NULL, outputStruct, outputStructCnt);
358 }
359 
360 kern_return_t
wrap_ScalarMethod(mach_port_t connection,uint32_t selector,const uint64_t * input,uint32_t inputCnt,uint64_t * output,uint32_t * outputCnt)361 wrap_ScalarMethod(mach_port_t connection, uint32_t selector,
362                   const uint64_t *input, uint32_t inputCnt, uint64_t *output,
363                   uint32_t *outputCnt)
364 {
365    return wrap_Method(connection, selector, input, inputCnt, NULL, 0, output,
366                       outputCnt, NULL, NULL);
367 }
368 
369 kern_return_t
wrap_AsyncScalarMethod(mach_port_t connection,uint32_t selector,mach_port_t wakePort,uint64_t * reference,uint32_t referenceCnt,const uint64_t * input,uint32_t inputCnt,uint64_t * output,uint32_t * outputCnt)370 wrap_AsyncScalarMethod(mach_port_t connection, uint32_t selector,
371                        mach_port_t wakePort, uint64_t *reference,
372                        uint32_t referenceCnt, const uint64_t *input,
373                        uint32_t inputCnt, uint64_t *output, uint32_t *outputCnt)
374 {
375    return wrap_AsyncMethod(connection, selector, wakePort, reference,
376                            referenceCnt, input, inputCnt, NULL, 0, output,
377                            outputCnt, NULL, NULL);
378 }
379 
380 mach_port_t
wrap_DataQueueAllocateNotificationPort()381 wrap_DataQueueAllocateNotificationPort()
382 {
383    mach_port_t ret = IODataQueueAllocateNotificationPort();
384    printf("Allocated notif port %X\n", ret);
385    return ret;
386 }
387 
388 kern_return_t
wrap_SetNotificationPort(io_connect_t connect,uint32_t type,mach_port_t port,uintptr_t reference)389 wrap_SetNotificationPort(io_connect_t connect, uint32_t type, mach_port_t port,
390                          uintptr_t reference)
391 {
392    printf(
393       "Set noficiation port connect=%X, type=%X, port=%X, reference=%" PRIx64
394       "\n",
395       connect, type, port, (uint64_t)reference);
396 
397    return IOConnectSetNotificationPort(connect, type, port, reference);
398 }
399 
400 IOReturn
wrap_DataQueueWaitForAvailableData(IODataQueueMemory * dataQueue,mach_port_t notificationPort)401 wrap_DataQueueWaitForAvailableData(IODataQueueMemory *dataQueue,
402                                    mach_port_t notificationPort)
403 {
404    printf("Waiting for data queue at notif port %X\n", notificationPort);
405    IOReturn ret = IODataQueueWaitForAvailableData(dataQueue, notificationPort);
406    printf("ret=%X\n", ret);
407    return ret;
408 }
409 
410 IODataQueueEntry *
wrap_DataQueuePeek(IODataQueueMemory * dataQueue)411 wrap_DataQueuePeek(IODataQueueMemory *dataQueue)
412 {
413    printf("Peeking data queue\n");
414    return IODataQueuePeek(dataQueue);
415 }
416 
417 IOReturn
wrap_DataQueueDequeue(IODataQueueMemory * dataQueue,void * data,uint32_t * dataSize)418 wrap_DataQueueDequeue(IODataQueueMemory *dataQueue, void *data,
419                       uint32_t *dataSize)
420 {
421    printf("Dequeueing (dataQueue=%p, data=%p, buffer %u)\n", dataQueue, data,
422           *dataSize);
423    IOReturn ret = IODataQueueDequeue(dataQueue, data, dataSize);
424    printf("Return \"%s\", got %u bytes\n", mach_error_string(ret), *dataSize);
425 
426    uint8_t *data8 = data;
427    for (unsigned i = 0; i < *dataSize; ++i) {
428       printf("%02X ", data8[i]);
429    }
430    printf("\n");
431 
432    return ret;
433 }
434 
435 DYLD_INTERPOSE(wrap_Method, IOConnectCallMethod);
436 DYLD_INTERPOSE(wrap_AsyncMethod, IOConnectCallAsyncMethod);
437 DYLD_INTERPOSE(wrap_StructMethod, IOConnectCallStructMethod);
438 DYLD_INTERPOSE(wrap_AsyncStructMethod, IOConnectCallAsyncStructMethod);
439 DYLD_INTERPOSE(wrap_ScalarMethod, IOConnectCallScalarMethod);
440 DYLD_INTERPOSE(wrap_AsyncScalarMethod, IOConnectCallAsyncScalarMethod);
441 DYLD_INTERPOSE(wrap_SetNotificationPort, IOConnectSetNotificationPort);
442 DYLD_INTERPOSE(wrap_DataQueueAllocateNotificationPort,
443                IODataQueueAllocateNotificationPort);
444 DYLD_INTERPOSE(wrap_DataQueueWaitForAvailableData,
445                IODataQueueWaitForAvailableData);
446 DYLD_INTERPOSE(wrap_DataQueuePeek, IODataQueuePeek);
447 DYLD_INTERPOSE(wrap_DataQueueDequeue, IODataQueueDequeue);
448