forked from Minki/linux
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: David Herrmann <dh.herrmann@googlemail.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
parent
3b3baa82e4
commit
fcfcf0deb8
@ -42,6 +42,12 @@ struct uhid_device {
|
||||
__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;
|
||||
@ -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,
|
||||
__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,
|
||||
@ -265,7 +348,11 @@ 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);
|
||||
@ -284,6 +371,31 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
|
||||
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;
|
||||
@ -293,9 +405,12 @@ static int uhid_char_open(struct inode *inode, struct file *file)
|
||||
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);
|
||||
@ -398,6 +513,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
|
||||
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;
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ enum uhid_event_type {
|
||||
UHID_OUTPUT,
|
||||
UHID_OUTPUT_EV,
|
||||
UHID_INPUT,
|
||||
UHID_FEATURE,
|
||||
UHID_FEATURE_ANSWER,
|
||||
};
|
||||
|
||||
struct uhid_create_req {
|
||||
@ -73,6 +75,19 @@ struct uhid_output_ev_req {
|
||||
__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;
|
||||
|
||||
@ -81,6 +96,8 @@ struct uhid_event {
|
||||
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__));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user