Inter-revision diff: patch 7

Comparing v3 (message) to v2 (message)

--- v3
+++ v2
@@ -1,330 +1,103 @@
 From: Roderick Colenbrander <roderick.colenbrander@sony.com>
 
-The DualSense features a haptics system based on voicecoil motors,
-which requires PCM data (or special HID packets using Bluetooth). There
-is no appropriate API yet in the Linux kernel to expose these. The
-controller also provides a classic rumble feature for backwards
-compatibility. Expose this classic rumble feature using the FF framework.
+This patch adds support for the DualSense when operating in Bluetooth mode.
+The device has the same behavior as the DualShock 4 in that by default it
+sends a limited input report (0x1), but after requesting calibration data,
+it switches to an extended input report (report 49), which adds data for
+touchpad, motion sensors, battery and more.
 
 Signed-off-by: Roderick Colenbrander <roderick.colenbrander@sony.com>
 ---
- drivers/hid/Kconfig           |   8 ++
- drivers/hid/hid-playstation.c | 208 +++++++++++++++++++++++++++++++++-
- 2 files changed, 214 insertions(+), 2 deletions(-)
+ drivers/hid/hid-playstation.c | 35 +++++++++++++++++++++++++++++++++++
+ drivers/hid/hid-quirks.c      |  1 +
+ 2 files changed, 36 insertions(+)
 
-diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
-index b3ec01c7a0b7..54b4eee222f9 100644
---- a/drivers/hid/Kconfig
-+++ b/drivers/hid/Kconfig
-@@ -863,6 +863,14 @@ config HID_PLAYSTATION
- 	  its special functionalities e.g. touchpad, lights and motion
- 	  sensors.
- 
-+config PLAYSTATION_FF
-+	bool "PlayStation force feedback support"
-+	depends on HID_PLAYSTATION
-+	select INPUT_FF_MEMLESS
-+	help
-+	  Say Y here if you would like to enable force feedback support for
-+	  PlayStation game controllers.
-+
- config HID_PRIMAX
- 	tristate "Primax non-fully HID-compliant devices"
- 	depends on HID
 diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c
-index bcf93836beb2..49205bb323a7 100644
+index 91f3ed005fce..552a52a50b78 100644
 --- a/drivers/hid/hid-playstation.c
 +++ b/drivers/hid/hid-playstation.c
-@@ -48,12 +48,17 @@ struct ps_calibration_data {
- 
- /* Seed values for DualShock4 / DualSense CRC32 for different report types. */
- #define PS_INPUT_CRC32_SEED	0xA1
-+#define PS_OUTPUT_CRC32_SEED	0xA2
- #define PS_FEATURE_CRC32_SEED	0xA3
+@@ -47,6 +47,7 @@ struct ps_calibration_data {
+ };
  
  #define DS_INPUT_REPORT_USB			0x01
- #define DS_INPUT_REPORT_USB_SIZE		64
- #define DS_INPUT_REPORT_BT			0x31
- #define DS_INPUT_REPORT_BT_SIZE			78
-+#define DS_OUTPUT_REPORT_USB			0x02
-+#define DS_OUTPUT_REPORT_USB_SIZE		63
-+#define DS_OUTPUT_REPORT_BT			0x31
-+#define DS_OUTPUT_REPORT_BT_SIZE		78
++#define DS_INPUT_REPORT_BT			0x31
  
- #define DS_FEATURE_REPORT_CALIBRATION		0x05
+ #define DS_FEATURE_REPORT_CALIBRATION		5
  #define DS_FEATURE_REPORT_CALIBRATION_SIZE	41
-@@ -89,6 +94,12 @@ struct ps_calibration_data {
-  */
- #define DS_TOUCH_POINT_INACTIVE BIT(7)
- 
-+ /* Magic value required in tag field of Bluetooth output report. */
-+#define DS_OUTPUT_TAG 0x10
-+/* Flags for DualSense output report. */
-+#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
-+#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
-+
- /* DualSense hardware limits */
- #define DS_ACC_RES_PER_G	8192
- #define DS_ACC_RANGE		(4*DS_ACC_RES_PER_G)
-@@ -111,6 +122,15 @@ struct dualsense {
- 	bool sensor_timestamp_initialized;
- 	uint32_t prev_sensor_timestamp;
- 	uint32_t sensor_timestamp_us;
-+
-+	/* Compatible rumble state */
-+	bool update_rumble;
-+	uint8_t motor_left;
-+	uint8_t motor_right;
-+
-+	struct work_struct output_worker;
-+	void *output_report_dmabuf;
-+	uint8_t output_seq; /* Sequence number for output report. */
- };
- 
- struct dualsense_touch_point {
-@@ -146,6 +166,68 @@ struct dualsense_input_report {
- /* Common input report size shared equals the size of the USB report minus 1 byte for ReportID. */
- static_assert(sizeof(struct dualsense_input_report) == DS_INPUT_REPORT_USB_SIZE - 1);
- 
-+/* Common data between DualSense BT/USB main output report. */
-+struct dualsense_output_report_common {
-+	uint8_t valid_flag0;
-+	uint8_t valid_flag1;
-+
-+	/* For DualShock 4 compatibility mode. */
-+	uint8_t motor_right;
-+	uint8_t motor_left;
-+
-+	/* Audio controls */
-+	uint8_t reserved[4];
-+	uint8_t mute_button_led;
-+
-+	uint8_t power_save_control;
-+	uint8_t reserved2[28];
-+
-+	/* LEDs and lightbar */
-+	uint8_t valid_flag2;
-+	uint8_t reserved3[2];
-+	uint8_t lightbar_setup;
-+	uint8_t led_brightness;
-+	uint8_t player_leds;
-+	uint8_t lightbar_red;
-+	uint8_t lightbar_green;
-+	uint8_t lightbar_blue;
-+} __packed;
-+static_assert(sizeof(struct dualsense_output_report_common) == 47);
-+
-+struct dualsense_output_report_bt {
-+	uint8_t report_id; /* 0x31 */
-+	uint8_t seq_tag;
-+	uint8_t tag;
-+	struct dualsense_output_report_common common;
-+	uint8_t reserved[24];
-+	__le32 crc32;
-+} __packed;
-+static_assert(sizeof(struct dualsense_output_report_bt) == DS_OUTPUT_REPORT_BT_SIZE);
-+
-+struct dualsense_output_report_usb {
-+	uint8_t report_id; /* 0x02 */
-+	struct dualsense_output_report_common common;
-+	uint8_t reserved[15];
-+} __packed;
-+static_assert(sizeof(struct dualsense_output_report_usb) == DS_OUTPUT_REPORT_USB_SIZE);
-+
-+/*
-+ * The DualSense has a main output report used to control most features. It is
-+ * largely the same between Bluetooth and USB except for different headers and CRC.
-+ * This structure hide the differences between the two to simplify sending output reports.
-+ */
-+struct dualsense_output_report {
-+	uint8_t *data; /* Start of data */
-+	uint8_t len; /* Size of output report */
-+
-+	/* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
-+	struct dualsense_output_report_bt *bt;
-+	/* Points to USB data payload in case for a USB report else NULL. */
-+	struct dualsense_output_report_usb *usb;
-+	/* Points to common section of report, so past any headers. */
-+	struct dualsense_output_report_common *common;
-+};
-+
- /*
-  * Common gamepad buttons across DualShock 3 / 4 and DualSense.
-  * Note: for device with a touchpad, touchpad button is not included
-@@ -310,7 +392,8 @@ static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t rep
- 	return crc == report_crc;
- }
- 
--static struct input_dev *ps_gamepad_create(struct hid_device *hdev)
-+static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
-+		int (*play_effect)(struct input_dev *, void *, struct ff_effect *))
- {
- 	struct input_dev *gamepad;
- 	unsigned int i;
-@@ -333,6 +416,13 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev)
- 	for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++)
- 		input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]);
- 
-+#if IS_ENABLED(CONFIG_PLAYSTATION_FF)
-+	if (play_effect) {
-+		input_set_capability(gamepad, EV_FF, FF_RUMBLE);
-+		input_ff_create_memless(gamepad, NULL, play_effect);
-+	}
-+#endif
-+
- 	ret = input_register_device(gamepad);
- 	if (ret)
- 		return ERR_PTR(ret);
-@@ -552,6 +642,94 @@ static int dualsense_get_mac_address(struct dualsense *ds)
- 	return ret;
- }
- 
-+static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp,
-+		void *buf)
-+{
-+	struct hid_device *hdev = ds->base.hdev;
-+
-+	if (hdev->bus == BUS_BLUETOOTH) {
-+		struct dualsense_output_report_bt *bt = buf;
-+
-+		memset(bt, 0, sizeof(*bt));
-+		bt->report_id = DS_OUTPUT_REPORT_BT;
-+		bt->tag = DS_OUTPUT_TAG; /* Tag must be set. Exact meaning is unclear. */
-+
-+		/*
-+		 * Highest 4-bit is a sequence number, which needs to be increased
-+		 * every report. Lowest 4-bit is tag and can be zero for now.
-+		 */
-+		bt->seq_tag = (ds->output_seq << 4) | 0x0;
-+		if (++ds->output_seq == 16)
-+			ds->output_seq = 0;
-+
-+		rp->data = buf;
-+		rp->len = sizeof(*bt);
-+		rp->bt = bt;
-+		rp->usb = NULL;
-+		rp->common = &bt->common;
-+	} else { /* USB */
-+		struct dualsense_output_report_usb *usb = buf;
-+
-+		memset(usb, 0, sizeof(*usb));
-+		usb->report_id = DS_OUTPUT_REPORT_USB;
-+
-+		rp->data = buf;
-+		rp->len = sizeof(*usb);
-+		rp->bt = NULL;
-+		rp->usb = usb;
-+		rp->common = &usb->common;
-+	}
-+}
-+
-+/*
-+ * Helper function to send DualSense output reports. Applies a CRC at the end of a report
-+ * for Bluetooth reports.
-+ */
-+static void dualsense_send_output_report(struct dualsense *ds,
-+		struct dualsense_output_report *report)
-+{
-+	struct hid_device *hdev = ds->base.hdev;
-+
-+	/* Bluetooth packets need to be signed with a CRC in the last 4 bytes. */
-+	if (report->bt) {
-+		uint32_t crc;
-+		uint8_t seed = PS_OUTPUT_CRC32_SEED;
-+
-+		crc = crc32_le(0xFFFFFFFF, &seed, 1);
-+		crc = ~crc32_le(crc, report->data, report->len - 4);
-+
-+		report->bt->crc32 = cpu_to_le32(crc);
-+	}
-+
-+	hid_hw_output_report(hdev, report->data, report->len);
-+}
-+
-+static void dualsense_output_worker(struct work_struct *work)
-+{
-+	struct dualsense *ds = container_of(work, struct dualsense, output_worker);
-+	struct dualsense_output_report report;
-+	struct dualsense_output_report_common *common;
-+	unsigned long flags;
-+
-+	dualsense_init_output_report(ds, &report, ds->output_report_dmabuf);
-+	common = report.common;
-+
-+	spin_lock_irqsave(&ds->base.lock, flags);
-+
-+	if (ds->update_rumble) {
-+		/* Select classic rumble style haptics and enable it. */
-+		common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT;
-+		common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION;
-+		common->motor_left = ds->motor_left;
-+		common->motor_right = ds->motor_right;
-+		ds->update_rumble = false;
-+	}
-+
-+	spin_unlock_irqrestore(&ds->base.lock, flags);
-+
-+	dualsense_send_output_report(ds, &report);
-+}
-+
- static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report,
- 		u8 *data, int size)
- {
-@@ -712,10 +890,30 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
+@@ -285,6 +286,17 @@ static int ps_device_register_battery(struct ps_device *dev)
  	return 0;
  }
  
-+static int dualsense_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
++/* Compute crc32 of HID data and compare against expected CRC. */
++static bool ps_check_crc32(uint8_t seed, uint8_t *data, size_t len, uint32_t report_crc)
 +{
-+	struct hid_device *hdev = input_get_drvdata(dev);
-+	struct dualsense *ds = hid_get_drvdata(hdev);
-+	unsigned long flags;
++	uint32_t crc;
 +
-+	if (effect->type != FF_RUMBLE)
-+		return 0;
++	crc = crc32_le(0xFFFFFFFF, &seed, 1);
++	crc = ~crc32_le(crc, data, len);
 +
-+	spin_lock_irqsave(&ds->base.lock, flags);
-+	ds->update_rumble = true;
-+	ds->motor_left = effect->u.rumble.strong_magnitude / 256;
-+	ds->motor_right = effect->u.rumble.weak_magnitude / 256;
-+	spin_unlock_irqrestore(&ds->base.lock, flags);
-+
-+	schedule_work(&ds->output_worker);
-+	return 0;
++	return crc == report_crc;
 +}
 +
- static struct ps_device *dualsense_create(struct hid_device *hdev)
+ static struct input_dev *ps_gamepad_create(struct hid_device *hdev)
  {
- 	struct dualsense *ds;
- 	struct ps_device *ps_dev;
-+	uint8_t max_output_report_size;
- 	int ret;
- 
- 	ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL);
-@@ -734,8 +932,14 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
- 	ps_dev->battery_capacity = 100; /* initial value until parse_report. */
- 	ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
- 	ps_dev->parse_report = dualsense_parse_report;
-+	INIT_WORK(&ds->output_worker, dualsense_output_worker);
- 	hid_set_drvdata(hdev, ds);
- 
-+	max_output_report_size = sizeof(struct dualsense_output_report_bt);
-+	ds->output_report_dmabuf = devm_kzalloc(&hdev->dev, max_output_report_size, GFP_KERNEL);
-+	if (!ds->output_report_dmabuf)
-+		return ERR_PTR(-ENOMEM);
-+
- 	ret = dualsense_get_mac_address(ds);
- 	if (ret) {
- 		hid_err(hdev, "Failed to get MAC address from DualSense\n");
-@@ -753,7 +957,7 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
- 		goto err;
+ 	struct input_dev *gamepad;
+@@ -406,6 +418,18 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
+ 		goto err_free;
  	}
  
--	ds->gamepad = ps_gamepad_create(hdev);
-+	ds->gamepad = ps_gamepad_create(hdev, dualsense_play_effect);
- 	if (IS_ERR(ds->gamepad)) {
- 		ret = PTR_ERR(ds->gamepad);
- 		goto err;
++	if (ds->base.hdev->bus == BUS_BLUETOOTH) {
++		/* Last 4 bytes contains crc32 */
++		uint8_t crc_offset = DS_FEATURE_REPORT_CALIBRATION_SIZE - 4;
++		uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]);
++
++		if (!ps_check_crc32(0xa3, buf, crc_offset, report_crc)) {
++			hid_err(ds->base.hdev, "DualSense calibration report CRC's check failed\n");
++			ret = -EILSEQ;
++			goto err_free;
++		}
++	}
++
+ 	gyro_pitch_bias  = get_unaligned_le16(&buf[1]);
+ 	gyro_yaw_bias    = get_unaligned_le16(&buf[3]);
+ 	gyro_roll_bias   = get_unaligned_le16(&buf[5]);
+@@ -515,6 +539,16 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
+ 	 */
+ 	if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) {
+ 		ds_report = (struct dualsense_input_report *)&data[1];
++	} else if (report->id == DS_INPUT_REPORT_BT && hdev->bus == BUS_BLUETOOTH) {
++		/* Last 4 bytes of input report contain crc32 */
++		uint32_t report_crc = get_unaligned_le32(&data[size - 4]);
++
++		if (!ps_check_crc32(0xa1, data, size - 4, report_crc)) {
++			hid_err(hdev, "DualSense input CRC's check failed, size=%d\n", size);
++			return -EILSEQ;
++		}
++
++		ds_report = (struct dualsense_input_report *)&data[2];
+ 	} else {
+ 		hid_err(hdev, "Unhandled reportID=%d\n", report->id);
+ 		return -1;
+@@ -774,6 +808,7 @@ static void ps_remove(struct hid_device *hdev)
+ }
+ 
+ static const struct hid_device_id ps_devices[] = {
++	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+ 	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+ 	{ }
+ };
+diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
+index 1ca46cb445be..541c8837debd 100644
+--- a/drivers/hid/hid-quirks.c
++++ b/drivers/hid/hid-quirks.c
+@@ -567,6 +567,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
+ #endif
+ #if IS_ENABLED(CONFIG_HID_PLAYSTATION)
+ 	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+++	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
+ #endif
+ #if IS_ENABLED(CONFIG_HID_PRIMAX)
+ 	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
 -- 
 2.26.2
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help