Commit fcfcf0de authored by David Herrmann's avatar David Herrmann Committed by Jiri Kosina

HID: uhid: implement feature requests

HID standard allows sending a feature request to the device which is
answered by an HID report. uhid implements this by sending a UHID_FEATURE
event to user-space which then must answer with UHID_FEATURE_ANSWER. If it
doesn't do this in a timely manner, the request is discarded silently.

We serialize the feature requests, that is, there is always only a single
active feature-request sent to user-space, other requests have to wait.
HIDP and USB-HID do it the same way.

Because we discard feature-requests silently, we must make sure to match
a response to the corresponding request. We use sequence-IDs for this so
user-space must copy the ID from the request into the answer.
Feature-answers are ignored if they do not contain the same ID as the
currently pending feature request.

Internally, we must make sure that feature-requests are synchronized with
UHID_DESTROY and close() events. We must not dead-lock when closing the
HID device, either, so we have to use separate locks.
Signed-off-by: default avatarDavid Herrmann <dh.herrmann@googlemail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 3b3baa82
...@@ -42,6 +42,12 @@ struct uhid_device { ...@@ -42,6 +42,12 @@ struct uhid_device {
__u8 head; __u8 head;
__u8 tail; __u8 tail;
struct uhid_event *outq[UHID_BUFSIZE]; 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 struct miscdevice uhid_misc;
...@@ -143,7 +149,84 @@ static int uhid_hid_parse(struct hid_device *hid) ...@@ -143,7 +149,84 @@ static int uhid_hid_parse(struct hid_device *hid)
static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum,
__u8 *buf, size_t count, unsigned char rtype) __u8 *buf, size_t count, unsigned char rtype)
{ {
return 0; struct uhid_device *uhid = hid->driver_data;
__u8 report_type;
struct uhid_event *ev;
unsigned long flags;
int ret;
size_t 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, static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
...@@ -265,7 +348,11 @@ static int uhid_dev_destroy(struct uhid_device *uhid) ...@@ -265,7 +348,11 @@ static int uhid_dev_destroy(struct uhid_device *uhid)
if (!uhid->running) if (!uhid->running)
return -EINVAL; return -EINVAL;
/* clear "running" before setting "report_done" */
uhid->running = false; uhid->running = false;
smp_wmb();
atomic_set(&uhid->report_done, 1);
wake_up_interruptible(&uhid->report_wait);
hid_destroy_device(uhid->hid); hid_destroy_device(uhid->hid);
kfree(uhid->rd_data); kfree(uhid->rd_data);
...@@ -284,6 +371,31 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) ...@@ -284,6 +371,31 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
return 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) static int uhid_char_open(struct inode *inode, struct file *file)
{ {
struct uhid_device *uhid; struct uhid_device *uhid;
...@@ -293,9 +405,12 @@ static int uhid_char_open(struct inode *inode, struct file *file) ...@@ -293,9 +405,12 @@ static int uhid_char_open(struct inode *inode, struct file *file)
return -ENOMEM; return -ENOMEM;
mutex_init(&uhid->devlock); mutex_init(&uhid->devlock);
mutex_init(&uhid->report_lock);
spin_lock_init(&uhid->qlock); spin_lock_init(&uhid->qlock);
init_waitqueue_head(&uhid->waitq); init_waitqueue_head(&uhid->waitq);
init_waitqueue_head(&uhid->report_wait);
uhid->running = false; uhid->running = false;
atomic_set(&uhid->report_done, 1);
file->private_data = uhid; file->private_data = uhid;
nonseekable_open(inode, file); nonseekable_open(inode, file);
...@@ -398,6 +513,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer, ...@@ -398,6 +513,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
case UHID_INPUT: case UHID_INPUT:
ret = uhid_dev_input(uhid, &uhid->input_buf); ret = uhid_dev_input(uhid, &uhid->input_buf);
break; break;
case UHID_FEATURE_ANSWER:
ret = uhid_dev_feature_answer(uhid, &uhid->input_buf);
break;
default: default:
ret = -EOPNOTSUPP; ret = -EOPNOTSUPP;
} }
......
...@@ -32,6 +32,8 @@ enum uhid_event_type { ...@@ -32,6 +32,8 @@ enum uhid_event_type {
UHID_OUTPUT, UHID_OUTPUT,
UHID_OUTPUT_EV, UHID_OUTPUT_EV,
UHID_INPUT, UHID_INPUT,
UHID_FEATURE,
UHID_FEATURE_ANSWER,
}; };
struct uhid_create_req { struct uhid_create_req {
...@@ -73,6 +75,19 @@ struct uhid_output_ev_req { ...@@ -73,6 +75,19 @@ struct uhid_output_ev_req {
__s32 value; __s32 value;
} __attribute__((__packed__)); } __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 { struct uhid_event {
__u32 type; __u32 type;
...@@ -81,6 +96,8 @@ struct uhid_event { ...@@ -81,6 +96,8 @@ struct uhid_event {
struct uhid_input_req input; struct uhid_input_req input;
struct uhid_output_req output; struct uhid_output_req output;
struct uhid_output_ev_req output_ev; struct uhid_output_ev_req output_ev;
struct uhid_feature_req feature;
struct uhid_feature_answer_req feature_answer;
} u; } u;
} __attribute__((__packed__)); } __attribute__((__packed__));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment