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