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 | ||
e15c8f4b DV |
39 | #include "drm_crtc_helper_internal.h" |
40 | ||
e94cb37b RA |
41 | struct drm_dp_aux_dev { |
42 | unsigned index; | |
43 | struct drm_dp_aux *aux; | |
44 | struct device *dev; | |
45 | struct kref refcount; | |
46 | atomic_t usecount; | |
47 | }; | |
48 | ||
49 | #define DRM_AUX_MINORS 256 | |
50 | #define AUX_MAX_OFFSET (1 << 20) | |
51 | static DEFINE_IDR(aux_idr); | |
52 | static DEFINE_MUTEX(aux_idr_mutex); | |
53 | static struct class *drm_dp_aux_dev_class; | |
54 | static int drm_dev_major = -1; | |
55 | ||
56 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) | |
57 | { | |
58 | struct drm_dp_aux_dev *aux_dev = NULL; | |
59 | ||
60 | mutex_lock(&aux_idr_mutex); | |
61 | aux_dev = idr_find(&aux_idr, index); | |
62 | if (!kref_get_unless_zero(&aux_dev->refcount)) | |
63 | aux_dev = NULL; | |
64 | mutex_unlock(&aux_idr_mutex); | |
65 | ||
66 | return aux_dev; | |
67 | } | |
68 | ||
69 | static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) | |
70 | { | |
71 | struct drm_dp_aux_dev *aux_dev; | |
72 | int index; | |
73 | ||
74 | aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); | |
75 | if (!aux_dev) | |
76 | return ERR_PTR(-ENOMEM); | |
77 | aux_dev->aux = aux; | |
78 | atomic_set(&aux_dev->usecount, 1); | |
79 | kref_init(&aux_dev->refcount); | |
80 | ||
81 | mutex_lock(&aux_idr_mutex); | |
82 | index = idr_alloc_cyclic(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, | |
83 | GFP_KERNEL); | |
84 | mutex_unlock(&aux_idr_mutex); | |
85 | if (index < 0) { | |
86 | kfree(aux_dev); | |
87 | return ERR_PTR(index); | |
88 | } | |
89 | aux_dev->index = index; | |
90 | ||
91 | return aux_dev; | |
92 | } | |
93 | ||
94 | static void release_drm_dp_aux_dev(struct kref *ref) | |
95 | { | |
96 | struct drm_dp_aux_dev *aux_dev = | |
97 | container_of(ref, struct drm_dp_aux_dev, refcount); | |
98 | ||
99 | kfree(aux_dev); | |
100 | } | |
101 | ||
102 | static ssize_t name_show(struct device *dev, | |
103 | struct device_attribute *attr, char *buf) | |
104 | { | |
105 | ssize_t res; | |
106 | struct drm_dp_aux_dev *aux_dev = | |
107 | drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); | |
108 | ||
109 | if (!aux_dev) | |
110 | return -ENODEV; | |
111 | ||
112 | res = sprintf(buf, "%s\n", aux_dev->aux->name); | |
113 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
114 | ||
115 | return res; | |
116 | } | |
117 | static DEVICE_ATTR_RO(name); | |
118 | ||
119 | static struct attribute *drm_dp_aux_attrs[] = { | |
120 | &dev_attr_name.attr, | |
121 | NULL, | |
122 | }; | |
123 | ATTRIBUTE_GROUPS(drm_dp_aux); | |
124 | ||
125 | static int auxdev_open(struct inode *inode, struct file *file) | |
126 | { | |
127 | unsigned int minor = iminor(inode); | |
128 | struct drm_dp_aux_dev *aux_dev; | |
129 | ||
130 | aux_dev = drm_dp_aux_dev_get_by_minor(minor); | |
131 | if (!aux_dev) | |
132 | return -ENODEV; | |
133 | ||
134 | file->private_data = aux_dev; | |
135 | return 0; | |
136 | } | |
137 | ||
138 | static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) | |
139 | { | |
140 | return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); | |
141 | } | |
142 | ||
143 | static ssize_t auxdev_read(struct file *file, char __user *buf, size_t count, | |
144 | loff_t *offset) | |
145 | { | |
146 | size_t bytes_pending, num_bytes_processed = 0; | |
147 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
148 | ssize_t res = 0; | |
149 | ||
150 | if (!atomic_inc_not_zero(&aux_dev->usecount)) | |
151 | return -ENODEV; | |
152 | ||
153 | bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - (*offset)); | |
154 | ||
155 | if (!access_ok(VERIFY_WRITE, buf, bytes_pending)) { | |
156 | res = -EFAULT; | |
157 | goto out; | |
158 | } | |
159 | ||
160 | while (bytes_pending > 0) { | |
161 | uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; | |
162 | ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); | |
163 | ||
36230cb5 VS |
164 | if (signal_pending(current)) { |
165 | res = num_bytes_processed ? | |
166 | num_bytes_processed : -ERESTARTSYS; | |
167 | goto out; | |
168 | } | |
169 | ||
e94cb37b RA |
170 | res = drm_dp_dpcd_read(aux_dev->aux, *offset, localbuf, todo); |
171 | if (res <= 0) { | |
172 | res = num_bytes_processed ? num_bytes_processed : res; | |
173 | goto out; | |
174 | } | |
175 | if (__copy_to_user(buf + num_bytes_processed, localbuf, res)) { | |
176 | res = num_bytes_processed ? | |
177 | num_bytes_processed : -EFAULT; | |
178 | goto out; | |
179 | } | |
180 | bytes_pending -= res; | |
181 | *offset += res; | |
182 | num_bytes_processed += res; | |
183 | res = num_bytes_processed; | |
184 | } | |
185 | ||
186 | out: | |
187 | atomic_dec(&aux_dev->usecount); | |
188 | wake_up_atomic_t(&aux_dev->usecount); | |
189 | return res; | |
190 | } | |
191 | ||
192 | static ssize_t auxdev_write(struct file *file, const char __user *buf, | |
193 | size_t count, loff_t *offset) | |
194 | { | |
195 | size_t bytes_pending, num_bytes_processed = 0; | |
196 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
197 | ssize_t res = 0; | |
198 | ||
199 | if (!atomic_inc_not_zero(&aux_dev->usecount)) | |
200 | return -ENODEV; | |
201 | ||
202 | bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - *offset); | |
203 | ||
204 | if (!access_ok(VERIFY_READ, buf, bytes_pending)) { | |
205 | res = -EFAULT; | |
206 | goto out; | |
207 | } | |
208 | ||
209 | while (bytes_pending > 0) { | |
210 | uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; | |
211 | ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); | |
212 | ||
36230cb5 VS |
213 | if (signal_pending(current)) { |
214 | res = num_bytes_processed ? | |
215 | num_bytes_processed : -ERESTARTSYS; | |
216 | goto out; | |
217 | } | |
218 | ||
e94cb37b RA |
219 | if (__copy_from_user(localbuf, |
220 | buf + num_bytes_processed, todo)) { | |
221 | res = num_bytes_processed ? | |
222 | num_bytes_processed : -EFAULT; | |
223 | goto out; | |
224 | } | |
225 | ||
226 | res = drm_dp_dpcd_write(aux_dev->aux, *offset, localbuf, todo); | |
227 | if (res <= 0) { | |
228 | res = num_bytes_processed ? num_bytes_processed : res; | |
229 | goto out; | |
230 | } | |
231 | bytes_pending -= res; | |
232 | *offset += res; | |
233 | num_bytes_processed += res; | |
234 | res = num_bytes_processed; | |
235 | } | |
236 | ||
237 | out: | |
238 | atomic_dec(&aux_dev->usecount); | |
239 | wake_up_atomic_t(&aux_dev->usecount); | |
240 | return res; | |
241 | } | |
242 | ||
243 | static int auxdev_release(struct inode *inode, struct file *file) | |
244 | { | |
245 | struct drm_dp_aux_dev *aux_dev = file->private_data; | |
246 | ||
247 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
248 | return 0; | |
249 | } | |
250 | ||
251 | static const struct file_operations auxdev_fops = { | |
252 | .owner = THIS_MODULE, | |
253 | .llseek = auxdev_llseek, | |
254 | .read = auxdev_read, | |
255 | .write = auxdev_write, | |
256 | .open = auxdev_open, | |
257 | .release = auxdev_release, | |
258 | }; | |
259 | ||
260 | #define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) | |
261 | ||
262 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) | |
263 | { | |
264 | struct drm_dp_aux_dev *iter, *aux_dev = NULL; | |
265 | int id; | |
266 | ||
267 | /* don't increase kref count here because this function should only be | |
268 | * used by drm_dp_aux_unregister_devnode. Thus, it will always have at | |
269 | * least one reference - the one that drm_dp_aux_register_devnode | |
270 | * created | |
271 | */ | |
272 | mutex_lock(&aux_idr_mutex); | |
273 | idr_for_each_entry(&aux_idr, iter, id) { | |
274 | if (iter->aux == aux) { | |
275 | aux_dev = iter; | |
276 | break; | |
277 | } | |
278 | } | |
279 | mutex_unlock(&aux_idr_mutex); | |
280 | return aux_dev; | |
281 | } | |
282 | ||
283 | static int auxdev_wait_atomic_t(atomic_t *p) | |
284 | { | |
285 | schedule(); | |
286 | return 0; | |
287 | } | |
e15c8f4b | 288 | |
e94cb37b RA |
289 | void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) |
290 | { | |
291 | struct drm_dp_aux_dev *aux_dev; | |
292 | unsigned int minor; | |
293 | ||
294 | aux_dev = drm_dp_aux_dev_get_by_aux(aux); | |
295 | if (!aux_dev) /* attach must have failed */ | |
296 | return; | |
297 | ||
298 | mutex_lock(&aux_idr_mutex); | |
299 | idr_remove(&aux_idr, aux_dev->index); | |
300 | mutex_unlock(&aux_idr_mutex); | |
301 | ||
302 | atomic_dec(&aux_dev->usecount); | |
303 | wait_on_atomic_t(&aux_dev->usecount, auxdev_wait_atomic_t, | |
304 | TASK_UNINTERRUPTIBLE); | |
305 | ||
306 | minor = aux_dev->index; | |
307 | if (aux_dev->dev) | |
308 | device_destroy(drm_dp_aux_dev_class, | |
309 | MKDEV(drm_dev_major, minor)); | |
310 | ||
311 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); | |
312 | kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); | |
313 | } | |
e94cb37b | 314 | |
e94cb37b RA |
315 | int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) |
316 | { | |
317 | struct drm_dp_aux_dev *aux_dev; | |
318 | int res; | |
319 | ||
320 | aux_dev = alloc_drm_dp_aux_dev(aux); | |
321 | if (IS_ERR(aux_dev)) | |
322 | return PTR_ERR(aux_dev); | |
323 | ||
324 | aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, | |
325 | MKDEV(drm_dev_major, aux_dev->index), NULL, | |
326 | "drm_dp_aux%d", aux_dev->index); | |
327 | if (IS_ERR(aux_dev->dev)) { | |
328 | res = PTR_ERR(aux_dev->dev); | |
329 | aux_dev->dev = NULL; | |
330 | goto error; | |
331 | } | |
332 | ||
333 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", | |
334 | aux->name, aux_dev->index); | |
335 | return 0; | |
336 | error: | |
337 | drm_dp_aux_unregister_devnode(aux); | |
338 | return res; | |
339 | } | |
e94cb37b RA |
340 | |
341 | int drm_dp_aux_dev_init(void) | |
342 | { | |
343 | int res; | |
344 | ||
345 | drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); | |
346 | if (IS_ERR(drm_dp_aux_dev_class)) { | |
da82ee99 | 347 | return PTR_ERR(drm_dp_aux_dev_class); |
e94cb37b RA |
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 | } | |
e94cb37b RA |
361 | |
362 | void drm_dp_aux_dev_exit(void) | |
363 | { | |
364 | unregister_chrdev(drm_dev_major, "aux"); | |
365 | class_destroy(drm_dp_aux_dev_class); | |
366 | } |