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 | |
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>
-rw-r--r-- | drivers/hid/uhid.c | 206 | ||||
-rw-r--r-- | include/uapi/linux/uhid.h | 17 |
2 files changed, 155 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 | } |
diff --git a/include/uapi/linux/uhid.h b/include/uapi/linux/uhid.h index 116536eeae62..62aac0e4edf3 100644 --- a/include/uapi/linux/uhid.h +++ b/include/uapi/linux/uhid.h | |||
@@ -37,6 +37,8 @@ enum uhid_event_type { | |||
37 | UHID_GET_REPORT_REPLY, | 37 | UHID_GET_REPORT_REPLY, |
38 | UHID_CREATE2, | 38 | UHID_CREATE2, |
39 | UHID_INPUT2, | 39 | UHID_INPUT2, |
40 | UHID_SET_REPORT, | ||
41 | UHID_SET_REPORT_REPLY, | ||
40 | }; | 42 | }; |
41 | 43 | ||
42 | struct uhid_create2_req { | 44 | struct uhid_create2_req { |
@@ -84,6 +86,19 @@ struct uhid_get_report_reply_req { | |||
84 | __u8 data[UHID_DATA_MAX]; | 86 | __u8 data[UHID_DATA_MAX]; |
85 | } __attribute__((__packed__)); | 87 | } __attribute__((__packed__)); |
86 | 88 | ||
89 | struct uhid_set_report_req { | ||
90 | __u32 id; | ||
91 | __u8 rnum; | ||
92 | __u8 rtype; | ||
93 | __u16 size; | ||
94 | __u8 data[UHID_DATA_MAX]; | ||
95 | } __attribute__((__packed__)); | ||
96 | |||
97 | struct uhid_set_report_reply_req { | ||
98 | __u32 id; | ||
99 | __u16 err; | ||
100 | } __attribute__((__packed__)); | ||
101 | |||
87 | /* | 102 | /* |
88 | * Compat Layer | 103 | * Compat Layer |
89 | * All these commands and requests are obsolete. You should avoid using them in | 104 | * All these commands and requests are obsolete. You should avoid using them in |
@@ -165,6 +180,8 @@ struct uhid_event { | |||
165 | struct uhid_get_report_reply_req get_report_reply; | 180 | struct uhid_get_report_reply_req get_report_reply; |
166 | struct uhid_create2_req create2; | 181 | struct uhid_create2_req create2; |
167 | struct uhid_input2_req input2; | 182 | struct uhid_input2_req input2; |
183 | struct uhid_set_report_req set_report; | ||
184 | struct uhid_set_report_reply_req set_report_reply; | ||
168 | } u; | 185 | } u; |
169 | } __attribute__((__packed__)); | 186 | } __attribute__((__packed__)); |
170 | 187 | ||