diff options
| author | David Herrmann <dh.herrmann@gmail.com> | 2014-07-29 11:14:24 -0400 |
|---|---|---|
| committer | Jiri Kosina <jkosina@suse.cz> | 2014-08-25 04:28:08 -0400 |
| commit | 11c221553080408b203a00b91ad5f647dfb218d1 (patch) | |
| tree | ba4aa924ec09d7775ba887fbdaeaee45cff191b0 /drivers/hid | |
| parent | 7c4003bc367d5ff1cbce579a883f17698a9a6da2 (diff) | |
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>
Diffstat (limited to 'drivers/hid')
| -rw-r--r-- | drivers/hid/uhid.c | 206 |
1 files changed, 138 insertions, 68 deletions
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 8bf613e3783d..19511481a7d3 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c | |||
| @@ -49,6 +49,7 @@ struct uhid_device { | |||
| 49 | wait_queue_head_t report_wait; | 49 | wait_queue_head_t report_wait; |
| 50 | bool report_running; | 50 | bool report_running; |
| 51 | u32 report_id; | 51 | u32 report_id; |
| 52 | u32 report_type; | ||
| 52 | struct uhid_event report_buf; | 53 | struct uhid_event report_buf; |
| 53 | }; | 54 | }; |
| 54 | 55 | ||
| @@ -124,95 +125,166 @@ static int uhid_hid_parse(struct hid_device *hid) | |||
| 124 | return hid_parse_report(hid, uhid->rd_data, uhid->rd_size); | 125 | return hid_parse_report(hid, uhid->rd_data, uhid->rd_size); |
| 125 | } | 126 | } |
| 126 | 127 | ||
| 128 | /* must be called with report_lock held */ | ||
| 129 | static int __uhid_report_queue_and_wait(struct uhid_device *uhid, | ||
| 130 | struct uhid_event *ev, | ||
| 131 | __u32 *report_id) | ||
| 132 | { | ||
| 133 | unsigned long flags; | ||
| 134 | int ret; | ||
| 135 | |||
| 136 | spin_lock_irqsave(&uhid->qlock, flags); | ||
| 137 | *report_id = ++uhid->report_id; | ||
| 138 | uhid->report_type = ev->type; | ||
| 139 | uhid->report_running = true; | ||
| 140 | uhid_queue(uhid, ev); | ||
| 141 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
| 142 | |||
| 143 | ret = wait_event_interruptible_timeout(uhid->report_wait, | ||
| 144 | !uhid->report_running || !uhid->running, | ||
| 145 | 5 * HZ); | ||
| 146 | if (!ret || !uhid->running || uhid->report_running) | ||
| 147 | ret = -EIO; | ||
| 148 | else if (ret < 0) | ||
| 149 | ret = -ERESTARTSYS; | ||
| 150 | else | ||
| 151 | ret = 0; | ||
| 152 | |||
| 153 | uhid->report_running = false; | ||
| 154 | |||
| 155 | return ret; | ||
| 156 | } | ||
| 157 | |||
| 158 | static void uhid_report_wake_up(struct uhid_device *uhid, u32 id, | ||
| 159 | const struct uhid_event *ev) | ||
| 160 | { | ||
| 161 | unsigned long flags; | ||
| 162 | |||
| 163 | spin_lock_irqsave(&uhid->qlock, flags); | ||
| 164 | |||
| 165 | /* id for old report; drop it silently */ | ||
| 166 | if (uhid->report_type != ev->type || uhid->report_id != id) | ||
| 167 | goto unlock; | ||
| 168 | if (!uhid->report_running) | ||
| 169 | goto unlock; | ||
| 170 | |||
| 171 | memcpy(&uhid->report_buf, ev, sizeof(*ev)); | ||
| 172 | uhid->report_running = false; | ||
| 173 | wake_up_interruptible(&uhid->report_wait); | ||
| 174 | |||
| 175 | unlock: | ||
| 176 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
| 177 | } | ||
| 178 | |||
| 127 | static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, | 179 | static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum, |
| 128 | __u8 *buf, size_t count, unsigned char rtype) | 180 | u8 *buf, size_t count, u8 rtype) |
| 129 | { | 181 | { |
| 130 | struct uhid_device *uhid = hid->driver_data; | 182 | struct uhid_device *uhid = hid->driver_data; |
| 131 | __u8 report_type; | 183 | struct uhid_get_report_reply_req *req; |
| 132 | struct uhid_event *ev; | 184 | struct uhid_event *ev; |
| 133 | unsigned long flags; | ||
| 134 | int ret; | 185 | int ret; |
| 135 | size_t uninitialized_var(len); | ||
| 136 | struct uhid_get_report_reply_req *req; | ||
| 137 | 186 | ||
| 138 | if (!uhid->running) | 187 | if (!uhid->running) |
| 139 | return -EIO; | 188 | return -EIO; |
| 140 | 189 | ||
| 141 | switch (rtype) { | 190 | ev = kzalloc(sizeof(*ev), GFP_KERNEL); |
| 142 | case HID_FEATURE_REPORT: | 191 | if (!ev) |
| 143 | report_type = UHID_FEATURE_REPORT; | 192 | return -ENOMEM; |
| 144 | break; | 193 | |
| 145 | case HID_OUTPUT_REPORT: | 194 | ev->type = UHID_GET_REPORT; |
| 146 | report_type = UHID_OUTPUT_REPORT; | 195 | ev->u.get_report.rnum = rnum; |
| 147 | break; | 196 | ev->u.get_report.rtype = rtype; |
| 148 | case HID_INPUT_REPORT: | ||
| 149 | report_type = UHID_INPUT_REPORT; | ||
| 150 | break; | ||
| 151 | default: | ||
| 152 | return -EINVAL; | ||
| 153 | } | ||
| 154 | 197 | ||
| 155 | ret = mutex_lock_interruptible(&uhid->report_lock); | 198 | ret = mutex_lock_interruptible(&uhid->report_lock); |
| 156 | if (ret) | 199 | if (ret) { |
| 200 | kfree(ev); | ||
| 157 | return ret; | 201 | return ret; |
| 202 | } | ||
| 158 | 203 | ||
| 159 | ev = kzalloc(sizeof(*ev), GFP_KERNEL); | 204 | /* this _always_ takes ownership of @ev */ |
| 160 | if (!ev) { | 205 | ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.get_report.id); |
| 161 | ret = -ENOMEM; | 206 | if (ret) |
| 162 | goto unlock; | 207 | goto unlock; |
| 208 | |||
| 209 | req = &uhid->report_buf.u.get_report_reply; | ||
| 210 | if (req->err) { | ||
| 211 | ret = -EIO; | ||
| 212 | } else { | ||
| 213 | ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX); | ||
| 214 | memcpy(buf, req->data, ret); | ||
| 163 | } | 215 | } |
| 164 | 216 | ||
| 165 | spin_lock_irqsave(&uhid->qlock, flags); | 217 | unlock: |
| 166 | ev->type = UHID_GET_REPORT; | 218 | mutex_unlock(&uhid->report_lock); |
| 167 | ev->u.get_report.id = ++uhid->report_id; | 219 | return ret; |
| 168 | ev->u.get_report.rnum = rnum; | 220 | } |
| 169 | ev->u.get_report.rtype = report_type; | ||
| 170 | 221 | ||
| 171 | uhid->report_running = true; | 222 | static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum, |
| 172 | uhid_queue(uhid, ev); | 223 | const u8 *buf, size_t count, u8 rtype) |
| 173 | spin_unlock_irqrestore(&uhid->qlock, flags); | 224 | { |
| 225 | struct uhid_device *uhid = hid->driver_data; | ||
| 226 | struct uhid_event *ev; | ||
| 227 | int ret; | ||
| 174 | 228 | ||
| 175 | ret = wait_event_interruptible_timeout(uhid->report_wait, | 229 | if (!uhid->running || count > UHID_DATA_MAX) |
| 176 | !uhid->report_running || !uhid->running, | 230 | return -EIO; |
| 177 | 5 * HZ); | ||
| 178 | 231 | ||
| 179 | if (!ret || !uhid->running) { | 232 | ev = kzalloc(sizeof(*ev), GFP_KERNEL); |
| 180 | ret = -EIO; | 233 | if (!ev) |
| 181 | } else if (ret < 0) { | 234 | return -ENOMEM; |
| 182 | ret = -ERESTARTSYS; | ||
| 183 | } else { | ||
| 184 | spin_lock_irqsave(&uhid->qlock, flags); | ||
| 185 | req = &uhid->report_buf.u.get_report_reply; | ||
| 186 | 235 | ||
| 187 | if (req->err) { | 236 | ev->type = UHID_SET_REPORT; |
| 188 | ret = -EIO; | 237 | ev->u.set_report.rnum = rnum; |
| 189 | } else { | 238 | ev->u.set_report.rtype = rtype; |
| 190 | ret = 0; | 239 | ev->u.set_report.size = count; |
| 191 | len = min(count, | 240 | memcpy(ev->u.set_report.data, buf, count); |
| 192 | min_t(size_t, req->size, UHID_DATA_MAX)); | ||
| 193 | memcpy(buf, req->data, len); | ||
| 194 | } | ||
| 195 | 241 | ||
| 196 | spin_unlock_irqrestore(&uhid->qlock, flags); | 242 | ret = mutex_lock_interruptible(&uhid->report_lock); |
| 243 | if (ret) { | ||
| 244 | kfree(ev); | ||
| 245 | return ret; | ||
| 197 | } | 246 | } |
| 198 | 247 | ||
| 199 | uhid->report_running = false; | 248 | /* this _always_ takes ownership of @ev */ |
| 249 | ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.set_report.id); | ||
| 250 | if (ret) | ||
| 251 | goto unlock; | ||
| 252 | |||
| 253 | if (uhid->report_buf.u.set_report_reply.err) | ||
| 254 | ret = -EIO; | ||
| 255 | else | ||
| 256 | ret = count; | ||
| 200 | 257 | ||
| 201 | unlock: | 258 | unlock: |
| 202 | mutex_unlock(&uhid->report_lock); | 259 | mutex_unlock(&uhid->report_lock); |
| 203 | return ret ? ret : len; | 260 | return ret; |
| 204 | } | 261 | } |
| 205 | 262 | ||
| 206 | static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum, | 263 | static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum, |
| 207 | __u8 *buf, size_t len, unsigned char rtype, | 264 | __u8 *buf, size_t len, unsigned char rtype, |
| 208 | int reqtype) | 265 | int reqtype) |
| 209 | { | 266 | { |
| 267 | u8 u_rtype; | ||
| 268 | |||
| 269 | switch (rtype) { | ||
| 270 | case HID_FEATURE_REPORT: | ||
| 271 | u_rtype = UHID_FEATURE_REPORT; | ||
| 272 | break; | ||
| 273 | case HID_OUTPUT_REPORT: | ||
| 274 | u_rtype = UHID_OUTPUT_REPORT; | ||
| 275 | break; | ||
| 276 | case HID_INPUT_REPORT: | ||
| 277 | u_rtype = UHID_INPUT_REPORT; | ||
| 278 | break; | ||
| 279 | default: | ||
| 280 | return -EINVAL; | ||
| 281 | } | ||
| 282 | |||
| 210 | switch (reqtype) { | 283 | switch (reqtype) { |
| 211 | case HID_REQ_GET_REPORT: | 284 | case HID_REQ_GET_REPORT: |
| 212 | return uhid_hid_get_report(hid, reportnum, buf, len, rtype); | 285 | return uhid_hid_get_report(hid, reportnum, buf, len, u_rtype); |
| 213 | case HID_REQ_SET_REPORT: | 286 | case HID_REQ_SET_REPORT: |
| 214 | /* TODO: implement proper SET_REPORT functionality */ | 287 | return uhid_hid_set_report(hid, reportnum, buf, len, u_rtype); |
| 215 | return -ENOSYS; | ||
| 216 | default: | 288 | default: |
| 217 | return -EIO; | 289 | return -EIO; |
| 218 | } | 290 | } |
| @@ -490,25 +562,20 @@ static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev) | |||
| 490 | static int uhid_dev_get_report_reply(struct uhid_device *uhid, | 562 | static int uhid_dev_get_report_reply(struct uhid_device *uhid, |
| 491 | struct uhid_event *ev) | 563 | struct uhid_event *ev) |
| 492 | { | 564 | { |
| 493 | unsigned long flags; | ||
| 494 | |||
| 495 | if (!uhid->running) | 565 | if (!uhid->running) |
| 496 | return -EINVAL; | 566 | return -EINVAL; |
| 497 | 567 | ||
| 498 | spin_lock_irqsave(&uhid->qlock, flags); | 568 | uhid_report_wake_up(uhid, ev->u.get_report_reply.id, ev); |
| 499 | 569 | return 0; | |
| 500 | /* id for old report; drop it silently */ | 570 | } |
| 501 | if (uhid->report_id != ev->u.get_report_reply.id) | ||
| 502 | goto unlock; | ||
| 503 | if (!uhid->report_running) | ||
| 504 | goto unlock; | ||
| 505 | 571 | ||
| 506 | memcpy(&uhid->report_buf, ev, sizeof(*ev)); | 572 | static int uhid_dev_set_report_reply(struct uhid_device *uhid, |
| 507 | uhid->report_running = false; | 573 | struct uhid_event *ev) |
| 508 | wake_up_interruptible(&uhid->report_wait); | 574 | { |
| 575 | if (!uhid->running) | ||
| 576 | return -EINVAL; | ||
| 509 | 577 | ||
| 510 | unlock: | 578 | uhid_report_wake_up(uhid, ev->u.set_report_reply.id, ev); |
| 511 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
| 512 | return 0; | 579 | return 0; |
| 513 | } | 580 | } |
| 514 | 581 | ||
| @@ -637,6 +704,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer, | |||
| 637 | case UHID_GET_REPORT_REPLY: | 704 | case UHID_GET_REPORT_REPLY: |
| 638 | ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf); | 705 | ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf); |
| 639 | break; | 706 | break; |
| 707 | case UHID_SET_REPORT_REPLY: | ||
| 708 | ret = uhid_dev_set_report_reply(uhid, &uhid->input_buf); | ||
| 709 | break; | ||
| 640 | default: | 710 | default: |
| 641 | ret = -EOPNOTSUPP; | 711 | ret = -EOPNOTSUPP; |
| 642 | } | 712 | } |
