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 | ||
36230cb5 VS |
162 | if (signal_pending(current)) { |
163 | res = num_bytes_processed ? | |
164 | num_bytes_processed : -ERESTARTSYS; | |
165 | goto out; | |
166 | } | |
167 | ||
e94cb37b RA |
168 | res = drm_dp_dpcd_read(aux_dev->aux, *offset, localbuf, todo); |
169 | if (res <= 0) { | |
170 | res = num_bytes_processed ? num_bytes_processed : res; | |
171 | goto out; | |
172 | } | |
173 | if (__copy_to_user(buf + num_bytes_processed, localbuf, res)) { | |
174 | res = num_bytes_processed ? | |
175 | num_bytes_processed : -EFAULT; | |
176 | goto out; | |
177 | } | |
178 | bytes_pending -= res; | |
179 | *offset += res; | |
180 | num_bytes_processed += res; | |
181 | res = num_bytes_processed; | |
182 | } | |
183 | ||
184 | out: | |
185 | atomic_dec(&aux_dev->usecount); | |
186 | wake_up_atomic_t(&aux_dev->usecount); | |
187 | return res; | |
188 | } | |
189 | ||
190 | static ssize_t auxdev_write(struct file *file, const char __user *buf, | |
191 | size_t count, loff_t *offset) | |
192 | { | |
193 | size_t bytes_pending, num_bytes_processed = 0; | |
194 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
195 | ssize_t res = 0; | |
196 | ||
197 | if (!atomic_inc_not_zero(&aux_dev->usecount)) | |
198 | return -ENODEV; | |
199 | ||
200 | bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - *offset); | |
201 | ||
202 | if (!access_ok(VERIFY_READ, buf, bytes_pending)) { | |
203 | res = -EFAULT; | |
204 | goto out; | |
205 | } | |
206 | ||
207 | while (bytes_pending > 0) { | |
208 | uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; | |
209 | ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); | |
210 | ||
36230cb5 VS |
211 | if (signal_pending(current)) { |
212 | res = num_bytes_processed ? | |
213 | num_bytes_processed : -ERESTARTSYS; | |
214 | goto out; | |
215 | } | |
216 | ||
e94cb37b RA |
217 | if (__copy_from_user(localbuf, |
218 | buf + num_bytes_processed, todo)) { | |
219 | res = num_bytes_processed ? | |
220 | num_bytes_processed : -EFAULT; | |
221 | goto out; | |
222 | } | |
223 | ||
224 | res = drm_dp_dpcd_write(aux_dev->aux, *offset, localbuf, todo); | |
225 | if (res <= 0) { | |
226 | res = num_bytes_processed ? num_bytes_processed : res; | |
227 | goto out; | |
228 | } | |
229 | bytes_pending -= res; | |
230 | *offset += res; | |
231 | num_bytes_processed += res; | |
232 | res = num_bytes_processed; | |
233 | } | |
234 | ||
235 | out: | |
236 | atomic_dec(&aux_dev->usecount); | |
237 | wake_up_atomic_t(&aux_dev->usecount); | |
238 | return res; | |
239 | } | |
240 | ||
241 | static int auxdev_release(struct inode *inode, struct file *file) | |
242 | { | |
243 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
244 | ||
245 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
246 | return 0; | |
247 | } | |
248 | ||
249 | static const struct file_operations auxdev_fops = { | |
250 | .owner = THIS_MODULE, | |
251 | .llseek = auxdev_llseek, | |
252 | .read = auxdev_read, | |
253 | .write = auxdev_write, | |
254 | .open = auxdev_open, | |
255 | .release = auxdev_release, | |
256 | }; | |
257 | ||
258 | #define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) | |
259 | ||
260 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) | |
261 | { | |
262 | struct drm_dp_aux_dev *iter, *aux_dev = NULL; | |
263 | int id; | |
264 | ||
265 | /* don't increase kref count here because this function should only be | |
266 | * used by drm_dp_aux_unregister_devnode. Thus, it will always have at | |
267 | * least one reference - the one that drm_dp_aux_register_devnode | |
268 | * created | |
269 | */ | |
270 | mutex_lock(&aux_idr_mutex); | |
271 | idr_for_each_entry(&aux_idr, iter, id) { | |
272 | if (iter->aux == aux) { | |
273 | aux_dev = iter; | |
274 | break; | |
275 | } | |
276 | } | |
277 | mutex_unlock(&aux_idr_mutex); | |
278 | return aux_dev; | |
279 | } | |
280 | ||
281 | static int auxdev_wait_atomic_t(atomic_t *p) | |
282 | { | |
283 | schedule(); | |
284 | return 0; | |
285 | } | |
286 | /** | |
287 | * drm_dp_aux_unregister_devnode() - unregister a devnode for this aux channel | |
288 | * @aux: DisplayPort AUX channel | |
289 | * | |
290 | * Returns 0 on success or a negative error code on failure. | |
291 | */ | |
292 | void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) | |
293 | { | |
294 | struct drm_dp_aux_dev *aux_dev; | |
295 | unsigned int minor; | |
296 | ||
297 | aux_dev = drm_dp_aux_dev_get_by_aux(aux); | |
298 | if (!aux_dev) /* attach must have failed */ | |
299 | return; | |
300 | ||
301 | mutex_lock(&aux_idr_mutex); | |
302 | idr_remove(&aux_idr, aux_dev->index); | |
303 | mutex_unlock(&aux_idr_mutex); | |
304 | ||
305 | atomic_dec(&aux_dev->usecount); | |
306 | wait_on_atomic_t(&aux_dev->usecount, auxdev_wait_atomic_t, | |
307 | TASK_UNINTERRUPTIBLE); | |
308 | ||
309 | minor = aux_dev->index; | |
310 | if (aux_dev->dev) | |
311 | device_destroy(drm_dp_aux_dev_class, | |
312 | MKDEV(drm_dev_major, minor)); | |
313 | ||
314 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); | |
315 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
316 | } | |
317 | EXPORT_SYMBOL(drm_dp_aux_unregister_devnode); | |
318 | ||
319 | /** | |
320 | * drm_dp_aux_register_devnode() - register a devnode for this aux channel | |
321 | * @aux: DisplayPort AUX channel | |
322 | * | |
323 | * Returns 0 on success or a negative error code on failure. | |
324 | */ | |
325 | int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) | |
326 | { | |
327 | struct drm_dp_aux_dev *aux_dev; | |
328 | int res; | |
329 | ||
330 | aux_dev = alloc_drm_dp_aux_dev(aux); | |
331 | if (IS_ERR(aux_dev)) | |
332 | return PTR_ERR(aux_dev); | |
333 | ||
334 | aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, | |
335 | MKDEV(drm_dev_major, aux_dev->index), NULL, | |
336 | "drm_dp_aux%d", aux_dev->index); | |
337 | if (IS_ERR(aux_dev->dev)) { | |
338 | res = PTR_ERR(aux_dev->dev); | |
339 | aux_dev->dev = NULL; | |
340 | goto error; | |
341 | } | |
342 | ||
343 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", | |
344 | aux->name, aux_dev->index); | |
345 | return 0; | |
346 | error: | |
347 | drm_dp_aux_unregister_devnode(aux); | |
348 | return res; | |
349 | } | |
350 | EXPORT_SYMBOL(drm_dp_aux_register_devnode); | |
351 | ||
352 | int drm_dp_aux_dev_init(void) | |
353 | { | |
354 | int res; | |
355 | ||
356 | drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); | |
357 | if (IS_ERR(drm_dp_aux_dev_class)) { | |
da82ee99 | 358 | return PTR_ERR(drm_dp_aux_dev_class); |
e94cb37b RA |
359 | } |
360 | drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; | |
361 | ||
362 | res = register_chrdev(0, "aux", &auxdev_fops); | |
363 | if (res < 0) | |
364 | goto out; | |
365 | drm_dev_major = res; | |
366 | ||
367 | return 0; | |
368 | out: | |
369 | class_destroy(drm_dp_aux_dev_class); | |
370 | return res; | |
371 | } | |
372 | EXPORT_SYMBOL(drm_dp_aux_dev_init); | |
373 | ||
374 | void drm_dp_aux_dev_exit(void) | |
375 | { | |
376 | unregister_chrdev(drm_dev_major, "aux"); | |
377 | class_destroy(drm_dp_aux_dev_class); | |
378 | } | |
379 | EXPORT_SYMBOL(drm_dp_aux_dev_exit); |