diff options
author | David Herrmann <dh.herrmann@googlemail.com> | 2012-06-10 09:16:25 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2012-06-18 07:42:03 -0400 |
commit | fcfcf0deb89ece6eb9ae23768fec1bc1718f9b7f (patch) | |
tree | fcf86b0c55d4feea91ee00a6e59c423b6442708a /drivers/hid | |
parent | 3b3baa82e4306b5160692643fab2fa322ceb94f9 (diff) |
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>
Diffstat (limited to 'drivers/hid')
-rw-r--r-- | drivers/hid/uhid.c | 120 |
1 files changed, 119 insertions, 1 deletions
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 421c492dc824..ea560bfa033d 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c | |||
@@ -42,6 +42,12 @@ struct uhid_device { | |||
42 | __u8 head; | 42 | __u8 head; |
43 | __u8 tail; | 43 | __u8 tail; |
44 | struct uhid_event *outq[UHID_BUFSIZE]; | 44 | struct uhid_event *outq[UHID_BUFSIZE]; |
45 | |||
46 | struct mutex report_lock; | ||
47 | wait_queue_head_t report_wait; | ||
48 | atomic_t report_done; | ||
49 | atomic_t report_id; | ||
50 | struct uhid_event report_buf; | ||
45 | }; | 51 | }; |
46 | 52 | ||
47 | static struct miscdevice uhid_misc; | 53 | static struct miscdevice uhid_misc; |
@@ -143,7 +149,84 @@ static int uhid_hid_parse(struct hid_device *hid) | |||
143 | static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, | 149 | static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, |
144 | __u8 *buf, size_t count, unsigned char rtype) | 150 | __u8 *buf, size_t count, unsigned char rtype) |
145 | { | 151 | { |
146 | return 0; | 152 | struct uhid_device *uhid = hid->driver_data; |
153 | __u8 report_type; | ||
154 | struct uhid_event *ev; | ||
155 | unsigned long flags; | ||
156 | int ret; | ||
157 | size_t len; | ||
158 | struct uhid_feature_answer_req *req; | ||
159 | |||
160 | if (!uhid->running) | ||
161 | return -EIO; | ||
162 | |||
163 | switch (rtype) { | ||
164 | case HID_FEATURE_REPORT: | ||
165 | report_type = UHID_FEATURE_REPORT; | ||
166 | break; | ||
167 | case HID_OUTPUT_REPORT: | ||
168 | report_type = UHID_OUTPUT_REPORT; | ||
169 | break; | ||
170 | case HID_INPUT_REPORT: | ||
171 | report_type = UHID_INPUT_REPORT; | ||
172 | break; | ||
173 | default: | ||
174 | return -EINVAL; | ||
175 | } | ||
176 | |||
177 | ret = mutex_lock_interruptible(&uhid->report_lock); | ||
178 | if (ret) | ||
179 | return ret; | ||
180 | |||
181 | ev = kzalloc(sizeof(*ev), GFP_KERNEL); | ||
182 | if (!ev) { | ||
183 | ret = -ENOMEM; | ||
184 | goto unlock; | ||
185 | } | ||
186 | |||
187 | spin_lock_irqsave(&uhid->qlock, flags); | ||
188 | ev->type = UHID_FEATURE; | ||
189 | ev->u.feature.id = atomic_inc_return(&uhid->report_id); | ||
190 | ev->u.feature.rnum = rnum; | ||
191 | ev->u.feature.rtype = report_type; | ||
192 | |||
193 | atomic_set(&uhid->report_done, 0); | ||
194 | uhid_queue(uhid, ev); | ||
195 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
196 | |||
197 | ret = wait_event_interruptible_timeout(uhid->report_wait, | ||
198 | atomic_read(&uhid->report_done), 5 * HZ); | ||
199 | |||
200 | /* | ||
201 | * Make sure "uhid->running" is cleared on shutdown before | ||
202 | * "uhid->report_done" is set. | ||
203 | */ | ||
204 | smp_rmb(); | ||
205 | if (!ret || !uhid->running) { | ||
206 | ret = -EIO; | ||
207 | } else if (ret < 0) { | ||
208 | ret = -ERESTARTSYS; | ||
209 | } else { | ||
210 | spin_lock_irqsave(&uhid->qlock, flags); | ||
211 | req = &uhid->report_buf.u.feature_answer; | ||
212 | |||
213 | if (req->err) { | ||
214 | ret = -EIO; | ||
215 | } else { | ||
216 | ret = 0; | ||
217 | len = min(count, | ||
218 | min_t(size_t, req->size, UHID_DATA_MAX)); | ||
219 | memcpy(buf, req->data, len); | ||
220 | } | ||
221 | |||
222 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
223 | } | ||
224 | |||
225 | atomic_set(&uhid->report_done, 1); | ||
226 | |||
227 | unlock: | ||
228 | mutex_unlock(&uhid->report_lock); | ||
229 | return ret ? ret : len; | ||
147 | } | 230 | } |
148 | 231 | ||
149 | static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, | 232 | 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 | if (!uhid->running) | 348 | if (!uhid->running) |
266 | return -EINVAL; | 349 | return -EINVAL; |
267 | 350 | ||
351 | /* clear "running" before setting "report_done" */ | ||
268 | uhid->running = false; | 352 | uhid->running = false; |
353 | smp_wmb(); | ||
354 | atomic_set(&uhid->report_done, 1); | ||
355 | wake_up_interruptible(&uhid->report_wait); | ||
269 | 356 | ||
270 | hid_destroy_device(uhid->hid); | 357 | hid_destroy_device(uhid->hid); |
271 | kfree(uhid->rd_data); | 358 | kfree(uhid->rd_data); |
@@ -284,6 +371,31 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev) | |||
284 | return 0; | 371 | return 0; |
285 | } | 372 | } |
286 | 373 | ||
374 | static int uhid_dev_feature_answer(struct uhid_device *uhid, | ||
375 | struct uhid_event *ev) | ||
376 | { | ||
377 | unsigned long flags; | ||
378 | |||
379 | if (!uhid->running) | ||
380 | return -EINVAL; | ||
381 | |||
382 | spin_lock_irqsave(&uhid->qlock, flags); | ||
383 | |||
384 | /* id for old report; drop it silently */ | ||
385 | if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id) | ||
386 | goto unlock; | ||
387 | if (atomic_read(&uhid->report_done)) | ||
388 | goto unlock; | ||
389 | |||
390 | memcpy(&uhid->report_buf, ev, sizeof(*ev)); | ||
391 | atomic_set(&uhid->report_done, 1); | ||
392 | wake_up_interruptible(&uhid->report_wait); | ||
393 | |||
394 | unlock: | ||
395 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
396 | return 0; | ||
397 | } | ||
398 | |||
287 | static int uhid_char_open(struct inode *inode, struct file *file) | 399 | static int uhid_char_open(struct inode *inode, struct file *file) |
288 | { | 400 | { |
289 | struct uhid_device *uhid; | 401 | struct uhid_device *uhid; |
@@ -293,9 +405,12 @@ static int uhid_char_open(struct inode *inode, struct file *file) | |||
293 | return -ENOMEM; | 405 | return -ENOMEM; |
294 | 406 | ||
295 | mutex_init(&uhid->devlock); | 407 | mutex_init(&uhid->devlock); |
408 | mutex_init(&uhid->report_lock); | ||
296 | spin_lock_init(&uhid->qlock); | 409 | spin_lock_init(&uhid->qlock); |
297 | init_waitqueue_head(&uhid->waitq); | 410 | init_waitqueue_head(&uhid->waitq); |
411 | init_waitqueue_head(&uhid->report_wait); | ||
298 | uhid->running = false; | 412 | uhid->running = false; |
413 | atomic_set(&uhid->report_done, 1); | ||
299 | 414 | ||
300 | file->private_data = uhid; | 415 | file->private_data = uhid; |
301 | nonseekable_open(inode, file); | 416 | nonseekable_open(inode, file); |
@@ -398,6 +513,9 @@ static ssize_t uhid_char_write(struct file *file, const char __user *buffer, | |||
398 | case UHID_INPUT: | 513 | case UHID_INPUT: |
399 | ret = uhid_dev_input(uhid, &uhid->input_buf); | 514 | ret = uhid_dev_input(uhid, &uhid->input_buf); |
400 | break; | 515 | break; |
516 | case UHID_FEATURE_ANSWER: | ||
517 | ret = uhid_dev_feature_answer(uhid, &uhid->input_buf); | ||
518 | break; | ||
401 | default: | 519 | default: |
402 | ret = -EOPNOTSUPP; | 520 | ret = -EOPNOTSUPP; |
403 | } | 521 | } |