Repository navigation
Expand file tree
/
Copy pathNativeOfflineGPU.cpp
More file actions
312 lines (312 loc) · 20.6 KB
/
Copy pathNativeOfflineGPU.cpp
File metadata and controls
312 lines (312 loc) · 20.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// SPDX-License-Identifier: MIT
// Explicit offline GPU broker. No normal Metal/IOAccelerator publication.
// The kernel, not request bytes, selects each userclient's host namespace.
#include <IOKit/IOService.h>
#include <IOKit/IOUserClient.h>
#include <IOKit/IOLib.h>
#include <sys/proc.h>
#include "NativeVirtioConsoleTransport.h"
#include "OfflineMetalSessionWire.h"
#include "OfflineMetalQueueWire.h"
#include "OfflineMetalTextureWire.h"
#include "OfflineMetalDepthStencilWire.h"
#include "OfflineMetalSharedEventWire.h"
#include <kern/task.h>
extern "C" {
volatile uint32_t native_gpu_broker_stage,native_gpu_broker_clients,native_gpu_broker_active,native_gpu_broker_closed;
volatile uint32_t native_gpu_broker_requests,native_gpu_broker_failures,native_gpu_broker_refusals;
volatile int native_gpu_broker_status=-1;
volatile uint64_t native_gpu_broker_reply_bytes;
volatile uint32_t native_gpu_broker_cpu_context_count;
volatile uint32_t native_gpu_shared_exports,native_gpu_shared_imports,native_gpu_shared_drops,native_gpu_shared_refusals,native_gpu_shared_tokens_live,native_gpu_shared_ports;
volatile uint64_t native_gpu_broker_cpu_contexts[8][8];
}
class NativeOfflineGPUClient;
class NativeGPUSharedEventToken;
class NativeOfflineGPU : public IOService {
OSDeclareDefaultStructors(NativeOfflineGPU)
friend class NativeOfflineGPUClient;
friend class NativeGPUSharedEventToken;
IOService *transport=nullptr;
IOLock *gate=nullptr;
bool online=false;
uint32_t nextControlRequest=1;
IOReturn readExact(void *bytes,size_t size) {
auto p=static_cast<unsigned char *>(bytes);
while(size) {
size_t count=size>4096?4096:size;
IOReturn status=native_console_receive(transport,this,p,&count);
if(status!=kIOReturnSuccess)return status;
if(!count || count>size)return kIOReturnIOError;
p+=count;size-=count;
}
return kIOReturnSuccess;
}
// gate covers the complete actual request/reply, including every byte
// beyond a VirtIO descriptor. Userclients retain separate reply buffers.
IOReturn exchange(uint64_t session,const OMWireHeader &request,const void *input,
unsigned char **reply,size_t *length) {
*reply=nullptr;*length=0;if(!online)return kIOReturnNotReady;
OMSessionWireHeader header={request,session};header.wire.version=OFFLINE_METAL_SESSION_VERSION;
IOReturn status=native_console_send(transport,this,&header,sizeof(header));
if(status==kIOReturnSuccess && request.bytes)status=native_console_send(transport,this,input,request.bytes);
OMSessionWireHeader response={};OMResult result={};
if(status==kIOReturnSuccess)status=readExact(&response,sizeof(response));
if(status==kIOReturnSuccess && (response.wire.magic!=OM_WIRE_MAGIC || response.wire.version!=OFFLINE_METAL_SESSION_VERSION ||
response.wire.operation!=(request.operation|OM_WIRE_RESPONSE) || response.wire.request!=request.request ||
response.session!=session || response.wire.bytes<sizeof(result) ||
response.wire.bytes-sizeof(result)>OFFLINE_METAL_MAX_REPLY))status=kIOReturnIOError;
if(status==kIOReturnSuccess)status=readExact(&result,sizeof(result));
if(status==kIOReturnSuccess && result.reserved)status=kIOReturnIOError;
unsigned char *buffer=nullptr;size_t size=sizeof(OMWireHeader)+response.wire.bytes;
if(status==kIOReturnSuccess) {
buffer=static_cast<unsigned char *>(IOMallocData(size));
if(!buffer)status=kIOReturnNoMemory;
}
if(status==kIOReturnSuccess) {
response.wire.version=OFFLINE_METAL_VERSION;
memcpy(buffer,&response.wire,sizeof(OMWireHeader));memcpy(buffer+sizeof(OMWireHeader),&result,sizeof(result));
status=readExact(buffer+sizeof(OMWireHeader)+sizeof(result),response.wire.bytes-sizeof(result));
}
native_gpu_broker_status=status;
if(status!=kIOReturnSuccess) {
if(buffer)IOFreeData(buffer,size);
// A partial stream cannot be reused for another client's reply.
online=false;native_gpu_broker_failures=native_gpu_broker_failures+1;return status;
}
native_gpu_broker_requests=native_gpu_broker_requests+1;
native_gpu_broker_reply_bytes=native_gpu_broker_reply_bytes+size;
if(result.error)native_gpu_broker_refusals=native_gpu_broker_refusals+1;
*reply=buffer;*length=size;return kIOReturnSuccess;
}
IOReturn createSession(task_t owner,uint64_t *session) {
*session=0;IOLockLock(gate);
uint64_t tcr=0,ttbr0=0,ttbr1=0,sctlr=0;
__asm__ volatile("mrs %0, tcr_el1" : "=r"(tcr));
__asm__ volatile("mrs %0, ttbr0_el1" : "=r"(ttbr0));
__asm__ volatile("mrs %0, ttbr1_el1" : "=r"(ttbr1));
__asm__ volatile("mrs %0, sctlr_el1" : "=r"(sctlr));
bool native4k=((tcr>>14)&3)==0 && ((tcr>>30)&3)==2 && (sctlr&1) && ttbr0 && ttbr1;
unsigned index=native_gpu_broker_cpu_context_count;
native_gpu_broker_cpu_context_count=index+1;
if(index<8) {
uint64_t values[8]={(uint64_t)proc_selfpid(),0,tcr,ttbr0,ttbr1,sctlr,(uintptr_t)owner,(uint64_t)native4k};
for(unsigned n=0;n<8;n++)native_gpu_broker_cpu_contexts[index][n]=values[n];
}
if(!native4k) {
native_gpu_broker_status=kIOReturnUnsupported;native_gpu_broker_failures=native_gpu_broker_failures+1;
IOLockUnlock(gate);return kIOReturnUnsupported;
}
OMWireHeader request={OM_WIRE_MAGIC,OFFLINE_METAL_VERSION,OM_SESSION_OPEN,0,1};
unsigned char *reply=nullptr;size_t length=0;IOReturn status=exchange(0,request,nullptr,&reply,&length);
if(status==kIOReturnSuccess) {
OMResult result={};memcpy(&result,reply+sizeof(OMWireHeader),sizeof(result));
if(result.error || !result.handle || length!=sizeof(OMWireHeader)+sizeof(result))status=kIOReturnNotOpen;
else {
*session=result.handle;native_gpu_broker_clients=native_gpu_broker_clients+1;native_gpu_broker_active=native_gpu_broker_active+1;
if(index<8)native_gpu_broker_cpu_contexts[index][1]=result.handle;
}
}
if(reply)IOFreeData(reply,length);IOLockUnlock(gate);return status;
}
void closeSession(uint64_t session,uint32_t number) {
IOLockLock(gate);OMWireHeader request={OM_WIRE_MAGIC,OFFLINE_METAL_VERSION,OM_SESSION_CLOSE,0,number};
unsigned char *reply=nullptr;size_t length=0;IOReturn status=exchange(session,request,nullptr,&reply,&length);
if(status==kIOReturnSuccess) {
OMResult result={};memcpy(&result,reply+sizeof(OMWireHeader),sizeof(result));
if(!result.error){native_gpu_broker_closed=native_gpu_broker_closed+1;native_gpu_broker_active=native_gpu_broker_active-1;}
}
if(reply)IOFreeData(reply,length);IOLockUnlock(gate);
}
void dropSharedExport(uint64_t exported) {
if(!exported)return;IOLockLock(gate);
OMWireHeader header={OM_WIRE_MAGIC,OFFLINE_METAL_VERSION,OM_SHARED_EXPORT_DROP,sizeof(OMHandle),nextControlRequest};OMHandle input={exported};
unsigned char *reply=nullptr;size_t length=0;IOReturn status=exchange(0,header,&input,&reply,&length);
if(status==kIOReturnSuccess){nextControlRequest++;OMResult result={};memcpy(&result,reply+sizeof(OMWireHeader),sizeof(result));if(!result.error)__atomic_fetch_add(&native_gpu_shared_drops,1,__ATOMIC_RELAXED);}
if(reply)IOFreeData(reply,length);IOLockUnlock(gate);
}
public:
bool start(IOService *provider) override {
if(!IOService::start(provider))return false;
gate=IOLockAlloc();if(!gate){IOService::stop(provider);return false;}
if(!provider->open(this)){IOLockFree(gate);gate=nullptr;IOService::stop(provider);return false;}
transport=provider;online=true;native_gpu_broker_stage=2;return true;
}
void stop(IOService *provider) override {
if(gate){IOLockLock(gate);online=false;if(transport)transport->close(this);transport=nullptr;IOLockUnlock(gate);}
IOService::stop(provider);
}
void free() override {
if(transport){transport->close(this);transport=nullptr;}
if(gate){IOLockFree(gate);gate=nullptr;}IOService::free();
}
IOReturn newUserClient(task_t,void *,UInt32,OSDictionary *,IOUserClient **) override;
};
// A real kernel OSObject carried exclusively by an IKOT_IOKIT_IDENT Mach
// right. No user-supplied integer can stand in for this object's authority.
class NativeGPUSharedEventToken : public OSObject {
OSDeclareDefaultStructors(NativeGPUSharedEventToken)
friend class NativeOfflineGPUClient;
NativeOfflineGPU *controller=nullptr;
uint64_t exported=0;
bool initWithExport(NativeOfflineGPU *owner,uint64_t identifier) {
if(!owner || !identifier || !OSObject::init())return false;
controller=owner;controller->retain();exported=identifier;
__atomic_fetch_add(&native_gpu_shared_tokens_live,1,__ATOMIC_RELAXED);return true;
}
void free() override {
if(controller){uint64_t identifier=exported;exported=0;controller->dropSharedExport(identifier);controller->release();controller=nullptr;__atomic_fetch_sub(&native_gpu_shared_tokens_live,1,__ATOMIC_RELAXED);}
OSObject::free();
}
};
class NativeOfflineGPUClient : public IOUserClient {
OSDeclareDefaultStructors(NativeOfflineGPUClient)
NativeOfflineGPU *controller=nullptr;
task_t owner=nullptr; // The native IOUserClient base owns this task reference.
IOLock *gate=nullptr;
uint64_t session=0;
uint32_t nextRequest=1,nextHostRequest=1;
unsigned char *reply=nullptr;size_t replyLength=0,replyOffset=0;
void clearReply() {if(reply)IOFreeData(reply,replyLength);reply=nullptr;replyLength=replyOffset=0;}
void closeContext() {
if(gate)IOLockLock(gate);
clearReply();if(session && controller)controller->closeSession(session,nextHostRequest);
session=0;controller=nullptr;if(gate)IOLockUnlock(gate);
}
static IOReturn sendAction(OSObject *target,void *,IOExternalMethodArguments *args) {
auto self=OSDynamicCast(NativeOfflineGPUClient,target);if(!self || !self->gate)return kIOReturnNotOpen;
if(args->structureInputDescriptor)return kIOReturnUnsupported;
if(!args->structureInput || args->structureInputSize<sizeof(OMWireHeader) || args->structureInputSize>4096)return kIOReturnBadArgument;
OMWireHeader header={};memcpy(&header,args->structureInput,sizeof(header));
IOLockLock(self->gate);IOReturn status=kIOReturnNotOpen;
if(self->session && self->controller) {
if(self->replyOffset!=self->replyLength)status=kIOReturnBusy;
else if(header.magic!=OM_WIRE_MAGIC || header.version!=OFFLINE_METAL_VERSION || header.request!=self->nextRequest ||
header.bytes+sizeof(header)!=args->structureInputSize || header.bytes>4080 ||
header.operation<OM_DEVICE || header.operation>OM_SHARED_LABEL_SET || (header.operation==OM_FINISH && header.bytes))status=kIOReturnBadArgument;
else {
self->clearReply();uint16_t operation=header.operation;if(operation==OM_FINISH)header.operation=OM_SESSION_CLOSE;
IOLockLock(self->controller->gate);
OMWireHeader hostHeader=header;hostHeader.request=self->nextHostRequest;
status=self->controller->exchange(self->session,hostHeader,static_cast<const unsigned char *>(args->structureInput)+sizeof(header),&self->reply,&self->replyLength);
if(status==kIOReturnSuccess) {
self->nextRequest++;self->nextHostRequest++;
// Preserve the real namespace-close result in the client's
// own framing; no host-wide Finish is forwarded.
header.operation=operation|OM_WIRE_RESPONSE;header.bytes=(uint32_t)(self->replyLength-sizeof(header));
memcpy(self->reply,&header,sizeof(header));OMResult result={};memcpy(&result,self->reply+sizeof(header),sizeof(result));
if(operation==OM_FINISH && !result.error) {
self->session=0;native_gpu_broker_closed=native_gpu_broker_closed+1;native_gpu_broker_active=native_gpu_broker_active-1;
}
}
IOLockUnlock(self->controller->gate);
}
}
IOLockUnlock(self->gate);return status;
}
static IOReturn receiveAction(OSObject *target,void *,IOExternalMethodArguments *args) {
auto self=OSDynamicCast(NativeOfflineGPUClient,target);if(!self || !self->gate)return kIOReturnNotOpen;
if(args->structureOutputDescriptor)return kIOReturnUnsupported;
if(!args->structureOutput || !args->structureOutputSize || args->structureOutputSize>4096)return kIOReturnBadArgument;
IOLockLock(self->gate);size_t available=self->replyLength-self->replyOffset;
size_t count=args->structureOutputSize;if(count>available)count=available;
if(count){memcpy(args->structureOutput,self->reply+self->replyOffset,count);self->replyOffset+=count;}
args->structureOutputSize=(uint32_t)count;IOLockUnlock(self->gate);return count?kIOReturnSuccess:kIOReturnNotReady;
}
IOReturn sharedExchange(uint16_t operation,uint64_t identifier,unsigned char **output,size_t *length) {
*output=nullptr;*length=0;
if(!session || !controller)return kIOReturnNotOpen;
if(replyOffset!=replyLength)return kIOReturnBusy;
OMWireHeader header={OM_WIRE_MAGIC,OFFLINE_METAL_VERSION,operation,sizeof(OMHandle),nextHostRequest};OMHandle input={identifier};
IOLockLock(controller->gate);IOReturn status=controller->exchange(session,header,&input,output,length);
if(status==kIOReturnSuccess)nextHostRequest++;IOLockUnlock(controller->gate);return status;
}
static IOReturn exportSharedAction(OSObject *target,void *,IOExternalMethodArguments *args) {
auto self=OSDynamicCast(NativeOfflineGPUClient,target);if(!self || !self->gate)return kIOReturnNotOpen;
if(current_task()!=self->owner || args->structureOutputDescriptor || !args->structureOutput || !args->structureOutputSize || args->structureOutputSize>4096)return kIOReturnBadArgument;
args->scalarOutput[0]=0;IOLockLock(self->gate);unsigned char *bytes=nullptr;size_t length=0;
IOReturn status=self->sharedExchange(OM_SHARED_EXPORT,args->scalarInput[0],&bytes,&length);OMResult result={};
if(status==kIOReturnSuccess) {
memcpy(&result,bytes+sizeof(OMWireHeader),sizeof(result));size_t prefix=sizeof(OMWireHeader)+sizeof(result);
if(result.error || !result.handle)status=kIOReturnBadArgument;
else {
__atomic_fetch_add(&native_gpu_shared_exports,1,__ATOMIC_RELAXED);
size_t count=length-prefix;
if(!count || count>args->structureOutputSize){self->controller->dropSharedExport(result.handle);status=kIOReturnNoSpace;}
else {
auto token=new NativeGPUSharedEventToken;
if(!token || !token->initWithExport(self->controller,result.handle)) {
OSSafeReleaseNULL(token);self->controller->dropSharedExport(result.handle);status=kIOReturnNoMemory;
} else {
// Kernel MACH_PORT_NULL is an ipc_port pointer, while
// this API returns a name in the owner's namespace.
mach_port_name_t name=0;status=IOUserClient::copyPortNameForObjectInTask(self->owner,token,&name);
if(status==kIOReturnSuccess && name && name!=MACH_PORT_DEAD) {
args->scalarOutput[0]=name;memcpy(args->structureOutput,bytes+prefix,count);args->structureOutputSize=(uint32_t)count;
__atomic_fetch_add(&native_gpu_shared_ports,1,__ATOMIC_RELAXED);
} else if(status==kIOReturnSuccess)status=kIOReturnNoResources;
token->release();
}
}
}
}
if(bytes)IOFreeData(bytes,length);IOLockUnlock(self->gate);
if(status!=kIOReturnSuccess)__atomic_fetch_add(&native_gpu_shared_refusals,1,__ATOMIC_RELAXED);return status;
}
static IOReturn importSharedAction(OSObject *target,void *,IOExternalMethodArguments *args) {
auto self=OSDynamicCast(NativeOfflineGPUClient,target);if(!self || !self->gate)return kIOReturnNotOpen;
if(current_task()!=self->owner || args->scalarInput[0]>UINT32_MAX || args->structureOutputDescriptor || !args->structureOutput || !args->structureOutputSize || args->structureOutputSize>4096)return kIOReturnBadArgument;
args->scalarOutput[0]=0;OSObject *object=nullptr;IOReturn status=IOUserClient::copyObjectForPortNameInTask(self->owner,(mach_port_name_t)args->scalarInput[0],&object);
auto token=OSDynamicCast(NativeGPUSharedEventToken,object);unsigned char *bytes=nullptr;size_t length=0;IOLockLock(self->gate);
if(status!=kIOReturnSuccess || !token || token->controller!=self->controller || !token->exported)status=kIOReturnBadArgument;
else {
status=self->sharedExchange(OM_SHARED_IMPORT,token->exported,&bytes,&length);
if(status==kIOReturnSuccess) {
OMResult result={};memcpy(&result,bytes+sizeof(OMWireHeader),sizeof(result));size_t prefix=sizeof(OMWireHeader)+sizeof(result),count=length-prefix;
if(result.error || !result.handle)status=kIOReturnBadArgument;
else if(!count || count>args->structureOutputSize) {
// Retire the just-created independent event handle through
// the same real session without consuming a user sequence.
unsigned char *released=nullptr;size_t releasedLength=0;
(void)self->sharedExchange(OM_RELEASE,result.handle,&released,&releasedLength);if(released)IOFreeData(released,releasedLength);status=kIOReturnNoSpace;
} else {args->scalarOutput[0]=result.handle;memcpy(args->structureOutput,bytes+prefix,count);args->structureOutputSize=(uint32_t)count;__atomic_fetch_add(&native_gpu_shared_imports,1,__ATOMIC_RELAXED);}
}
}
if(bytes)IOFreeData(bytes,length);OSSafeReleaseNULL(object);IOLockUnlock(self->gate);
if(status!=kIOReturnSuccess)__atomic_fetch_add(&native_gpu_shared_refusals,1,__ATOMIC_RELAXED);return status;
}
public:
bool initWithTask(task_t task,void *security,UInt32 type,OSDictionary *properties) override {
if(type || clientHasPrivilege(security,kIOClientPrivilegeAdministrator)!=kIOReturnSuccess ||
!IOUserClient::initWithTask(task,security,type,properties))return false;
owner=task;gate=IOLockAlloc();return gate!=nullptr;
}
bool start(IOService *provider) override {
controller=OSDynamicCast(NativeOfflineGPU,provider);
if(!controller || !IOUserClient::start(provider))return false;
if(controller->createSession(owner,&session)!=kIOReturnSuccess){controller=nullptr;IOUserClient::stop(provider);return false;}
return true;
}
void stop(IOService *provider) override {closeContext();IOUserClient::stop(provider);}
void free() override {closeContext();if(gate){IOLockFree(gate);gate=nullptr;}IOUserClient::free();}
IOReturn clientClose() override {terminate();return kIOReturnSuccess;}
IOReturn externalMethod(uint32_t selector,IOExternalMethodArguments *args,IOExternalMethodDispatch *,OSObject *,void *) override {
static const IOExternalMethodDispatch methods[]={{sendAction,0,kIOUCVariableStructureSize,0,0},{receiveAction,0,0,0,kIOUCVariableStructureSize},
{exportSharedAction,1,0,1,kIOUCVariableStructureSize},{importSharedAction,1,0,1,kIOUCVariableStructureSize}};
if(selector>=4)return kIOReturnUnsupported;
return IOUserClient::externalMethod(selector,args,const_cast<IOExternalMethodDispatch *>(methods+selector),this,nullptr);
}
};
OSDefineMetaClassAndStructors(NativeOfflineGPU,IOService)
OSDefineMetaClassAndStructors(NativeOfflineGPUClient,IOUserClient)
OSDefineMetaClassAndStructors(NativeGPUSharedEventToken,OSObject)
IOReturn NativeOfflineGPU::newUserClient(task_t task,void *security,UInt32 type,OSDictionary *properties,IOUserClient **out) {
if(!out)return kIOReturnBadArgument;*out=nullptr;
auto client=new NativeOfflineGPUClient;if(!client)return kIOReturnNoMemory;
bool attached=false;
if(!client->initWithTask(task,security,type,properties) || !(attached=client->attach(this)) || !client->start(this)) {
if(attached)client->detach(this);client->release();return kIOReturnNotOpen;
}
*out=client;return kIOReturnSuccess;
}
extern "C" IOService *native_offline_gpu_create(void) {return new NativeOfflineGPU;}