The open source OpenXR runtime
1// Copyright 2020-2024, Collabora, Ltd.
2// Copyright 2024-2025, NVIDIA CORPORATION.
3// SPDX-License-Identifier: BSL-1.0
4/*!
5 * @file
6 * @brief Server process functions.
7 * @author Pete Black <pblack@collabora.com>
8 * @author Jakob Bornecrantz <jakob@collabora.com>
9 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
10 * @author Korcan Hussein <korcan.hussein@collabora.com>
11 * @ingroup ipc_server
12 */
13
14#include "xrt/xrt_device.h"
15#include "xrt/xrt_system.h"
16#include "xrt/xrt_instance.h"
17#include "xrt/xrt_compositor.h"
18#include "xrt/xrt_config_have.h"
19#include "xrt/xrt_config_os.h"
20
21#include "os/os_time.h"
22#include "util/u_var.h"
23#include "util/u_misc.h"
24#include "util/u_debug.h"
25#include "util/u_trace_marker.h"
26#include "util/u_verify.h"
27#include "util/u_process.h"
28#include "util/u_debug_gui.h"
29#include "util/u_pretty_print.h"
30
31#include "util/u_git_tag.h"
32
33#include "shared/ipc_protocol.h"
34#include "shared/ipc_shmem.h"
35#include "server/ipc_server.h"
36#include "server/ipc_server_interface.h"
37
38#include <stdlib.h>
39#include <stdbool.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <fcntl.h>
43#include <errno.h>
44#include <stdio.h>
45#include <string.h>
46#include <assert.h>
47#include <limits.h>
48
49#if defined(XRT_OS_WINDOWS)
50#include <timeapi.h>
51#endif
52
53
54/*
55 *
56 * Defines and helpers.
57 *
58 */
59
60DEBUG_GET_ONCE_BOOL_OPTION(exit_when_idle, "IPC_EXIT_WHEN_IDLE", false)
61DEBUG_GET_ONCE_NUM_OPTION(exit_when_idle_delay_ms, "IPC_EXIT_WHEN_IDLE_DELAY_MS", 5000)
62DEBUG_GET_ONCE_LOG_OPTION(ipc_log, "IPC_LOG", U_LOGGING_INFO)
63
64/*
65 * "XRT_NO_STDIN" option disables stdin and prevents monado-service from terminating.
66 * This could be useful for situations where there is no proper or in a non-interactive shell.
67 * Two example scenarios are:
68 * * IDE terminals,
69 * * Some scripting environments where monado-service is spawned in the background
70 */
71DEBUG_GET_ONCE_BOOL_OPTION(no_stdin, "XRT_NO_STDIN", false)
72
73
74/*
75 *
76 * Idev functions.
77 *
78 */
79
80static int32_t
81find_xdev_index(struct ipc_server *s, struct xrt_device *xdev)
82{
83 if (xdev == NULL) {
84 return -1;
85 }
86
87 for (int32_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
88 if (s->xsysd->xdevs[i] == xdev) {
89 return i;
90 }
91 }
92
93 IPC_WARN(s, "Could not find index for xdev: '%s'", xdev->str);
94
95 return -1;
96}
97
98static void
99init_idev(struct ipc_device *idev, struct xrt_device *xdev)
100{
101 idev->xdev = xdev;
102}
103
104static void
105teardown_idev(struct ipc_device *idev)
106{
107 idev->xdev = NULL;
108}
109
110static void
111init_idevs(struct ipc_server *s)
112{
113 // Copy the devices over into the idevs array.
114 for (size_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
115 if (s->xsysd->xdevs[i] == NULL) {
116 continue;
117 }
118
119 init_idev(&s->idevs[i], s->xsysd->xdevs[i]);
120 }
121}
122
123static void
124teardown_idevs(struct ipc_server *s)
125{
126 for (size_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
127 teardown_idev(&s->idevs[i]);
128 }
129}
130
131
132/*
133 *
134 * Static functions.
135 *
136 */
137
138XRT_MAYBE_UNUSED static void
139print_linux_end_user_failed_information(enum u_logging_level log_level)
140{
141 struct u_pp_sink_stack_only sink;
142 u_pp_delegate_t dg = u_pp_sink_stack_only_init(&sink);
143
144 // Print Newline
145#define PN() u_pp(dg, "\n")
146 // Print Newline, Hash, Space
147#define PNH() u_pp(dg, "\n#")
148 // Print Newline, Hash, Space
149#define PNHS(...) u_pp(dg, "\n# "__VA_ARGS__)
150 // Print Newline, 80 Hashes
151#define PN80H() \
152 do { \
153 PN(); \
154 for (uint32_t i = 0; i < 8; i++) { \
155 u_pp(dg, "##########"); \
156 } \
157 } while (false)
158
159 PN80H();
160 PNHS(" #");
161 PNHS(" The Monado service has failed to start. #");
162 PNHS(" #");
163 PNHS("If you want to report please upload the logs of the service as a text file. #");
164 PNHS("You can also capture the output the monado-cli info command to provide more #");
165 PNHS("information about your system, that will help diagnosing your problem. The #");
166 PNHS("below commands is how you best capture the information from the commands. #");
167 PNHS(" #");
168 PNHS(" monado-cli info 2>&1 | tee info.txt #");
169 PNHS(" monado-service 2>&1 | tee logs.txt #");
170 PNHS(" #");
171 PN80H();
172
173 U_LOG_IFL_I(log_level, "%s", sink.buffer);
174}
175
176XRT_MAYBE_UNUSED static void
177print_linux_end_user_started_information(enum u_logging_level log_level)
178{
179 struct u_pp_sink_stack_only sink;
180 u_pp_delegate_t dg = u_pp_sink_stack_only_init(&sink);
181
182
183 PN80H();
184 PNHS(" #");
185 PNHS(" The Monado service has started. #");
186 PNHS(" #");
187 PN80H();
188
189#undef PN
190#undef PNH
191#undef PNHS
192#undef PN80H
193
194 U_LOG_IFL_I(log_level, "%s", sink.buffer);
195}
196
197static void
198teardown_all(struct ipc_server *s)
199{
200 u_var_remove_root(s);
201
202 xrt_syscomp_destroy(&s->xsysc);
203
204 teardown_idevs(s);
205
206 xrt_space_overseer_destroy(&s->xso);
207 xrt_system_devices_destroy(&s->xsysd);
208 xrt_system_destroy(&s->xsys);
209
210 xrt_instance_destroy(&s->xinst);
211
212 ipc_server_mainloop_deinit(&s->ml);
213
214 u_process_destroy(s->process);
215
216 // Destroyed last.
217 os_mutex_destroy(&s->global_state.lock);
218}
219
220static void
221init_tracking_origins(struct ipc_server *s)
222{
223 for (size_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
224 struct xrt_device *xdev = s->idevs[i].xdev;
225 if (xdev == NULL) {
226 continue;
227 }
228
229 struct xrt_tracking_origin *xtrack = xdev->tracking_origin;
230 assert(xtrack != NULL);
231 size_t index = 0;
232
233 for (; index < XRT_SYSTEM_MAX_DEVICES; index++) {
234 if (s->xtracks[index] == NULL) {
235 s->xtracks[index] = xtrack;
236 break;
237 }
238 if (s->xtracks[index] == xtrack) {
239 break;
240 }
241 }
242 }
243}
244
245static void
246handle_binding(struct ipc_shared_memory *ism,
247 struct xrt_binding_profile *xbp,
248 struct ipc_shared_binding_profile *isbp,
249 uint32_t *input_pair_index_ptr,
250 uint32_t *output_pair_index_ptr)
251{
252 uint32_t input_pair_index = *input_pair_index_ptr;
253 uint32_t output_pair_index = *output_pair_index_ptr;
254
255 isbp->name = xbp->name;
256
257 // Copy the initial state and also count the number in input_pairs.
258 uint32_t input_pair_start = input_pair_index;
259 for (size_t k = 0; k < xbp->input_count; k++) {
260 ism->input_pairs[input_pair_index++] = xbp->inputs[k];
261 }
262
263 // Setup the 'offsets' and number of input_pairs.
264 if (input_pair_start != input_pair_index) {
265 isbp->input_count = input_pair_index - input_pair_start;
266 isbp->first_input_index = input_pair_start;
267 }
268
269 // Copy the initial state and also count the number in outputs.
270 uint32_t output_pair_start = output_pair_index;
271 for (size_t k = 0; k < xbp->output_count; k++) {
272 ism->output_pairs[output_pair_index++] = xbp->outputs[k];
273 }
274
275 // Setup the 'offsets' and number of output_pairs.
276 if (output_pair_start != output_pair_index) {
277 isbp->output_count = output_pair_index - output_pair_start;
278 isbp->first_output_index = output_pair_start;
279 }
280
281 *input_pair_index_ptr = input_pair_index;
282 *output_pair_index_ptr = output_pair_index;
283}
284
285XRT_CHECK_RESULT static xrt_result_t
286init_shm_and_instance_state(struct ipc_server *s, volatile struct ipc_client_state *ics)
287{
288 const size_t size = sizeof(struct ipc_shared_memory);
289 xrt_shmem_handle_t handle;
290
291 xrt_result_t xret = ipc_shmem_create(size, &handle, (void **)&s->isms[ics->server_thread_index]);
292 IPC_CHK_AND_RET(s, xret, "ipc_shmem_create");
293
294 // we have a filehandle, we will pass this to our client
295 ics->ism_handle = handle;
296
297 // Convenience
298 struct ipc_shared_memory *ism = s->isms[ics->server_thread_index];
299
300 // Clients expect git version info and timestamp available upon connect.
301 snprintf(ism->u_git_tag, IPC_VERSION_NAME_LEN, "%s", u_git_tag);
302
303 // Used to synchronize all client's xrt_instance::startup_timestamp.
304 ism->startup_timestamp = os_monotonic_get_ns();
305
306 return XRT_SUCCESS;
307}
308
309static void
310init_system_shm_state(struct ipc_server *s, volatile struct ipc_client_state *cs)
311{
312 /*
313 *
314 * Setup the shared memory state.
315 *
316 */
317
318 uint32_t count = 0;
319 struct ipc_shared_memory *ism = s->isms[cs->server_thread_index];
320
321 // Setup the tracking origins.
322 count = 0;
323 for (size_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
324 struct xrt_tracking_origin *xtrack = s->xtracks[i];
325 if (xtrack == NULL) {
326 continue;
327 }
328
329 // The position of the tracking origin matches that in the
330 // server's memory.
331 assert(i < XRT_SYSTEM_MAX_DEVICES);
332
333 struct ipc_shared_tracking_origin *itrack = &ism->itracks[count++];
334 memcpy(itrack->name, xtrack->name, sizeof(itrack->name));
335 itrack->type = xtrack->type;
336 itrack->offset = xtrack->initial_offset;
337 }
338
339 ism->itrack_count = count;
340
341 count = 0;
342 uint32_t input_index = 0;
343 uint32_t output_index = 0;
344 uint32_t binding_index = 0;
345 uint32_t input_pair_index = 0;
346 uint32_t output_pair_index = 0;
347
348 for (size_t i = 0; i < XRT_SYSTEM_MAX_DEVICES; i++) {
349 struct xrt_device *xdev = s->idevs[i].xdev;
350 if (xdev == NULL) {
351 continue;
352 }
353
354 struct ipc_shared_device *isdev = &ism->isdevs[count++];
355
356 isdev->name = xdev->name;
357 memcpy(isdev->str, xdev->str, sizeof(isdev->str));
358 memcpy(isdev->serial, xdev->serial, sizeof(isdev->serial));
359
360 // Copy information.
361 isdev->device_type = xdev->device_type;
362 isdev->supported = xdev->supported;
363
364 // Setup the tracking origin.
365 isdev->tracking_origin_index = (uint32_t)-1;
366 for (uint32_t k = 0; k < XRT_SYSTEM_MAX_DEVICES; k++) {
367 if (xdev->tracking_origin != s->xtracks[k]) {
368 continue;
369 }
370
371 isdev->tracking_origin_index = k;
372 break;
373 }
374
375 assert(isdev->tracking_origin_index != (uint32_t)-1);
376
377 // Initial update.
378 xrt_device_update_inputs(xdev);
379
380 // Bindings
381 uint32_t binding_start = binding_index;
382 for (size_t k = 0; k < xdev->binding_profile_count; k++) {
383 handle_binding(ism, &xdev->binding_profiles[k], &ism->binding_profiles[binding_index++],
384 &input_pair_index, &output_pair_index);
385 }
386
387 // Setup the 'offsets' and number of bindings.
388 if (binding_start != binding_index) {
389 isdev->binding_profile_count = binding_index - binding_start;
390 isdev->first_binding_profile_index = binding_start;
391 }
392
393 // Copy the initial state and also count the number in inputs.
394 uint32_t input_start = input_index;
395 for (size_t k = 0; k < xdev->input_count; k++) {
396 ism->inputs[input_index++] = xdev->inputs[k];
397 }
398
399 // Setup the 'offsets' and number of inputs.
400 if (input_start != input_index) {
401 isdev->input_count = input_index - input_start;
402 isdev->first_input_index = input_start;
403 }
404
405 // Copy the initial state and also count the number in outputs.
406 uint32_t output_start = output_index;
407 for (size_t k = 0; k < xdev->output_count; k++) {
408 ism->outputs[output_index++] = xdev->outputs[k];
409 }
410
411 // Setup the 'offsets' and number of outputs.
412 if (output_start != output_index) {
413 isdev->output_count = output_index - output_start;
414 isdev->first_output_index = output_start;
415 }
416 }
417
418 // Setup the HMD
419 // set view count
420 const struct xrt_device *xhead = s->xsysd->static_roles.head;
421 const struct xrt_hmd_parts *xhmd = xhead != NULL ? xhead->hmd : NULL;
422 U_ZERO(&ism->hmd);
423 if (xhmd != NULL) {
424 ism->hmd.view_count = xhmd->view_count;
425 for (uint32_t view = 0; view < xhmd->view_count; ++view) {
426 ism->hmd.views[view].display.w_pixels = xhmd->views[view].display.w_pixels;
427 ism->hmd.views[view].display.h_pixels = xhmd->views[view].display.h_pixels;
428 }
429
430 for (uint32_t i = 0; i < xhmd->blend_mode_count; i++) {
431 // Not super necessary, we also do this assert in oxr_system.c
432 assert(u_verify_blend_mode_valid(xhmd->blend_modes[i]));
433 ism->hmd.blend_modes[i] = xhmd->blend_modes[i];
434 }
435 ism->hmd.blend_mode_count = xhmd->blend_mode_count;
436 }
437
438 // Finally tell the client how many devices we have.
439 ism->isdev_count = count;
440
441 // Assign all of the roles.
442 ism->roles.head = find_xdev_index(s, s->xsysd->static_roles.head);
443 ism->roles.eyes = find_xdev_index(s, s->xsysd->static_roles.eyes);
444 ism->roles.face = find_xdev_index(s, s->xsysd->static_roles.face);
445 ism->roles.body = find_xdev_index(s, s->xsysd->static_roles.body);
446
447#define SET_HT_ROLE(SRC) \
448 ism->roles.hand_tracking.SRC.left = find_xdev_index(s, s->xsysd->static_roles.hand_tracking.SRC.left); \
449 ism->roles.hand_tracking.SRC.right = find_xdev_index(s, s->xsysd->static_roles.hand_tracking.SRC.right);
450 SET_HT_ROLE(unobstructed)
451 SET_HT_ROLE(conforming)
452#undef SET_HT_ROLE
453}
454
455static void
456init_server_state(struct ipc_server *s)
457{
458 // set up initial state for global vars, and each client state
459
460 s->global_state.active_client_index = -1; // we start off with no active client.
461 s->global_state.last_active_client_index = -1;
462 s->global_state.connected_client_count = 0; // No clients connected initially
463 s->current_slot_index = 0;
464
465 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
466 volatile struct ipc_client_state *ics = &s->threads[i].ics;
467 ics->server = s;
468 ics->server_thread_index = -1;
469 }
470}
471
472static xrt_result_t
473init_all(struct ipc_server *s,
474 enum u_logging_level log_level,
475 const struct ipc_server_callbacks *callbacks,
476 void *callback_data,
477 bool exit_on_disconnect)
478{
479 xrt_result_t xret = XRT_SUCCESS;
480 int ret;
481
482 // First order of business set the log level.
483 s->log_level = log_level;
484
485 // Store callbacks and data
486 s->callbacks = callbacks;
487 s->callback_data = callback_data;
488
489 // This should never fail.
490 ret = os_mutex_init(&s->global_state.lock);
491 if (ret < 0) {
492 IPC_ERROR(s, "Global state lock mutex failed to init!");
493 // Do not call teardown_all here, os_mutex_destroy will assert.
494 return XRT_ERROR_SYNC_PRIMITIVE_CREATION_FAILED;
495 }
496
497 s->process = u_process_create_if_not_running();
498 if (!s->process) {
499 IPC_ERROR(s, "monado-service is already running! Use XRT_LOG=trace for more information.");
500 xret = XRT_ERROR_IPC_SERVICE_ALREADY_RUNNING;
501 }
502 IPC_CHK_WITH_GOTO(s, xret, "u_process_create_if_not_running", error);
503
504 // Yes we should be running.
505 s->running = true;
506 s->exit_on_disconnect = exit_on_disconnect;
507 s->exit_when_idle = debug_get_bool_option_exit_when_idle();
508 s->last_client_disconnect_ns = 0;
509 uint64_t delay_ms = debug_get_num_option_exit_when_idle_delay_ms();
510 s->exit_when_idle_delay_ns = delay_ms * U_TIME_1MS_IN_NS;
511
512 xret = xrt_instance_create(NULL, &s->xinst);
513 IPC_CHK_WITH_GOTO(s, xret, "xrt_instance_create", error);
514
515 ret = ipc_server_mainloop_init(&s->ml, s->no_stdin);
516 if (ret < 0) {
517 xret = XRT_ERROR_IPC_MAINLOOP_FAILED_TO_INIT;
518 }
519 IPC_CHK_WITH_GOTO(s, xret, "ipc_server_mainloop_init", error);
520
521 // Never fails, do this second last.
522 init_server_state(s);
523
524 u_var_add_root(s, "IPC Server", false);
525 u_var_add_log_level(s, &s->log_level, "Log level");
526 u_var_add_bool(s, &s->exit_on_disconnect, "exit_on_disconnect");
527 u_var_add_bool(s, &s->exit_when_idle, "exit_when_idle");
528 u_var_add_u64(s, &s->exit_when_idle_delay_ns, "exit_when_idle_delay_ns");
529 u_var_add_bool(s, (bool *)&s->running, "running");
530
531 return XRT_SUCCESS;
532
533error:
534 teardown_all(s);
535
536 return xret;
537}
538
539static int
540main_loop(struct ipc_server *s)
541{
542 while (s->running) {
543 os_nanosleep(U_TIME_1S_IN_NS / 20);
544
545 // Check polling.
546 ipc_server_mainloop_poll(s, &s->ml);
547 }
548
549 return 0;
550}
551
552
553/*
554 *
555 * Client management functions.
556 *
557 */
558
559static void
560handle_overlay_client_events(volatile struct ipc_client_state *ics, int active_id, int prev_active_id)
561{
562 // Is an overlay session?
563 if (!ics->client_state.session_overlay) {
564 return;
565 }
566
567 // Does this client have a compositor yet, if not return?
568 if (ics->xc == NULL) {
569 return;
570 }
571
572 // Switch between main applications
573 if (active_id >= 0 && prev_active_id >= 0) {
574 xrt_syscomp_set_main_app_visibility(ics->server->xsysc, ics->xc, false);
575 xrt_syscomp_set_main_app_visibility(ics->server->xsysc, ics->xc, true);
576 }
577
578 // Switch from idle to active application
579 if (active_id >= 0 && prev_active_id < 0) {
580 xrt_syscomp_set_main_app_visibility(ics->server->xsysc, ics->xc, true);
581 }
582
583 // Switch from active application to idle
584 if (active_id < 0 && prev_active_id >= 0) {
585 xrt_syscomp_set_main_app_visibility(ics->server->xsysc, ics->xc, false);
586 }
587}
588
589static void
590handle_focused_client_events(volatile struct ipc_client_state *ics, int active_id, int prev_active_id)
591{
592 // Set start z_order at the bottom.
593 int64_t z_order = INT64_MIN;
594
595 // Set visibility/focus to false on all applications.
596 bool focused = false;
597 bool visible = false;
598
599 // Set visible + focused if we are the primary application
600 if (ics->server_thread_index == active_id) {
601 visible = true;
602 focused = true;
603 z_order = INT64_MIN;
604 }
605
606 // Set all overlays to always active and focused.
607 if (ics->client_state.session_overlay) {
608 visible = true;
609 focused = true;
610 z_order = ics->client_state.z_order;
611 }
612
613 ics->client_state.session_visible = visible;
614 ics->client_state.session_focused = focused;
615 ics->client_state.z_order = z_order;
616
617 if (ics->xc != NULL) {
618 xrt_syscomp_set_state(ics->server->xsysc, ics->xc, visible, focused, os_monotonic_get_ns());
619 xrt_syscomp_set_z_order(ics->server->xsysc, ics->xc, z_order);
620 }
621}
622
623static void
624flush_state_to_all_clients_locked(struct ipc_server *s)
625{
626 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
627 volatile struct ipc_client_state *ics = &s->threads[i].ics;
628
629 // Not running?
630 if (ics->server_thread_index < 0) {
631 continue;
632 }
633
634 handle_focused_client_events(ics, s->global_state.active_client_index,
635 s->global_state.last_active_client_index);
636 handle_overlay_client_events(ics, s->global_state.active_client_index,
637 s->global_state.last_active_client_index);
638 }
639}
640
641static void
642update_server_state_locked(struct ipc_server *s)
643{
644 // if our client that is set to active is still active,
645 // and it is the same as our last active client, we can
646 // early-out, as no events need to be sent
647
648 if (s->global_state.active_client_index >= 0) {
649
650 volatile struct ipc_client_state *ics = &s->threads[s->global_state.active_client_index].ics;
651
652 if (ics->client_state.session_active &&
653 s->global_state.active_client_index == s->global_state.last_active_client_index) {
654 return;
655 }
656 }
657
658
659 // our active application has changed - this would typically be
660 // switched by the monado-ctl application or other app making a
661 // 'set active application' ipc call, or it could be a
662 // connection loss resulting in us needing to 'fall through' to
663 // the first active application
664 //, or finally to the idle 'wallpaper' images.
665
666
667 bool set_idle = true;
668 int fallback_active_application = -1;
669
670 // do we have a fallback application?
671 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
672 volatile struct ipc_client_state *ics = &s->threads[i].ics;
673 if (ics->client_state.session_overlay == false && ics->server_thread_index >= 0 &&
674 ics->client_state.session_active) {
675 fallback_active_application = i;
676 set_idle = false;
677 }
678 }
679
680 // if there is a currently-set active primary application and it is not
681 // actually active/displayable, use the fallback application
682 // instead.
683 if (s->global_state.active_client_index >= 0) {
684 volatile struct ipc_client_state *ics = &s->threads[s->global_state.active_client_index].ics;
685 if (!(ics->client_state.session_overlay == false && ics->client_state.session_active)) {
686 s->global_state.active_client_index = fallback_active_application;
687 }
688 }
689
690
691 // if we have no applications to fallback to, enable the idle
692 // wallpaper.
693 if (set_idle) {
694 s->global_state.active_client_index = -1;
695 }
696
697 flush_state_to_all_clients_locked(s);
698
699 s->global_state.last_active_client_index = s->global_state.active_client_index;
700}
701
702static volatile struct ipc_client_state *
703find_client_locked(struct ipc_server *s, uint32_t client_id)
704{
705 // Check for invalid IDs.
706 if (client_id == 0 || client_id > INT_MAX) {
707 IPC_WARN(s, "Invalid ID '%u', failing operation.", client_id);
708 return NULL;
709 }
710
711 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
712 volatile struct ipc_client_state *ics = &s->threads[i].ics;
713
714 // Is this the client we are looking for?
715 if (ics->client_state.id != client_id) {
716 continue;
717 }
718
719 // Just in case of state data.
720 if (!xrt_ipc_handle_is_valid(ics->imc.ipc_handle)) {
721 IPC_WARN(s, "Encountered invalid state while searching for client with ID '%d'", client_id);
722 return NULL;
723 }
724
725 return ics;
726 }
727
728 IPC_WARN(s, "No client with ID '%u', failing operation.", client_id);
729
730 return NULL;
731}
732
733static xrt_result_t
734get_client_app_state_locked(struct ipc_server *s, uint32_t client_id, struct ipc_app_state *out_ias)
735{
736 volatile struct ipc_client_state *ics = find_client_locked(s, client_id);
737 if (ics == NULL) {
738 return XRT_ERROR_IPC_FAILURE;
739 }
740
741 struct ipc_app_state ias = ics->client_state;
742 ias.io_active = ics->io_active;
743
744 // @todo: track this data in the ipc_client_state struct
745 ias.primary_application = false;
746
747 // The active client is decided by index, so get that from the ics.
748 int index = ics->server_thread_index;
749
750 if (s->global_state.active_client_index == index) {
751 ias.primary_application = true;
752 }
753
754 *out_ias = ias;
755
756 return XRT_SUCCESS;
757}
758
759static xrt_result_t
760set_active_client_locked(struct ipc_server *s, uint32_t client_id)
761{
762 volatile struct ipc_client_state *ics = find_client_locked(s, client_id);
763 if (ics == NULL) {
764 return XRT_ERROR_IPC_FAILURE;
765 }
766
767 // The active client is decided by index, so get that from the ics.
768 int index = ics->server_thread_index;
769
770 if (index != s->global_state.active_client_index) {
771 s->global_state.active_client_index = index;
772 }
773
774 return XRT_SUCCESS;
775}
776
777static xrt_result_t
778toggle_io_client_locked(struct ipc_server *s, uint32_t client_id)
779{
780 volatile struct ipc_client_state *ics = find_client_locked(s, client_id);
781 if (ics == NULL) {
782 return XRT_ERROR_IPC_FAILURE;
783 }
784
785 ics->io_active = !ics->io_active;
786
787 return XRT_SUCCESS;
788}
789
790static uint32_t
791allocate_id_locked(struct ipc_server *s)
792{
793 uint32_t id = 0;
794 while (id == 0) {
795 // Allocate a new one.
796 id = ++s->id_generator;
797
798 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
799 volatile struct ipc_client_state *ics = &s->threads[i].ics;
800
801 // If we find the ID, get a new one by setting to zero.
802 if (ics->client_state.id == id) {
803 id = 0;
804 break;
805 }
806 }
807 }
808
809 // Paranoia.
810 if (id == 0) {
811 U_LOG_E("Got app(client) id 0, not allowed!");
812 assert(id > 0);
813 }
814
815 return id;
816}
817
818
819/*
820 *
821 * Exported functions.
822 *
823 */
824
825xrt_result_t
826ipc_server_init_system_if_available_locked(struct ipc_server *s,
827 volatile struct ipc_client_state *ics,
828 bool *out_available)
829{
830 xrt_result_t xret = XRT_SUCCESS;
831
832 bool available = false;
833
834 if (s->xsys) {
835 available = true;
836 } else {
837 xret = xrt_instance_is_system_available(s->xinst, &available);
838 IPC_CHK_WITH_GOTO(s, xret, "xrt_instance_is_system_available", error);
839
840 if (available) {
841 xret = xrt_instance_create_system(s->xinst, &s->xsys, &s->xsysd, &s->xso, &s->xsysc);
842 IPC_CHK_WITH_GOTO(s, xret, "xrt_instance_create_system", error);
843
844 // Always succeeds.
845 init_idevs(s);
846 init_tracking_origins(s);
847 }
848 }
849
850 if (available && ics != NULL && !ics->has_init_shm_system) {
851 init_system_shm_state(s, ics);
852 ics->has_init_shm_system = true;
853 }
854
855 if (out_available) {
856 *out_available = available;
857 }
858
859 return XRT_SUCCESS;
860
861error:
862 return xret;
863}
864
865xrt_result_t
866ipc_server_get_client_app_state(struct ipc_server *s, uint32_t client_id, struct ipc_app_state *out_ias)
867{
868 os_mutex_lock(&s->global_state.lock);
869 xrt_result_t xret = get_client_app_state_locked(s, client_id, out_ias);
870 os_mutex_unlock(&s->global_state.lock);
871
872 return xret;
873}
874
875xrt_result_t
876ipc_server_set_active_client(struct ipc_server *s, uint32_t client_id)
877{
878 os_mutex_lock(&s->global_state.lock);
879 xrt_result_t xret = set_active_client_locked(s, client_id);
880 os_mutex_unlock(&s->global_state.lock);
881
882 return xret;
883}
884
885xrt_result_t
886ipc_server_toggle_io_client(struct ipc_server *s, uint32_t client_id)
887{
888 os_mutex_lock(&s->global_state.lock);
889 xrt_result_t xret = toggle_io_client_locked(s, client_id);
890 os_mutex_unlock(&s->global_state.lock);
891
892 return xret;
893}
894
895void
896ipc_server_activate_session(volatile struct ipc_client_state *ics)
897{
898 struct ipc_server *s = ics->server;
899
900 // Already active, noop.
901 if (ics->client_state.session_active) {
902 return;
903 }
904
905 assert(ics->server_thread_index >= 0);
906
907 // Multiple threads could call this at the same time.
908 os_mutex_lock(&s->global_state.lock);
909
910 ics->client_state.session_active = true;
911
912 if (ics->client_state.session_overlay) {
913 // For new active overlay sessions only update this session.
914 handle_focused_client_events(ics, s->global_state.active_client_index,
915 s->global_state.last_active_client_index);
916 handle_overlay_client_events(ics, s->global_state.active_client_index,
917 s->global_state.last_active_client_index);
918 } else {
919 // Update active client
920 set_active_client_locked(s, ics->client_state.id);
921
922 // For new active regular sessions update all clients.
923 update_server_state_locked(s);
924 }
925
926 os_mutex_unlock(&s->global_state.lock);
927}
928
929void
930ipc_server_deactivate_session(volatile struct ipc_client_state *ics)
931{
932 struct ipc_server *s = ics->server;
933
934 // Multiple threads could call this at the same time.
935 os_mutex_lock(&s->global_state.lock);
936
937 ics->client_state.session_active = false;
938
939 update_server_state_locked(s);
940
941 os_mutex_unlock(&s->global_state.lock);
942}
943
944void
945ipc_server_update_state(struct ipc_server *s)
946{
947 // Multiple threads could call this at the same time.
948 os_mutex_lock(&s->global_state.lock);
949
950 update_server_state_locked(s);
951
952 os_mutex_unlock(&s->global_state.lock);
953}
954
955void
956ipc_server_handle_failure(struct ipc_server *vs)
957{
958 // Right now handled just the same as a graceful shutdown.
959 vs->running = false;
960}
961
962void
963ipc_server_handle_shutdown_signal(struct ipc_server *vs)
964{
965 vs->running = false;
966}
967
968void
969ipc_server_handle_client_connected(struct ipc_server *vs, xrt_ipc_handle_t ipc_handle)
970{
971 volatile struct ipc_client_state *ics = NULL;
972 int32_t cs_index = -1;
973
974 os_mutex_lock(&vs->global_state.lock);
975
976 // Increment the connected client counter
977 vs->global_state.connected_client_count++;
978
979 // A client connected, so we're no longer in a delayed exit state
980 // (The delay thread will still check the client count before exiting)
981 vs->last_client_disconnect_ns = 0;
982
983 // find the next free thread in our array (server_thread_index is -1)
984 // and have it handle this connection
985 for (uint32_t i = 0; i < IPC_MAX_CLIENTS; i++) {
986 volatile struct ipc_client_state *_cs = &vs->threads[i].ics;
987 if (_cs->server_thread_index < 0) {
988 ics = _cs;
989 cs_index = i;
990 break;
991 }
992 }
993 if (ics == NULL) {
994 xrt_ipc_handle_close(ipc_handle);
995
996 // Unlock when we are done.
997 os_mutex_unlock(&vs->global_state.lock);
998
999 U_LOG_E("Max client count reached!");
1000 return;
1001 }
1002
1003 struct ipc_thread *it = &vs->threads[cs_index];
1004 if (it->state != IPC_THREAD_READY && it->state != IPC_THREAD_STOPPING) {
1005 // we should not get here
1006 xrt_ipc_handle_close(ipc_handle);
1007
1008 // Unlock when we are done.
1009 os_mutex_unlock(&vs->global_state.lock);
1010
1011 U_LOG_E("Client state management error!");
1012 return;
1013 }
1014
1015 if (it->state != IPC_THREAD_READY) {
1016 os_thread_join(&it->thread);
1017 os_thread_destroy(&it->thread);
1018 it->state = IPC_THREAD_READY;
1019 }
1020
1021 it->state = IPC_THREAD_STARTING;
1022
1023 // Allocate a new ID, avoid zero.
1024 uint32_t id = allocate_id_locked(vs);
1025
1026 // Reset everything.
1027 U_ZERO((struct ipc_client_state *)ics);
1028
1029 // Set state.
1030 ics->local_space_overseer_index = UINT32_MAX;
1031 ics->client_state.id = id;
1032 ics->imc.ipc_handle = ipc_handle;
1033 ics->server = vs;
1034 ics->server_thread_index = cs_index;
1035 ics->io_active = true;
1036
1037 ics->plane_detection_size = 0;
1038 ics->plane_detection_count = 0;
1039 ics->plane_detection_ids = NULL;
1040 ics->plane_detection_xdev = NULL;
1041
1042 xrt_result_t xret = init_shm_and_instance_state(vs, ics);
1043 if (xret != XRT_SUCCESS) {
1044
1045 // Unlock when we are done.
1046 os_mutex_unlock(&vs->global_state.lock);
1047
1048 U_LOG_E("Failed to allocate shared memory!");
1049 return;
1050 }
1051
1052 os_thread_start(&it->thread, ipc_server_client_thread, (void *)ics);
1053
1054 // Unlock when we are done.
1055 os_mutex_unlock(&vs->global_state.lock);
1056}
1057
1058xrt_result_t
1059ipc_server_get_system_properties(struct ipc_server *vs, struct xrt_system_properties *out_properties)
1060{
1061 memcpy(out_properties, &vs->xsys->properties, sizeof(*out_properties));
1062 return XRT_SUCCESS;
1063}
1064
1065int
1066ipc_server_main_common(const struct ipc_server_main_info *ismi,
1067 const struct ipc_server_callbacks *callbacks,
1068 void *data)
1069{
1070 xrt_result_t xret = XRT_SUCCESS;
1071 int ret = -1;
1072
1073 // Get log level first.
1074 enum u_logging_level log_level = debug_get_log_option_ipc_log();
1075
1076 // Log very early who we are.
1077 U_LOG_IFL_I(log_level, "%s '%s' starting up...", u_runtime_description, u_git_tag);
1078
1079 // Allocate the server itself.
1080 struct ipc_server *s = U_TYPED_CALLOC(struct ipc_server);
1081
1082 // Can be set by either.
1083 s->no_stdin = ismi->no_stdin || debug_get_bool_option_no_stdin();
1084
1085#ifdef XRT_OS_WINDOWS
1086 timeBeginPeriod(1);
1087#endif
1088
1089 /*
1090 * Need to create early before any vars are added. Not created in
1091 * init_all since that function is shared with Android and the debug
1092 * GUI isn't supported on Android.
1093 */
1094 u_debug_gui_create(&ismi->udgci, &s->debug_gui);
1095
1096 xret = init_all(s, log_level, callbacks, data, ismi->exit_on_disconnect);
1097 U_LOG_CHK_ONLY_PRINT(log_level, xret, "init_all");
1098 if (xret != XRT_SUCCESS) {
1099 // Propagate the failure.
1100 callbacks->init_failed(xret, data);
1101 u_debug_gui_stop(&s->debug_gui);
1102 free(s);
1103 return -1;
1104 }
1105
1106 // Tell the callbacks we are entering the main-loop.
1107 callbacks->mainloop_entering(s, s->xinst, data);
1108
1109 // Early init the system. If not available now, will try again per client request.
1110 xret = ipc_server_init_system_if_available_locked( //
1111 s, //
1112 NULL, // optional - ics
1113 NULL); // optional - out_available
1114 if (xret != XRT_SUCCESS) {
1115 U_LOG_CHK_ONLY_PRINT(log_level, xret, "ipc_server_init_system_if_available_locked");
1116 }
1117
1118 // Start the debug UI now (if enabled).
1119 u_debug_gui_start(s->debug_gui, s->xinst, s->xsysd);
1120
1121 // Main loop.
1122 ret = main_loop(s);
1123
1124 // Tell the callbacks we are leaving the main-loop.
1125 callbacks->mainloop_leaving(s, s->xinst, data);
1126
1127 // Stop the UI before tearing everything down.
1128 u_debug_gui_stop(&s->debug_gui);
1129
1130 // Done after UI stopped.
1131 teardown_all(s);
1132 free(s);
1133
1134#ifdef XRT_OS_WINDOWS
1135 timeEndPeriod(1);
1136#endif
1137
1138 U_LOG_IFL_I(log_level, "Server exiting: '%i'", ret);
1139
1140 return ret;
1141}
1142
1143int
1144ipc_server_stop(struct ipc_server *s)
1145{
1146 s->running = false;
1147 return 0;
1148}
1149
1150#ifndef XRT_OS_ANDROID
1151
1152static void
1153init_failed(xrt_result_t xret, void *data)
1154{
1155#ifdef XRT_OS_LINUX
1156 // Print information how to debug issues.
1157 print_linux_end_user_failed_information(debug_get_log_option_ipc_log());
1158#endif
1159}
1160
1161static void
1162mainloop_entering(struct ipc_server *s, struct xrt_instance *xinst, void *data)
1163{
1164#ifdef XRT_OS_LINUX
1165 // Print a very clear service started message.
1166 print_linux_end_user_started_information(s->log_level);
1167#endif
1168}
1169
1170static void
1171mainloop_leaving(struct ipc_server *s, struct xrt_instance *xinst, void *data)
1172{
1173 // No-op
1174}
1175
1176void
1177client_connected(struct ipc_server *s, uint32_t client_id, void *data)
1178{
1179 IPC_INFO(s, "Client %u connected", client_id);
1180}
1181
1182void
1183client_disconnected(struct ipc_server *s, uint32_t client_id, void *data)
1184{
1185 IPC_INFO(s, "Client %u disconnected", client_id);
1186}
1187
1188int
1189ipc_server_main(int argc, char **argv, const struct ipc_server_main_info *ismi)
1190{
1191 const struct ipc_server_callbacks callbacks = {
1192 .init_failed = init_failed,
1193 .mainloop_entering = mainloop_entering,
1194 .mainloop_leaving = mainloop_leaving,
1195 .client_connected = client_connected,
1196 .client_disconnected = client_disconnected,
1197 };
1198
1199 return ipc_server_main_common(ismi, &callbacks, NULL);
1200}
1201
1202#endif // !XRT_OS_ANDROID