diff options
author | David Herrmann <dh.herrmann@googlemail.com> | 2012-06-10 09:16:16 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2012-06-18 07:42:00 -0400 |
commit | d937ae5fae17e63aaa97f029be221a6516b25475 (patch) | |
tree | 2ef5c99a73fe3d9c9ef2e85bdf72d0807b170d5b /drivers/hid | |
parent | 1f9dec1e0164b48da9b268a02197f38caa69b118 (diff) |
HID: uhid: implement read() on uhid devices
User-space can use read() to get a single event from uhid devices. read()
does never return multiple events. This allows us to extend the event
structure and still keep backwards compatibility.
If user-space wants to get multiple events in one syscall, they should use
the readv()/writev() syscalls which are supported by uhid.
This introduces a new lock which helps us synchronizing simultaneous reads
from user-space. We also correctly return -EINVAL/-EFAULT only on errors
and retry the read() when some other thread captured the event faster than
we did.
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 | 46 |
1 files changed, 45 insertions, 1 deletions
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index b1a477f8260c..93860826d629 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #define UHID_BUFSIZE 32 | 28 | #define UHID_BUFSIZE 32 |
29 | 29 | ||
30 | struct uhid_device { | 30 | struct uhid_device { |
31 | struct mutex devlock; | ||
31 | struct hid_device *hid; | 32 | struct hid_device *hid; |
32 | 33 | ||
33 | wait_queue_head_t waitq; | 34 | wait_queue_head_t waitq; |
@@ -81,6 +82,7 @@ static int uhid_char_open(struct inode *inode, struct file *file) | |||
81 | if (!uhid) | 82 | if (!uhid) |
82 | return -ENOMEM; | 83 | return -ENOMEM; |
83 | 84 | ||
85 | mutex_init(&uhid->devlock); | ||
84 | spin_lock_init(&uhid->qlock); | 86 | spin_lock_init(&uhid->qlock); |
85 | init_waitqueue_head(&uhid->waitq); | 87 | init_waitqueue_head(&uhid->waitq); |
86 | 88 | ||
@@ -106,7 +108,49 @@ static int uhid_char_release(struct inode *inode, struct file *file) | |||
106 | static ssize_t uhid_char_read(struct file *file, char __user *buffer, | 108 | static ssize_t uhid_char_read(struct file *file, char __user *buffer, |
107 | size_t count, loff_t *ppos) | 109 | size_t count, loff_t *ppos) |
108 | { | 110 | { |
109 | return 0; | 111 | struct uhid_device *uhid = file->private_data; |
112 | int ret; | ||
113 | unsigned long flags; | ||
114 | size_t len; | ||
115 | |||
116 | /* they need at least the "type" member of uhid_event */ | ||
117 | if (count < sizeof(__u32)) | ||
118 | return -EINVAL; | ||
119 | |||
120 | try_again: | ||
121 | if (file->f_flags & O_NONBLOCK) { | ||
122 | if (uhid->head == uhid->tail) | ||
123 | return -EAGAIN; | ||
124 | } else { | ||
125 | ret = wait_event_interruptible(uhid->waitq, | ||
126 | uhid->head != uhid->tail); | ||
127 | if (ret) | ||
128 | return ret; | ||
129 | } | ||
130 | |||
131 | ret = mutex_lock_interruptible(&uhid->devlock); | ||
132 | if (ret) | ||
133 | return ret; | ||
134 | |||
135 | if (uhid->head == uhid->tail) { | ||
136 | mutex_unlock(&uhid->devlock); | ||
137 | goto try_again; | ||
138 | } else { | ||
139 | len = min(count, sizeof(**uhid->outq)); | ||
140 | if (copy_to_user(buffer, &uhid->outq[uhid->tail], len)) { | ||
141 | ret = -EFAULT; | ||
142 | } else { | ||
143 | kfree(uhid->outq[uhid->tail]); | ||
144 | uhid->outq[uhid->tail] = NULL; | ||
145 | |||
146 | spin_lock_irqsave(&uhid->qlock, flags); | ||
147 | uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE; | ||
148 | spin_unlock_irqrestore(&uhid->qlock, flags); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | mutex_unlock(&uhid->devlock); | ||
153 | return ret ? ret : len; | ||
110 | } | 154 | } |
111 | 155 | ||
112 | static ssize_t uhid_char_write(struct file *file, const char __user *buffer, | 156 | static ssize_t uhid_char_write(struct file *file, const char __user *buffer, |