The open source OpenXR runtime

d/vp2: Create basic VP2 driver

This driver does nothing but set the HMD's resolution option. It defaults to the highest "safe" value without AMDGPU DSC patches.

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

authored by

Beyley Cardellio and committed by
Marge Bot
7722196d c7a5451d

+338
+7
src/xrt/drivers/CMakeLists.txt
··· 483 483 endif() 484 484 485 485 if(XRT_BUILD_DRIVER_STEAMVR_LIGHTHOUSE) 486 + add_library(drv_vp2 STATIC vp2/vp2_hid.c vp2/vp2_hid.h) 487 + target_link_libraries(drv_vp2 PRIVATE aux_os aux_util aux_math aux_vive) 488 + endif() 489 + 490 + if(XRT_BUILD_DRIVER_STEAMVR_LIGHTHOUSE) 486 491 add_library( 487 492 drv_steamvr_lh STATIC 488 493 steamvr_lh/steamvr_lh.cpp ··· 500 505 aux_util 501 506 aux_math 502 507 aux_vive 508 + drv_vp2 509 + drv_includes 503 510 xrt-interfaces 504 511 xrt-external-openvr 505 512 xrt-external-cjson
+259
src/xrt/drivers/vp2/vp2_hid.c
··· 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 + 22 + DEBUG_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. 25 + DEBUG_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 + 33 + struct 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 + 46 + struct vp2_feature_header 47 + { 48 + uint8_t id; 49 + __le16 sub_id; 50 + uint8_t size; 51 + }; 52 + 53 + #pragma pack(pop) 54 + 55 + static int 56 + vp2_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 + 69 + static int 70 + vp2_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 + 89 + int 90 + vp2_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 + 112 + static int 113 + vp2_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 + 149 + static int 150 + vp2_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 + 175 + static int 176 + vp2_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 + 207 + int 208 + vp2_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 + 240 + enum vp2_resolution 241 + vp2_get_resolution(struct vp2_hid *vp2) 242 + { 243 + assert(vp2 != NULL); 244 + 245 + return vp2->resolution; 246 + } 247 + 248 + void 249 + vp2_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 + }
+72
src/xrt/drivers/vp2/vp2_hid.h
··· 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 + 11 + #ifdef __cplusplus 12 + extern "C" { 13 + #endif 14 + 15 + 16 + #define VP2_VID 0x0bb4 17 + #define VP2_PID 0x0342 18 + 19 + enum vp2_resolution 20 + { 21 + VP2_RESOLUTION_2448_1224_90_03 = 0, 22 + VP2_RESOLUTION_2448_1224_120_05 = 1, 23 + VP2_RESOLUTION_3264_1632_90_00 = 2, 24 + VP2_RESOLUTION_3680_1836_90_02 = 3, 25 + VP2_RESOLUTION_4896_2448_90_02 = 4, 26 + VP2_RESOLUTION_4896_2448_120_02 = 5, 27 + }; 28 + 29 + static inline void 30 + vp2_resolution_get_extents(enum vp2_resolution res, int *out_w, int *out_h) 31 + { 32 + switch (res) { 33 + case VP2_RESOLUTION_2448_1224_90_03: 34 + case VP2_RESOLUTION_2448_1224_120_05: 35 + *out_w = 2448; 36 + *out_h = 1224; 37 + break; 38 + case VP2_RESOLUTION_3264_1632_90_00: 39 + *out_w = 3264; 40 + *out_h = 1632; 41 + break; 42 + case VP2_RESOLUTION_3680_1836_90_02: 43 + *out_w = 3680; 44 + *out_h = 1836; 45 + break; 46 + case VP2_RESOLUTION_4896_2448_90_02: 47 + case VP2_RESOLUTION_4896_2448_120_02: 48 + *out_w = 4896; 49 + *out_h = 2448; 50 + break; 51 + default: 52 + assert(!"unreachable: bad resolution"); 53 + *out_w = 0; 54 + *out_h = 0; 55 + break; 56 + } 57 + } 58 + 59 + struct vp2_hid; 60 + 61 + int 62 + vp2_hid_open(struct os_hid_device *hid_dev, struct vp2_hid **out_hid); 63 + 64 + enum vp2_resolution 65 + vp2_get_resolution(struct vp2_hid *vp2); 66 + 67 + void 68 + vp2_hid_destroy(struct vp2_hid *vp2); 69 + 70 + #ifdef __cplusplus 71 + } // extern "C" 72 + #endif