The open source OpenXR runtime
at main 33 kB view raw
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