Commit | Line | Data |
---|---|---|
e94cb37b RA |
1 | /* |
2 | * Copyright © 2015 Intel Corporation | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
21 | * IN THE SOFTWARE. | |
22 | * | |
23 | * Authors: | |
24 | * Rafael Antognolli <rafael.antognolli@intel.com> | |
25 | * | |
26 | */ | |
27 | ||
28 | #include <linux/device.h> | |
29 | #include <linux/fs.h> | |
30 | #include <linux/slab.h> | |
31 | #include <linux/init.h> | |
32 | #include <linux/kernel.h> | |
33 | #include <linux/module.h> | |
34 | #include <linux/uaccess.h> | |
35 | #include <drm/drm_dp_helper.h> | |
36 | #include <drm/drm_crtc.h> | |
37 | #include <drm/drmP.h> | |
38 | ||
39 | struct drm_dp_aux_dev { | |
40 | unsigned index; | |
41 | struct drm_dp_aux *aux; | |
42 | struct device *dev; | |
43 | struct kref refcount; | |
44 | atomic_t usecount; | |
45 | }; | |
46 | ||
47 | #define DRM_AUX_MINORS 256 | |
48 | #define AUX_MAX_OFFSET (1 << 20) | |
49 | static DEFINE_IDR(aux_idr); | |
50 | static DEFINE_MUTEX(aux_idr_mutex); | |
51 | static struct class *drm_dp_aux_dev_class; | |
52 | static int drm_dev_major = -1; | |
53 | ||
54 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) | |
55 | { | |
56 | struct drm_dp_aux_dev *aux_dev = NULL; | |
57 | ||
58 | mutex_lock(&aux_idr_mutex); | |
59 | aux_dev = idr_find(&aux_idr, index); | |
60 | if (!kref_get_unless_zero(&aux_dev->refcount)) | |
61 | aux_dev = NULL; | |
62 | mutex_unlock(&aux_idr_mutex); | |
63 | ||
64 | return aux_dev; | |
65 | } | |
66 | ||
67 | static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) | |
68 | { | |
69 | struct drm_dp_aux_dev *aux_dev; | |
70 | int index; | |
71 | ||
72 | aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); | |
73 | if (!aux_dev) | |
74 | return ERR_PTR(-ENOMEM); | |
75 | aux_dev->aux = aux; | |
76 | atomic_set(&aux_dev->usecount, 1); | |
77 | kref_init(&aux_dev->refcount); | |
78 | ||
79 | mutex_lock(&aux_idr_mutex); | |
80 | index = idr_alloc_cyclic(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, | |
81 | GFP_KERNEL); | |
82 | mutex_unlock(&aux_idr_mutex); | |
83 | if (index < 0) { | |
84 | kfree(aux_dev); | |
85 | return ERR_PTR(index); | |
86 | } | |
87 | aux_dev->index = index; | |
88 | ||
89 | return aux_dev; | |
90 | } | |
91 | ||
92 | static void release_drm_dp_aux_dev(struct kref *ref) | |
93 | { | |
94 | struct drm_dp_aux_dev *aux_dev = | |
95 | container_of(ref, struct drm_dp_aux_dev, refcount); | |
96 | ||
97 | kfree(aux_dev); | |
98 | } | |
99 | ||
100 | static ssize_t name_show(struct device *dev, | |
101 | struct device_attribute *attr, char *buf) | |
102 | { | |
103 | ssize_t res; | |
104 | struct drm_dp_aux_dev *aux_dev = | |
105 | drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); | |
106 | ||
107 | if (!aux_dev) | |
108 | return -ENODEV; | |
109 | ||
110 | res = sprintf(buf, "%s\n", aux_dev->aux->name); | |
111 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
112 | ||
113 | return res; | |
114 | } | |
115 | static DEVICE_ATTR_RO(name); | |
116 | ||
117 | static struct attribute *drm_dp_aux_attrs[] = { | |
118 | &dev_attr_name.attr, | |
119 | NULL, | |
120 | }; | |
121 | ATTRIBUTE_GROUPS(drm_dp_aux); | |
122 | ||
123 | static int auxdev_open(struct inode *inode, struct file *file) | |
124 | { | |
125 | unsigned int minor = iminor(inode); | |
126 | struct drm_dp_aux_dev *aux_dev; | |
127 | ||
128 | aux_dev = drm_dp_aux_dev_get_by_minor(minor); | |
129 | if (!aux_dev) | |
130 | return -ENODEV; | |
131 | ||
132 | file->private_data = aux_dev; | |
133 | return 0; | |
134 | } | |
135 | ||
136 | static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) | |
137 | { | |
138 | return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); | |
139 | } | |
140 | ||
141 | static ssize_t auxdev_read(struct file *file, char __user *buf, size_t count, | |
142 | loff_t *offset) | |
143 | { | |
144 | size_t bytes_pending, num_bytes_processed = 0; | |
145 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
146 | ssize_t res = 0; | |
147 | ||
148 | if (!atomic_inc_not_zero(&aux_dev->usecount)) | |
149 | return -ENODEV; | |
150 | ||
151 | bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - (*offset)); | |
152 | ||
153 | if (!access_ok(VERIFY_WRITE, buf, bytes_pending)) { | |
154 | res = -EFAULT; | |
155 | goto out; | |
156 | } | |
157 | ||
158 | while (bytes_pending > 0) { | |
159 | uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; | |
160 | ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); | |
161 | ||
162 | res = drm_dp_dpcd_read(aux_dev->aux, *offset, localbuf, todo); | |
163 | if (res <= 0) { | |
164 | res = num_bytes_processed ? num_bytes_processed : res; | |
165 | goto out; | |
166 | } | |
167 | if (__copy_to_user(buf + num_bytes_processed, localbuf, res)) { | |
168 | res = num_bytes_processed ? | |
169 | num_bytes_processed : -EFAULT; | |
170 | goto out; | |
171 | } | |
172 | bytes_pending -= res; | |
173 | *offset += res; | |
174 | num_bytes_processed += res; | |
175 | res = num_bytes_processed; | |
176 | } | |
177 | ||
178 | out: | |
179 | atomic_dec(&aux_dev->usecount); | |
180 | wake_up_atomic_t(&aux_dev->usecount); | |
181 | return res; | |
182 | } | |
183 | ||
184 | static ssize_t auxdev_write(struct file *file, const char __user *buf, | |
185 | size_t count, loff_t *offset) | |
186 | { | |
187 | size_t bytes_pending, num_bytes_processed = 0; | |
188 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
189 | ssize_t res = 0; | |
190 | ||
191 | if (!atomic_inc_not_zero(&aux_dev->usecount)) | |
192 | return -ENODEV; | |
193 | ||
194 | bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - *offset); | |
195 | ||
196 | if (!access_ok(VERIFY_READ, buf, bytes_pending)) { | |
197 | res = -EFAULT; | |
198 | goto out; | |
199 | } | |
200 | ||
201 | while (bytes_pending > 0) { | |
202 | uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; | |
203 | ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); | |
204 | ||
205 | if (__copy_from_user(localbuf, | |
206 | buf + num_bytes_processed, todo)) { | |
207 | res = num_bytes_processed ? | |
208 | num_bytes_processed : -EFAULT; | |
209 | goto out; | |
210 | } | |
211 | ||
212 | res = drm_dp_dpcd_write(aux_dev->aux, *offset, localbuf, todo); | |
213 | if (res <= 0) { | |
214 | res = num_bytes_processed ? num_bytes_processed : res; | |
215 | goto out; | |
216 | } | |
217 | bytes_pending -= res; | |
218 | *offset += res; | |
219 | num_bytes_processed += res; | |
220 | res = num_bytes_processed; | |
221 | } | |
222 | ||
223 | out: | |
224 | atomic_dec(&aux_dev->usecount); | |
225 | wake_up_atomic_t(&aux_dev->usecount); | |
226 | return res; | |
227 | } | |
228 | ||
229 | static int auxdev_release(struct inode *inode, struct file *file) | |
230 | { | |
231 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
232 | ||
233 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
234 | return 0; | |
235 | } | |
236 | ||
237 | static const struct file_operations auxdev_fops = { | |
238 | .owner = THIS_MODULE, | |
239 | .llseek = auxdev_llseek, | |
240 | .read = auxdev_read, | |
241 | .write = auxdev_write, | |
242 | .open = auxdev_open, | |
243 | .release = auxdev_release, | |
244 | }; | |
245 | ||
246 | #define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) | |
247 | ||
248 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) | |
249 | { | |
250 | struct drm_dp_aux_dev *iter, *aux_dev = NULL; | |
251 | int id; | |
252 | ||
253 | /* don't increase kref count here because this function should only be | |
254 | * used by drm_dp_aux_unregister_devnode. Thus, it will always have at | |
255 | * least one reference - the one that drm_dp_aux_register_devnode | |
256 | * created | |
257 | */ | |
258 | mutex_lock(&aux_idr_mutex); | |
259 | idr_for_each_entry(&aux_idr, iter, id) { | |
260 | if (iter->aux == aux) { | |
261 | aux_dev = iter; | |
262 | break; | |
263 | } | |
264 | } | |
265 | mutex_unlock(&aux_idr_mutex); | |
266 | return aux_dev; | |
267 | } | |
268 | ||
269 | static int auxdev_wait_atomic_t(atomic_t *p) | |
270 | { | |
271 | schedule(); | |
272 | return 0; | |
273 | } | |
274 | /** | |
275 | * drm_dp_aux_unregister_devnode() - unregister a devnode for this aux channel | |
276 | * @aux: DisplayPort AUX channel | |
277 | * | |
278 | * Returns 0 on success or a negative error code on failure. | |
279 | */ | |
280 | void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) | |
281 | { | |
282 | struct drm_dp_aux_dev *aux_dev; | |
283 | unsigned int minor; | |
284 | ||
285 | aux_dev = drm_dp_aux_dev_get_by_aux(aux); | |
286 | if (!aux_dev) /* attach must have failed */ | |
287 | return; | |
288 | ||
289 | mutex_lock(&aux_idr_mutex); | |
290 | idr_remove(&aux_idr, aux_dev->index); | |
291 | mutex_unlock(&aux_idr_mutex); | |
292 | ||
293 | atomic_dec(&aux_dev->usecount); | |
294 | wait_on_atomic_t(&aux_dev->usecount, auxdev_wait_atomic_t, | |
295 | TASK_UNINTERRUPTIBLE); | |
296 | ||
297 | minor = aux_dev->index; | |
298 | if (aux_dev->dev) | |
299 | device_destroy(drm_dp_aux_dev_class, | |
300 | MKDEV(drm_dev_major, minor)); | |
301 | ||
302 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); | |
303 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
304 | } | |
305 | EXPORT_SYMBOL(drm_dp_aux_unregister_devnode); | |
306 | ||
307 | /** | |
308 | * drm_dp_aux_register_devnode() - register a devnode for this aux channel | |
309 | * @aux: DisplayPort AUX channel | |
310 | * | |
311 | * Returns 0 on success or a negative error code on failure. | |
312 | */ | |
313 | int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) | |
314 | { | |
315 | struct drm_dp_aux_dev *aux_dev; | |
316 | int res; | |
317 | ||
318 | aux_dev = alloc_drm_dp_aux_dev(aux); | |
319 | if (IS_ERR(aux_dev)) | |
320 | return PTR_ERR(aux_dev); | |
321 | ||
322 | aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, | |
323 | MKDEV(drm_dev_major, aux_dev->index), NULL, | |
324 | "drm_dp_aux%d", aux_dev->index); | |
325 | if (IS_ERR(aux_dev->dev)) { | |
326 | res = PTR_ERR(aux_dev->dev); | |
327 | aux_dev->dev = NULL; | |
328 | goto error; | |
329 | } | |
330 | ||
331 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", | |
332 | aux->name, aux_dev->index); | |
333 | return 0; | |
334 | error: | |
335 | drm_dp_aux_unregister_devnode(aux); | |
336 | return res; | |
337 | } | |
338 | EXPORT_SYMBOL(drm_dp_aux_register_devnode); | |
339 | ||
340 | int drm_dp_aux_dev_init(void) | |
341 | { | |
342 | int res; | |
343 | ||
344 | drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); | |
345 | if (IS_ERR(drm_dp_aux_dev_class)) { | |
346 | res = PTR_ERR(drm_dp_aux_dev_class); | |
347 | goto out; | |
348 | } | |
349 | drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; | |
350 | ||
351 | res = register_chrdev(0, "aux", &auxdev_fops); | |
352 | if (res < 0) | |
353 | goto out; | |
354 | drm_dev_major = res; | |
355 | ||
356 | return 0; | |
357 | out: | |
358 | class_destroy(drm_dp_aux_dev_class); | |
359 | return res; | |
360 | } | |
361 | EXPORT_SYMBOL(drm_dp_aux_dev_init); | |
362 | ||
363 | void drm_dp_aux_dev_exit(void) | |
364 | { | |
365 | unregister_chrdev(drm_dev_major, "aux"); | |
366 | class_destroy(drm_dp_aux_dev_class); | |
367 | } | |
368 | EXPORT_SYMBOL(drm_dp_aux_dev_exit); |