diff mbox series

[RFC,8/8] HID: USI: add support for ioctls

Message ID 20211104133701.1733551-9-tero.kristo@linux.intel.com (mailing list archive)
State New, archived
Headers show
Series HID: add support for USI style pens | expand

Commit Message

Tero Kristo Nov. 4, 2021, 1:37 p.m. UTC
Add a new device node /dev/usi, which can be used to apply ioctls for
USI to modify pen parameters.

Signed-off-by: Tero Kristo <tero.kristo@linux.intel.com>
---
 .../userspace-api/ioctl/ioctl-number.rst      |   1 +
 drivers/hid/hid-usi.c                         | 134 ++++++++++++++++--
 include/linux/hid-usi.h                       |  22 +++
 3 files changed, 149 insertions(+), 8 deletions(-)
 create mode 100644 include/linux/hid-usi.h
diff mbox series

Patch

diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 6655d929a351..bfaba2748592 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -154,6 +154,7 @@  Code  Seq#    Include File                                           Comments
 'H'   C0-DF  net/bluetooth/cmtp/cmtp.h                               conflict!
 'H'   C0-DF  net/bluetooth/bnep/bnep.h                               conflict!
 'H'   F1     linux/hid-roccat.h                                      <mailto:erazor_de@users.sourceforge.net>
+'H'   F2-F3  linux/hid-usi.h
 'H'   F8-FA  sound/firewire.h
 'I'   all    linux/isdn.h                                            conflict!
 'I'   00-0F  drivers/isdn/divert/isdn_divert.h                       conflict!
diff --git a/drivers/hid/hid-usi.c b/drivers/hid/hid-usi.c
index a465c140d25a..f7e739d9f554 100644
--- a/drivers/hid/hid-usi.c
+++ b/drivers/hid/hid-usi.c
@@ -10,14 +10,18 @@ 
 
 #include <linux/module.h>
 #include <linux/sysfs.h>
+#include <linux/cdev.h>
 #include <linux/device.h>
 #include <linux/hid.h>
+#include <linux/hid-usi.h>
 #include <linux/input.h>
 #include <linux/leds.h>
 #include <linux/workqueue.h>
 
 #include "hid-ids.h"
 
+#define USI_MAX_DEVICES		1
+
 #define USI_HAS_PENS		0
 #define USI_PENS_CONFIGURED	1
 
@@ -67,6 +71,12 @@  struct usi_drvdata {
 	bool need_flush;
 	struct delayed_work work;
 	u8 saved_data[USI_NUM_ATTRS];
+	bool user_pending;
+	struct completion user_work_done;
+	struct cdev cdev;
+	struct device *dev;
+	dev_t dev_id;
+	struct class *class;
 	spinlock_t lock; /* private data lock */
 };
 
@@ -237,9 +247,9 @@  static struct usi_pen *_usi_select_pen(struct usi_drvdata *usi, int index,
  *
  * Parses input event passed from input layer. This is used to detect
  * any writes to the USI driver from userspace and to program hardware
- * to the new value. No return value.
+ * to the new value. Returns 0 on success, or negative error value.
  */
-static void __usi_input_event(struct usi_drvdata *usi, unsigned int code,
+static int __usi_input_event(struct usi_drvdata *usi, unsigned int code,
 			      int value)
 {
 	struct usi_pen *pen = usi->current_pen;
@@ -251,13 +261,13 @@  static void __usi_input_event(struct usi_drvdata *usi, unsigned int code,
 		usi->update_pending);
 
 	if (code < MSC_PEN_SET_COLOR || code > MSC_PEN_SET_LINE_STYLE)
-		return;
+		return -EINVAL;
 
 	if (!pen)
-		return;
+		return -ENODEV;
 
 	if (test_bit(__msc_to_usi_id(code), &usi->update_pending))
-		return;
+		return -EBUSY;
 
 	/*
 	 * New value received, kick off the work for actually re-programming HW
@@ -276,6 +286,8 @@  static void __usi_input_event(struct usi_drvdata *usi, unsigned int code,
 
 		schedule_delayed_work(&usi->work, delay);
 	}
+
+	return 0;
 }
 
 static int _usi_input_event(struct input_dev *input, unsigned int type,
@@ -459,6 +471,11 @@  static int usi_raw_event(struct hid_device *hdev,
 				spin_lock_irqsave(&usi->lock, flags);
 				clear_bit(i, &usi->update_running);
 				spin_unlock_irqrestore(&usi->lock, flags);
+				if (usi->user_pending) {
+					complete(&usi->user_work_done);
+					usi->user_pending = false;
+				}
+
 				check_work = true;
 			}
 		}
@@ -516,6 +533,74 @@  static void _apply_quirks(struct usi_drvdata *usi, struct hid_device *hdev)
 	}
 }
 
+static long usi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *p = (void __user *)arg;
+	struct usi_pen_info info;
+	struct usi_pen *pen;
+	struct usi_drvdata *usi = file->private_data;
+	int ret;
+
+	if (cmd != USIIOCSET && cmd != USIIOCGET)
+		return -EINVAL;
+
+	if (copy_from_user(&info, p, sizeof(info)))
+		return -EFAULT;
+
+	pen = usi_find_pen(usi, info.index);
+	if (!pen)
+		return -ENODEV;
+
+	switch (cmd) {
+	case USIIOCSET:
+		if (info.code < MSC_PEN_SET_COLOR ||
+		    info.code > MSC_PEN_SET_LINE_STYLE)
+			return -EINVAL;
+
+		init_completion(&usi->user_work_done);
+
+		ret = __usi_input_event(usi, info.code, info.value);
+		if (ret)
+			return ret;
+
+		usi->user_pending = true;
+		ret = wait_for_completion_timeout(&usi->user_work_done,
+						  usi->timeout * 2);
+		if (!ret) {
+			usi->user_pending = false;
+			return -ETIMEDOUT;
+		}
+		return 0;
+
+	case USIIOCGET:
+		ret = __usi_pen_get_value(pen, info.code);
+		if (ret < 0)
+			return ret;
+
+		info.value = ret;
+
+		if (copy_to_user(p, &info, sizeof(info)))
+			return -EFAULT;
+
+		return sizeof(info);
+	}
+
+	return -EINVAL;
+}
+
+static int usi_open(struct inode *inode, struct file *file)
+{
+	struct usi_drvdata *usi = container_of(inode->i_cdev,
+					       struct usi_drvdata, cdev);
+	file->private_data = usi;
+	return 0;
+}
+
+static const struct file_operations usi_ops = {
+	.unlocked_ioctl = usi_ioctl,
+	.open = usi_open,
+};
+
 static int usi_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
 	int ret;
@@ -529,28 +614,61 @@  static int usi_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
 	usi->hdev = hdev;
 
+	ret = alloc_chrdev_region(&usi->dev_id, 0, USI_MAX_DEVICES, "usi");
+	if (ret < 0)
+		return ret;
+
+	cdev_init(&usi->cdev, &usi_ops);
+	ret = cdev_add(&usi->cdev, usi->dev_id, USI_MAX_DEVICES);
+	if (ret < 0)
+		goto err;
+
+	usi->class = class_create(THIS_MODULE, "usi");
+	if (IS_ERR(usi->class)) {
+		ret = PTR_ERR(usi->class);
+		goto err;
+	}
+
+	usi->dev = device_create(usi->class, &hdev->dev, usi->dev_id, NULL,
+				 "usi");
+	if (IS_ERR(usi->dev)) {
+		ret = PTR_ERR(usi->dev);
+		goto err_class;
+	}
+
 	hid_set_drvdata(hdev, usi);
 
 	ret = hid_parse(hdev);
 	if (ret)
-		return ret;
+		goto err_dev;
 
 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
 	if (ret)
-		goto err;
+		goto err_dev;
 
 	_apply_quirks(usi, hdev);
 
 	return 0;
 
+err_dev:
+	device_destroy(usi->class, usi->dev_id);
+
+err_class:
+	class_destroy(usi->class);
+
 err:
-	hid_hw_stop(hdev);
+	unregister_chrdev_region(usi->dev_id, USI_MAX_DEVICES);
 	return ret;
 }
 
 static void usi_remove(struct hid_device *hdev)
 {
+	struct usi_drvdata *usi = hid_get_drvdata(hdev);
+
 	hid_hw_stop(hdev);
+	device_destroy(usi->class, usi->dev_id);
+	class_destroy(usi->class);
+	unregister_chrdev_region(usi->dev_id, USI_MAX_DEVICES);
 }
 
 /**
diff --git a/include/linux/hid-usi.h b/include/linux/hid-usi.h
new file mode 100644
index 000000000000..1840285c7bc1
--- /dev/null
+++ b/include/linux/hid-usi.h
@@ -0,0 +1,22 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021, Intel Corporation
+ * Authors: Tero Kristo <tero.kristo@linux.intel.com>
+ */
+
+#ifndef __HID_USI_H
+#define __HID_USI_H
+
+#include <linux/hid.h>
+#include <linux/types.h>
+
+struct usi_pen_info {
+	__s32 index;
+	__u32 code;
+	__s32 value;
+};
+
+#define USIIOCGET _IOR('H', 0xf2, struct usi_pen_info)
+#define USIIOCSET _IOW('H', 0xf3, struct usi_pen_info)
+
+#endif