The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

d/hydra: Rewrite to add prediction, relation history, and a dedicated USB thread

Also cleans up how logging is done and brings it closer to modern standards.

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2471>

+223 -104
+223 -104
src/xrt/drivers/hydra/hydra_driver.c
··· 21 21 22 22 #include "os/os_hid.h" 23 23 #include "os/os_time.h" 24 + #include "os/os_threading.h" 24 25 25 26 #include "math/m_api.h" 26 27 #include "math/m_relation_history.h" ··· 31 32 #include "util/u_misc.h" 32 33 #include "util/u_time.h" 33 34 #include "util/u_logging.h" 35 + #include "util/u_linux.h" 36 + #include "util/u_trace_marker.h" 37 + #include "util/u_var.h" 34 38 35 39 #include "hydra_interface.h" 36 40 ··· 42 46 * 43 47 */ 44 48 45 - #define HYDRA_TRACE(d, ...) U_LOG_XDEV_IFL_T(&d->base, d->sys->log_level, __VA_ARGS__) 46 - #define HYDRA_DEBUG(d, ...) U_LOG_XDEV_IFL_D(&d->base, d->sys->log_level, __VA_ARGS__) 47 - #define HYDRA_INFO(d, ...) U_LOG_XDEV_IFL_I(&d->base, d->sys->log_level, __VA_ARGS__) 48 - #define HYDRA_WARN(d, ...) U_LOG_XDEV_IFL_W(&d->base, d->sys->log_level, __VA_ARGS__) 49 - #define HYDRA_ERROR(d, ...) U_LOG_XDEV_IFL_E(&d->base, d->sys->log_level, __VA_ARGS__) 49 + #define HD_TRACE(d, ...) U_LOG_XDEV_IFL_T(&d->base, d->sys->log_level, __VA_ARGS__) 50 + #define HD_DEBUG(d, ...) U_LOG_XDEV_IFL_D(&d->base, d->sys->log_level, __VA_ARGS__) 51 + #define HD_INFO(d, ...) U_LOG_XDEV_IFL_I(&d->base, d->sys->log_level, __VA_ARGS__) 52 + #define HD_WARN(d, ...) U_LOG_XDEV_IFL_W(&d->base, d->sys->log_level, __VA_ARGS__) 53 + #define HD_ERROR(d, ...) U_LOG_XDEV_IFL_E(&d->base, d->sys->log_level, __VA_ARGS__) 54 + 55 + #define HS_TRACE(d, ...) U_LOG_IFL_T(d->log_level, __VA_ARGS__) 56 + #define HS_DEBUG(d, ...) U_LOG_IFL_D(d->log_level, __VA_ARGS__) 57 + #define HS_INFO(d, ...) U_LOG_IFL_I(d->log_level, __VA_ARGS__) 58 + #define HS_WARN(d, ...) U_LOG_IFL_W(d->log_level, __VA_ARGS__) 59 + #define HS_ERROR(d, ...) U_LOG_IFL_E(d->log_level, __VA_ARGS__) 50 60 51 61 DEBUG_GET_ONCE_LOG_OPTION(hydra_log, "HYDRA_LOG", U_LOGGING_WARN) 52 62 ··· 99 109 100 110 struct hydra_controller_state 101 111 { 102 - struct xrt_pose pose; 112 + struct m_relation_history_filters motion_vector_filters; 113 + struct m_relation_history *relation_history; 114 + 103 115 struct xrt_vec2 js; 104 116 float trigger; 105 117 uint8_t buttons; ··· 139 151 struct os_hid_device *data_hid; 140 152 struct os_hid_device *command_hid; 141 153 154 + struct os_thread_helper usb_thread; 155 + 156 + struct os_mutex data_mutex; 157 + 142 158 struct hydra_state_machine sm; 143 159 struct hydra_device *devs[2]; 144 160 ··· 175 191 struct xrt_device base; 176 192 struct hydra_system *sys; 177 193 178 - 179 194 //! Last time that we updated inputs 180 195 timepoint_ns input_time; 181 196 ··· 196 211 */ 197 212 198 213 static void 199 - hydra_device_parse_controller(struct hydra_device *hd, uint8_t *buf); 214 + hydra_device_parse_controller(struct hydra_device *hd, uint8_t *buf, int64_t now); 200 215 201 216 static inline struct hydra_device * 202 217 hydra_device(struct xrt_device *xdev) ··· 238 253 * 239 254 * @relates hydra_sm 240 255 */ 241 - static int 256 + static void 242 257 hydra_sm_transition(struct hydra_state_machine *hsm, enum hydra_sm_state new_state, timepoint_ns now) 243 258 { 244 259 if (hsm->transition_time == 0) { ··· 248 263 hsm->current_state = new_state; 249 264 hsm->transition_time = now; 250 265 } 251 - return 0; 252 266 } 253 267 static inline uint8_t 254 268 hydra_read_uint8(uint8_t **bufptr) ··· 277 291 * Parse the controller-specific part of a buffer into a hydra device. 278 292 */ 279 293 static void 280 - hydra_device_parse_controller(struct hydra_device *hd, uint8_t *buf) 294 + hydra_device_parse_controller(struct hydra_device *hd, uint8_t *buf, int64_t now) 281 295 { 282 296 struct hydra_controller_state *state = &hd->state; 283 297 ··· 285 299 static const float SCALE_INT16_TO_FLOAT_PLUSMINUS_1 = 1.0f / 32768.0f; 286 300 static const float SCALE_UINT8_TO_FLOAT_0_TO_1 = 1.0f / 255.0f; 287 301 288 - state->pose.position.x = hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 289 - state->pose.position.z = hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 290 - state->pose.position.y = -hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 302 + struct xrt_pose pose; 303 + 304 + pose.position.x = hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 305 + pose.position.z = hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 306 + pose.position.y = -hydra_read_int16_le(&buf) * SCALE_MM_TO_METER; 291 307 292 308 // the negatives are to fix handedness 293 - state->pose.orientation.w = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 294 - state->pose.orientation.x = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 295 - state->pose.orientation.y = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 296 - state->pose.orientation.z = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 309 + pose.orientation.w = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 310 + pose.orientation.x = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 311 + pose.orientation.y = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 312 + pose.orientation.z = hydra_read_int16_le(&buf) * SCALE_INT16_TO_FLOAT_PLUSMINUS_1; 297 313 298 314 //! @todo the presence of this suggest we're not decoding the 299 315 //! orientation right. 300 - math_quat_normalize(&state->pose.orientation); 316 + math_quat_normalize(&pose.orientation); 301 317 302 318 struct xrt_quat fixed = { 303 - .x = state->pose.orientation.x, 304 - .y = -state->pose.orientation.z, 305 - .z = state->pose.orientation.y, 306 - .w = state->pose.orientation.w, 319 + .x = pose.orientation.x, 320 + .y = -pose.orientation.z, 321 + .z = pose.orientation.y, 322 + .w = pose.orientation.w, 307 323 }; 308 324 309 325 struct xrt_quat adjustment = {.x = 0, .y = 1, .z = 0, .w = 0}; ··· 312 328 adjustment = (struct xrt_quat){.x = 0, .y = 0, .z = 1, .w = 0}; 313 329 math_quat_rotate(&fixed, &adjustment, &fixed); 314 330 315 - state->pose.orientation = fixed; 331 + pose.orientation = fixed; 332 + 333 + struct xrt_space_relation space_relation = {0}; 334 + space_relation.pose = pose; 335 + space_relation.relation_flags = 336 + (XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | XRT_SPACE_RELATION_ORIENTATION_VALID_BIT) | 337 + (XRT_SPACE_RELATION_POSITION_TRACKED_BIT | XRT_SPACE_RELATION_POSITION_VALID_BIT); 338 + 339 + m_relation_history_estimate_motion(state->relation_history, &space_relation, now, &space_relation); 340 + 341 + m_relation_history_push(state->relation_history, &space_relation, now); 316 342 317 343 state->buttons = hydra_read_uint8(&buf); 318 344 ··· 321 347 322 348 state->trigger = hydra_read_uint8(&buf) * SCALE_UINT8_TO_FLOAT_0_TO_1; 323 349 324 - HYDRA_TRACE(hd, 325 - "\n\t" 326 - "controller: %i\n\t" 327 - "position: (%-1.2f, %-1.2f, %-1.2f)\n\t" 328 - "orientation: (%-1.2f, %-1.2f, %-1.2f, %-1.2f)\n\t" 329 - "buttons: %08x\n\t" 330 - "joystick: (%-1.2f, %-1.2f)\n\t" 331 - "trigger: %01.2f\n", 332 - (int)hd->index, state->pose.position.x, state->pose.position.y, state->pose.position.z, 333 - state->pose.orientation.x, state->pose.orientation.y, state->pose.orientation.z, 334 - state->pose.orientation.w, state->buttons, state->js.x, state->js.y, state->trigger); 350 + HD_TRACE(hd, 351 + "\n\t" 352 + "controller: %i\n\t" 353 + "position: (%-1.2f, %-1.2f, %-1.2f)\n\t" 354 + "orientation: (%-1.2f, %-1.2f, %-1.2f, %-1.2f)\n\t" 355 + "buttons: %08x\n\t" 356 + "joystick: (%-1.2f, %-1.2f)\n\t" 357 + "trigger: %01.2f\n", 358 + (int)hd->index, pose.position.x, pose.position.y, pose.position.z, pose.orientation.x, 359 + pose.orientation.y, pose.orientation.z, pose.orientation.w, state->buttons, state->js.x, state->js.y, 360 + state->trigger); 335 361 } 336 362 337 363 static int 338 - hydra_system_read_data_hid(struct hydra_system *hs, timepoint_ns now) 364 + hydra_system_read_data_hid(struct hydra_system *hs) 339 365 { 340 366 assert(hs); 341 367 uint8_t buffer[128]; 342 - bool got_message = false; 343 - do { 344 - int ret = os_hid_read(hs->data_hid, buffer, sizeof(buffer), 0); 345 - if (ret < 0) { 346 - return ret; 347 - } 348 - if (ret == 0) { 349 - return got_message ? 1 : 0; 350 - } 351 - if (ret != 52) { 352 - U_LOG_IFL_E(hs->log_level, "Unexpected data report of size %d", ret); 353 - return -1; 354 - } 355 - got_message = true; 356 - uint8_t new_counter = buffer[7]; 357 - bool missed = false; 358 - if (hs->report_counter != -1) { 359 - uint8_t expected_counter = ((hs->report_counter + 1) & 0xff); 360 - missed = new_counter != expected_counter; 361 - } 362 - hs->report_counter = new_counter; 363 368 364 - if (hs->devs[0] != NULL) { 365 - hydra_device_parse_controller(hs->devs[0], buffer + 8); 366 - } 367 - if (hs->devs[1] != NULL) { 368 - hydra_device_parse_controller(hs->devs[1], buffer + 30); 369 - } 369 + int ret = os_hid_read(hs->data_hid, buffer, sizeof(buffer), 370 + 20); // 20ms is a generous number above the 16.66ms we expect to receive reports at (60hz) 371 + 372 + timepoint_ns now = os_monotonic_get_ns(); 373 + 374 + // we dont care if we get no data 375 + if (ret <= 0) { 376 + return ret; 377 + } 378 + 379 + if (ret != 52) { 380 + HS_ERROR(hs, "Unexpected data report of size %d", ret); 381 + return -1; 382 + } 383 + 384 + os_mutex_lock(&hs->data_mutex); 385 + 386 + uint8_t new_counter = buffer[7]; 387 + bool missed = false; 388 + if (hs->report_counter != -1) { 389 + uint8_t expected_counter = ((hs->report_counter + 1) & 0xff); 390 + missed = new_counter != expected_counter; 391 + } 392 + hs->report_counter = new_counter; 370 393 371 - hs->report_time = now; 372 - U_LOG_IFL_T(hs->log_level, 373 - "\n\t" 374 - "missed: %s\n\t" 375 - "seq_no: %x\n", 376 - missed ? "yes" : "no", new_counter); 377 - } while (true); 394 + 395 + if (hs->devs[0] != NULL) { 396 + hydra_device_parse_controller(hs->devs[0], buffer + 8, now); 397 + } 398 + if (hs->devs[1] != NULL) { 399 + hydra_device_parse_controller(hs->devs[1], buffer + 30, now); 400 + } 401 + 402 + hs->report_time = now; 403 + 404 + os_mutex_unlock(&hs->data_mutex); 405 + 406 + HS_TRACE(hs, 407 + "\n\t" 408 + "missed: %s\n\t" 409 + "seq_no: %x\n", 410 + missed ? "yes" : "no", new_counter); 378 411 379 - return 0; 412 + return ret; 380 413 } 381 414 382 415 383 416 /*! 384 417 * Switch to motion controller mode. 385 418 */ 386 - static int 419 + static void 387 420 hydra_system_enter_motion_control(struct hydra_system *hs, timepoint_ns now) 388 421 { 389 422 assert(hs); 390 423 391 424 hs->was_in_gamepad_mode = true; 392 425 hs->motion_attempt_number++; 393 - U_LOG_IFL_D(hs->log_level, 394 - "Setting feature report to start motion-controller mode, " 395 - "attempt %d", 396 - hs->motion_attempt_number); 426 + HS_DEBUG(hs, 427 + "Setting feature report to start motion-controller mode, " 428 + "attempt %d", 429 + hs->motion_attempt_number); 397 430 398 431 os_hid_set_feature(hs->command_hid, HYDRA_REPORT_START_MOTION, sizeof(HYDRA_REPORT_START_MOTION)); 399 432 ··· 401 434 uint8_t buf[91] = {0}; 402 435 os_hid_get_feature(hs->command_hid, 0, buf, sizeof(buf)); 403 436 404 - return hydra_sm_transition(&hs->sm, HYDRA_SM_LISTENING_AFTER_SET_FEATURE, now); 437 + hydra_sm_transition(&hs->sm, HYDRA_SM_LISTENING_AFTER_SET_FEATURE, now); 405 438 } 406 439 /*! 407 440 * Update the internal state of the Hydra driver. ··· 413 446 hydra_system_update(struct hydra_system *hs) 414 447 { 415 448 assert(hs); 416 - timepoint_ns now = os_monotonic_get_ns(); 417 449 418 450 // In all states of the state machine: 419 451 // Try reading a report: will only return >0 if we get a full motion 420 452 // report. 421 - int received = hydra_system_read_data_hid(hs, now); 453 + int received = hydra_system_read_data_hid(hs); 422 454 423 - if (received > 0) { 424 - return hydra_sm_transition(&hs->sm, HYDRA_SM_REPORTING, now); 455 + // we got an error 456 + if (received < 0) { 457 + return received; 425 458 } 426 459 460 + os_mutex_lock(&hs->data_mutex); 427 461 428 - switch (hs->sm.current_state) { 462 + timepoint_ns now = os_monotonic_get_ns(); 429 463 464 + // if we got data, transition to "reporting" mode 465 + if (received > 0) { 466 + hydra_sm_transition(&hs->sm, HYDRA_SM_REPORTING, now); 467 + } 468 + 469 + switch (hs->sm.current_state) { 430 470 case HYDRA_SM_LISTENING_AFTER_CONNECT: { 431 471 float state_duration_s = hydra_sm_seconds_since_transition(&hs->sm, now); 432 472 if (state_duration_s > 1.0f) { ··· 435 475 hydra_system_enter_motion_control(hs, now); 436 476 } 437 477 } break; 438 - 439 478 case HYDRA_SM_LISTENING_AFTER_SET_FEATURE: { 440 479 float state_duration_s = hydra_sm_seconds_since_transition(&hs->sm, now); 441 480 if (state_duration_s > 5.0f) { ··· 443 482 hydra_system_enter_motion_control(hs, now); 444 483 } 445 484 } break; 446 - 447 485 default: break; 448 486 } 487 + 488 + os_mutex_unlock(&hs->data_mutex); 449 489 450 490 return 0; 451 491 } ··· 456 496 assert(hd); 457 497 hd->base.inputs[index].timestamp = now; 458 498 hd->base.inputs[index].value.boolean = (hd->state.buttons & bit) != 0; 499 + } 500 + 501 + static void * 502 + hydra_usb_thread_run(void *user_data) 503 + { 504 + struct hydra_system *hs = (struct hydra_system *)user_data; 505 + 506 + const char *thread_name = "Hydra USB"; 507 + 508 + U_TRACE_SET_THREAD_NAME(thread_name); 509 + os_thread_helper_name(&hs->usb_thread, thread_name); 510 + 511 + #ifdef XRT_OS_LINUX 512 + // Try to raise priority of this thread. 513 + u_linux_try_to_set_realtime_priority_on_thread(hs->log_level, thread_name); 514 + #endif 515 + 516 + os_thread_helper_lock(&hs->usb_thread); 517 + 518 + int result = 0; 519 + #if 0 520 + int ticks = 0; 521 + #endif 522 + 523 + while (os_thread_helper_is_running_locked(&hs->usb_thread) && result >= 0) { 524 + os_thread_helper_unlock(&hs->usb_thread); 525 + 526 + result = hydra_system_update(hs); 527 + 528 + os_thread_helper_lock(&hs->usb_thread); 529 + #if 0 530 + ticks += 1; 531 + #endif 532 + } 533 + 534 + os_thread_helper_unlock(&hs->usb_thread); 535 + 536 + return NULL; 459 537 } 460 538 461 539 /* ··· 470 548 struct hydra_device *hd = hydra_device(xdev); 471 549 struct hydra_system *hs = hydra_system(xdev->tracking_origin); 472 550 473 - hydra_system_update(hs); 551 + os_mutex_lock(&hs->data_mutex); 474 552 475 553 if (hd->input_time != hs->report_time) { 476 554 timepoint_ns now = hs->report_time; ··· 493 571 494 572 inputs[HYDRA_INDEX_TRIGGER_VALUE].timestamp = now; 495 573 inputs[HYDRA_INDEX_TRIGGER_VALUE].value.vec1.x = state->trigger; 496 - 574 + } 497 575 498 - //! @todo report pose 499 - // inputs[HYDRA_INDEX_POSE].timestamp = now; 500 - // inputs[HYDRA_INDEX_POSE].value. 501 - } 576 + os_mutex_unlock(&hs->data_mutex); 502 577 503 578 return XRT_SUCCESS; 504 579 } ··· 510 585 struct xrt_space_relation *out_relation) 511 586 { 512 587 struct hydra_device *hd = hydra_device(xdev); 513 - struct hydra_system *hs = hydra_system(xdev->tracking_origin); 514 - 515 - hydra_system_update(hs); 516 588 517 589 struct xrt_relation_chain xrc = {0}; 518 590 ··· 529 601 default: break; 530 602 } 531 603 532 - struct xrt_space_relation device_relation = { 533 - .pose = hd->state.pose, 534 - .relation_flags = XRT_SPACE_RELATION_POSITION_VALID_BIT | XRT_SPACE_RELATION_POSITION_TRACKED_BIT | 535 - XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT}; 604 + struct xrt_space_relation device_relation = {0}; 605 + m_relation_history_get(hd->state.relation_history, at_timestamp_ns, &device_relation); 536 606 537 607 m_relation_chain_push_relation(&xrc, &device_relation); 538 608 ··· 558 628 hs->refs--; 559 629 560 630 if (hs->refs == 0) { 631 + os_thread_helper_destroy(&hs->usb_thread); 632 + 633 + os_mutex_destroy(&hs->data_mutex); 634 + 561 635 // No more children, destroy system. 562 636 if (hs->data_hid != NULL && hs->command_hid != NULL && hs->sm.current_state == HYDRA_SM_REPORTING && 563 637 hs->was_in_gamepad_mode) { 564 638 565 - U_LOG_IFL_D(hs->log_level, 566 - "hydra: Sending command to re-enter gamepad mode " 567 - "and pausing while it takes effect."); 639 + HS_DEBUG(hs, 640 + "Sending command to re-enter gamepad mode " 641 + "and pausing while it takes effect."); 568 642 569 643 os_hid_set_feature(hs->command_hid, HYDRA_REPORT_START_GAMEPAD, 570 644 sizeof(HYDRA_REPORT_START_GAMEPAD)); ··· 587 661 { 588 662 struct hydra_device *hd = hydra_device(xdev); 589 663 struct hydra_system *hs = hydra_system(xdev->tracking_origin); 664 + 665 + m_relation_history_destroy(&hd->state.relation_history); 590 666 591 667 hydra_system_remove_child(hs, hd); 592 668 ··· 655 731 int ret; 656 732 657 733 struct os_hid_device *data_hid = NULL; 658 - ret = xp->open_hid_interface(xp, dev, 0, &data_hid); 734 + ret = xrt_prober_open_hid_interface(xp, dev, 0, &data_hid); 659 735 if (ret != 0) { 660 736 return -1; 661 737 } 662 738 struct os_hid_device *command_hid = NULL; 663 - ret = xp->open_hid_interface(xp, dev, 1, &command_hid); 739 + ret = xrt_prober_open_hid_interface(xp, dev, 1, &command_hid); 664 740 if (ret != 0) { 665 - data_hid->destroy(data_hid); 741 + os_hid_destroy(data_hid); 666 742 return -1; 667 743 } 668 744 ··· 675 751 hs->base.initial_offset.position.z = -0.25f; 676 752 hs->base.initial_offset.orientation.w = 1.0f; 677 753 754 + ret = os_thread_helper_init(&hs->usb_thread); 755 + if (ret < 0) { 756 + HS_ERROR(hs, "Failed to init USB thread."); 757 + after_system_err: 758 + free(hs); 759 + os_hid_destroy(command_hid); 760 + os_hid_destroy(data_hid); 761 + return -1; 762 + } 763 + 764 + ret = os_mutex_init(&hs->data_mutex); 765 + if (ret < 0) { 766 + HS_ERROR(hs, "Failed to init data mutex."); 767 + os_thread_helper_destroy(&hs->usb_thread); 768 + goto after_system_err; 769 + } 770 + 678 771 hs->data_hid = data_hid; 679 772 hs->command_hid = command_hid; 680 773 ··· 686 779 hs->refs = 2; 687 780 688 781 hs->log_level = debug_get_log_option_hydra_log(); 782 + 783 + u_var_add_root(hs, "Razer Hydra System", false); 784 + u_var_add_log_level(hs, &hs->log_level, "Log Level"); 785 + u_var_add_bool(hs, &hs->was_in_gamepad_mode, "Was In Gamepad Mode"); 786 + u_var_add_i32(hs, &hs->motion_attempt_number, "Motion Attempt Number"); 787 + u_var_add_ro_i16(hs, &hs->report_counter, "Report Counter"); 689 788 690 789 // Populate the individual devices 691 790 for (size_t i = 0; i < 2; ++i) { ··· 712 811 hd->index = i; 713 812 hd->sys = hs; 714 813 814 + const float fc_min = 1.0; 815 + const float fc_min_d = 1.0; 816 + const float beta = 0.007; 817 + 818 + m_filter_euro_vec3_init(&hd->state.motion_vector_filters.position, fc_min, fc_min_d, beta); 819 + m_filter_euro_quat_init(&hd->state.motion_vector_filters.orientation, fc_min, fc_min_d, beta); 820 + 821 + m_relation_history_create(&hd->state.relation_history, &hd->state.motion_vector_filters); 822 + 715 823 hd->base.binding_profiles = binding_profiles; 716 824 hd->base.binding_profile_count = ARRAY_SIZE(binding_profiles); 717 825 ··· 724 832 out_xdevs[i] = &(hd->base); 725 833 } 726 834 727 - U_LOG_I("Opened razer hydra!"); 835 + ret = os_thread_helper_start(&hs->usb_thread, hydra_usb_thread_run, hs); 836 + if (ret < 0) { 837 + HS_ERROR(hs, "Failed to start USB thread."); 838 + 839 + // doing this will destroy the system as well 840 + xrt_device_destroy((struct xrt_device **)&hs->devs[0]); 841 + xrt_device_destroy((struct xrt_device **)&hs->devs[1]); 842 + 843 + return ret; 844 + } 845 + 846 + HS_INFO(hs, "Opened Razer Hydra!"); 728 847 return 2; 729 848 }