The open source OpenXR runtime
at main 259 lines 6.3 kB view raw
1// Copyright 2025, Beyley Cardellio 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Implementation of the Vive Pro 2 HID interface. 6 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com> 7 * @ingroup drv_vp2 8 */ 9 10#include "os/os_hid.h" 11#include "os/os_time.h" 12 13#include "util/u_logging.h" 14#include "util/u_debug.h" 15#include "util/u_misc.h" 16 17#include "xrt/xrt_byte_order.h" 18 19#include "vp2_hid.h" 20 21 22DEBUG_GET_ONCE_LOG_OPTION(vp2_log, "VP2_LOG", U_LOGGING_WARN) 23 24// NOTE: VP2_RESOLUTION_3680_1836_90_02 is the safest resolution that works without needing the kernel patches. 25DEBUG_GET_ONCE_NUM_OPTION(vp2_resolution, "VP2_RESOLUTION", VP2_RESOLUTION_3680_1836_90_02) 26 27#define VP2_TRACE(vp2, ...) U_LOG_IFL_T(vp2->log_level, __VA_ARGS__) 28#define VP2_DEBUG(vp2, ...) U_LOG_IFL_D(vp2->log_level, __VA_ARGS__) 29#define VP2_INFO(vp2, ...) U_LOG_IFL_I(vp2->log_level, __VA_ARGS__) 30#define VP2_WARN(vp2, ...) U_LOG_IFL_W(vp2->log_level, __VA_ARGS__) 31#define VP2_ERROR(vp2, ...) U_LOG_IFL_E(vp2->log_level, __VA_ARGS__) 32 33struct vp2_hid 34{ 35 enum u_logging_level log_level; 36 37 struct os_hid_device *hid; 38 39 enum vp2_resolution resolution; 40}; 41 42#define VP2_DATA_SIZE 64 43 44#pragma pack(push, 1) 45 46struct vp2_feature_header 47{ 48 uint8_t id; 49 __le16 sub_id; 50 uint8_t size; 51}; 52 53#pragma pack(pop) 54 55static int 56vp2_write(struct vp2_hid *vp2, uint8_t id, const uint8_t *data, size_t size) 57{ 58 uint8_t buffer[VP2_DATA_SIZE] = {0}; 59 buffer[0] = id; 60 if (size > sizeof(buffer) - 1) { 61 VP2_ERROR(vp2, "Data size too large to write."); 62 return -1; 63 } 64 65 memcpy(&buffer[1], data, size); 66 return os_hid_write(vp2->hid, buffer, sizeof(buffer)); 67} 68 69static int 70vp2_write_feature(struct vp2_hid *vp2, uint8_t id, uint16_t sub_id, const uint8_t *data, size_t size) 71{ 72 struct vp2_feature_header header = { 73 .id = id, 74 .sub_id = __cpu_to_le16(sub_id), 75 .size = (uint8_t)size, 76 }; 77 78 uint8_t buffer[VP2_DATA_SIZE] = {0}; 79 memcpy(buffer, &header, sizeof(header)); 80 if (size > sizeof(buffer) - sizeof(header)) { 81 VP2_ERROR(vp2, "Data size too large to write."); 82 return -1; 83 } 84 85 memcpy(&buffer[sizeof(header)], data, size); 86 return os_hid_set_feature(vp2->hid, buffer, sizeof(buffer)); 87} 88 89int 90vp2_set_resolution(struct vp2_hid *vp2, enum vp2_resolution resolution) 91{ 92 const uint8_t *wireless_command = (const uint8_t *)"wireless,0"; 93 int ret = vp2_write_feature(vp2, 0x04, 0x2970, wireless_command, strlen((const char *)wireless_command)); 94 if (ret < 0) { 95 VP2_ERROR(vp2, "Failed to write no wireless command."); 96 return ret; 97 } 98 99 uint8_t resolution_command[16]; 100 int command_length = 101 snprintf((char *)resolution_command, sizeof(resolution_command), "dtd,%d", (uint8_t)resolution); 102 103 ret = vp2_write_feature(vp2, 0x04, 0x2970, resolution_command, command_length); 104 if (ret < 0) { 105 VP2_ERROR(vp2, "Failed to write resolution command."); 106 return ret; 107 } 108 109 return 0; 110} 111 112static int 113vp2_read(struct vp2_hid *vp2, uint8_t id, const uint8_t *prefix, size_t prefix_size, uint8_t *out_data, size_t out_size) 114{ 115 uint8_t buffer[VP2_DATA_SIZE] = {0}; 116 int ret = os_hid_read(vp2->hid, buffer, sizeof(buffer), 500); 117 if (ret < 0) { 118 VP2_ERROR(vp2, "Failed to read from HID device."); 119 return ret; 120 } 121 if (ret == 0) { 122 VP2_WARN(vp2, "Timeout reading from HID device."); 123 return -1; 124 } 125 126 if (buffer[0] != id) { 127 VP2_ERROR(vp2, "Unexpected report ID: got %02x, expected %02x", buffer[0], id); 128 return -1; 129 } 130 131 if (prefix_size > 0 && memcmp(&buffer[1], prefix, prefix_size) != 0) { 132 VP2_ERROR(vp2, "Unexpected report prefix."); 133 U_LOG_IFL_D_HEX(vp2->log_level, &buffer[1], prefix_size); 134 U_LOG_IFL_D_HEX(vp2->log_level, prefix, prefix_size); 135 return -1; 136 } 137 138 uint8_t size = buffer[1 + prefix_size]; 139 if (size > out_size) { 140 VP2_ERROR(vp2, "Output buffer too small: got %zu, need %u", out_size, size); 141 return -1; 142 } 143 144 memcpy(out_data, &buffer[2 + prefix_size], size); 145 146 return size; 147} 148 149static int 150vp2_read_int(struct vp2_hid *vp2, const char *command, int *out_value) 151{ 152 int ret; 153 154 ret = vp2_write(vp2, 0x02, (const uint8_t *)command, strlen((const char *)command)); 155 if (ret < 0) { 156 VP2_ERROR(vp2, "Failed to write IPD read command."); 157 return ret; 158 } 159 160 uint8_t response[VP2_DATA_SIZE] = {0}; 161 ret = vp2_read(vp2, 0x02, NULL, 0, response, sizeof(response)); 162 if (ret < 0) { 163 VP2_ERROR(vp2, "Failed to read IPD response."); 164 return ret; 165 } 166 167 // Null-terminate the response string 168 response[ret] = '\0'; 169 170 *out_value = strtol((const char *)response, NULL, 10); 171 172 return 0; 173} 174 175static int 176vp2_read_ipd(struct vp2_hid *vp2, int *out, int *out_min, int *out_max, int *out_lap) 177{ 178 int ret; 179 180 ret = vp2_read_int(vp2, "mfg-r-ipdadc", out); 181 if (ret < 0) { 182 VP2_ERROR(vp2, "Failed to read IPD."); 183 return ret; 184 } 185 186 ret = vp2_read_int(vp2, "mfg-r-ipdmin", out_min); 187 if (ret < 0) { 188 VP2_ERROR(vp2, "Failed to read IPD min."); 189 return ret; 190 } 191 192 ret = vp2_read_int(vp2, "mfg-r-ipdmax", out_max); 193 if (ret < 0) { 194 VP2_ERROR(vp2, "Failed to read IPD max."); 195 return ret; 196 } 197 198 ret = vp2_read_int(vp2, "mfg-r-ipdlap", out_lap); 199 if (ret < 0) { 200 VP2_ERROR(vp2, "Failed to read IPD lap."); 201 return ret; 202 } 203 204 return 0; 205} 206 207int 208vp2_hid_open(struct os_hid_device *hid_dev, struct vp2_hid **out_hid) 209{ 210 int ret; 211 212 struct vp2_hid *vp2 = U_TYPED_CALLOC(struct vp2_hid); 213 214 vp2->log_level = debug_get_log_option_vp2_log(); 215 vp2->hid = hid_dev; 216 vp2->resolution = (enum vp2_resolution)debug_get_num_option_vp2_resolution(); 217 218 *out_hid = vp2; 219 220 VP2_INFO(vp2, "Opened Vive Pro 2 HID device."); 221 222 ret = vp2_set_resolution(vp2, vp2->resolution); 223 if (ret < 0) { 224 VP2_WARN(vp2, "Failed to set resolution."); 225 } 226 os_nanosleep(U_TIME_1S_IN_NS * 3llu); // wait 3s for headset to reset 227 228 // @todo: Figure out how to compute this into a mm value. 229 int ipd, ipd_min, ipd_max, ipd_lap; 230 ret = vp2_read_ipd(vp2, &ipd, &ipd_min, &ipd_max, &ipd_lap); 231 if (ret == 0) { 232 VP2_DEBUG(vp2, "IPD: %d (min: %d, max: %d, lap: %d)", ipd, ipd_min, ipd_max, ipd_lap); 233 } else { 234 VP2_WARN(vp2, "Failed to read IPD values."); 235 } 236 237 return 0; 238} 239 240enum vp2_resolution 241vp2_get_resolution(struct vp2_hid *vp2) 242{ 243 assert(vp2 != NULL); 244 245 return vp2->resolution; 246} 247 248void 249vp2_hid_destroy(struct vp2_hid *vp2) 250{ 251 if (vp2->hid != NULL) { 252 os_hid_destroy(vp2->hid); 253 vp2->hid = NULL; 254 } 255 256 VP2_INFO(vp2, "Destroyed Vive Pro 2 HID device."); 257 258 free(vp2); 259}