aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@googlemail.com>2012-06-10 09:16:25 -0400
committerJiri Kosina <jkosina@suse.cz>2012-06-18 07:42:03 -0400
commitfcfcf0deb89ece6eb9ae23768fec1bc1718f9b7f (patch)
treefcf86b0c55d4feea91ee00a6e59c423b6442708a /drivers/hid
parent3b3baa82e4306b5160692643fab2fa322ceb94f9 (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.c120
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
47static struct miscdevice uhid_misc; 53static struct miscdevice uhid_misc;
@@ -143,7 +149,84 @@ static int uhid_hid_parse(struct hid_device *hid)
143static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, 149static 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
227unlock:
228 mutex_unlock(&uhid->report_lock);
229 return ret ? ret : len;
147} 230}
148 231
149static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count, 232static 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
374static 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
394unlock:
395 spin_unlock_irqrestore(&uhid->qlock, flags);
396 return 0;
397}
398
287static int uhid_char_open(struct inode *inode, struct file *file) 399static 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 }