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