1 /*
2 * Copyright 2021 Google LLC
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include "render_client.h"
7
8 #include <unistd.h>
9 #include <vulkan/vulkan.h>
10
11 #include "render_context.h"
12 #include "render_server.h"
13 #include "render_virgl.h"
14 #include "render_worker.h"
15
16 /* There is a render_context_record for each worker.
17 *
18 * When the client process destroys a context, it closes the connection to the
19 * worker, which leads to worker termination. It also sends a
20 * RENDER_CLIENT_OP_DESTROY_CONTEXT to us to remove the record. Because we
21 * are responsible for cleaning up the worker, we don't care if the worker has
22 * terminated or not. We always kill, reap, and remove the record.
23 */
24 struct render_context_record {
25 uint32_t ctx_id;
26 struct render_worker *worker;
27
28 struct list_head head;
29 };
30
31 static struct render_context_record *
render_client_find_record(struct render_client * client,uint32_t ctx_id)32 render_client_find_record(struct render_client *client, uint32_t ctx_id)
33 {
34 list_for_each_entry (struct render_context_record, rec, &client->context_records,
35 head) {
36 if (rec->ctx_id == ctx_id)
37 return rec;
38 }
39 return NULL;
40 }
41
42 static void
render_client_detach_all_records(struct render_client * client)43 render_client_detach_all_records(struct render_client *client)
44 {
45 struct render_server *srv = client->server;
46
47 /* free all render_workers without killing nor reaping */
48 render_worker_jail_detach_workers(srv->worker_jail);
49
50 list_for_each_entry_safe (struct render_context_record, rec, &client->context_records,
51 head)
52 free(rec);
53 list_inithead(&client->context_records);
54 }
55
56 static void
render_client_remove_record(struct render_client * client,struct render_context_record * rec)57 render_client_remove_record(struct render_client *client,
58 struct render_context_record *rec)
59 {
60 struct render_server *srv = client->server;
61
62 render_worker_destroy(srv->worker_jail, rec->worker);
63
64 list_del(&rec->head);
65 free(rec);
66 }
67
68 static void
render_client_clear_records(struct render_client * client)69 render_client_clear_records(struct render_client *client)
70 {
71 list_for_each_entry_safe (struct render_context_record, rec, &client->context_records,
72 head)
73 render_client_remove_record(client, rec);
74 }
75
76 static void
init_context_args(struct render_context_args * ctx_args,uint32_t init_flags,const struct render_client_op_create_context_request * req,int ctx_fd)77 init_context_args(struct render_context_args *ctx_args,
78 uint32_t init_flags,
79 const struct render_client_op_create_context_request *req,
80 int ctx_fd)
81 {
82 *ctx_args = (struct render_context_args){
83 .valid = true,
84 .init_flags = init_flags,
85 .ctx_id = req->ctx_id,
86 .ctx_fd = ctx_fd,
87 };
88
89 static_assert(sizeof(ctx_args->ctx_name) == sizeof(req->ctx_name), "");
90 memcpy(ctx_args->ctx_name, req->ctx_name, sizeof(req->ctx_name) - 1);
91 }
92
93 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
94
95 static int
render_client_worker_thread(void * thread_data)96 render_client_worker_thread(void *thread_data)
97 {
98 const struct render_context_args *ctx_args = thread_data;
99 return render_context_main(ctx_args) ? 0 : -1;
100 }
101
102 #endif /* ENABLE_RENDER_SERVER_WORKER_THREAD */
103
104 static bool
render_client_create_context(struct render_client * client,const struct render_client_op_create_context_request * req,int * out_remote_fd)105 render_client_create_context(struct render_client *client,
106 const struct render_client_op_create_context_request *req,
107 int *out_remote_fd)
108 {
109 struct render_server *srv = client->server;
110
111 struct render_context_record *rec = calloc(1, sizeof(*rec));
112 if (!rec)
113 return false;
114
115 int socket_fds[2];
116 if (!render_socket_pair(socket_fds)) {
117 free(rec);
118 return false;
119 }
120 int ctx_fd = socket_fds[0];
121 int remote_fd = socket_fds[1];
122
123 struct render_context_args ctx_args;
124 init_context_args(&ctx_args, client->init_flags, req, ctx_fd);
125
126 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
127 rec->worker = render_worker_create(srv->worker_jail, render_client_worker_thread,
128 &ctx_args, sizeof(ctx_args));
129 if (rec->worker)
130 ctx_fd = -1; /* ownership transferred */
131 #else
132 rec->worker = render_worker_create(srv->worker_jail, NULL, NULL, 0);
133 #endif
134 if (!rec->worker) {
135 render_log("failed to create a context worker");
136 close(ctx_fd);
137 close(remote_fd);
138 free(rec);
139 return false;
140 }
141
142 rec->ctx_id = req->ctx_id;
143 list_addtail(&rec->head, &client->context_records);
144
145 if (!render_worker_is_record(rec->worker)) {
146 /* this is the child process */
147 srv->state = RENDER_SERVER_STATE_SUBPROCESS;
148 *srv->context_args = ctx_args;
149
150 render_client_detach_all_records(client);
151
152 /* ctx_fd ownership transferred */
153 assert(srv->context_args->ctx_fd == ctx_fd);
154
155 close(remote_fd);
156 *out_remote_fd = -1;
157
158 return true;
159 }
160
161 /* this is the parent process */
162 if (ctx_fd >= 0)
163 close(ctx_fd);
164 *out_remote_fd = remote_fd;
165
166 return true;
167 }
168
169 static bool
render_client_dispatch_destroy_context(struct render_client * client,const union render_client_op_request * req)170 render_client_dispatch_destroy_context(struct render_client *client,
171 const union render_client_op_request *req)
172 {
173 const uint32_t ctx_id = req->destroy_context.ctx_id;
174 struct render_context_record *rec = render_client_find_record(client, ctx_id);
175 if (rec)
176 render_client_remove_record(client, rec);
177
178 return true;
179 }
180
181 static bool
render_client_dispatch_create_context(struct render_client * client,const union render_client_op_request * req)182 render_client_dispatch_create_context(struct render_client *client,
183 const union render_client_op_request *req)
184 {
185 struct render_server *srv = client->server;
186
187 int remote_fd;
188 bool ok = render_client_create_context(client, &req->create_context, &remote_fd);
189 if (!ok)
190 return false;
191
192 if (srv->state == RENDER_SERVER_STATE_SUBPROCESS) {
193 assert(remote_fd < 0);
194 return true;
195 }
196
197 const struct render_client_op_create_context_reply reply = {
198 .ok = ok,
199 };
200 if (!ok)
201 return render_socket_send_reply(&client->socket, &reply, sizeof(reply));
202
203 ok = render_socket_send_reply_with_fds(&client->socket, &reply, sizeof(reply),
204 &remote_fd, 1);
205 close(remote_fd);
206
207 return ok;
208 }
209
210 static bool
render_client_dispatch_reset(struct render_client * client,UNUSED const union render_client_op_request * req)211 render_client_dispatch_reset(struct render_client *client,
212 UNUSED const union render_client_op_request *req)
213 {
214 render_client_clear_records(client);
215 return true;
216 }
217
218 static bool
render_client_dispatch_init(struct render_client * client,const union render_client_op_request * req)219 render_client_dispatch_init(struct render_client *client,
220 const union render_client_op_request *req)
221 {
222 client->init_flags = req->init.flags;
223
224 /* init now to avoid doing it in each worker, but only when tracing is
225 * disabled because perfetto can get confused
226 */
227 #ifndef ENABLE_TRACING
228 render_virgl_init(client->init_flags);
229 #endif
230
231 /* this makes the Vulkan loader loads ICDs */
232 uint32_t unused_count;
233 vkEnumerateInstanceExtensionProperties(NULL, &unused_count, NULL);
234
235 return true;
236 }
237
238 static bool
render_client_dispatch_nop(UNUSED struct render_client * client,UNUSED const union render_client_op_request * req)239 render_client_dispatch_nop(UNUSED struct render_client *client,
240 UNUSED const union render_client_op_request *req)
241 {
242 return true;
243 }
244
245 struct render_client_dispatch_entry {
246 size_t expect_size;
247 bool (*dispatch)(struct render_client *client,
248 const union render_client_op_request *req);
249 };
250
251 static const struct render_client_dispatch_entry
252 render_client_dispatch_table[RENDER_CLIENT_OP_COUNT] = {
253 #define RENDER_CLIENT_DISPATCH(NAME, name) \
254 [RENDER_CLIENT_OP_## \
255 NAME] = { .expect_size = sizeof(struct render_client_op_##name##_request), \
256 .dispatch = render_client_dispatch_##name }
257 RENDER_CLIENT_DISPATCH(NOP, nop),
258 RENDER_CLIENT_DISPATCH(INIT, init),
259 RENDER_CLIENT_DISPATCH(RESET, reset),
260 RENDER_CLIENT_DISPATCH(CREATE_CONTEXT, create_context),
261 RENDER_CLIENT_DISPATCH(DESTROY_CONTEXT, destroy_context),
262 #undef RENDER_CLIENT_DISPATCH
263 };
264
265 bool
render_client_dispatch(struct render_client * client)266 render_client_dispatch(struct render_client *client)
267 {
268 union render_client_op_request req;
269 size_t req_size;
270 if (!render_socket_receive_request(&client->socket, &req, sizeof(req), &req_size))
271 return false;
272
273 if (req.header.op >= RENDER_CLIENT_OP_COUNT) {
274 render_log("invalid client op %d", req.header.op);
275 return false;
276 }
277
278 const struct render_client_dispatch_entry *entry =
279 &render_client_dispatch_table[req.header.op];
280 if (entry->expect_size != req_size) {
281 render_log("invalid request size %zu for client op %d", req_size, req.header.op);
282 return false;
283 }
284
285 if (!entry->dispatch(client, &req))
286 render_log("failed to dispatch client op %d", req.header.op);
287
288 return true;
289 }
290
291 void
render_client_destroy(struct render_client * client)292 render_client_destroy(struct render_client *client)
293 {
294 struct render_server *srv = client->server;
295
296 if (srv->state == RENDER_SERVER_STATE_SUBPROCESS) {
297 assert(list_is_empty(&client->context_records));
298 } else {
299 render_client_clear_records(client);
300
301 /* see render_client_dispatch_init */
302 #ifndef ENABLE_TRACING
303 render_virgl_fini();
304 #endif
305 }
306
307 render_socket_fini(&client->socket);
308 free(client);
309 }
310
311 struct render_client *
render_client_create(struct render_server * srv,int client_fd)312 render_client_create(struct render_server *srv, int client_fd)
313 {
314 struct render_client *client = calloc(1, sizeof(*client));
315
316 if (!client)
317 return NULL;
318
319 client->server = srv;
320 render_socket_init(&client->socket, client_fd);
321
322 list_inithead(&client->context_records);
323
324 return client;
325 }
326