linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/4] new driver for Valve Steam Controller
@ 2018-02-28 18:43 Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 1/4] HID: add " Rodrigo Rivas Costa
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

This patchset implements a driver for Valve Steam Controller, based on a
reverse analysis by myself.

This is reroll v4, changes since v3:
 * Add command to check the wireless connection status on probe, without
   waiting for a message (thanks to Clément Vuchener for the tip).
 * Removed the error code on redundant connection/disconnection messages. That
   was harmless but polluted dmesg.
 * Added buttons for touching the left-pad and right-pad.
 * Fixed a misplaced #include from 2/4 to 1/4.

I've added the new command from the first point in a new commit, so now there
are 4 parts. Feel free to merge that with the previous (or all of them) if you
wish.

About the new buttons, before I considered those unnecesary because I thought
of the pads as a kind of flat joysticks, so when they are not touched they go
back to the virtual center (0,0), and the touch/untouch events are not useful.
But I've been proved short-sighted in this kind of decisions before, so I've
added them, sure they are useful for somebody.

Also, I've been running some tests, with the driver, the Steam Client and my
stemctrl utility [1], and all works just fine. Even unloading and reloading
the modules (with a UDEV rule to disable the lizard mode) while the Steam
Client is running works without any issue.

[1]: https://github.com/rodrigorc/steamctrl

Rodrigo Rivas Costa (4):
  HID: add driver for Valve Steam Controller
  HID: steam: add serial number information.
  HID: steam: command to check wireless connection
  HID: steam: add battery device.

 drivers/hid/Kconfig     |   8 +
 drivers/hid/Makefile    |   1 +
 drivers/hid/hid-ids.h   |   4 +
 drivers/hid/hid-steam.c | 797 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 810 insertions(+)
 create mode 100644 drivers/hid/hid-steam.c

-- 
2.16.2

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-02-28 18:43 [PATCH v4 0/4] new driver for Valve Steam Controller Rodrigo Rivas Costa
@ 2018-02-28 18:43 ` Rodrigo Rivas Costa
  2018-02-28 19:21   ` Andy Shevchenko
  2018-02-28 18:43 ` [PATCH v4 2/4] HID: steam: add serial number information Rodrigo Rivas Costa
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

There are two ways to connect the Steam Controller: directly to the USB
or with the USB wireless adapter.  Both methods are similar, but the
wireless adapter can connect up to 4 devices at the same time.

The wired device will appear as 3 interfaces: a virtual mouse, a virtual
keyboard and a custom HID device.

The wireless device will appear as 5 interfaces: a virtual keyboard and
4 custom HID devices, that will remain silent until a device is actually
connected.

The custom HID device has a report descriptor with all vendor specific
usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
Steam Client provices a software translation by using direct USB access
and a creates a uinput virtual gamepad.

This driver was reverse engineered to provide direct kernel support in
case you cannot, or do not want to, use Valve Steam Client. It disables
the virtual keyboard and mouse, as they are not so useful when you have
a working gamepad.

Working: buttons, axes, pads, wireless connect/disconnect.

TO-DO: Battery, force-feedback, accelerometer/gyro, led, beeper...

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/Kconfig     |   8 +
 drivers/hid/Makefile    |   1 +
 drivers/hid/hid-ids.h   |   4 +
 drivers/hid/hid-steam.c | 544 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 557 insertions(+)
 create mode 100644 drivers/hid/hid-steam.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 19c499f5623d..6e80fbf04e03 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -823,6 +823,14 @@ config HID_SPEEDLINK
 	---help---
 	Support for Speedlink Vicious and Divine Cezanne mouse.
 
+config HID_STEAM
+	tristate "Steam Controller support"
+	depends on HID
+	---help---
+	Say Y here if you have a Steam Controller if you want to use it
+	without running the Steam Client. It supports both the wired and
+	the wireless adaptor.
+
 config HID_STEELSERIES
 	tristate "Steelseries SRW-S1 steering wheel support"
 	depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index eb13b9e92d85..60a8abf84682 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -95,6 +95,7 @@ obj-$(CONFIG_HID_SAMSUNG)	+= hid-samsung.o
 obj-$(CONFIG_HID_SMARTJOYPLUS)	+= hid-sjoy.o
 obj-$(CONFIG_HID_SONY)		+= hid-sony.o
 obj-$(CONFIG_HID_SPEEDLINK)	+= hid-speedlink.o
+obj-$(CONFIG_HID_STEAM)		+= hid-steam.o
 obj-$(CONFIG_HID_STEELSERIES)	+= hid-steelseries.o
 obj-$(CONFIG_HID_SUNPLUS)	+= hid-sunplus.o
 obj-$(CONFIG_HID_GREENASIA)	+= hid-gaff.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 43ddcdfbd0da..be31a3c20818 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -988,6 +988,10 @@
 #define USB_VENDOR_ID_STANTUM_SITRONIX		0x1403
 #define USB_DEVICE_ID_MTP_SITRONIX		0x5001
 
+#define USB_VENDOR_ID_VALVE			0x28de
+#define USB_DEVICE_ID_STEAM_CONTROLLER		0x1102
+#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS	0x1142
+
 #define USB_VENDOR_ID_STEELSERIES	0x1038
 #define USB_DEVICE_ID_STEELSERIES_SRWS1	0x1410
 
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
new file mode 100644
index 000000000000..96256426c366
--- /dev/null
+++ b/drivers/hid/hid-steam.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID driver for Valve Steam Controller
+ *
+ * Copyright (c) 2018 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
+ *
+ * Supports both the wired and wireless interfaces.
+ *
+ * For additional functions, such as disabling the virtual mouse/keyboard or
+ * changing the right-pad margin you can use the user-space tool at:
+ *
+ *   https://github.com/rodrigorc/steamctrl
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/rcupdate.h>
+#include "hid-ids.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>");
+
+#define STEAM_QUIRK_WIRELESS		BIT(0)
+
+/* Touch pads are 40 mm in diameter and 65535 units */
+#define STEAM_PAD_RESOLUTION 1638
+/* Trigger runs are about 5 mm and 256 units */
+#define STEAM_TRIGGER_RESOLUTION 51
+/* Joystick runs are about 5 mm and 256 units */
+#define STEAM_JOYSTICK_RESOLUTION 51
+
+#define STEAM_PAD_FUZZ 256
+
+struct steam_device {
+	spinlock_t lock;
+	struct hid_device *hdev;
+	struct input_dev __rcu *input;
+	unsigned long quirks;
+	struct work_struct work_connect;
+	bool connected;
+};
+
+static int steam_input_open(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+
+	return hid_hw_open(steam->hdev);
+}
+
+static void steam_input_close(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+
+	hid_hw_close(steam->hdev);
+}
+
+static int steam_register(struct steam_device *steam)
+{
+	struct hid_device *hdev = steam->hdev;
+	struct input_dev *input;
+	int ret;
+
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+	if (input) {
+		dbg_hid("%s: already connected\n", __func__);
+		return 0;
+	}
+
+	hid_info(hdev, "Steam Controller connected");
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input_set_drvdata(input, steam);
+	input->dev.parent = &hdev->dev;
+	input->open = steam_input_open;
+	input->close = steam_input_close;
+
+	input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ?
+		"Wireless Steam Controller" :
+		"Steam Controller";
+	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_KEY, BTN_TR2);
+	input_set_capability(input, EV_KEY, BTN_TL2);
+	input_set_capability(input, EV_KEY, BTN_TR);
+	input_set_capability(input, EV_KEY, BTN_TL);
+	input_set_capability(input, EV_KEY, BTN_Y);
+	input_set_capability(input, EV_KEY, BTN_B);
+	input_set_capability(input, EV_KEY, BTN_X);
+	input_set_capability(input, EV_KEY, BTN_A);
+	input_set_capability(input, EV_KEY, BTN_SELECT);
+	input_set_capability(input, EV_KEY, BTN_MODE);
+	input_set_capability(input, EV_KEY, BTN_START);
+	input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
+	input_set_capability(input, EV_KEY, BTN_GEAR_UP);
+	input_set_capability(input, EV_KEY, BTN_THUMBR);
+	input_set_capability(input, EV_KEY, BTN_THUMBL);
+	input_set_capability(input, EV_KEY, BTN_THUMB);
+	input_set_capability(input, EV_KEY, BTN_THUMB2);
+
+	input_set_abs_params(input, ABS_Z, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_RZ, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_Y, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_RX, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_RY, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT1X, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT1Y, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+	input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+	input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT1X, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT1Y, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_Z, STEAM_TRIGGER_RESOLUTION);
+	input_abs_set_res(input, ABS_RZ, STEAM_TRIGGER_RESOLUTION);
+
+	ret = input_register_device(input);
+	if (ret)
+		goto input_register_fail;
+
+	rcu_assign_pointer(steam->input, input);
+
+	return 0;
+
+input_register_fail:
+	input_free_device(input);
+	return ret;
+}
+
+static void steam_unregister(struct steam_device *steam)
+{
+	struct input_dev *input;
+
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+
+	if (input) {
+		RCU_INIT_POINTER(steam->input, NULL);
+		synchronize_rcu();
+		hid_info(steam->hdev, "Steam Controller disconnected");
+		input_unregister_device(input);
+	}
+}
+
+static void steam_work_connect_cb(struct work_struct *work)
+{
+	struct steam_device *steam = container_of(work, struct steam_device,
+							work_connect);
+	unsigned long flags;
+	bool connected;
+	int ret;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	connected = steam->connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (connected) {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(steam->hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+			return;
+		}
+	} else {
+		steam_unregister(steam);
+	}
+}
+
+static bool steam_is_valve_interface(struct hid_device *hdev)
+{
+	struct hid_report_enum *rep_enum;
+	struct hid_report *hreport;
+
+	/*
+	 * The wired device creates 3 interfaces:
+	 *  0: emulated mouse.
+	 *  1: emulated keyboard.
+	 *  2: the real game pad.
+	 * The wireless device creates 5 interfaces:
+	 *  0: emulated keyboard.
+	 *  1-4: slots where up to 4 real game pads will be connected to.
+	 * We know which one is the real gamepad interface because they are the
+	 * only ones with a feature report.
+	 */
+	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(hreport, &rep_enum->report_list, list) {
+		/* should we check hreport->id == 0? */
+		return true;
+	}
+	return false;
+}
+
+static int steam_probe(struct hid_device *hdev,
+				const struct hid_device_id *id)
+{
+	struct steam_device *steam;
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev,
+			"%s:parse of hid interface failed\n", __func__);
+		return ret;
+	}
+
+	/*
+	 * Connect the HID device so that Steam Controller keeps on working
+	 * without changes.
+	 */
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev,
+			"%s:hid_hw_start failed with error %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	if (!steam_is_valve_interface(hdev))
+		return 0;
+
+	/*
+	 * From this point on, if anything fails return 0 and ignores
+	 * the error, so that the default HID devices are still bound.
+	 */
+	steam = devm_kzalloc(&hdev->dev,
+			sizeof(struct steam_device), GFP_KERNEL);
+	if (!steam) {
+		ret = -ENOMEM;
+		goto mem_fail;
+	}
+
+	spin_lock_init(&steam->lock);
+	steam->hdev = hdev;
+	hid_set_drvdata(hdev, steam);
+	steam->quirks = id->driver_data;
+	INIT_WORK(&steam->work_connect, steam_work_connect_cb);
+
+	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+		ret = hid_hw_open(hdev);
+		if (ret) {
+			hid_err(hdev,
+				"%s:hid_hw_open for wireless\n",
+				__func__);
+			goto hid_hw_open_fail;
+		}
+		hid_info(hdev, "Steam wireless receiver connected");
+	} else {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+			goto input_register_fail;
+		}
+	}
+
+	return 0;
+
+input_register_fail:
+hid_hw_open_fail:
+	cancel_work_sync(&steam->work_connect);
+	hid_set_drvdata(hdev, NULL);
+mem_fail:
+	hid_err(hdev, "%s: failed with error %d\n",
+			__func__, ret);
+	return 0;
+}
+
+static void steam_remove(struct hid_device *hdev)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+
+	if (steam && (steam->quirks & STEAM_QUIRK_WIRELESS)) {
+		hid_info(hdev, "Steam wireless receiver disconnected");
+		hid_hw_close(hdev);
+	}
+
+	hid_hw_stop(hdev);
+
+	if (steam) {
+		cancel_work_sync(&steam->work_connect);
+		steam_unregister(steam);
+		hid_set_drvdata(hdev, NULL);
+	}
+}
+
+static void steam_do_connect_event(struct steam_device *steam, bool connected)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	steam->connected = connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (schedule_work(&steam->work_connect) == 0)
+		dbg_hid("%s: connected=%d event already queued\n",
+				__func__, connected);
+}
+
+/*
+ * The size for this message payload is 60.
+ * The known values are:
+ *  (* values are not sent through wireless)
+ *  (* accelerator/gyro is disabled by default)
+ *  Offset| Type  | Mapped to |Meaning
+ * -------+-------+-----------+--------------------------
+ *  4-7   | u32   | --        | sequence number
+ *  8-10  | 24bit | see below | buttons
+ *  11    | u8    | ABS_Z     | left trigger
+ *  12    | u8    | ABS_RZ    | right trigger
+ *  13-15 | --    | --        | always 0
+ *  16-17 | s16   | ABS_X     | X value
+ *  18-19 | s16   | ABS_Y     | Y value
+ *  20-21 | s16   | ABS_RX    | right-pad X value
+ *  22-23 | s16   | ABS_RY    | right-pad Y value
+ *  24-25 | s16   | --        | * left trigger
+ *  26-27 | s16   | --        | * right trigger
+ *  28-29 | s16   | --        | * accelerometer X value
+ *  30-31 | s16   | --        | * accelerometer Y value
+ *  32-33 | s16   | --        | * accelerometer Z value
+ *  34-35 | s16   | --        | gyro X value
+ *  36-36 | s16   | --        | gyro Y value
+ *  38-39 | s16   | --        | gyro Z value
+ *  40-41 | s16   | --        | quaternion W value
+ *  42-43 | s16   | --        | quaternion X value
+ *  44-45 | s16   | --        | quaternion Y value
+ *  46-47 | s16   | --        | quaternion Z value
+ *  48-49 | --    | --        | always 0
+ *  50-51 | s16   | --        | * left trigger (uncalibrated)
+ *  52-53 | s16   | --        | * right trigger (uncalibrated)
+ *  54-55 | s16   | --        | * joystick X value (uncalibrated)
+ *  56-57 | s16   | --        | * joystick Y value (uncalibrated)
+ *  58-59 | s16   | --        | * left-pad X value
+ *  60-61 | s16   | --        | * left-pad Y value
+ *  62-63 | u16   | --        | * battery voltage
+ *
+ * The buttons are:
+ *  Bit  | Mapped to  | Description
+ * ------+------------+--------------------------------
+ *  8.0  | BTN_TR2    | right trigger fully pressed
+ *  8.1  | BTN_TL2    | left trigger fully pressed
+ *  8.2  | BTN_TR     | right shoulder
+ *  8.3  | BTN_TL     | left shoulder
+ *  8.4  | BTN_Y      | button Y
+ *  8.5  | BTN_B      | button B
+ *  8.6  | BTN_X      | button X
+ *  8.7  | BTN_A      | button A
+ *  9.0  | -ABS_HAT0Y | lef-pad up
+ *  9.1  | +ABS_HAT0X | lef-pad right
+ *  9.2  | -ABS_HAT0X | lef-pad left
+ *  9.3  | +ABS_HAT0Y | lef-pad down
+ *  9.4  | BTN_SELECT | menu left
+ *  9.5  | BTN_MODE   | steam logo
+ *  9.6  | BTN_START  | menu right
+ *  9.7  | BTN_GEAR_DOWN | left back lever
+ * 10.0  | BTN_GEAR_UP   | right back lever
+ * 10.1  | --         | left-pad clicked
+ * 10.2  | BTN_THUMBR | right-pad clicked
+ * 10.3  | BTN_THUMB  | left-pad touched (but see explanation below)
+ * 10.4  | BTN_THUMB2 | right-pad touched
+ * 10.5  | --         | unknown
+ * 10.6  | BTN_THUMBL | joystick clicked
+ * 10.7  | --         | lpad_and_joy
+ */
+
+static void steam_do_input_event(struct steam_device *steam,
+		struct input_dev *input, u8 *data)
+{
+	/* 24 bits of buttons */
+	u8 b8, b9, b10;
+	bool lpad_touched, lpad_and_joy;
+
+	b8 = data[8];
+	b9 = data[9];
+	b10 = data[10];
+
+	input_report_abs(input, ABS_Z, data[11]);
+	input_report_abs(input, ABS_RZ, data[12]);
+
+	/*
+	 * These two bits tells how to interpret the values X and Y.
+	 * lpad_and_joy tells that the joystick and the lpad are used at the
+	 * same time.
+	 * lpad_touched tells whether X/Y are to be read as lpad coord or
+	 * joystick values.
+	 * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
+	 */
+	lpad_touched = b10 & 0x08;
+	lpad_and_joy = b10 & 0x80;
+	input_report_abs(input, lpad_touched ? ABS_HAT1X : ABS_X,
+			(s16) le16_to_cpup((__le16 *)(data + 16)));
+	input_report_abs(input, lpad_touched ? ABS_HAT1Y : ABS_Y,
+			-(s16) le16_to_cpup((__le16 *)(data + 18)));
+	/* Check if joystick is centered */
+	if (lpad_touched && !lpad_and_joy) {
+		input_report_abs(input, ABS_X, 0);
+		input_report_abs(input, ABS_Y, 0);
+	}
+	/* Check if lpad is untouched */
+	if (!(lpad_touched || lpad_and_joy)) {
+		input_report_abs(input, ABS_HAT1X, 0);
+		input_report_abs(input, ABS_HAT1Y, 0);
+	}
+
+	input_report_abs(input, ABS_RX,
+			(s16) le16_to_cpup((__le16 *)(data + 20)));
+	input_report_abs(input, ABS_RY,
+			-(s16) le16_to_cpup((__le16 *)(data + 22)));
+
+	input_event(input, EV_KEY, BTN_TR2, !!(b8 & 0x01));
+	input_event(input, EV_KEY, BTN_TL2, !!(b8 & 0x02));
+	input_event(input, EV_KEY, BTN_TR, !!(b8 & 0x04));
+	input_event(input, EV_KEY, BTN_TL, !!(b8 & 0x08));
+	input_event(input, EV_KEY, BTN_Y, !!(b8 & 0x10));
+	input_event(input, EV_KEY, BTN_B, !!(b8 & 0x20));
+	input_event(input, EV_KEY, BTN_X, !!(b8 & 0x40));
+	input_event(input, EV_KEY, BTN_A, !!(b8 & 0x80));
+	input_event(input, EV_KEY, BTN_SELECT, !!(b9 & 0x10));
+	input_event(input, EV_KEY, BTN_MODE, !!(b9 & 0x20));
+	input_event(input, EV_KEY, BTN_START, !!(b9 & 0x40));
+	input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & 0x80));
+	input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & 0x01));
+	input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & 0x04));
+	input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & 0x40));
+	input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
+	input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & 0x10));
+
+	input_report_abs(input, ABS_HAT0X,
+			!!(b9 & 0x02) - !!(b9 & 0x04));
+	input_report_abs(input, ABS_HAT0Y,
+			!!(b9 & 0x08) - !!(b9 & 0x01));
+
+	input_sync(input);
+}
+
+static int steam_raw_event(struct hid_device *hdev,
+			struct hid_report *report, u8 *data,
+			int size)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+	struct input_dev *input;
+
+	if (!steam)
+		return 0;
+
+	/*
+	 * All messages are size=64, all values little-endian.
+	 * The format is:
+	 *  Offset| Meaning
+	 * -------+--------------------------------------------
+	 *  0-1   | always 0x01, 0x00, maybe protocol version?
+	 *  2     | type of message
+	 *  3     | length of the real payload (not checked)
+	 *  4-n   | payload data, depends on the type
+	 *
+	 * There are these known types of message:
+	 *  0x01: input data (60 bytes)
+	 *  0x03: wireless connect/disconnect (1 byte)
+	 *  0x04: battery status (11 bytes)
+	 */
+
+	if (size != 64 || data[0] != 1 || data[1] != 0)
+		return 0;
+
+	switch (data[2]) {
+	case 0x01:
+		rcu_read_lock();
+		input = rcu_dereference(steam->input);
+		if (likely(input)) {
+			steam_do_input_event(steam, input, data);
+		} else {
+			dbg_hid("%s: input data without connect event\n",
+					__func__);
+			steam_do_connect_event(steam, true);
+		}
+		rcu_read_unlock();
+		break;
+	case 0x03:
+		/*
+		 * The payload of this event is a single byte:
+		 *  0x01: disconnected.
+		 *  0x02: connected.
+		 */
+		switch (data[4]) {
+		case 0x01:
+			steam_do_connect_event(steam, false);
+			break;
+		case 0x02:
+			steam_do_connect_event(steam, true);
+			break;
+		}
+		break;
+	case 0x04:
+		/* TODO battery status */
+		break;
+	}
+	return 0;
+}
+
+static const struct hid_device_id steam_controllers[] = {
+	{ /* Wired Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER)
+	},
+	{ /* Wireless Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS),
+	  .driver_data = STEAM_QUIRK_WIRELESS
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, steam_controllers);
+
+static struct hid_driver steam_controller_driver = {
+	.name = "hid-steam",
+	.id_table = steam_controllers,
+	.probe = steam_probe,
+	.remove = steam_remove,
+	.raw_event = steam_raw_event,
+};
+
+module_hid_driver(steam_controller_driver);
-- 
2.16.2

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 2/4] HID: steam: add serial number information.
  2018-02-28 18:43 [PATCH v4 0/4] new driver for Valve Steam Controller Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 1/4] HID: add " Rodrigo Rivas Costa
@ 2018-02-28 18:43 ` Rodrigo Rivas Costa
  2018-02-28 20:17   ` Andy Shevchenko
  2018-02-28 18:43 ` [PATCH v4 3/4] HID: steam: command to check wireless connection Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 4/4] HID: steam: add battery device Rodrigo Rivas Costa
  3 siblings, 1 reply; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

This device has a feature report to send and receive commands.
Use it to get the serial number and set the device's uniq value.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 101 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 96256426c366..9e4b1f640bef 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 #include <linux/workqueue.h>
 #include <linux/rcupdate.h>
+#include <linux/delay.h>
 #include "hid-ids.h"
 
 MODULE_LICENSE("GPL");
@@ -41,8 +42,99 @@ struct steam_device {
 	unsigned long quirks;
 	struct work_struct work_connect;
 	bool connected;
+	char serial_no[11];
 };
 
+static int steam_recv_report(struct steam_device *steam,
+		u8 *data, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/*
+	 * The report ID is always 0, so strip the first byte from the output.
+	 * hid_report_len() is not counting the report ID, so +1 to the length
+	 * or else we get a EOVERFLOW. We are safe from a buffer overflow
+	 * because hid_alloc_report_buf() allocates +7 bytes.
+	 */
+	ret = hid_hw_raw_request(steam->hdev, 0x00,
+			buf, hid_report_len(r) + 1,
+			HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret > 0)
+		memcpy(data, buf + 1, min(size, ret - 1));
+	kfree(buf);
+	return ret;
+}
+
+static int steam_send_report(struct steam_device *steam,
+		u8 *cmd, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	int retry;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/* The report ID is always 0 */
+	memcpy(buf + 1, cmd, size);
+
+	/*
+	 * Sometimes the wireless controller fails with EPIPE
+	 * when sending a feature report.
+	 * Doing a HID_REQ_GET_REPORT and waiting for a while
+	 * seems to fix that.
+	 */
+	for (retry = 0; retry < 10; ++retry) {
+		ret = hid_hw_raw_request(steam->hdev, 0,
+				buf, size + 1,
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		if (ret != -EPIPE)
+			break;
+		steam_recv_report(steam, NULL, 0);
+		msleep(50);
+	}
+	kfree(buf);
+	if (ret < 0)
+		hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
+				ret, size, cmd);
+	return ret;
+}
+
+static int steam_get_serial(struct steam_device *steam)
+{
+	/*
+	 * Send: 0xae 0x15 0x01
+	 * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+	 */
+	int ret;
+	u8 cmd[] = {0xae, 0x15, 0x01};
+	u8 reply[14];
+
+	ret = steam_send_report(steam, cmd, sizeof(cmd));
+	if (ret < 0)
+		return ret;
+	ret = steam_recv_report(steam, reply, sizeof(reply));
+	if (ret < 0)
+		return ret;
+	reply[13] = 0;
+	strcpy(steam->serial_no, reply + 3);
+	return 0;
+}
+
 static int steam_input_open(struct input_dev *dev)
 {
 	struct steam_device *steam = input_get_drvdata(dev);
@@ -71,7 +163,12 @@ static int steam_register(struct steam_device *steam)
 		return 0;
 	}
 
-	hid_info(hdev, "Steam Controller connected");
+	ret = steam_get_serial(steam);
+	if (ret)
+		return ret;
+
+	hid_info(hdev, "Steam Controller '%s' connected",
+			steam->serial_no);
 
 	input = input_allocate_device();
 	if (!input)
@@ -86,7 +183,7 @@ static int steam_register(struct steam_device *steam)
 		"Wireless Steam Controller" :
 		"Steam Controller";
 	input->phys = hdev->phys;
-	input->uniq = hdev->uniq;
+	input->uniq = steam->serial_no;
 	input->id.bustype = hdev->bus;
 	input->id.vendor = hdev->vendor;
 	input->id.product = hdev->product;
@@ -157,7 +254,8 @@ static void steam_unregister(struct steam_device *steam)
 	if (input) {
 		RCU_INIT_POINTER(steam->input, NULL);
 		synchronize_rcu();
-		hid_info(steam->hdev, "Steam Controller disconnected");
+		hid_info(steam->hdev, "Steam Controller '%s' disconnected",
+				steam->serial_no);
 		input_unregister_device(input);
 	}
 }
-- 
2.16.2

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 3/4] HID: steam: command to check wireless connection
  2018-02-28 18:43 [PATCH v4 0/4] new driver for Valve Steam Controller Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 1/4] HID: add " Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 2/4] HID: steam: add serial number information Rodrigo Rivas Costa
@ 2018-02-28 18:43 ` Rodrigo Rivas Costa
  2018-02-28 18:43 ` [PATCH v4 4/4] HID: steam: add battery device Rodrigo Rivas Costa
  3 siblings, 0 replies; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

The wireless adaptor does not tell if a device is already connected when
steam_probe() is run.
Use a command to request the connection status.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 9e4b1f640bef..894b678685d3 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -114,6 +114,11 @@ static int steam_send_report(struct steam_device *steam,
 	return ret;
 }
 
+static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd)
+{
+	return steam_send_report(steam, &cmd, 1);
+}
+
 static int steam_get_serial(struct steam_device *steam)
 {
 	/*
@@ -135,6 +140,16 @@ static int steam_get_serial(struct steam_device *steam)
 	return 0;
 }
 
+/*
+ * This command requests the wireless adaptor to post an event
+ * with the connection status. Useful if this driver is loaded when
+ * the controller is already connected.
+ */
+static inline int steam_request_conn_status(struct steam_device *steam)
+{
+	return steam_send_report_byte(steam, 0xb4);
+}
+
 static int steam_input_open(struct input_dev *dev)
 {
 	struct steam_device *steam = input_get_drvdata(dev);
@@ -363,6 +378,7 @@ static int steam_probe(struct hid_device *hdev,
 			goto hid_hw_open_fail;
 		}
 		hid_info(hdev, "Steam wireless receiver connected");
+		steam_request_conn_status(steam);
 	} else {
 		ret = steam_register(steam);
 		if (ret) {
-- 
2.16.2

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 4/4] HID: steam: add battery device.
  2018-02-28 18:43 [PATCH v4 0/4] new driver for Valve Steam Controller Rodrigo Rivas Costa
                   ` (2 preceding siblings ...)
  2018-02-28 18:43 ` [PATCH v4 3/4] HID: steam: command to check wireless connection Rodrigo Rivas Costa
@ 2018-02-28 18:43 ` Rodrigo Rivas Costa
  3 siblings, 0 replies; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 18:43 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, linux-kernel, linux-input
  Cc: Rodrigo Rivas Costa

The wireless Steam Controller is battery operated, so add the battery
device and power information.

Signed-off-by: Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
---
 drivers/hid/hid-steam.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 140 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 894b678685d3..ff38fddb4f6c 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -19,6 +19,7 @@
 #include <linux/workqueue.h>
 #include <linux/rcupdate.h>
 #include <linux/delay.h>
+#include <linux/power_supply.h>
 #include "hid-ids.h"
 
 MODULE_LICENSE("GPL");
@@ -43,6 +44,10 @@ struct steam_device {
 	struct work_struct work_connect;
 	bool connected;
 	char serial_no[11];
+	struct power_supply_desc battery_desc;
+	struct power_supply __rcu *battery;
+	u8 battery_charge;
+	u16 voltage;
 };
 
 static int steam_recv_report(struct steam_device *steam,
@@ -164,6 +169,85 @@ static void steam_input_close(struct input_dev *dev)
 	hid_hw_close(steam->hdev);
 }
 
+static enum power_supply_property steam_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int steam_battery_get_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct steam_device *steam = power_supply_get_drvdata(psy);
+	unsigned long flags;
+	s16 volts;
+	u8 batt;
+	int ret = 0;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	volts = steam->voltage;
+	batt = steam->battery_charge;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = volts * 1000; /* mV -> uV */
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = batt;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int steam_battery_register(struct steam_device *steam)
+{
+	struct power_supply *battery;
+	struct power_supply_config battery_cfg = { .drv_data = steam, };
+	unsigned long flags;
+	int ret;
+
+	steam->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+	steam->battery_desc.properties = steam_battery_props;
+	steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props);
+	steam->battery_desc.get_property = steam_battery_get_property;
+	steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev,
+			GFP_KERNEL, "steam-controller-%s-battery",
+			steam->serial_no);
+	if (!steam->battery_desc.name)
+		return -ENOMEM;
+
+	/* avoid the warning of 0% battery while waiting for the first info */
+	spin_lock_irqsave(&steam->lock, flags);
+	steam->voltage = 3000;
+	steam->battery_charge = 100;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	battery = power_supply_register(&steam->hdev->dev,
+			&steam->battery_desc, &battery_cfg);
+	if (IS_ERR(battery)) {
+		ret = PTR_ERR(battery);
+		hid_err(steam->hdev,
+				"%s:power_supply_register failed with error %d\n",
+				__func__, ret);
+		return ret;
+	}
+	rcu_assign_pointer(steam->battery, battery);
+	power_supply_powers(battery, &steam->hdev->dev);
+	return 0;
+}
+
 static int steam_register(struct steam_device *steam)
 {
 	struct hid_device *hdev = steam->hdev;
@@ -251,6 +335,10 @@ static int steam_register(struct steam_device *steam)
 
 	rcu_assign_pointer(steam->input, input);
 
+	/* ignore battery errors, we can live without it */
+	if (steam->quirks & STEAM_QUIRK_WIRELESS)
+		steam_battery_register(steam);
+
 	return 0;
 
 input_register_fail:
@@ -261,11 +349,18 @@ static int steam_register(struct steam_device *steam)
 static void steam_unregister(struct steam_device *steam)
 {
 	struct input_dev *input;
+	struct power_supply *battery;
 
 	rcu_read_lock();
 	input = rcu_dereference(steam->input);
+	battery = rcu_dereference(steam->battery);
 	rcu_read_unlock();
 
+	if (battery) {
+		RCU_INIT_POINTER(steam->battery, NULL);
+		synchronize_rcu();
+		power_supply_unregister(battery);
+	}
 	if (input) {
 		RCU_INIT_POINTER(steam->input, NULL);
 		synchronize_rcu();
@@ -568,12 +663,44 @@ static void steam_do_input_event(struct steam_device *steam,
 	input_sync(input);
 }
 
+/*
+ * The size for this message payload is 11.
+ * The known values are:
+ *  Offset| Type  | Meaning
+ * -------+-------+---------------------------
+ *  4-7   | u32   | sequence number
+ *  8-11  | --    | always 0
+ *  12-13 | u16   | voltage (mV)
+ *  14    | u8    | battery percent
+ */
+static void steam_do_battery_event(struct steam_device *steam,
+		struct power_supply *battery, u8 *data)
+{
+	unsigned long flags;
+
+	s16 volts = le16_to_cpup((__le16 *)(data + 12));
+	u8 batt = data[14];
+
+	/* Creating the battery may have failed */
+	rcu_read_lock();
+	battery = rcu_dereference(steam->battery);
+	if (likely(battery)) {
+		spin_lock_irqsave(&steam->lock, flags);
+		steam->voltage = volts;
+		steam->battery_charge = batt;
+		spin_unlock_irqrestore(&steam->lock, flags);
+		power_supply_changed(battery);
+	}
+	rcu_read_unlock();
+}
+
 static int steam_raw_event(struct hid_device *hdev,
 			struct hid_report *report, u8 *data,
 			int size)
 {
 	struct steam_device *steam = hid_get_drvdata(hdev);
 	struct input_dev *input;
+	struct power_supply *battery;
 
 	if (!steam)
 		return 0;
@@ -626,7 +753,19 @@ static int steam_raw_event(struct hid_device *hdev,
 		}
 		break;
 	case 0x04:
-		/* TODO battery status */
+		if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+			rcu_read_lock();
+			battery = rcu_dereference(steam->battery);
+			if (likely(battery)) {
+				steam_do_battery_event(steam, battery, data);
+			} else {
+				dbg_hid(
+				  "%s: battery data without connect event\n",
+				  __func__);
+				steam_do_connect_event(steam, true);
+			}
+			rcu_read_unlock();
+		}
 		break;
 	}
 	return 0;
-- 
2.16.2

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-02-28 18:43 ` [PATCH v4 1/4] HID: add " Rodrigo Rivas Costa
@ 2018-02-28 19:21   ` Andy Shevchenko
  2018-02-28 22:49     ` Rodrigo Rivas Costa
  0 siblings, 1 reply; 13+ messages in thread
From: Andy Shevchenko @ 2018-02-28 19:21 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> There are two ways to connect the Steam Controller: directly to the USB
> or with the USB wireless adapter.  Both methods are similar, but the
> wireless adapter can connect up to 4 devices at the same time.
>
> The wired device will appear as 3 interfaces: a virtual mouse, a virtual
> keyboard and a custom HID device.
>
> The wireless device will appear as 5 interfaces: a virtual keyboard and
> 4 custom HID devices, that will remain silent until a device is actually
> connected.
>
> The custom HID device has a report descriptor with all vendor specific
> usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
> Steam Client provices a software translation by using direct USB access
> and a creates a uinput virtual gamepad.
>
> This driver was reverse engineered to provide direct kernel support in
> case you cannot, or do not want to, use Valve Steam Client. It disables
> the virtual keyboard and mouse, as they are not so useful when you have
> a working gamepad.


> +// SPDX-License-Identifier: GPL-2.0

> +MODULE_LICENSE("GPL");

Not the same.

> +static void steam_unregister(struct steam_device *steam)
> +{
> +       struct input_dev *input;
> +
> +       rcu_read_lock();
> +       input = rcu_dereference(steam->input);
> +       rcu_read_unlock();
> +

> +       if (input) {

if (!input)
 return;

?

> +               RCU_INIT_POINTER(steam->input, NULL);
> +               synchronize_rcu();
> +               hid_info(steam->hdev, "Steam Controller disconnected");
> +               input_unregister_device(input);
> +       }
> +}

> +static bool steam_is_valve_interface(struct hid_device *hdev)
> +{
> +       struct hid_report_enum *rep_enum;
> +       struct hid_report *hreport;
> +
> +       /*
> +        * The wired device creates 3 interfaces:
> +        *  0: emulated mouse.
> +        *  1: emulated keyboard.
> +        *  2: the real game pad.
> +        * The wireless device creates 5 interfaces:
> +        *  0: emulated keyboard.
> +        *  1-4: slots where up to 4 real game pads will be connected to.
> +        * We know which one is the real gamepad interface because they are the
> +        * only ones with a feature report.
> +        */
> +       rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];

> +       list_for_each_entry(hreport, &rep_enum->report_list, list) {
> +               /* should we check hreport->id == 0? */
> +               return true;
> +       }
> +       return false;

So, for now it's just an equivalent of

return !list_empty();

?

> +}

> +       /*
> +        * From this point on, if anything fails return 0 and ignores
> +        * the error, so that the default HID devices are still bound.
> +        */
> +       steam = devm_kzalloc(&hdev->dev,
> +                       sizeof(struct steam_device), GFP_KERNEL);

sizeof(*steam) saves a line.

> +       if (!steam) {
> +               ret = -ENOMEM;
> +               goto mem_fail;
> +       }

> +static void steam_remove(struct hid_device *hdev)
> +{
> +       struct steam_device *steam = hid_get_drvdata(hdev);
> +
> +       if (steam && (steam->quirks & STEAM_QUIRK_WIRELESS)) {
> +               hid_info(hdev, "Steam wireless receiver disconnected");
> +               hid_hw_close(hdev);
> +       }
> +
> +       hid_hw_stop(hdev);
> +
> +       if (steam) {
> +               cancel_work_sync(&steam->work_connect);
> +               steam_unregister(steam);

> +               hid_set_drvdata(hdev, NULL);

Hmm.. Doesn't HID do this?

> +       }

if (steam) {
...
       hid_hw_stop(hdev);
...
} else {
       hid_hw_stop(hdev);
}

?

> +}

> +static void steam_do_input_event(struct steam_device *steam,
> +               struct input_dev *input, u8 *data)
> +{
> +       /* 24 bits of buttons */
> +       u8 b8, b9, b10;
> +       bool lpad_touched, lpad_and_joy;
> +
> +       b8 = data[8];
> +       b9 = data[9];
> +       b10 = data[10];
> +
> +       input_report_abs(input, ABS_Z, data[11]);
> +       input_report_abs(input, ABS_RZ, data[12]);
> +
> +       /*
> +        * These two bits tells how to interpret the values X and Y.
> +        * lpad_and_joy tells that the joystick and the lpad are used at the
> +        * same time.
> +        * lpad_touched tells whether X/Y are to be read as lpad coord or
> +        * joystick values.
> +        * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
> +        */

> +       lpad_touched = b10 & 0x08;

BIT(3) ?

> +       lpad_and_joy = b10 & 0x80;

BIT(7) ?

> +       input_event(input, EV_KEY, BTN_TR2, !!(b8 & 0x01));
> +       input_event(input, EV_KEY, BTN_TL2, !!(b8 & 0x02));
> +       input_event(input, EV_KEY, BTN_TR, !!(b8 & 0x04));
> +       input_event(input, EV_KEY, BTN_TL, !!(b8 & 0x08));
> +       input_event(input, EV_KEY, BTN_Y, !!(b8 & 0x10));
> +       input_event(input, EV_KEY, BTN_B, !!(b8 & 0x20));
> +       input_event(input, EV_KEY, BTN_X, !!(b8 & 0x40));
> +       input_event(input, EV_KEY, BTN_A, !!(b8 & 0x80));
> +       input_event(input, EV_KEY, BTN_SELECT, !!(b9 & 0x10));
> +       input_event(input, EV_KEY, BTN_MODE, !!(b9 & 0x20));
> +       input_event(input, EV_KEY, BTN_START, !!(b9 & 0x40));
> +       input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & 0x80));
> +       input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & 0x01));
> +       input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & 0x04));
> +       input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & 0x40));
> +       input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
> +       input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & 0x10));

BIT(x) ?

> +
> +       input_report_abs(input, ABS_HAT0X,
> +                       !!(b9 & 0x02) - !!(b9 & 0x04));
> +       input_report_abs(input, ABS_HAT0Y,
> +                       !!(b9 & 0x08) - !!(b9 & 0x01));

BIT(x) ?

> +}

> +static int steam_raw_event(struct hid_device *hdev,
> +                       struct hid_report *report, u8 *data,
> +                       int size)
> +{
> +       struct steam_device *steam = hid_get_drvdata(hdev);
> +       struct input_dev *input;
> +

> +       if (!steam)
> +               return 0;

When it's possible?

> +       return 0;
> +}

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] HID: steam: add serial number information.
  2018-02-28 18:43 ` [PATCH v4 2/4] HID: steam: add serial number information Rodrigo Rivas Costa
@ 2018-02-28 20:17   ` Andy Shevchenko
  2018-02-28 23:12     ` Rodrigo Rivas Costa
  0 siblings, 1 reply; 13+ messages in thread
From: Andy Shevchenko @ 2018-02-28 20:17 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> This device has a feature report to send and receive commands.
> Use it to get the serial number and set the device's uniq value.

>  #include <linux/module.h>
>  #include <linux/workqueue.h>
>  #include <linux/rcupdate.h>

> +#include <linux/delay.h>

Better to keep it somehow sorted (yes, I see it's not originally, but
better to squeeze new header to the most ordered part).


> @@ -41,8 +42,99 @@ struct steam_device {
>         unsigned long quirks;
>         struct work_struct work_connect;
>         bool connected;

> +       char serial_no[11];

11 is a magic.

>  };
>
> +static int steam_recv_report(struct steam_device *steam,
> +               u8 *data, int size)
> +{
> +       struct hid_report *r;
> +       u8 *buf;
> +       int ret;
> +
> +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> +       if (hid_report_len(r) < 64)
> +               return -EINVAL;

+ empty line.

> +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       /*
> +        * The report ID is always 0, so strip the first byte from the output.
> +        * hid_report_len() is not counting the report ID, so +1 to the length
> +        * or else we get a EOVERFLOW. We are safe from a buffer overflow
> +        * because hid_alloc_report_buf() allocates +7 bytes.
> +        */
> +       ret = hid_hw_raw_request(steam->hdev, 0x00,
> +                       buf, hid_report_len(r) + 1,
> +                       HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> +       if (ret > 0)
> +               memcpy(data, buf + 1, min(size, ret - 1));
> +       kfree(buf);
> +       return ret;
> +}
> +
> +static int steam_send_report(struct steam_device *steam,
> +               u8 *cmd, int size)
> +{
> +       struct hid_report *r;
> +       u8 *buf;
> +       int retry;
> +       int ret;
> +
> +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> +       if (hid_report_len(r) < 64)
> +               return -EINVAL;

+empty line.

> +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       /* The report ID is always 0 */
> +       memcpy(buf + 1, cmd, size);
> +
> +       /*
> +        * Sometimes the wireless controller fails with EPIPE
> +        * when sending a feature report.
> +        * Doing a HID_REQ_GET_REPORT and waiting for a while
> +        * seems to fix that.
> +        */

> +       for (retry = 0; retry < 10; ++retry) {
> +               ret = hid_hw_raw_request(steam->hdev, 0,
> +                               buf, size + 1,
> +                               HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> +               if (ret != -EPIPE)
> +                       break;
> +               steam_recv_report(steam, NULL, 0);
> +               msleep(50);
> +       }

Personally I consider do{}while in case of "timeout loops" much easier to parse.

unsigned int retry = 10;
...

do {
...
} while (--retry);

> +       kfree(buf);
> +       if (ret < 0)
> +               hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
> +                               ret, size, cmd);
> +       return ret;
> +}
> +
> +static int steam_get_serial(struct steam_device *steam)
> +{
> +       /*
> +        * Send: 0xae 0x15 0x01
> +        * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
> +        */
> +       int ret;
> +       u8 cmd[] = {0xae, 0x15, 0x01};

> +       u8 reply[14];
> +
> +       ret = steam_send_report(steam, cmd, sizeof(cmd));
> +       if (ret < 0)
> +               return ret;
> +       ret = steam_recv_report(steam, reply, sizeof(reply));
> +       if (ret < 0)
> +               return ret;


> +       reply[13] = 0;
> +       strcpy(steam->serial_no, reply + 3);

strlcpy()

> +       return 0;
> +}


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-02-28 19:21   ` Andy Shevchenko
@ 2018-02-28 22:49     ` Rodrigo Rivas Costa
  2018-03-01  7:50       ` Marcus Folkesson
  2018-03-01 10:20       ` Andy Shevchenko
  0 siblings, 2 replies; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 22:49 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Wed, Feb 28, 2018 at 09:21:15PM +0200, Andy Shevchenko wrote:
> On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
> <rodrigorivascosta@gmail.com> wrote:
> > There are two ways to connect the Steam Controller: directly to the USB
> > or with the USB wireless adapter.  Both methods are similar, but the
> > wireless adapter can connect up to 4 devices at the same time.
> >
> > The wired device will appear as 3 interfaces: a virtual mouse, a virtual
> > keyboard and a custom HID device.
> >
> > The wireless device will appear as 5 interfaces: a virtual keyboard and
> > 4 custom HID devices, that will remain silent until a device is actually
> > connected.
> >
> > The custom HID device has a report descriptor with all vendor specific
> > usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
> > Steam Client provices a software translation by using direct USB access
> > and a creates a uinput virtual gamepad.
> >
> > This driver was reverse engineered to provide direct kernel support in
> > case you cannot, or do not want to, use Valve Steam Client. It disables
> > the virtual keyboard and mouse, as they are not so useful when you have
> > a working gamepad.
> 
> 
> > +// SPDX-License-Identifier: GPL-2.0
> 
> > +MODULE_LICENSE("GPL");
> 
> Not the same.

Hmmm... I copied from usb-skeleton.c, IIRC...
I'll change to GPL-2.0+, that would be correct, I think.

> 
> > +static void steam_unregister(struct steam_device *steam)
> > +{
> > +       struct input_dev *input;
> > +
> > +       rcu_read_lock();
> > +       input = rcu_dereference(steam->input);
> > +       rcu_read_unlock();
> > +
> 
> > +       if (input) {
> 
> if (!input)
>  return;
> 
> ?
That was because of symmetry, because further patches add more objects.
Then
if (input)
	free(input);
if (battery)
	free(battery);
/* in the future *(
if (input_gyro)
	free(input_gyro);

Sure, the last one can do an early return, but you break symmetry.

> 
> > +               RCU_INIT_POINTER(steam->input, NULL);
> > +               synchronize_rcu();
> > +               hid_info(steam->hdev, "Steam Controller disconnected");
> > +               input_unregister_device(input);
> > +       }
> > +}
> 
> > +static bool steam_is_valve_interface(struct hid_device *hdev)
> > +{
> > +       struct hid_report_enum *rep_enum;
> > +       struct hid_report *hreport;
> > +
> > +       /*
> > +        * The wired device creates 3 interfaces:
> > +        *  0: emulated mouse.
> > +        *  1: emulated keyboard.
> > +        *  2: the real game pad.
> > +        * The wireless device creates 5 interfaces:
> > +        *  0: emulated keyboard.
> > +        *  1-4: slots where up to 4 real game pads will be connected to.
> > +        * We know which one is the real gamepad interface because they are the
> > +        * only ones with a feature report.
> > +        */
> > +       rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
> 
> > +       list_for_each_entry(hreport, &rep_enum->report_list, list) {
> > +               /* should we check hreport->id == 0? */
> > +               return true;
> > +       }
> > +       return false;
> 
> So, for now it's just an equivalent of
> 
> return !list_empty();
> 
> ?

I was expecting to add a few more checks in the middle, but those
weren't needed at the end.
I'll change that.

> 
> > +}
> 
> > +       /*
> > +        * From this point on, if anything fails return 0 and ignores
> > +        * the error, so that the default HID devices are still bound.
> > +        */
> > +       steam = devm_kzalloc(&hdev->dev,
> > +                       sizeof(struct steam_device), GFP_KERNEL);
> 
> sizeof(*steam) saves a line.

Right, changed.

> 
> > +       if (!steam) {
> > +               ret = -ENOMEM;
> > +               goto mem_fail;
> > +       }
> 
> > +static void steam_remove(struct hid_device *hdev)
> > +{
> > +       struct steam_device *steam = hid_get_drvdata(hdev);
> > +
> > +       if (steam && (steam->quirks & STEAM_QUIRK_WIRELESS)) {
> > +               hid_info(hdev, "Steam wireless receiver disconnected");
> > +               hid_hw_close(hdev);
> > +       }
> > +
> > +       hid_hw_stop(hdev);
> > +
> > +       if (steam) {
> > +               cancel_work_sync(&steam->work_connect);
> > +               steam_unregister(steam);
> 
> > +               hid_set_drvdata(hdev, NULL);
> 
> Hmm.. Doesn't HID do this?

Do you mean the hid_set_drvdata(hdev, NULL)? I'm not sure, I didn't see
that on hid-core.c or hid-generic.c. And a call like this is done in
many modules... so I did the same, just to be sure.
> 
> > +       }
> 
> if (steam) {
> ...
>        hid_hw_stop(hdev);
> ...
> } else {
>        hid_hw_stop(hdev);
> }
> 
> ?

I have no real preference. Your version has two 'if', mine has two 'if'.
What about:

static void steam_remove(struct hid_device *hdev)
{
	struct steam_device *steam = hid_get_drvdata(hdev);

	if (!steam) {
		hid_hw_stop(hdev);
		return;
	}

	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
		hid_info(hdev, "Steam wireless receiver disconnected");
		hid_hw_close(hdev);
	}
	hid_hw_stop(hdev);
	cancel_work_sync(&steam->work_connect);
	steam_unregister(steam);
	hid_set_drvdata(hdev, NULL);
}


> 
> > +}
> 
> > +static void steam_do_input_event(struct steam_device *steam,
> > +               struct input_dev *input, u8 *data)
> > +{
> > +       /* 24 bits of buttons */
> > +       u8 b8, b9, b10;
> > +       bool lpad_touched, lpad_and_joy;
> > +
> > +       b8 = data[8];
> > +       b9 = data[9];
> > +       b10 = data[10];
> > +
> > +       input_report_abs(input, ABS_Z, data[11]);
> > +       input_report_abs(input, ABS_RZ, data[12]);
> > +
> > +       /*
> > +        * These two bits tells how to interpret the values X and Y.
> > +        * lpad_and_joy tells that the joystick and the lpad are used at the
> > +        * same time.
> > +        * lpad_touched tells whether X/Y are to be read as lpad coord or
> > +        * joystick values.
> > +        * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
> > +        */
> 
> > +       lpad_touched = b10 & 0x08;
> 
> BIT(3) ?
> 
> > +       lpad_and_joy = b10 & 0x80;
> 
> BIT(7) ?
> 
> > +       input_event(input, EV_KEY, BTN_TR2, !!(b8 & 0x01));
> > +       input_event(input, EV_KEY, BTN_TL2, !!(b8 & 0x02));
> > +       input_event(input, EV_KEY, BTN_TR, !!(b8 & 0x04));
> > +       input_event(input, EV_KEY, BTN_TL, !!(b8 & 0x08));
> > +       input_event(input, EV_KEY, BTN_Y, !!(b8 & 0x10));
> > +       input_event(input, EV_KEY, BTN_B, !!(b8 & 0x20));
> > +       input_event(input, EV_KEY, BTN_X, !!(b8 & 0x40));
> > +       input_event(input, EV_KEY, BTN_A, !!(b8 & 0x80));
> > +       input_event(input, EV_KEY, BTN_SELECT, !!(b9 & 0x10));
> > +       input_event(input, EV_KEY, BTN_MODE, !!(b9 & 0x20));
> > +       input_event(input, EV_KEY, BTN_START, !!(b9 & 0x40));
> > +       input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & 0x80));
> > +       input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & 0x01));
> > +       input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & 0x04));
> > +       input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & 0x40));
> > +       input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
> > +       input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & 0x10));
> 
> BIT(x) ?
> 
> > +
> > +       input_report_abs(input, ABS_HAT0X,
> > +                       !!(b9 & 0x02) - !!(b9 & 0x04));
> > +       input_report_abs(input, ABS_HAT0Y,
> > +                       !!(b9 & 0x08) - !!(b9 & 0x01));
> 
> BIT(x) ?

That's certainly nicer. Changed.
> 
> > +}
> 
> > +static int steam_raw_event(struct hid_device *hdev,
> > +                       struct hid_report *report, u8 *data,
> > +                       int size)
> > +{
> > +       struct steam_device *steam = hid_get_drvdata(hdev);
> > +       struct input_dev *input;
> > +
> 
> > +       if (!steam)
> > +               return 0;
> 
> When it's possible?

That's because how the new automatic unbinding/rebinding of hid-generic
works. This driver binds all the interfaces of the device, including the
virtual mouse and keyboard (!steam_is_valve_interface()).  Those
hid_devices do not have a steam_device, but they still generate
raw_events.
> 
> > +       return 0;
> > +}
> 
> -- 
> With Best Regards,
> Andy Shevchenko

Regards.
Rodrigo.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] HID: steam: add serial number information.
  2018-02-28 20:17   ` Andy Shevchenko
@ 2018-02-28 23:12     ` Rodrigo Rivas Costa
  0 siblings, 0 replies; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-02-28 23:12 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Wed, Feb 28, 2018 at 10:17:50PM +0200, Andy Shevchenko wrote:
> On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
> <rodrigorivascosta@gmail.com> wrote:
> > This device has a feature report to send and receive commands.
> > Use it to get the serial number and set the device's uniq value.
> 
> >  #include <linux/module.h>
> >  #include <linux/workqueue.h>
> >  #include <linux/rcupdate.h>
> 
> > +#include <linux/delay.h>
> 
> Better to keep it somehow sorted (yes, I see it's not originally, but
> better to squeeze new header to the most ordered part).

Do you mean alphabetically? Or by topic/submodule? I just added it to
the end of the include list.
> 
> 
> > @@ -41,8 +42,99 @@ struct steam_device {
> >         unsigned long quirks;
> >         struct work_struct work_connect;
> >         bool connected;
> 
> > +       char serial_no[11];
> 
> 11 is a magic.
Magic indeed, it is 10 because the Valve protocol says so, and +1 for
the NUL.  I'll add a #define for that 10.
> 
> >  };
> >
> > +static int steam_recv_report(struct steam_device *steam,
> > +               u8 *data, int size)
> > +{
> > +       struct hid_report *r;
> > +       u8 *buf;
> > +       int ret;
> > +
> > +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> > +       if (hid_report_len(r) < 64)
> > +               return -EINVAL;
> 
> + empty line.

Ok.

> 
> > +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> > +       if (!buf)
> > +               return -ENOMEM;
> > +
> > +       /*
> > +        * The report ID is always 0, so strip the first byte from the output.
> > +        * hid_report_len() is not counting the report ID, so +1 to the length
> > +        * or else we get a EOVERFLOW. We are safe from a buffer overflow
> > +        * because hid_alloc_report_buf() allocates +7 bytes.
> > +        */
> > +       ret = hid_hw_raw_request(steam->hdev, 0x00,
> > +                       buf, hid_report_len(r) + 1,
> > +                       HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
> > +       if (ret > 0)
> > +               memcpy(data, buf + 1, min(size, ret - 1));
> > +       kfree(buf);
> > +       return ret;
> > +}
> > +
> > +static int steam_send_report(struct steam_device *steam,
> > +               u8 *cmd, int size)
> > +{
> > +       struct hid_report *r;
> > +       u8 *buf;
> > +       int retry;
> > +       int ret;
> > +
> > +       r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
> > +       if (hid_report_len(r) < 64)
> > +               return -EINVAL;
> 
> +empty line.

Ok.

> 
> > +       buf = hid_alloc_report_buf(r, GFP_KERNEL);
> > +       if (!buf)
> > +               return -ENOMEM;
> > +
> > +       /* The report ID is always 0 */
> > +       memcpy(buf + 1, cmd, size);
> > +
> > +       /*
> > +        * Sometimes the wireless controller fails with EPIPE
> > +        * when sending a feature report.
> > +        * Doing a HID_REQ_GET_REPORT and waiting for a while
> > +        * seems to fix that.
> > +        */
> 
> > +       for (retry = 0; retry < 10; ++retry) {
> > +               ret = hid_hw_raw_request(steam->hdev, 0,
> > +                               buf, size + 1,
> > +                               HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> > +               if (ret != -EPIPE)
> > +                       break;
> > +               steam_recv_report(steam, NULL, 0);
> > +               msleep(50);
> > +       }
> 
> Personally I consider do{}while in case of "timeout loops" much easier to parse.
> 
> unsigned int retry = 10;
> ...
> 
> do {
> ...
> } while (--retry);
> 

Ok, it looks like it is done this way in most places. Also renamed to 'retries'.

> > +       kfree(buf);
> > +       if (ret < 0)
> > +               hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
> > +                               ret, size, cmd);
> > +       return ret;
> > +}
> > +
> > +static int steam_get_serial(struct steam_device *steam)
> > +{
> > +       /*
> > +        * Send: 0xae 0x15 0x01
> > +        * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
> > +        */
> > +       int ret;
> > +       u8 cmd[] = {0xae, 0x15, 0x01};
> 
> > +       u8 reply[14];
> > +
> > +       ret = steam_send_report(steam, cmd, sizeof(cmd));
> > +       if (ret < 0)
> > +               return ret;
> > +       ret = steam_recv_report(steam, reply, sizeof(reply));
> > +       if (ret < 0)
> > +               return ret;
> 
> 
> > +       reply[13] = 0;
> > +       strcpy(steam->serial_no, reply + 3);
> 
> strlcpy()

Well, I've set a NUL byte at the end so the overflow is impossible.
I'll change it anyway, for extra safety.

> 
> > +       return 0;
> > +}
> 
> 
> -- 
> With Best Regards,
> Andy Shevchenko

Regards.
Rodrigo

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-02-28 22:49     ` Rodrigo Rivas Costa
@ 2018-03-01  7:50       ` Marcus Folkesson
  2018-03-01 10:20       ` Andy Shevchenko
  1 sibling, 0 replies; 13+ messages in thread
From: Marcus Folkesson @ 2018-03-01  7:50 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Andy Shevchenko, Jiri Kosina, Benjamin Tissoires,
	Pierre-Loup A. Griffais, Cameron Gutman, Clément VUCHENER,
	Linux Kernel Mailing List, linux-input

Rodrigo,

On Wed, Feb 28, 2018 at 11:49:26PM +0100, Rodrigo Rivas Costa wrote:
> On Wed, Feb 28, 2018 at 09:21:15PM +0200, Andy Shevchenko wrote:
> > On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
> > <rodrigorivascosta@gmail.com> wrote:
> > > There are two ways to connect the Steam Controller: directly to the USB
> > > or with the USB wireless adapter.  Both methods are similar, but the
> > > wireless adapter can connect up to 4 devices at the same time.
> > >
> > > The wired device will appear as 3 interfaces: a virtual mouse, a virtual
> > > keyboard and a custom HID device.
> > >
> > > The wireless device will appear as 5 interfaces: a virtual keyboard and
> > > 4 custom HID devices, that will remain silent until a device is actually
> > > connected.
> > >
> > > The custom HID device has a report descriptor with all vendor specific
> > > usages, so the hid-generic is not very useful. In a PC/SteamBox Valve
> > > Steam Client provices a software translation by using direct USB access
> > > and a creates a uinput virtual gamepad.
> > >
> > > This driver was reverse engineered to provide direct kernel support in
> > > case you cannot, or do not want to, use Valve Steam Client. It disables
> > > the virtual keyboard and mouse, as they are not so useful when you have
> > > a working gamepad.
> > 
> > 
> > > +// SPDX-License-Identifier: GPL-2.0
> > 
> > > +MODULE_LICENSE("GPL");
> > 
> > Not the same.
> 
> Hmmm... I copied from usb-skeleton.c, IIRC...
> I'll change to GPL-2.0+, that would be correct, I think.

Yep, the usb-skeleton.c is wrong.
I have prepared a patch, just not submitted it yet..

GPL-2.0+ is "GPLv2 or later" if that is what you want.

Best regards
Marcus Folkesson

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-02-28 22:49     ` Rodrigo Rivas Costa
  2018-03-01  7:50       ` Marcus Folkesson
@ 2018-03-01 10:20       ` Andy Shevchenko
  2018-03-01 19:13         ` Rodrigo Rivas Costa
  1 sibling, 1 reply; 13+ messages in thread
From: Andy Shevchenko @ 2018-03-01 10:20 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Thu, Mar 1, 2018 at 12:49 AM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> On Wed, Feb 28, 2018 at 09:21:15PM +0200, Andy Shevchenko wrote:
>> On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
>> <rodrigorivascosta@gmail.com> wrote:

>> > +       if (input) {
>>
>> if (!input)
>>  return;
>>
>> ?
> That was because of symmetry, because further patches add more objects.
> Then
> if (input)
>         free(input);
> if (battery)
>         free(battery);
> /* in the future *(
> if (input_gyro)
>         free(input_gyro);
>
> Sure, the last one can do an early return, but you break symmetry.

The generic APIs when designed properly are NULL aware at freeing resources.

So, it should look like

free(input);
free(battery);
free(input_gyro);


>> > +       if (steam) {
>> > +               cancel_work_sync(&steam->work_connect);
>> > +               steam_unregister(steam);
>>
>> > +               hid_set_drvdata(hdev, NULL);
>>
>> Hmm.. Doesn't HID do this?
>
> Do you mean the hid_set_drvdata(hdev, NULL)? I'm not sure, I didn't see
> that on hid-core.c or hid-generic.c. And a call like this is done in
> many modules... so I did the same, just to be sure.

What you are looking for is, AFAIU:

drivers/base/dd.c:902:          dev_set_drvdata(dev, NULL);


>>
>> > +       }
>>
>> if (steam) {
>> ...
>>        hid_hw_stop(hdev);
>> ...
>> } else {
>>        hid_hw_stop(hdev);
>> }
>>
>> ?
>
> I have no real preference. Your version has two 'if', mine has two 'if'.
> What about:
>
> static void steam_remove(struct hid_device *hdev)
> {
>         struct steam_device *steam = hid_get_drvdata(hdev);
>
>         if (!steam) {
>                 hid_hw_stop(hdev);
>                 return;
>         }
>
>         if (steam->quirks & STEAM_QUIRK_WIRELESS) {
>                 hid_info(hdev, "Steam wireless receiver disconnected");
>                 hid_hw_close(hdev);
>         }
>         hid_hw_stop(hdev);
>         cancel_work_sync(&steam->work_connect);
>         steam_unregister(steam);

>         hid_set_drvdata(hdev, NULL);

w/o this one.

> }

For me my version looks more compact to read, but at the end it's up to you.
Another option split if (steam) variant into helper, and thus

if (steam)
 steam_non_null_device_remove();
else
 hid_hw_stop();

But again, up to you (that's why question mark in the first place above).

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-03-01 10:20       ` Andy Shevchenko
@ 2018-03-01 19:13         ` Rodrigo Rivas Costa
  2018-03-01 19:57           ` Andy Shevchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Rodrigo Rivas Costa @ 2018-03-01 19:13 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Thu, Mar 01, 2018 at 12:20:37PM +0200, Andy Shevchenko wrote:
> On Thu, Mar 1, 2018 at 12:49 AM, Rodrigo Rivas Costa
> <rodrigorivascosta@gmail.com> wrote:
> > On Wed, Feb 28, 2018 at 09:21:15PM +0200, Andy Shevchenko wrote:
> >> On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
> >> <rodrigorivascosta@gmail.com> wrote:
> 
> >> > +       if (input) {
> >>
> >> if (!input)
> >>  return;
> >>
> >> ?
> > That was because of symmetry, because further patches add more objects.
> > Then
> > if (input)
> >         free(input);
> > if (battery)
> >         free(battery);
> > /* in the future *(
> > if (input_gyro)
> >         free(input_gyro);
> >
> > Sure, the last one can do an early return, but you break symmetry.
> 
> The generic APIs when designed properly are NULL aware at freeing resources.
> 
> So, it should look like
> 
> free(input);
> free(battery);
> free(input_gyro);

My bad, I didn't mean a literal free() call. More like:

static void steam_unregister(struct steam_device *steam)
{
	struct input_dev *input;
	struct power_supply *battery;

	rcu_read_lock();
	input = rcu_dereference(steam->input);
	battery = rcu_dereference(steam->battery);
	input_gyro = rcu_dereference(steam->input_gyro);
	rcu_read_unlock();

	if (input) {
		RCU_INIT_POINTER(steam->input, NULL);
		synchronize_rcu();
		hid_info(steam->hdev, "Steam Controller '%s' disconnected",
				steam->serial_no);
		input_unregister_device(input);
	}
	if (battery) {
		RCU_INIT_POINTER(steam->battery, NULL);
		synchronize_rcu();
		power_supply_unregister(battery);
	}
	if (input_gyro) {
		RCU_INIT_POINTER(steam->input_gyro, NULL);
		synchronize_rcu();
		input_unregister_device(input_gyro);
	}
}

Also I think input_unregister_device() does not admit a NULL pointer.
Having a special `if (!input_gyro)return;` at the end looks just
wrong to me, it is no different from the other ones.

> 
> >> > +       if (steam) {
> >> > +               cancel_work_sync(&steam->work_connect);
> >> > +               steam_unregister(steam);
> >>
> >> > +               hid_set_drvdata(hdev, NULL);
> >>
> >> Hmm.. Doesn't HID do this?
> >
> > Do you mean the hid_set_drvdata(hdev, NULL)? I'm not sure, I didn't see
> > that on hid-core.c or hid-generic.c. And a call like this is done in
> > many modules... so I did the same, just to be sure.
> 
> What you are looking for is, AFAIU:
> 
> drivers/base/dd.c:902:          dev_set_drvdata(dev, NULL);

Ah, yes. I usually feel more comfortable setting the pointer to NULL
just after freeing the object. But currently I'm not freeing the steam
anymore, it is devm'ed, and the actual free happens from dd.c:900.

You convinced me, I'll remove that line.

> 
> >>
> >> > +       }
> >>
> >> if (steam) {
> >> ...
> >>        hid_hw_stop(hdev);
> >> ...
> >> } else {
> >>        hid_hw_stop(hdev);
> >> }
> >>
> >> ?
> >
> > I have no real preference. Your version has two 'if', mine has two 'if'.
> > What about:
> >
> > static void steam_remove(struct hid_device *hdev)
> > {
> >         struct steam_device *steam = hid_get_drvdata(hdev);
> >
> >         if (!steam) {
> >                 hid_hw_stop(hdev);
> >                 return;
> >         }
> >
> >         if (steam->quirks & STEAM_QUIRK_WIRELESS) {
> >                 hid_info(hdev, "Steam wireless receiver disconnected");
> >                 hid_hw_close(hdev);
> >         }
> >         hid_hw_stop(hdev);
> >         cancel_work_sync(&steam->work_connect);
> >         steam_unregister(steam);
> 
> >         hid_set_drvdata(hdev, NULL);
> 
> w/o this one.
> 
> > }
> 
> For me my version looks more compact to read, but at the end it's up to you.
> Another option split if (steam) variant into helper, and thus
> 
> if (steam)
>  steam_non_null_device_remove();
> else
>  hid_hw_stop();
> 
> But again, up to you (that's why question mark in the first place above).

Well, I don't like code such as:

	if (cond) {
		/* a lot of code */
	} else {
		/* one line */
	}

because at the time I get to the else I don't remember what the
condition was about.

Regards.
Rodrigo

> -- 
> With Best Regards,
> Andy Shevchenko

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 1/4] HID: add driver for Valve Steam Controller
  2018-03-01 19:13         ` Rodrigo Rivas Costa
@ 2018-03-01 19:57           ` Andy Shevchenko
  0 siblings, 0 replies; 13+ messages in thread
From: Andy Shevchenko @ 2018-03-01 19:57 UTC (permalink / raw)
  To: Rodrigo Rivas Costa
  Cc: Jiri Kosina, Benjamin Tissoires, Pierre-Loup A. Griffais,
	Cameron Gutman, Clément VUCHENER, Linux Kernel Mailing List,
	linux-input

On Thu, Mar 1, 2018 at 9:13 PM, Rodrigo Rivas Costa
<rodrigorivascosta@gmail.com> wrote:
> On Thu, Mar 01, 2018 at 12:20:37PM +0200, Andy Shevchenko wrote:
>> On Thu, Mar 1, 2018 at 12:49 AM, Rodrigo Rivas Costa
>> <rodrigorivascosta@gmail.com> wrote:
>> > On Wed, Feb 28, 2018 at 09:21:15PM +0200, Andy Shevchenko wrote:
>> >> On Wed, Feb 28, 2018 at 8:43 PM, Rodrigo Rivas Costa
>> >> <rodrigorivascosta@gmail.com> wrote:

> My bad, I didn't mean a literal free() call. More like:
>
> static void steam_unregister(struct steam_device *steam)
> {
>         struct input_dev *input;
>         struct power_supply *battery;
>
>         rcu_read_lock();
>         input = rcu_dereference(steam->input);
>         battery = rcu_dereference(steam->battery);
>         input_gyro = rcu_dereference(steam->input_gyro);
>         rcu_read_unlock();
>

>         if (input) {
>                 RCU_INIT_POINTER(steam->input, NULL);
>                 synchronize_rcu();
>                 hid_info(steam->hdev, "Steam Controller '%s' disconnected",
>                                 steam->serial_no);
>                 input_unregister_device(input);

Candidate for a helper

static void steam_unregister_input(...)
{
...
}


steam_unregister_input(input);

>         }

>         if (battery) {
>                 RCU_INIT_POINTER(steam->battery, NULL);
>                 synchronize_rcu();
>                 power_supply_unregister(battery);
>         }

Ditto.

>         if (input_gyro) {
>                 RCU_INIT_POINTER(steam->input_gyro, NULL);
>                 synchronize_rcu();
>                 input_unregister_device(input_gyro);
>         }

Ditto.

> }
>
> Also I think input_unregister_device() does not admit a NULL pointer.
> Having a special `if (!input_gyro)return;` at the end looks just
> wrong to me, it is no different from the other ones.

See above. Hide that detail in a helper function.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2018-03-01 19:57 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-28 18:43 [PATCH v4 0/4] new driver for Valve Steam Controller Rodrigo Rivas Costa
2018-02-28 18:43 ` [PATCH v4 1/4] HID: add " Rodrigo Rivas Costa
2018-02-28 19:21   ` Andy Shevchenko
2018-02-28 22:49     ` Rodrigo Rivas Costa
2018-03-01  7:50       ` Marcus Folkesson
2018-03-01 10:20       ` Andy Shevchenko
2018-03-01 19:13         ` Rodrigo Rivas Costa
2018-03-01 19:57           ` Andy Shevchenko
2018-02-28 18:43 ` [PATCH v4 2/4] HID: steam: add serial number information Rodrigo Rivas Costa
2018-02-28 20:17   ` Andy Shevchenko
2018-02-28 23:12     ` Rodrigo Rivas Costa
2018-02-28 18:43 ` [PATCH v4 3/4] HID: steam: command to check wireless connection Rodrigo Rivas Costa
2018-02-28 18:43 ` [PATCH v4 4/4] HID: steam: add battery device Rodrigo Rivas Costa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).