1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org> 5 * Copyright (c) 2026 Christos Longros <chris.longros@gmail.com> 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 /* 30 * Sony PS5 DualSense driver 31 * https://controllers.fandom.com/wiki/Sony_DualSense 32 * https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c 33 * 34 * Touchpad and motion sensors are not supported yet. 35 */ 36 37 #include "opt_hid.h" 38 39 #include <sys/param.h> 40 #include <sys/bus.h> 41 #include <sys/conf.h> 42 #include <sys/kernel.h> 43 #include <sys/lock.h> 44 #include <sys/module.h> 45 #include <sys/stat.h> 46 #include <sys/sx.h> 47 #include <sys/sysctl.h> 48 49 #include <dev/evdev/input.h> 50 #include <dev/evdev/evdev.h> 51 52 #define HID_DEBUG_VAR ps5ds_debug 53 #include <dev/hid/hgame.h> 54 #include <dev/hid/hid.h> 55 #include <dev/hid/hidbus.h> 56 #include <dev/hid/hidquirk.h> 57 #include <dev/hid/hidmap.h> 58 #include "usbdevs.h" 59 60 #ifdef HID_DEBUG 61 static int ps5ds_debug = 1; 62 63 static SYSCTL_NODE(_hw_hid, OID_AUTO, ps5dsense, CTLFLAG_RW, 0, 64 "Sony PS5 DualSense Gamepad"); 65 SYSCTL_INT(_hw_hid_ps5dsense, OID_AUTO, debug, CTLFLAG_RWTUN, 66 &ps5ds_debug, 0, "Debug level"); 67 #endif 68 69 /* 70 * Fixed HID report descriptor. Output is 62 bytes, not 47: shorter reports 71 * are ignored. 72 */ 73 static const uint8_t ps5ds_rdesc[] = { 74 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ 75 0x09, 0x05, /* Usage (Game Pad) */ 76 0xA1, 0x01, /* Collection (Application) */ 77 0x85, 0x01, /* Report ID (1) */ 78 0x09, 0x30, /* Usage (X) - left stick X */ 79 0x09, 0x31, /* Usage (Y) - left stick Y */ 80 0x09, 0x33, /* Usage (Rx) - right stick X */ 81 0x09, 0x34, /* Usage (Ry) - right stick Y */ 82 0x15, 0x00, /* Logical Minimum (0) */ 83 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 84 0x75, 0x08, /* Report Size (8) */ 85 0x95, 0x04, /* Report Count (4) */ 86 0x81, 0x02, /* Input (Data,Var,Abs) */ 87 0x09, 0x32, /* Usage (Z) - L2 trigger */ 88 0x09, 0x35, /* Usage (Rz) - R2 trigger */ 89 0x15, 0x00, /* Logical Minimum (0) */ 90 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 91 0x75, 0x08, /* Report Size (8) */ 92 0x95, 0x02, /* Report Count (2) */ 93 0x81, 0x02, /* Input (Data,Var,Abs) */ 94 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ 95 0x09, 0x20, /* Usage (0x20) */ 96 0x15, 0x00, /* Logical Minimum (0) */ 97 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 98 0x75, 0x08, /* Report Size (8) */ 99 0x95, 0x01, /* Report Count (1) */ 100 0x81, 0x02, /* Input (Data,Var,Abs) - counter */ 101 0x05, 0x01, /* Usage Page (Generic Desktop) */ 102 0x09, 0x39, /* Usage (Hat switch) */ 103 0x15, 0x00, /* Logical Minimum (0) */ 104 0x25, 0x07, /* Logical Maximum (7) */ 105 0x35, 0x00, /* Physical Minimum (0) */ 106 0x46, 0x3B, 0x01, /* Physical Maximum (315) */ 107 0x65, 0x14, /* Unit (Eng Rot: Degree) */ 108 0x75, 0x04, /* Report Size (4) */ 109 0x95, 0x01, /* Report Count (1) */ 110 0x81, 0x42, /* Input (Data,Var,Abs,Null State) */ 111 0x65, 0x00, /* Unit (None) */ 112 0x45, 0x00, /* Physical Maximum (0) */ 113 0x05, 0x09, /* Usage Page (Button) */ 114 0x19, 0x01, /* Usage Minimum (0x01) */ 115 0x29, 0x0F, /* Usage Maximum (0x0F) */ 116 0x15, 0x00, /* Logical Minimum (0) */ 117 0x25, 0x01, /* Logical Maximum (1) */ 118 0x75, 0x01, /* Report Size (1) */ 119 0x95, 0x0F, /* Report Count (15) */ 120 0x81, 0x02, /* Input (Data,Var,Abs) */ 121 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ 122 0x09, 0x21, /* Usage (0x21) */ 123 0x75, 0x01, /* Report Size (1) */ 124 0x95, 0x0D, /* Report Count (13) */ 125 0x81, 0x03, /* Input (Const) - unused button bits */ 126 0x09, 0x22, /* Usage (0x22) */ 127 0x15, 0x00, /* Logical Minimum (0) */ 128 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 129 0x75, 0x08, /* Report Size (8) */ 130 0x95, 0x34, /* Report Count (52) */ 131 0x81, 0x03, /* Input (Const) - sensors, touchpad */ 132 /* Output report (63 bytes total) for LED and rumble control */ 133 0x85, 0x02, /* Report ID (2) */ 134 0x09, 0x23, /* Usage (0x23) */ 135 0x75, 0x08, /* Report Size (8) */ 136 0x95, 0x3E, /* Report Count (62) */ 137 0x91, 0x02, /* Output (Data,Var,Abs) */ 138 0xC0, /* End Collection */ 139 }; 140 141 #define PS5DS_OUTPUT_REPORT2_SIZE 63 142 143 struct ps5ds_out2 { 144 uint8_t valid_flag0; 145 uint8_t valid_flag1; 146 uint8_t rumble_right; 147 uint8_t rumble_left; 148 uint8_t reserved1[4]; 149 uint8_t mute_button_led; 150 uint8_t power_save_control; 151 uint8_t reserved2[28]; 152 uint8_t valid_flag2; 153 uint8_t reserved3[2]; 154 uint8_t lightbar_setup; 155 uint8_t led_brightness; 156 uint8_t player_leds; 157 uint8_t led_color_r; 158 uint8_t led_color_g; 159 uint8_t led_color_b; 160 } __attribute__((packed)); 161 162 #define PS5DS_FLAG1_LIGHTBAR 0x04 163 #define PS5DS_FLAG1_PLAYER_LEDS 0x10 164 #define PS5DS_FLAG2_LIGHTBAR_SETUP 0x02 165 #define PS5DS_LIGHTBAR_FADE_OUT 0x02 166 167 static const uint8_t ps5ds_player_leds[] = { 168 0x04, /* Player 1: --x-- */ 169 0x0A, /* Player 2: -x-x- */ 170 0x15, /* Player 3: x-x-x */ 171 0x1B, /* Player 4: xx-xx */ 172 0x1F, /* Player 5: xxxxx */ 173 }; 174 175 static const struct ps5ds_led { 176 int r; 177 int g; 178 int b; 179 } ps5ds_leds[] = { 180 /* The first 4 entries match the PS4, other from Linux driver */ 181 { 0x00, 0x00, 0x40 }, /* Blue */ 182 { 0x40, 0x00, 0x00 }, /* Red */ 183 { 0x00, 0x40, 0x00 }, /* Green */ 184 { 0x20, 0x00, 0x20 }, /* Pink */ 185 { 0x02, 0x01, 0x00 }, /* Orange */ 186 { 0x00, 0x01, 0x01 }, /* Teal */ 187 { 0x01, 0x01, 0x01 } /* White */ 188 }; 189 190 enum ps5ds_led_state { 191 PS5DS_LED_OFF, 192 PS5DS_LED_ON, 193 PS5DS_LED_CNT, 194 }; 195 196 struct ps5ds_softc { 197 struct hidmap hm; 198 199 struct sx lock; 200 enum ps5ds_led_state led_state; 201 struct ps5ds_led led_color; 202 int player_leds; /* 0-4: player number, -1: off */ 203 }; 204 205 #define PS5DS_OFFSET(field) offsetof(struct ps5ds_softc, field) 206 enum { 207 PS5DS_SYSCTL_LED_STATE = PS5DS_OFFSET(led_state), 208 PS5DS_SYSCTL_LED_COLOR_R = PS5DS_OFFSET(led_color.r), 209 PS5DS_SYSCTL_LED_COLOR_G = PS5DS_OFFSET(led_color.g), 210 PS5DS_SYSCTL_LED_COLOR_B = PS5DS_OFFSET(led_color.b), 211 PS5DS_SYSCTL_PLAYER_LEDS = PS5DS_OFFSET(player_leds), 212 #define PS5DS_SYSCTL_LAST PS5DS_SYSCTL_PLAYER_LEDS 213 }; 214 215 #define PS5DS_MAP_BTN(number, code) \ 216 { HIDMAP_KEY(HUP_BUTTON, number, code) } 217 #define PS5DS_MAP_ABS(usage, code) \ 218 { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } 219 #define PS5DS_MAP_FLT(usage, code) \ 220 { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), .flat = 15 } 221 #define PS5DS_MAP_GCB(usage, callback) \ 222 { HIDMAP_ANY_CB(HUP_GENERIC_DESKTOP, HUG_##usage, callback) } 223 #define PS5DS_FINALCB(cb) \ 224 { HIDMAP_FINAL_CB(&cb) } 225 226 static hidmap_cb_t ps5ds_final_cb; 227 228 static const struct hidmap_item ps5ds_map[] = { 229 PS5DS_MAP_FLT(X, ABS_X), 230 PS5DS_MAP_FLT(Y, ABS_Y), 231 PS5DS_MAP_ABS(Z, ABS_Z), 232 PS5DS_MAP_ABS(RZ, ABS_RZ), 233 PS5DS_MAP_FLT(RX, ABS_RX), 234 PS5DS_MAP_FLT(RY, ABS_RY), 235 PS5DS_MAP_BTN(1, BTN_WEST), 236 PS5DS_MAP_BTN(2, BTN_SOUTH), 237 PS5DS_MAP_BTN(3, BTN_EAST), 238 PS5DS_MAP_BTN(4, BTN_NORTH), 239 PS5DS_MAP_BTN(5, BTN_TL), 240 PS5DS_MAP_BTN(6, BTN_TR), 241 PS5DS_MAP_BTN(7, BTN_TL2), 242 PS5DS_MAP_BTN(8, BTN_TR2), 243 PS5DS_MAP_BTN(9, BTN_SELECT), 244 PS5DS_MAP_BTN(10, BTN_START), 245 PS5DS_MAP_BTN(11, BTN_THUMBL), 246 PS5DS_MAP_BTN(12, BTN_THUMBR), 247 PS5DS_MAP_BTN(13, BTN_MODE), 248 /* Click button is handled by touchpad driver */ 249 /* PS5DS_MAP_BTN(14, BTN_LEFT), */ 250 /* Linux mutes the microphone in hardware and emits no event */ 251 PS5DS_MAP_BTN(15, KEY_MICMUTE), 252 PS5DS_MAP_GCB(HAT_SWITCH, hgame_hat_switch_cb), 253 PS5DS_FINALCB( ps5ds_final_cb), 254 }; 255 256 static const struct hid_device_id ps5ds_devs[] = { 257 { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x0ce6), 258 HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD) }, 259 }; 260 261 static int 262 ps5ds_final_cb(HIDMAP_CB_ARGS) 263 { 264 struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); 265 266 if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) { 267 evdev_support_prop(evdev, INPUT_PROP_DIRECT); 268 evdev_set_cdev_mode(evdev, UID_ROOT, GID_GAMES, 269 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); 270 } 271 272 /* Do not execute callback at interrupt handler and detach */ 273 return (ENOSYS); 274 } 275 276 static int 277 ps5ds_write(struct ps5ds_softc *sc) 278 { 279 uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE]; 280 bool led_on; 281 282 sx_assert(&sc->lock, SA_XLOCKED); 283 284 memset(buf, 0, sizeof(buf)); 285 buf[0] = 0x02; 286 led_on = sc->led_state != PS5DS_LED_OFF; 287 *(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) { 288 .valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS, 289 .player_leds = sc->player_leds < 0 ? 290 0 : ps5ds_player_leds[sc->player_leds], 291 .led_color_r = led_on ? sc->led_color.r : 0, 292 .led_color_g = led_on ? sc->led_color.g : 0, 293 .led_color_b = led_on ? sc->led_color.b : 0, 294 }; 295 296 return (hid_write(sc->hm.dev, buf, sizeof(buf))); 297 } 298 299 static int 300 ps5ds_blank(struct ps5ds_softc *sc) 301 { 302 uint8_t buf[PS5DS_OUTPUT_REPORT2_SIZE]; 303 304 memset(buf, 0, sizeof(buf)); 305 buf[0] = 0x02; 306 *(struct ps5ds_out2 *)(buf + 1) = (struct ps5ds_out2) { 307 .valid_flag1 = PS5DS_FLAG1_LIGHTBAR | PS5DS_FLAG1_PLAYER_LEDS, 308 .valid_flag2 = PS5DS_FLAG2_LIGHTBAR_SETUP, 309 .lightbar_setup = PS5DS_LIGHTBAR_FADE_OUT, 310 }; 311 312 return (hid_write(sc->hm.dev, buf, sizeof(buf))); 313 } 314 315 static int 316 ps5ds_sysctl(SYSCTL_HANDLER_ARGS) 317 { 318 struct ps5ds_softc *sc; 319 int error, arg; 320 321 if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 || 322 oidp->oid_arg2 > PS5DS_SYSCTL_LAST) 323 return (EINVAL); 324 325 sc = oidp->oid_arg1; 326 sx_xlock(&sc->lock); 327 328 arg = *(int *)((char *)sc + oidp->oid_arg2); 329 error = sysctl_handle_int(oidp, &arg, 0, req); 330 331 if (error || !req->newptr) 332 goto unlock; 333 334 switch (oidp->oid_arg2) { 335 case PS5DS_SYSCTL_LED_STATE: 336 if (arg < 0 || arg >= PS5DS_LED_CNT) 337 error = EINVAL; 338 break; 339 case PS5DS_SYSCTL_LED_COLOR_R: 340 case PS5DS_SYSCTL_LED_COLOR_G: 341 case PS5DS_SYSCTL_LED_COLOR_B: 342 if (arg < 0 || arg > UINT8_MAX) 343 error = EINVAL; 344 break; 345 case PS5DS_SYSCTL_PLAYER_LEDS: 346 if (arg < -1 || arg >= (int)nitems(ps5ds_player_leds)) 347 error = EINVAL; 348 break; 349 default: 350 error = EINVAL; 351 } 352 353 if (error == 0) { 354 *(int *)((char *)sc + oidp->oid_arg2) = arg; 355 ps5ds_write(sc); 356 } 357 unlock: 358 sx_unlock(&sc->lock); 359 360 return (error); 361 } 362 363 static void 364 ps5ds_identify(driver_t *driver, device_t parent) 365 { 366 367 /* Replace the gamepad's rudimentary report descriptor */ 368 if (HIDBUS_LOOKUP_ID(parent, ps5ds_devs) != NULL) 369 hid_set_report_descr(parent, ps5ds_rdesc, 370 sizeof(ps5ds_rdesc)); 371 } 372 373 static int 374 ps5ds_probe(device_t dev) 375 { 376 struct ps5ds_softc *sc = device_get_softc(dev); 377 378 hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); 379 return ( 380 HIDMAP_PROBE(&sc->hm, dev, ps5ds_devs, ps5ds_map, NULL) 381 ); 382 } 383 384 static int 385 ps5ds_attach(device_t dev) 386 { 387 struct ps5ds_softc *sc = device_get_softc(dev); 388 struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); 389 struct sysctl_oid *tree = device_get_sysctl_tree(dev); 390 int error, unit; 391 392 sx_init(&sc->lock, "ps5dsense"); 393 394 unit = device_get_unit(dev); 395 sc->led_state = PS5DS_LED_ON; 396 sc->led_color = ps5ds_leds[unit % nitems(ps5ds_leds)]; 397 sc->player_leds = unit < (int)nitems(ps5ds_player_leds) ? unit : -1; 398 399 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 400 "led_state", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, 401 PS5DS_SYSCTL_LED_STATE, ps5ds_sysctl, "I", 402 "LED state: 0 - off, 1 - on."); 403 404 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 405 "led_color_r", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, 406 PS5DS_SYSCTL_LED_COLOR_R, ps5ds_sysctl, "I", 407 "Lightbar color. Red component."); 408 409 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 410 "led_color_g", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, 411 PS5DS_SYSCTL_LED_COLOR_G, ps5ds_sysctl, "I", 412 "Lightbar color. Green component."); 413 414 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 415 "led_color_b", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, 416 PS5DS_SYSCTL_LED_COLOR_B, ps5ds_sysctl, "I", 417 "Lightbar color. Blue component."); 418 419 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, 420 "player_leds", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, 421 PS5DS_SYSCTL_PLAYER_LEDS, ps5ds_sysctl, "I", 422 "Player indicator LEDs: -1 - off, 0-4 - player number."); 423 424 error = hidmap_attach(&sc->hm); 425 if (error) { 426 sx_destroy(&sc->lock); 427 return (error); 428 } 429 430 sx_xlock(&sc->lock); 431 ps5ds_write(sc); 432 sx_xunlock(&sc->lock); 433 434 return (0); 435 } 436 437 static int 438 ps5ds_detach(device_t dev) 439 { 440 struct ps5ds_softc *sc = device_get_softc(dev); 441 int error; 442 443 error = hidmap_detach(&sc->hm); 444 if (error) 445 return (error); 446 447 ps5ds_blank(sc); 448 sx_destroy(&sc->lock); 449 450 return (0); 451 } 452 453 static device_method_t ps5ds_methods[] = { 454 DEVMETHOD(device_identify, ps5ds_identify), 455 DEVMETHOD(device_probe, ps5ds_probe), 456 DEVMETHOD(device_attach, ps5ds_attach), 457 DEVMETHOD(device_detach, ps5ds_detach), 458 459 DEVMETHOD_END 460 }; 461 462 DEFINE_CLASS_0(ps5dsense, ps5ds_driver, ps5ds_methods, 463 sizeof(struct ps5ds_softc)); 464 DRIVER_MODULE(ps5dsense, hidbus, ps5ds_driver, NULL, NULL); 465 466 MODULE_DEPEND(ps5dsense, hid, 1, 1, 1); 467 MODULE_DEPEND(ps5dsense, hidbus, 1, 1, 1); 468 MODULE_DEPEND(ps5dsense, hidmap, 1, 1, 1); 469 MODULE_DEPEND(ps5dsense, hgame, 1, 1, 1); 470 MODULE_DEPEND(ps5dsense, evdev, 1, 1, 1); 471 MODULE_VERSION(ps5dsense, 1); 472 HID_PNP_INFO(ps5ds_devs); 473