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> |
@ -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"); |
@ -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