Merge branch 'uhid' into for-linus
authorJiri Kosina <jkosina@suse.cz>
Tue, 24 Jul 2012 11:40:06 +0000 (13:40 +0200)
committerJiri Kosina <jkosina@suse.cz>
Tue, 24 Jul 2012 11:40:06 +0000 (13:40 +0200)
Conflicts:
drivers/hid/Kconfig

Documentation/hid/uhid.txt [new file with mode: 0644]
MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/uhid.c [new file with mode: 0644]
include/linux/Kbuild
include/linux/uhid.h [new file with mode: 0644]
samples/uhid/Makefile [new file with mode: 0644]
samples/uhid/uhid-example.c [new file with mode: 0644]

diff --git a/Documentation/hid/uhid.txt b/Documentation/hid/uhid.txt
new file mode 100644 (file)
index 0000000..4627c42
--- /dev/null
@@ -0,0 +1,169 @@
+      UHID - User-space I/O driver support for HID subsystem
+     ========================================================
+
+The HID subsystem needs two kinds of drivers. In this document we call them:
+
+ 1. The "HID I/O Driver" is the driver that performs raw data I/O to the
+    low-level device. Internally, they register an hid_ll_driver structure with
+    the HID core. They perform device setup, read raw data from the device and
+    push it into the HID subsystem and they provide a callback so the HID
+    subsystem can send data to the device.
+
+ 2. The "HID Device Driver" is the driver that parses HID reports and reacts on
+    them. There are generic drivers like "generic-usb" and "generic-bluetooth"
+    which adhere to the HID specification and provide the standardizes features.
+    But there may be special drivers and quirks for each non-standard device out
+    there. Internally, they use the hid_driver structure.
+
+Historically, the USB stack was the first subsystem to provide an HID I/O
+Driver. However, other standards like Bluetooth have adopted the HID specs and
+may provide HID I/O Drivers, too. The UHID driver allows to implement HID I/O
+Drivers in user-space and feed the data into the kernel HID-subsystem.
+
+This allows user-space to operate on the same level as USB-HID, Bluetooth-HID
+and similar. It does not provide a way to write HID Device Drivers, though. Use
+hidraw for this purpose.
+
+There is an example user-space application in ./samples/uhid/uhid-example.c
+
+The UHID API
+------------
+
+UHID is accessed through a character misc-device. The minor-number is allocated
+dynamically so you need to rely on udev (or similar) to create the device node.
+This is /dev/uhid by default.
+
+If a new device is detected by your HID I/O Driver and you want to register this
+device with the HID subsystem, then you need to open /dev/uhid once for each
+device you want to register. All further communication is done by read()'ing or
+write()'ing "struct uhid_event" objects. Non-blocking operations are supported
+by setting O_NONBLOCK.
+
+struct uhid_event {
+        __u32 type;
+        union {
+                struct uhid_create_req create;
+                struct uhid_data_req data;
+                ...
+        } u;
+};
+
+The "type" field contains the ID of the event. Depending on the ID different
+payloads are sent. You must not split a single event across multiple read()'s or
+multiple write()'s. A single event must always be sent as a whole. Furthermore,
+only a single event can be sent per read() or write(). Pending data is ignored.
+If you want to handle multiple events in a single syscall, then use vectored
+I/O with readv()/writev().
+
+The first thing you should do is sending an UHID_CREATE event. This will
+register the device. UHID will respond with an UHID_START event. You can now
+start sending data to and reading data from UHID. However, unless UHID sends the
+UHID_OPEN event, the internally attached HID Device Driver has no user attached.
+That is, you might put your device asleep unless you receive the UHID_OPEN
+event. If you receive the UHID_OPEN event, you should start I/O. If the last
+user closes the HID device, you will receive an UHID_CLOSE event. This may be
+followed by an UHID_OPEN event again and so on. There is no need to perform
+reference-counting in user-space. That is, you will never receive multiple
+UHID_OPEN events without an UHID_CLOSE event. The HID subsystem performs
+ref-counting for you.
+You may decide to ignore UHID_OPEN/UHID_CLOSE, though. I/O is allowed even
+though the device may have no users.
+
+If you want to send data to the HID subsystem, you send an HID_INPUT event with
+your raw data payload. If the kernel wants to send data to the device, you will
+read an UHID_OUTPUT or UHID_OUTPUT_EV event.
+
+If your device disconnects, you should send an UHID_DESTROY event. This will
+unregister the device. You can now send UHID_CREATE again to register a new
+device.
+If you close() the fd, the device is automatically unregistered and destroyed
+internally.
+
+write()
+-------
+write() allows you to modify the state of the device and feed input data into
+the kernel. The following types are supported: UHID_CREATE, UHID_DESTROY and
+UHID_INPUT. The kernel will parse the event immediately and if the event ID is
+not supported, it will return -EOPNOTSUPP. If the payload is invalid, then
+-EINVAL is returned, otherwise, the amount of data that was read is returned and
+the request was handled successfully.
+
+  UHID_CREATE:
+  This creates the internal HID device. No I/O is possible until you send this
+  event to the kernel. The payload is of type struct uhid_create_req and
+  contains information about your device. You can start I/O now.
+
+  UHID_DESTROY:
+  This destroys the internal HID device. No further I/O will be accepted. There
+  may still be pending messages that you can receive with read() but no further
+  UHID_INPUT events can be sent to the kernel.
+  You can create a new device by sending UHID_CREATE again. There is no need to
+  reopen the character device.
+
+  UHID_INPUT:
+  You must send UHID_CREATE before sending input to the kernel! This event
+  contains a data-payload. This is the raw data that you read from your device.
+  The kernel will parse the HID reports and react on it.
+
+  UHID_FEATURE_ANSWER:
+  If you receive a UHID_FEATURE request you must answer with this request. You
+  must copy the "id" field from the request into the answer. Set the "err" field
+  to 0 if no error occured or to EIO if an I/O error occurred.
+  If "err" is 0 then you should fill the buffer of the answer with the results
+  of the feature request and set "size" correspondingly.
+
+read()
+------
+read() will return a queued ouput report. These output reports can be of type
+UHID_START, UHID_STOP, UHID_OPEN, UHID_CLOSE, UHID_OUTPUT or UHID_OUTPUT_EV. No
+reaction is required to any of them but you should handle them according to your
+needs. Only UHID_OUTPUT and UHID_OUTPUT_EV have payloads.
+
+  UHID_START:
+  This is sent when the HID device is started. Consider this as an answer to
+  UHID_CREATE. This is always the first event that is sent.
+
+  UHID_STOP:
+  This is sent when the HID device is stopped. Consider this as an answer to
+  UHID_DESTROY.
+  If the kernel HID device driver closes the device manually (that is, you
+  didn't send UHID_DESTROY) then you should consider this device closed and send
+  an UHID_DESTROY event. You may want to reregister your device, though. This is
+  always the last message that is sent to you unless you reopen the device with
+  UHID_CREATE.
+
+  UHID_OPEN:
+  This is sent when the HID device is opened. That is, the data that the HID
+  device provides is read by some other process. You may ignore this event but
+  it is useful for power-management. As long as you haven't received this event
+  there is actually no other process that reads your data so there is no need to
+  send UHID_INPUT events to the kernel.
+
+  UHID_CLOSE:
+  This is sent when there are no more processes which read the HID data. It is
+  the counterpart of UHID_OPEN and you may as well ignore this event.
+
+  UHID_OUTPUT:
+  This is sent if the HID device driver wants to send raw data to the I/O
+  device. You should read the payload and forward it to the device. The payload
+  is of type "struct uhid_data_req".
+  This may be received even though you haven't received UHID_OPEN, yet.
+
+  UHID_OUTPUT_EV:
+  Same as UHID_OUTPUT but this contains a "struct input_event" as payload. This
+  is called for force-feedback, LED or similar events which are received through
+  an input device by the HID subsystem. You should convert this into raw reports
+  and send them to your device similar to events of type UHID_OUTPUT.
+
+  UHID_FEATURE:
+  This event is sent if the kernel driver wants to perform a feature request as
+  described in the HID specs. The report-type and report-number are available in
+  the payload.
+  The kernel serializes feature requests so there will never be two in parallel.
+  However, if you fail to respond with a UHID_FEATURE_ANSWER in a time-span of 5
+  seconds, then the requests will be dropped and a new one might be sent.
+  Therefore, the payload also contains an "id" field that identifies every
+  request.
+
+Document by:
+  David Herrmann <dh.herrmann@googlemail.com>
index c82c343168e80cbc0d5823d357c53f5206bafda6..12dde1f795dd85a9c9d0ba45417144b19f41f7c9 100644 (file)
@@ -6948,6 +6948,13 @@ S:       Maintained
 F:     Documentation/filesystems/ufs.txt
 F:     fs/ufs/
 
+UHID USERSPACE HID IO DRIVER:
+M:     David Herrmann <dh.herrmann@googlemail.com>
+L:     linux-input@vger.kernel.org
+S:     Maintained
+F:     drivers/hid/uhid.c
+F:     include/linux/uhid.h
+
 ULTRA-WIDEBAND (UWB) SUBSYSTEM:
 L:     linux-usb@vger.kernel.org
 S:     Orphan
index 00445bc3019c019e51aed46880aaebb81170d5c9..fbf49503508d8aa79a5e0cc4c901b716bb1233b6 100644 (file)
@@ -53,6 +53,27 @@ config HIDRAW
 
        If unsure, say Y.
 
+config UHID
+       tristate "User-space I/O driver support for HID subsystem"
+       depends on HID
+       default n
+       ---help---
+       Say Y here if you want to provide HID I/O Drivers from user-space.
+       This allows to write I/O drivers in user-space and feed the data from
+       the device into the kernel. The kernel parses the HID reports, loads the
+       corresponding HID Device Driver or provides input devices on top of your
+       user-space device.
+
+       This driver cannot be used to parse HID-reports in user-space and write
+       special HID-drivers. You should use hidraw for that.
+       Instead, this driver allows to write the transport-layer driver in
+       user-space like USB-HID and Bluetooth-HID do in kernel-space.
+
+       If unsure, say N.
+
+       To compile this driver as a module, choose M here: the
+       module will be called uhid.
+
 config HID_GENERIC
        tristate "Generic HID driver"
        depends on HID
index 02fa9389695143fef5b77e0d0826d9fbf609a317..f975485f88b29636a6f26afba0b463924b154b05 100644 (file)
@@ -8,6 +8,7 @@ ifdef CONFIG_DEBUG_FS
 endif
 
 obj-$(CONFIG_HID)              += hid.o
+obj-$(CONFIG_UHID)             += uhid.o
 
 obj-$(CONFIG_HID_GENERIC)      += hid-generic.o
 
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
new file mode 100644 (file)
index 0000000..714cd8c
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * User-space I/O driver support for HID subsystem
+ * Copyright (c) 2012 David Herrmann
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/uhid.h>
+#include <linux/wait.h>
+
+#define UHID_NAME      "uhid"
+#define UHID_BUFSIZE   32
+
+struct uhid_device {
+       struct mutex devlock;
+       bool running;
+
+       __u8 *rd_data;
+       uint rd_size;
+
+       struct hid_device *hid;
+       struct uhid_event input_buf;
+
+       wait_queue_head_t waitq;
+       spinlock_t qlock;
+       __u8 head;
+       __u8 tail;
+       struct uhid_event *outq[UHID_BUFSIZE];
+
+       struct mutex report_lock;
+       wait_queue_head_t report_wait;
+       atomic_t report_done;
+       atomic_t report_id;
+       struct uhid_event report_buf;
+};
+
+static struct miscdevice uhid_misc;
+
+static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
+{
+       __u8 newhead;
+
+       newhead = (uhid->head + 1) % UHID_BUFSIZE;
+
+       if (newhead != uhid->tail) {
+               uhid->outq[uhid->head] = ev;
+               uhid->head = newhead;
+               wake_up_interruptible(&uhid->waitq);
+       } else {
+               hid_warn(uhid->hid, "Output queue is full\n");
+               kfree(ev);
+       }
+}
+
+static int uhid_queue_event(struct uhid_device *uhid, __u32 event)
+{
+       unsigned long flags;
+       struct uhid_event *ev;
+
+       ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+       if (!ev)
+               return -ENOMEM;
+
+       ev->type = event;
+
+       spin_lock_irqsave(&uhid->qlock, flags);
+       uhid_queue(uhid, ev);
+       spin_unlock_irqrestore(&uhid->qlock, flags);
+
+       return 0;
+}
+
+static int uhid_hid_start(struct hid_device *hid)
+{
+       struct uhid_device *uhid = hid->driver_data;
+
+       return uhid_queue_event(uhid, UHID_START);
+}
+
+static void uhid_hid_stop(struct hid_device *hid)
+{
+       struct uhid_device *uhid = hid->driver_data;
+
+       hid->claimed = 0;
+       uhid_queue_event(uhid, UHID_STOP);
+}
+
+static int uhid_hid_open(struct hid_device *hid)
+{
+       struct uhid_device *uhid = hid->driver_data;
+
+       return uhid_queue_event(uhid, UHID_OPEN);
+}
+
+static void uhid_hid_close(struct hid_device *hid)
+{
+       struct uhid_device *uhid = hid->driver_data;
+
+       uhid_queue_event(uhid, UHID_CLOSE);
+}
+
+static int uhid_hid_input(struct input_dev *input, unsigned int type,
+                         unsigned int code, int value)
+{
+       struct hid_device *hid = input_get_drvdata(input);
+       struct uhid_device *uhid = hid->driver_data;
+       unsigned long flags;
+       struct uhid_event *ev;
+
+       ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
+       if (!ev)
+               return -ENOMEM;
+
+       ev->type = UHID_OUTPUT_EV;
+       ev->u.output_ev.type = type;
+       ev->u.output_ev.code = code;
+       ev->u.output_ev.value = value;
+
+       spin_lock_irqsave(&uhid->qlock, flags);
+       uhid_queue(uhid, ev);
+       spin_unlock_irqrestore(&uhid->qlock, flags);
+
+       return 0;
+}
+
+static int uhid_hid_parse(struct hid_device *hid)
+{
+       struct uhid_device *uhid = hid->driver_data;
+
+       return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
+}
+
+static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum,
+                           __u8 *buf, size_t count, unsigned char rtype)
+{
+       struct uhid_device *uhid = hid->driver_data;
+       __u8 report_type;
+       struct uhid_event *ev;
+       unsigned long flags;
+       int ret;
+       size_t uninitialized_var(len);
+       struct uhid_feature_answer_req *req;
+
+       if (!uhid->running)
+               return -EIO;
+
+       switch (rtype) {
+       case HID_FEATURE_REPORT:
+               report_type = UHID_FEATURE_REPORT;
+               break;
+       case HID_OUTPUT_REPORT:
+               report_type = UHID_OUTPUT_REPORT;
+               break;
+       case HID_INPUT_REPORT:
+               report_type = UHID_INPUT_REPORT;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ret = mutex_lock_interruptible(&uhid->report_lock);
+       if (ret)
+               return ret;
+
+       ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+       if (!ev) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       spin_lock_irqsave(&uhid->qlock, flags);
+       ev->type = UHID_FEATURE;
+       ev->u.feature.id = atomic_inc_return(&uhid->report_id);
+       ev->u.feature.rnum = rnum;
+       ev->u.feature.rtype = report_type;
+
+       atomic_set(&uhid->report_done, 0);
+       uhid_queue(uhid, ev);
+       spin_unlock_irqrestore(&uhid->qlock, flags);
+
+       ret = wait_event_interruptible_timeout(uhid->report_wait,
+                               atomic_read(&uhid->report_done), 5 * HZ);
+
+       /*
+        * Make sure "uhid->running" is cleared on shutdown before
+        * "uhid->report_done" is set.
+        */
+       smp_rmb();
+       if (!ret || !uhid->running) {
+               ret = -EIO;
+       } else if (ret < 0) {
+               ret = -ERESTARTSYS;
+       } else {
+               spin_lock_irqsave(&uhid->qlock, flags);
+               req = &uhid->report_buf.u.feature_answer;
+
+               if (req->err) {
+                       ret = -EIO;
+               } else {
+                       ret = 0;
+                       len = min(count,
+                               min_t(size_t, req->size, UHID_DATA_MAX));
+                       memcpy(buf, req->data, len);
+               }
+
+               spin_unlock_irqrestore(&uhid->qlock, flags);
+       }
+
+       atomic_set(&uhid->report_done, 1);
+
+unlock:
+       mutex_unlock(&uhid->report_lock);
+       return ret ? ret : len;
+}
+
+static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
+                              unsigned char report_type)
+{
+       struct uhid_device *uhid = hid->driver_data;
+       __u8 rtype;
+       unsigned long flags;
+       struct uhid_event *ev;
+
+       switch (report_type) {
+       case HID_FEATURE_REPORT:
+               rtype = UHID_FEATURE_REPORT;
+               break;
+       case HID_OUTPUT_REPORT:
+               rtype = UHID_OUTPUT_REPORT;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (count < 1 || count > UHID_DATA_MAX)
+               return -EINVAL;
+
+       ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+       if (!ev)
+               return -ENOMEM;
+
+       ev->type = UHID_OUTPUT;
+       ev->u.output.size = count;
+       ev->u.output.rtype = rtype;
+       memcpy(ev->u.output.data, buf, count);
+
+       spin_lock_irqsave(&uhid->qlock, flags);
+       uhid_queue(uhid, ev);
+       spin_unlock_irqrestore(&uhid->qlock, flags);
+
+       return count;
+}
+
+static struct hid_ll_driver uhid_hid_driver = {
+       .start = uhid_hid_start,
+       .stop = uhid_hid_stop,
+       .open = uhid_hid_open,
+       .close = uhid_hid_close,
+       .hidinput_input_event = uhid_hid_input,
+       .parse = uhid_hid_parse,
+};
+
+static int uhid_dev_create(struct uhid_device *uhid,
+                          const struct uhid_event *ev)
+{
+       struct hid_device *hid;
+       int ret;
+
+       if (uhid->running)
+               return -EALREADY;
+
+       uhid->rd_size = ev->u.create.rd_size;
+       if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE)
+               return -EINVAL;
+
+       uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL);
+       if (!uhid->rd_data)
+               return -ENOMEM;
+
+       if (copy_from_user(uhid->rd_data, ev->u.create.rd_data,
+                          uhid->rd_size)) {
+               ret = -EFAULT;
+               goto err_free;
+       }
+
+       hid = hid_allocate_device();
+       if (IS_ERR(hid)) {
+               ret = PTR_ERR(hid);
+               goto err_free;
+       }
+
+       strncpy(hid->name, ev->u.create.name, 127);
+       hid->name[127] = 0;
+       strncpy(hid->phys, ev->u.create.phys, 63);
+       hid->phys[63] = 0;
+       strncpy(hid->uniq, ev->u.create.uniq, 63);
+       hid->uniq[63] = 0;
+
+       hid->ll_driver = &uhid_hid_driver;
+       hid->hid_get_raw_report = uhid_hid_get_raw;
+       hid->hid_output_raw_report = uhid_hid_output_raw;
+       hid->bus = ev->u.create.bus;
+       hid->vendor = ev->u.create.vendor;
+       hid->product = ev->u.create.product;
+       hid->version = ev->u.create.version;
+       hid->country = ev->u.create.country;
+       hid->driver_data = uhid;
+       hid->dev.parent = uhid_misc.this_device;
+
+       uhid->hid = hid;
+       uhid->running = true;
+
+       ret = hid_add_device(hid);
+       if (ret) {
+               hid_err(hid, "Cannot register HID device\n");
+               goto err_hid;
+       }
+
+       return 0;
+
+err_hid:
+       hid_destroy_device(hid);
+       uhid->hid = NULL;
+       uhid->running = false;
+err_free:
+       kfree(uhid->rd_data);
+       return ret;
+}
+
+static int uhid_dev_destroy(struct uhid_device *uhid)
+{
+       if (!uhid->running)
+               return -EINVAL;
+
+       /* clear "running" before setting "report_done" */
+       uhid->running = false;
+       smp_wmb();
+       atomic_set(&uhid->report_done, 1);
+       wake_up_interruptible(&uhid->report_wait);
+
+       hid_destroy_device(uhid->hid);
+       kfree(uhid->rd_data);
+
+       return 0;
+}
+
+static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
+{
+       if (!uhid->running)
+               return -EINVAL;
+
+       hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data,
+                        min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
+
+       return 0;
+}
+
+static int uhid_dev_feature_answer(struct uhid_device *uhid,
+                                  struct uhid_event *ev)
+{
+       unsigned long flags;
+
+       if (!uhid->running)
+               return -EINVAL;
+
+       spin_lock_irqsave(&uhid->qlock, flags);
+
+       /* id for old report; drop it silently */
+       if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id)
+               goto unlock;
+       if (atomic_read(&uhid->report_done))
+               goto unlock;
+
+       memcpy(&uhid->report_buf, ev, sizeof(*ev));
+       atomic_set(&uhid->report_done, 1);
+       wake_up_interruptible(&uhid->report_wait);
+
+unlock:
+       spin_unlock_irqrestore(&uhid->qlock, flags);
+       return 0;
+}
+
+static int uhid_char_open(struct inode *inode, struct file *file)
+{
+       struct uhid_device *uhid;
+
+       uhid = kzalloc(sizeof(*uhid), GFP_KERNEL);
+       if (!uhid)
+               return -ENOMEM;
+
+       mutex_init(&uhid->devlock);
+       mutex_init(&uhid->report_lock);
+       spin_lock_init(&uhid->qlock);
+       init_waitqueue_head(&uhid->waitq);
+       init_waitqueue_head(&uhid->report_wait);
+       uhid->running = false;
+       atomic_set(&uhid->report_done, 1);
+
+       file->private_data = uhid;
+       nonseekable_open(inode, file);
+
+       return 0;
+}
+
+static int uhid_char_release(struct inode *inode, struct file *file)
+{
+       struct uhid_device *uhid = file->private_data;
+       unsigned int i;
+
+       uhid_dev_destroy(uhid);
+
+       for (i = 0; i < UHID_BUFSIZE; ++i)
+               kfree(uhid->outq[i]);
+
+       kfree(uhid);
+
+       return 0;
+}
+
+static ssize_t uhid_char_read(struct file *file, char __user *buffer,
+                               size_t count, loff_t *ppos)
+{
+       struct uhid_device *uhid = file->private_data;
+       int ret;
+       unsigned long flags;
+       size_t len;
+
+       /* they need at least the "type" member of uhid_event */
+       if (count < sizeof(__u32))
+               return -EINVAL;
+
+try_again:
+       if (file->f_flags & O_NONBLOCK) {
+               if (uhid->head == uhid->tail)
+                       return -EAGAIN;
+       } else {
+               ret = wait_event_interruptible(uhid->waitq,
+                                               uhid->head != uhid->tail);
+               if (ret)
+                       return ret;
+       }
+
+       ret = mutex_lock_interruptible(&uhid->devlock);
+       if (ret)
+               return ret;
+
+       if (uhid->head == uhid->tail) {
+               mutex_unlock(&uhid->devlock);
+               goto try_again;
+       } else {
+               len = min(count, sizeof(**uhid->outq));
+               if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) {
+                       ret = -EFAULT;
+               } else {
+                       kfree(uhid->outq[uhid->tail]);
+                       uhid->outq[uhid->tail] = NULL;
+
+                       spin_lock_irqsave(&uhid->qlock, flags);
+                       uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE;
+                       spin_unlock_irqrestore(&uhid->qlock, flags);
+               }
+       }
+
+       mutex_unlock(&uhid->devlock);
+       return ret ? ret : len;
+}
+
+static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
+                               size_t count, loff_t *ppos)
+{
+       struct uhid_device *uhid = file->private_data;
+       int ret;
+       size_t len;
+
+       /* we need at least the "type" member of uhid_event */
+       if (count < sizeof(__u32))
+               return -EINVAL;
+
+       ret = mutex_lock_interruptible(&uhid->devlock);
+       if (ret)
+               return ret;
+
+       memset(&uhid->input_buf, 0, sizeof(uhid->input_buf));
+       len = min(count, sizeof(uhid->input_buf));
+       if (copy_from_user(&uhid->input_buf, buffer, len)) {
+               ret = -EFAULT;
+               goto unlock;
+       }
+
+       switch (uhid->input_buf.type) {
+       case UHID_CREATE:
+               ret = uhid_dev_create(uhid, &uhid->input_buf);
+               break;
+       case UHID_DESTROY:
+               ret = uhid_dev_destroy(uhid);
+               break;
+       case UHID_INPUT:
+               ret = uhid_dev_input(uhid, &uhid->input_buf);
+               break;
+       case UHID_FEATURE_ANSWER:
+               ret = uhid_dev_feature_answer(uhid, &uhid->input_buf);
+               break;
+       default:
+               ret = -EOPNOTSUPP;
+       }
+
+unlock:
+       mutex_unlock(&uhid->devlock);
+
+       /* return "count" not "len" to not confuse the caller */
+       return ret ? ret : count;
+}
+
+static unsigned int uhid_char_poll(struct file *file, poll_table *wait)
+{
+       struct uhid_device *uhid = file->private_data;
+
+       poll_wait(file, &uhid->waitq, wait);
+
+       if (uhid->head != uhid->tail)
+               return POLLIN | POLLRDNORM;
+
+       return 0;
+}
+
+static const struct file_operations uhid_fops = {
+       .owner          = THIS_MODULE,
+       .open           = uhid_char_open,
+       .release        = uhid_char_release,
+       .read           = uhid_char_read,
+       .write          = uhid_char_write,
+       .poll           = uhid_char_poll,
+       .llseek         = no_llseek,
+};
+
+static struct miscdevice uhid_misc = {
+       .fops           = &uhid_fops,
+       .minor          = MISC_DYNAMIC_MINOR,
+       .name           = UHID_NAME,
+};
+
+static int __init uhid_init(void)
+{
+       return misc_register(&uhid_misc);
+}
+
+static void __exit uhid_exit(void)
+{
+       misc_deregister(&uhid_misc);
+}
+
+module_init(uhid_init);
+module_exit(uhid_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
index 8760be30b3750a8f4d8e8e01692ccf92bb3b1931..cb2a7d1ad47b6bb38a15ed8138aaa1af861d4010 100644 (file)
@@ -376,6 +376,7 @@ header-y += tty.h
 header-y += types.h
 header-y += udf_fs_i.h
 header-y += udp.h
+header-y += uhid.h
 header-y += uinput.h
 header-y += uio.h
 header-y += ultrasound.h
diff --git a/include/linux/uhid.h b/include/linux/uhid.h
new file mode 100644 (file)
index 0000000..9c6974f
--- /dev/null
@@ -0,0 +1,104 @@
+#ifndef __UHID_H_
+#define __UHID_H_
+
+/*
+ * User-space I/O driver support for HID subsystem
+ * Copyright (c) 2012 David Herrmann
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * Public header for user-space communication. We try to keep every structure
+ * aligned but to be safe we also use __attribute__((__packed__)). Therefore,
+ * the communication should be ABI compatible even between architectures.
+ */
+
+#include <linux/input.h>
+#include <linux/types.h>
+
+enum uhid_event_type {
+       UHID_CREATE,
+       UHID_DESTROY,
+       UHID_START,
+       UHID_STOP,
+       UHID_OPEN,
+       UHID_CLOSE,
+       UHID_OUTPUT,
+       UHID_OUTPUT_EV,
+       UHID_INPUT,
+       UHID_FEATURE,
+       UHID_FEATURE_ANSWER,
+};
+
+struct uhid_create_req {
+       __u8 name[128];
+       __u8 phys[64];
+       __u8 uniq[64];
+       __u8 __user *rd_data;
+       __u16 rd_size;
+
+       __u16 bus;
+       __u32 vendor;
+       __u32 product;
+       __u32 version;
+       __u32 country;
+} __attribute__((__packed__));
+
+#define UHID_DATA_MAX 4096
+
+enum uhid_report_type {
+       UHID_FEATURE_REPORT,
+       UHID_OUTPUT_REPORT,
+       UHID_INPUT_REPORT,
+};
+
+struct uhid_input_req {
+       __u8 data[UHID_DATA_MAX];
+       __u16 size;
+} __attribute__((__packed__));
+
+struct uhid_output_req {
+       __u8 data[UHID_DATA_MAX];
+       __u16 size;
+       __u8 rtype;
+} __attribute__((__packed__));
+
+struct uhid_output_ev_req {
+       __u16 type;
+       __u16 code;
+       __s32 value;
+} __attribute__((__packed__));
+
+struct uhid_feature_req {
+       __u32 id;
+       __u8 rnum;
+       __u8 rtype;
+} __attribute__((__packed__));
+
+struct uhid_feature_answer_req {
+       __u32 id;
+       __u16 err;
+       __u16 size;
+       __u8 data[UHID_DATA_MAX];
+};
+
+struct uhid_event {
+       __u32 type;
+
+       union {
+               struct uhid_create_req create;
+               struct uhid_input_req input;
+               struct uhid_output_req output;
+               struct uhid_output_ev_req output_ev;
+               struct uhid_feature_req feature;
+               struct uhid_feature_answer_req feature_answer;
+       } u;
+} __attribute__((__packed__));
+
+#endif /* __UHID_H_ */
diff --git a/samples/uhid/Makefile b/samples/uhid/Makefile
new file mode 100644 (file)
index 0000000..c95a696
--- /dev/null
@@ -0,0 +1,10 @@
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
+obj- := dummy.o
+
+# List of programs to build
+hostprogs-y := uhid-example
+
+# Tell kbuild to always build the programs
+always := $(hostprogs-y)
+
+HOSTCFLAGS_uhid-example.o += -I$(objtree)/usr/include
diff --git a/samples/uhid/uhid-example.c b/samples/uhid/uhid-example.c
new file mode 100644 (file)
index 0000000..03ce3c0
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * UHID Example
+ *
+ * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using uhid.
+ */
+
+/* UHID Example
+ * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
+ * program as root and then use the following keys to control the mouse:
+ *   q: Quit the application
+ *   1: Toggle left button (down, up, ...)
+ *   2: Toggle right button
+ *   3: Toggle middle button
+ *   a: Move mouse left
+ *   d: Move mouse right
+ *   w: Move mouse up
+ *   s: Move mouse down
+ *   r: Move wheel up
+ *   f: Move wheel down
+ *
+ * If uhid is not available as /dev/uhid, then you can pass a different path as
+ * first argument.
+ * If <linux/uhid.h> is not installed in /usr, then compile this with:
+ *   gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
+ * And ignore the warning about kernel headers. However, it is recommended to
+ * use the installed uhid.h if available.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+
+/* HID Report Desciptor
+ * We emulate a basic 3 button mouse with wheel. This is the report-descriptor
+ * as the kernel will parse it:
+ *
+ * INPUT[INPUT]
+ *   Field(0)
+ *     Physical(GenericDesktop.Pointer)
+ *     Application(GenericDesktop.Mouse)
+ *     Usage(3)
+ *       Button.0001
+ *       Button.0002
+ *       Button.0003
+ *     Logical Minimum(0)
+ *     Logical Maximum(1)
+ *     Report Size(1)
+ *     Report Count(3)
+ *     Report Offset(0)
+ *     Flags( Variable Absolute )
+ *   Field(1)
+ *     Physical(GenericDesktop.Pointer)
+ *     Application(GenericDesktop.Mouse)
+ *     Usage(3)
+ *       GenericDesktop.X
+ *       GenericDesktop.Y
+ *       GenericDesktop.Wheel
+ *     Logical Minimum(-128)
+ *     Logical Maximum(127)
+ *     Report Size(8)
+ *     Report Count(3)
+ *     Report Offset(8)
+ *     Flags( Variable Relative )
+ *
+ * This is the mapping that we expect:
+ *   Button.0001 ---> Key.LeftBtn
+ *   Button.0002 ---> Key.RightBtn
+ *   Button.0003 ---> Key.MiddleBtn
+ *   GenericDesktop.X ---> Relative.X
+ *   GenericDesktop.Y ---> Relative.Y
+ *   GenericDesktop.Wheel ---> Relative.Wheel
+ *
+ * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
+ * This file should print the same information as showed above.
+ */
+
+static unsigned char rdesc[] = {
+       0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
+       0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
+       0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
+       0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
+       0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
+       0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
+       0x81, 0x06, 0xc0, 0xc0,
+};
+
+static int uhid_write(int fd, const struct uhid_event *ev)
+{
+       ssize_t ret;
+
+       ret = write(fd, ev, sizeof(*ev));
+       if (ret < 0) {
+               fprintf(stderr, "Cannot write to uhid: %m\n");
+               return -errno;
+       } else if (ret != sizeof(*ev)) {
+               fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n",
+                       ret, sizeof(ev));
+               return -EFAULT;
+       } else {
+               return 0;
+       }
+}
+
+static int create(int fd)
+{
+       struct uhid_event ev;
+
+       memset(&ev, 0, sizeof(ev));
+       ev.type = UHID_CREATE;
+       strcpy((char*)ev.u.create.name, "test-uhid-device");
+       ev.u.create.rd_data = rdesc;
+       ev.u.create.rd_size = sizeof(rdesc);
+       ev.u.create.bus = BUS_USB;
+       ev.u.create.vendor = 0x15d9;
+       ev.u.create.product = 0x0a37;
+       ev.u.create.version = 0;
+       ev.u.create.country = 0;
+
+       return uhid_write(fd, &ev);
+}
+
+static void destroy(int fd)
+{
+       struct uhid_event ev;
+
+       memset(&ev, 0, sizeof(ev));
+       ev.type = UHID_DESTROY;
+
+       uhid_write(fd, &ev);
+}
+
+static int event(int fd)
+{
+       struct uhid_event ev;
+       ssize_t ret;
+
+       memset(&ev, 0, sizeof(ev));
+       ret = read(fd, &ev, sizeof(ev));
+       if (ret == 0) {
+               fprintf(stderr, "Read HUP on uhid-cdev\n");
+               return -EFAULT;
+       } else if (ret < 0) {
+               fprintf(stderr, "Cannot read uhid-cdev: %m\n");
+               return -errno;
+       } else if (ret != sizeof(ev)) {
+               fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n",
+                       ret, sizeof(ev));
+               return -EFAULT;
+       }
+
+       switch (ev.type) {
+       case UHID_START:
+               fprintf(stderr, "UHID_START from uhid-dev\n");
+               break;
+       case UHID_STOP:
+               fprintf(stderr, "UHID_STOP from uhid-dev\n");
+               break;
+       case UHID_OPEN:
+               fprintf(stderr, "UHID_OPEN from uhid-dev\n");
+               break;
+       case UHID_CLOSE:
+               fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
+               break;
+       case UHID_OUTPUT:
+               fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
+               break;
+       case UHID_OUTPUT_EV:
+               fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
+               break;
+       default:
+               fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
+       }
+
+       return 0;
+}
+
+static bool btn1_down;
+static bool btn2_down;
+static bool btn3_down;
+static signed char abs_hor;
+static signed char abs_ver;
+static signed char wheel;
+
+static int send_event(int fd)
+{
+       struct uhid_event ev;
+
+       memset(&ev, 0, sizeof(ev));
+       ev.type = UHID_INPUT;
+       ev.u.input.size = 4;
+
+       if (btn1_down)
+               ev.u.input.data[0] |= 0x1;
+       if (btn2_down)
+               ev.u.input.data[0] |= 0x2;
+       if (btn3_down)
+               ev.u.input.data[0] |= 0x4;
+
+       ev.u.input.data[1] = abs_hor;
+       ev.u.input.data[2] = abs_ver;
+       ev.u.input.data[3] = wheel;
+
+       return uhid_write(fd, &ev);
+}
+
+static int keyboard(int fd)
+{
+       char buf[128];
+       ssize_t ret, i;
+
+       ret = read(STDIN_FILENO, buf, sizeof(buf));
+       if (ret == 0) {
+               fprintf(stderr, "Read HUP on stdin\n");
+               return -EFAULT;
+       } else if (ret < 0) {
+               fprintf(stderr, "Cannot read stdin: %m\n");
+               return -errno;
+       }
+
+       for (i = 0; i < ret; ++i) {
+               switch (buf[i]) {
+               case '1':
+                       btn1_down = !btn1_down;
+                       ret = send_event(fd);
+                       if (ret)
+                               return ret;
+                       break;
+               case '2':
+                       btn2_down = !btn2_down;
+                       ret = send_event(fd);
+                       if (ret)
+                               return ret;
+                       break;
+               case '3':
+                       btn3_down = !btn3_down;
+                       ret = send_event(fd);
+                       if (ret)
+                               return ret;
+                       break;
+               case 'a':
+                       abs_hor = -20;
+                       ret = send_event(fd);
+                       abs_hor = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 'd':
+                       abs_hor = 20;
+                       ret = send_event(fd);
+                       abs_hor = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 'w':
+                       abs_ver = -20;
+                       ret = send_event(fd);
+                       abs_ver = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 's':
+                       abs_ver = 20;
+                       ret = send_event(fd);
+                       abs_ver = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 'r':
+                       wheel = 1;
+                       ret = send_event(fd);
+                       wheel = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 'f':
+                       wheel = -1;
+                       ret = send_event(fd);
+                       wheel = 0;
+                       if (ret)
+                               return ret;
+                       break;
+               case 'q':
+                       return -ECANCELED;
+               default:
+                       fprintf(stderr, "Invalid input: %c\n", buf[i]);
+               }
+       }
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int fd;
+       const char *path = "/dev/uhid";
+       struct pollfd pfds[2];
+       int ret;
+       struct termios state;
+
+       ret = tcgetattr(STDIN_FILENO, &state);
+       if (ret) {
+               fprintf(stderr, "Cannot get tty state\n");
+       } else {
+               state.c_lflag &= ~ICANON;
+               state.c_cc[VMIN] = 1;
+               ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
+               if (ret)
+                       fprintf(stderr, "Cannot set tty state\n");
+       }
+
+       if (argc >= 2) {
+               if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+                       fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
+                       return EXIT_SUCCESS;
+               } else {
+                       path = argv[1];
+               }
+       }
+
+       fprintf(stderr, "Open uhid-cdev %s\n", path);
+       fd = open(path, O_RDWR | O_CLOEXEC);
+       if (fd < 0) {
+               fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
+               return EXIT_FAILURE;
+       }
+
+       fprintf(stderr, "Create uhid device\n");
+       ret = create(fd);
+       if (ret) {
+               close(fd);
+               return EXIT_FAILURE;
+       }
+
+       pfds[0].fd = STDIN_FILENO;
+       pfds[0].events = POLLIN;
+       pfds[1].fd = fd;
+       pfds[1].events = POLLIN;
+
+       fprintf(stderr, "Press 'q' to quit...\n");
+       while (1) {
+               ret = poll(pfds, 2, -1);
+               if (ret < 0) {
+                       fprintf(stderr, "Cannot poll for fds: %m\n");
+                       break;
+               }
+               if (pfds[0].revents & POLLHUP) {
+                       fprintf(stderr, "Received HUP on stdin\n");
+                       break;
+               }
+               if (pfds[1].revents & POLLHUP) {
+                       fprintf(stderr, "Received HUP on uhid-cdev\n");
+                       break;
+               }
+
+               if (pfds[0].revents & POLLIN) {
+                       ret = keyboard(fd);
+                       if (ret)
+                               break;
+               }
+               if (pfds[1].revents & POLLIN) {
+                       ret = event(fd);
+                       if (ret)
+                               break;
+               }
+       }
+
+       fprintf(stderr, "Destroy uhid device\n");
+       destroy(fd);
+       return EXIT_SUCCESS;
+}