// SPDX-License-Identifier: GPL-2.0 /* * HID driver for the Sony PS5 Media Remote (Bluetooth, 054c:0d5a). * * On its own the remote pairs and connects but stays silent: it reports no * button until the host sends it an "enable buttons" command. This driver * sends that command when the remote connects and turns the button reports * into ordinary key events (KEY_UP, KEY_ENTER, KEY_PLAYPAUSE, ...) on a * dedicated input device, instead of the generic gamepad the HID descriptor * would give (a hat switch and BTN_* buttons). * * There is no vendor documentation. The protocol below was reverse engineered * from Bluetooth captures and confirmed on real hardware: * * Sony frames these reports like DualSense Bluetooth reports: a trailing * little-endian CRC32 computed as ~crc32_le(~0, header || report[:-4]) where * the "header" is the HIDP transaction byte: 0xa2 (DATA, output) for reports * we send and 0xa1 (DATA, input) for reports we receive. * * Enable buttons (output report 0x03, 12 bytes, interrupt channel): * * 03 91 01 00 00 00 * * "91 01 " is a length-1 command packet; bit 0 of the mask enables the * microphone stream (input report 0x02) and bit 1 enables the buttons. Only * the buttons are enabled here. The report must carry a valid CRC and the * tag/seq byte; a control-channel SET_REPORT is acknowledged but ignored. * * Button state (input report 0x01, 12 bytes), sent on every change: * * 01 00 * * with state = b3 | b4 << 8 | b5 << 16 | b6 << 24: the low nibble is a hat * (0..7 = up, up-right, ... up-left, anything larger = centred) and the upper * 28 bits are buttons 1..28. * * The enable command has only been seen to work on a session that the remote * itself opened (it reconnects when a button is pressed) after a fixed * sequence of feature/input reads. The reads are therefore reproduced in * probe() exactly as they were when the command was first shown to work; * "init_reads" allows bisecting which of them matter (it is a diagnostic * knob, the default enables all of them). Whether they are strictly required * has not been established. */ #include #include #include #include #include #include #include #define USB_VENDOR_ID_SONY 0x054c #define USB_DEVICE_ID_SONY_PS5_MEDIA_REMOTE 0x0d5a #define PS5MR_HIDP_INPUT_SEED 0xa1 /* DATA | input */ #define PS5MR_HIDP_OUTPUT_SEED 0xa2 /* DATA | output */ #define PS5MR_BUTTONS_REPORT_ID 0x01 #define PS5MR_BUTTONS_REPORT_SIZE 12 /* report id + 11 data bytes */ #define PS5MR_STATE_OFFSET 3 /* b3..b6 within the report */ #define PS5MR_ENABLE_REPORT_ID 0x03 #define PS5MR_ENABLE_REPORT_SIZE 12 #define PS5MR_ENABLE_KEYS 0x02 /* mask bit: buttons (bit 0: mic) */ static bool verbose; module_param(verbose, bool, 0644); MODULE_PARM_DESC(verbose, "Log every decoded button state change"); /* Bit n enables entry n of ps5mr_init_reads[]; default: all of them. */ static unsigned int init_reads = 0x3f; module_param(init_reads, uint, 0444); MODULE_PARM_DESC(init_reads, "Bitmask of the probe-time reads sent before the enable command (default 0x3f = all)"); /* * Key table. Index n (0..11) is HID button n + 1 in the report; the last four * entries are the D-pad, decoded from the hat. "usage" is reported as * MSC_SCAN so keys can be remapped from userspace with a udev hwdb entry * (KEYBOARD_KEY_=); the hat entries use the Generic Desktop * D-pad usages 0x90..0x93 and the buttons use Button page (0x09) usages. * * The four "app" buttons are report buttons 4..1 (KEY_PROG1..KEY_PROG4). The * remote's other keys (Stop, volume, mute, TV power, channel) send no * Bluetooth report at all and cannot be handled here. */ enum { PS5MR_KEY_DPAD_UP = 12, PS5MR_KEY_DPAD_DOWN, PS5MR_KEY_DPAD_RIGHT, PS5MR_KEY_DPAD_LEFT, PS5MR_NUM_KEYS, }; struct ps5mr_key { unsigned int code; u32 usage; }; static const struct ps5mr_key ps5mr_keys[PS5MR_NUM_KEYS] = { [0] = { KEY_PROG4, 0x00090001 }, /* App 4 */ [1] = { KEY_PROG3, 0x00090002 }, /* App 3 */ [2] = { KEY_PROG2, 0x00090003 }, /* App 2 */ [3] = { KEY_PROG1, 0x00090004 }, /* App 1 */ [4] = { KEY_HOMEPAGE, 0x00090005 }, /* PS / Home */ [5] = { KEY_VOICECOMMAND, 0x00090006 }, /* Mic */ [6] = { KEY_FASTFORWARD, 0x00090007 }, [7] = { KEY_REWIND, 0x00090008 }, [8] = { KEY_PLAYPAUSE, 0x00090009 }, [9] = { KEY_MENU, 0x0009000a }, /* Options */ [10] = { KEY_BACK, 0x0009000b }, [11] = { KEY_ENTER, 0x0009000c }, /* centre of the D-pad */ [PS5MR_KEY_DPAD_UP] = { KEY_UP, 0x00010090 }, [PS5MR_KEY_DPAD_DOWN] = { KEY_DOWN, 0x00010091 }, [PS5MR_KEY_DPAD_RIGHT] = { KEY_RIGHT, 0x00010092 }, [PS5MR_KEY_DPAD_LEFT] = { KEY_LEFT, 0x00010093 }, }; /* * Reads sent in probe() before the enable command. They were part of every * probe sequence in which the enable command was observed to work. Failures * are expected and ignored: the GET_INPUT requests are rejected by the remote. */ struct ps5mr_init_read { u8 report_id; u8 len; /* including the report id */ u8 type; /* HID_FEATURE_REPORT or HID_INPUT_REPORT */ }; static const struct ps5mr_init_read ps5mr_init_reads[] = { { 0x09, 20, HID_FEATURE_REPORT }, /* pairing info */ { 0x20, 64, HID_FEATURE_REPORT }, /* firmware info */ { 0x22, 64, HID_FEATURE_REPORT }, /* hardware info */ { 0x71, 6, HID_FEATURE_REPORT }, { 0x01, 12, HID_INPUT_REPORT }, { 0x02, 78, HID_INPUT_REPORT }, }; struct ps5mr { struct hid_device *hdev; struct input_dev *input; u32 pressed; /* bitmask over ps5mr_keys[] */ }; static u32 ps5mr_crc(u8 seed, const u8 *data, size_t len) { u32 crc = crc32_le(0xffffffff, &seed, 1); return ~crc32_le(crc, data, len); } static u32 ps5mr_pressed_from_state(u32 state) { unsigned int hat = state & 0xf; u32 pressed = (state >> 4) & GENMASK(11, 0); /* buttons 1..12 */ /* hat: 0 up, 1 up-right, 2 right, 3 down-right, 4 down, ... 7 up-left */ if (hat == 0 || hat == 1 || hat == 7) pressed |= BIT(PS5MR_KEY_DPAD_UP); if (hat >= 1 && hat <= 3) pressed |= BIT(PS5MR_KEY_DPAD_RIGHT); if (hat >= 3 && hat <= 5) pressed |= BIT(PS5MR_KEY_DPAD_DOWN); if (hat >= 5 && hat <= 7) pressed |= BIT(PS5MR_KEY_DPAD_LEFT); return pressed; } static void ps5mr_report_keys(struct ps5mr *remote, u32 pressed) { u32 changed = pressed ^ remote->pressed; unsigned int i; if (!changed) return; for (i = 0; i < PS5MR_NUM_KEYS; i++) { if (!(changed & BIT(i))) continue; input_event(remote->input, EV_MSC, MSC_SCAN, ps5mr_keys[i].usage); input_report_key(remote->input, ps5mr_keys[i].code, !!(pressed & BIT(i))); } input_sync(remote->input); remote->pressed = pressed; } static int ps5mr_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { struct ps5mr *remote = hid_get_drvdata(hdev); u32 state; /* Only the button report is decoded; anything else (e.g. mic) passes. */ if (!remote || size != PS5MR_BUTTONS_REPORT_SIZE || data[0] != PS5MR_BUTTONS_REPORT_ID) return 0; if (ps5mr_crc(PS5MR_HIDP_INPUT_SEED, data, size - 4) != get_unaligned_le32(&data[size - 4])) { hid_dbg_ratelimited(hdev, "dropping button report with bad CRC\n"); return 1; } state = get_unaligned_le32(&data[PS5MR_STATE_OFFSET]); if (verbose) hid_info(hdev, "raw_event: state=0x%08x hat=%u buttons=0x%07x\n", state, state & 0xf, state >> 4); ps5mr_report_keys(remote, ps5mr_pressed_from_state(state)); return 0; /* still let hidraw see it */ } static void ps5mr_read_init_reports(struct hid_device *hdev) { unsigned int i; for (i = 0; i < ARRAY_SIZE(ps5mr_init_reads); i++) { const struct ps5mr_init_read *read = &ps5mr_init_reads[i]; u8 *buf; int ret; if (!(init_reads & BIT(i))) continue; buf = kzalloc(read->len, GFP_KERNEL); if (!buf) return; buf[0] = read->report_id; ret = hid_hw_raw_request(hdev, read->report_id, buf, read->len, read->type, HID_REQ_GET_REPORT); hid_dbg(hdev, "init read report 0x%02x (type %u) -> %d\n", read->report_id, read->type, ret); kfree(buf); } } /* Send: 03 00 91 01 02 00 00 00 ; see the protocol notes above. */ static int ps5mr_enable_buttons(struct hid_device *hdev) { u8 *buf; int ret; buf = kzalloc(PS5MR_ENABLE_REPORT_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; buf[0] = PS5MR_ENABLE_REPORT_ID; buf[1] = 0x00; /* tag/seq */ buf[2] = 0x91; /* command packet, pid 0x11, sized */ buf[3] = 0x01; /* length */ buf[4] = PS5MR_ENABLE_KEYS; /* mask */ put_unaligned_le32(ps5mr_crc(PS5MR_HIDP_OUTPUT_SEED, buf, PS5MR_ENABLE_REPORT_SIZE - 4), &buf[PS5MR_ENABLE_REPORT_SIZE - 4]); ret = hid_hw_output_report(hdev, buf, PS5MR_ENABLE_REPORT_SIZE); kfree(buf); return ret; } static int ps5mr_register_input(struct ps5mr *remote) { struct hid_device *hdev = remote->hdev; struct input_dev *input; unsigned int i; int ret; input = devm_input_allocate_device(&hdev->dev); if (!input) return -ENOMEM; input->name = "Sony PS5 Media Remote"; input->phys = hdev->phys; input->uniq = hdev->uniq; input->id.bustype = hdev->bus; input->id.vendor = hdev->vendor; input->id.product = hdev->product; input->id.version = hdev->version; input_set_capability(input, EV_MSC, MSC_SCAN); for (i = 0; i < ARRAY_SIZE(ps5mr_keys); i++) input_set_capability(input, EV_KEY, ps5mr_keys[i].code); ret = input_register_device(input); if (ret) return ret; remote->input = input; return 0; } static int ps5mr_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct ps5mr *remote; int ret; remote = devm_kzalloc(&hdev->dev, sizeof(*remote), GFP_KERNEL); if (!remote) return -ENOMEM; remote->hdev = hdev; hid_set_drvdata(hdev, remote); ret = hid_parse(hdev); if (ret) { hid_err(hdev, "hid_parse failed: %d\n", ret); return ret; } /* Our own input device carries the events; keep hidraw for debugging. */ ret = ps5mr_register_input(remote); if (ret) { hid_err(hdev, "cannot register input device: %d\n", ret); return ret; } ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER); if (ret) { hid_err(hdev, "hid_hw_start failed: %d\n", ret); return ret; } /* Keep the transport open so button reports are always delivered. */ ret = hid_hw_open(hdev); if (ret) { hid_err(hdev, "hid_hw_open failed: %d\n", ret); goto err_stop; } ps5mr_read_init_reports(hdev); ret = ps5mr_enable_buttons(hdev); if (ret < 0) hid_warn(hdev, "enable-buttons command failed: %d\n", ret); else hid_info(hdev, "buttons enabled\n"); return 0; err_stop: hid_hw_stop(hdev); return ret; } static void ps5mr_remove(struct hid_device *hdev) { hid_hw_close(hdev); hid_hw_stop(hdev); } static const struct hid_device_id ps5mr_devices[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_MEDIA_REMOTE) }, { } }; MODULE_DEVICE_TABLE(hid, ps5mr_devices); static struct hid_driver ps5mr_driver = { .name = "hid-ps5-media-remote", .id_table = ps5mr_devices, .probe = ps5mr_probe, .remove = ps5mr_remove, .raw_event = ps5mr_raw_event, }; module_hid_driver(ps5mr_driver); MODULE_LICENSE("GPL"); MODULE_VERSION("0.1.0"); MODULE_AUTHOR("PS5 Media Remote driver contributors"); MODULE_DESCRIPTION("Sony PS5 Media Remote (Bluetooth) input driver");