Merge branch 'uhid' into for-linus
Conflicts: drivers/hid/Kconfig
This commit is contained in:
commit
c062c4d1de
|
@ -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>
|
|
@ -6948,6 +6948,13 @@ S: Maintained
|
||||||
F: Documentation/filesystems/ufs.txt
|
F: Documentation/filesystems/ufs.txt
|
||||||
F: fs/ufs/
|
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:
|
ULTRA-WIDEBAND (UWB) SUBSYSTEM:
|
||||||
L: linux-usb@vger.kernel.org
|
L: linux-usb@vger.kernel.org
|
||||||
S: Orphan
|
S: Orphan
|
||||||
|
|
|
@ -53,6 +53,27 @@ config HIDRAW
|
||||||
|
|
||||||
If unsure, say Y.
|
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
|
config HID_GENERIC
|
||||||
tristate "Generic HID driver"
|
tristate "Generic HID driver"
|
||||||
depends on HID
|
depends on HID
|
||||||
|
|
|
@ -8,6 +8,7 @@ ifdef CONFIG_DEBUG_FS
|
||||||
endif
|
endif
|
||||||
|
|
||||||
obj-$(CONFIG_HID) += hid.o
|
obj-$(CONFIG_HID) += hid.o
|
||||||
|
obj-$(CONFIG_UHID) += uhid.o
|
||||||
|
|
||||||
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
|
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
|
||||||
|
|
||||||
|
|
|
@ -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");
|
|
@ -376,6 +376,7 @@ header-y += tty.h
|
||||||
header-y += types.h
|
header-y += types.h
|
||||||
header-y += udf_fs_i.h
|
header-y += udf_fs_i.h
|
||||||
header-y += udp.h
|
header-y += udp.h
|
||||||
|
header-y += uhid.h
|
||||||
header-y += uinput.h
|
header-y += uinput.h
|
||||||
header-y += uio.h
|
header-y += uio.h
|
||||||
header-y += ultrasound.h
|
header-y += ultrasound.h
|
||||||
|
|
|
@ -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_ */
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue