HID: uhid: implement SET_REPORT

We so far lacked support for hid_hw_raw_request(..., HID_REQ_SET_REPORT);
Add support for it and simply forward the request to user-space. Note that
SET_REPORT is synchronous, just like GET_REPORT, even though it does not
provide any data back besides an error code.

If a transport layer does SET_REPORT asynchronously, they can just ACK it
immediately by writing an uhid_set_report_reply to uhid.

This patch re-uses the synchronous uhid-report infrastructure to query
user-space. Note that this means you cannot run SET_REPORT and GET_REPORT
in parallel. However, that has always been a restriction of HID and due to
its blocking nature, this is just fine. Maybe some future transport layer
supports parallel requests (very unlikely), however, until then lets not
over-complicate things and avoid request-lookup-tables.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
David Herrmann 2014-07-29 17:14:24 +02:00 committed by Jiri Kosina
commit 11c2215530
2 changed files with 162 additions and 75 deletions

View file

@ -49,6 +49,7 @@ struct uhid_device {
wait_queue_head_t report_wait; wait_queue_head_t report_wait;
bool report_running; bool report_running;
u32 report_id; u32 report_id;
u32 report_type;
struct uhid_event report_buf; struct uhid_event report_buf;
}; };
@ -124,50 +125,17 @@ static int uhid_hid_parse(struct hid_device *hid)
return hid_parse_report(hid, uhid->rd_data, uhid->rd_size); return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
} }
static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, /* must be called with report_lock held */
__u8 *buf, size_t count, unsigned char rtype) static int __uhid_report_queue_and_wait(struct uhid_device *uhid,
struct uhid_event *ev,
__u32 *report_id)
{ {
struct uhid_device *uhid = hid->driver_data;
__u8 report_type;
struct uhid_event *ev;
unsigned long flags; unsigned long flags;
int ret; int ret;
size_t uninitialized_var(len);
struct uhid_get_report_reply_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); spin_lock_irqsave(&uhid->qlock, flags);
ev->type = UHID_GET_REPORT; *report_id = ++uhid->report_id;
ev->u.get_report.id = ++uhid->report_id; uhid->report_type = ev->type;
ev->u.get_report.rnum = rnum;
ev->u.get_report.rtype = report_type;
uhid->report_running = true; uhid->report_running = true;
uhid_queue(uhid, ev); uhid_queue(uhid, ev);
spin_unlock_irqrestore(&uhid->qlock, flags); spin_unlock_irqrestore(&uhid->qlock, flags);
@ -175,44 +143,148 @@ static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
ret = wait_event_interruptible_timeout(uhid->report_wait, ret = wait_event_interruptible_timeout(uhid->report_wait,
!uhid->report_running || !uhid->running, !uhid->report_running || !uhid->running,
5 * HZ); 5 * HZ);
if (!ret || !uhid->running || uhid->report_running)
if (!ret || !uhid->running) {
ret = -EIO; ret = -EIO;
} else if (ret < 0) { else if (ret < 0)
ret = -ERESTARTSYS; ret = -ERESTARTSYS;
} else { else
spin_lock_irqsave(&uhid->qlock, flags);
req = &uhid->report_buf.u.get_report_reply;
if (req->err) {
ret = -EIO;
} else {
ret = 0; 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);
}
uhid->report_running = false; uhid->report_running = false;
return ret;
}
static void uhid_report_wake_up(struct uhid_device *uhid, u32 id,
const struct uhid_event *ev)
{
unsigned long flags;
spin_lock_irqsave(&uhid->qlock, flags);
/* id for old report; drop it silently */
if (uhid->report_type != ev->type || uhid->report_id != id)
goto unlock;
if (!uhid->report_running)
goto unlock;
memcpy(&uhid->report_buf, ev, sizeof(*ev));
uhid->report_running = false;
wake_up_interruptible(&uhid->report_wait);
unlock:
spin_unlock_irqrestore(&uhid->qlock, flags);
}
static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
u8 *buf, size_t count, u8 rtype)
{
struct uhid_device *uhid = hid->driver_data;
struct uhid_get_report_reply_req *req;
struct uhid_event *ev;
int ret;
if (!uhid->running)
return -EIO;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_GET_REPORT;
ev->u.get_report.rnum = rnum;
ev->u.get_report.rtype = rtype;
ret = mutex_lock_interruptible(&uhid->report_lock);
if (ret) {
kfree(ev);
return ret;
}
/* this _always_ takes ownership of @ev */
ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.get_report.id);
if (ret)
goto unlock;
req = &uhid->report_buf.u.get_report_reply;
if (req->err) {
ret = -EIO;
} else {
ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX);
memcpy(buf, req->data, ret);
}
unlock: unlock:
mutex_unlock(&uhid->report_lock); mutex_unlock(&uhid->report_lock);
return ret ? ret : len; return ret;
}
static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum,
const u8 *buf, size_t count, u8 rtype)
{
struct uhid_device *uhid = hid->driver_data;
struct uhid_event *ev;
int ret;
if (!uhid->running || count > UHID_DATA_MAX)
return -EIO;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_SET_REPORT;
ev->u.set_report.rnum = rnum;
ev->u.set_report.rtype = rtype;
ev->u.set_report.size = count;
memcpy(ev->u.set_report.data, buf, count);
ret = mutex_lock_interruptible(&uhid->report_lock);
if (ret) {
kfree(ev);
return ret;
}
/* this _always_ takes ownership of @ev */
ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.set_report.id);
if (ret)
goto unlock;
if (uhid->report_buf.u.set_report_reply.err)
ret = -EIO;
else
ret = count;
unlock:
mutex_unlock(&uhid->report_lock);
return ret;
} }
static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum, static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
__u8 *buf, size_t len, unsigned char rtype, __u8 *buf, size_t len, unsigned char rtype,
int reqtype) int reqtype)
{ {
u8 u_rtype;
switch (rtype) {
case HID_FEATURE_REPORT:
u_rtype = UHID_FEATURE_REPORT;
break;
case HID_OUTPUT_REPORT:
u_rtype = UHID_OUTPUT_REPORT;
break;
case HID_INPUT_REPORT:
u_rtype = UHID_INPUT_REPORT;
break;
default:
return -EINVAL;
}
switch (reqtype) { switch (reqtype) {
case HID_REQ_GET_REPORT: case HID_REQ_GET_REPORT:
return uhid_hid_get_report(hid, reportnum, buf, len, rtype); return uhid_hid_get_report(hid, reportnum, buf, len, u_rtype);
case HID_REQ_SET_REPORT: case HID_REQ_SET_REPORT:
/* TODO: implement proper SET_REPORT functionality */ return uhid_hid_set_report(hid, reportnum, buf, len, u_rtype);
return -ENOSYS;
default: default:
return -EIO; return -EIO;
} }
@ -490,25 +562,20 @@ static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev)
static int uhid_dev_get_report_reply(struct uhid_device *uhid, static int uhid_dev_get_report_reply(struct uhid_device *uhid,
struct uhid_event *ev) struct uhid_event *ev)
{ {
unsigned long flags;
if (!uhid->running) if (!uhid->running)
return -EINVAL; return -EINVAL;
spin_lock_irqsave(&uhid->qlock, flags); uhid_report_wake_up(uhid, ev->u.get_report_reply.id, ev);
return 0;
}
/* id for old report; drop it silently */ static int uhid_dev_set_report_reply(struct uhid_device *uhid,
if (uhid->report_id != ev->u.get_report_reply.id) struct uhid_event *ev)
goto unlock; {
if (!uhid->report_running) if (!uhid->running)
goto unlock; return -EINVAL;
memcpy(&uhid->report_buf, ev, sizeof(*ev)); uhid_report_wake_up(uhid, ev->u.set_report_reply.id, ev);
uhid->report_running = false;
wake_up_interruptible(&uhid->report_wait);
unlock:
spin_unlock_irqrestore(&uhid->qlock, flags);
return 0; return 0;
} }
@ -637,6 +704,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
case UHID_GET_REPORT_REPLY: case UHID_GET_REPORT_REPLY:
ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf); ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf);
break; break;
case UHID_SET_REPORT_REPLY:
ret = uhid_dev_set_report_reply(uhid, &uhid->input_buf);
break;
default: default:
ret = -EOPNOTSUPP; ret = -EOPNOTSUPP;
} }

View file

@ -37,6 +37,8 @@ enum uhid_event_type {
UHID_GET_REPORT_REPLY, UHID_GET_REPORT_REPLY,
UHID_CREATE2, UHID_CREATE2,
UHID_INPUT2, UHID_INPUT2,
UHID_SET_REPORT,
UHID_SET_REPORT_REPLY,
}; };
struct uhid_create2_req { struct uhid_create2_req {
@ -84,6 +86,19 @@ struct uhid_get_report_reply_req {
__u8 data[UHID_DATA_MAX]; __u8 data[UHID_DATA_MAX];
} __attribute__((__packed__)); } __attribute__((__packed__));
struct uhid_set_report_req {
__u32 id;
__u8 rnum;
__u8 rtype;
__u16 size;
__u8 data[UHID_DATA_MAX];
} __attribute__((__packed__));
struct uhid_set_report_reply_req {
__u32 id;
__u16 err;
} __attribute__((__packed__));
/* /*
* Compat Layer * Compat Layer
* All these commands and requests are obsolete. You should avoid using them in * All these commands and requests are obsolete. You should avoid using them in
@ -165,6 +180,8 @@ struct uhid_event {
struct uhid_get_report_reply_req get_report_reply; struct uhid_get_report_reply_req get_report_reply;
struct uhid_create2_req create2; struct uhid_create2_req create2;
struct uhid_input2_req input2; struct uhid_input2_req input2;
struct uhid_set_report_req set_report;
struct uhid_set_report_reply_req set_report_reply;
} u; } u;
} __attribute__((__packed__)); } __attribute__((__packed__));