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/init.h> |
31 | #include <linux/kernel.h> |
32 | #include <linux/module.h> |
33 | #include <linux/sched/signal.h> |
34 | #include <linux/slab.h> |
35 | #include <linux/uaccess.h> |
36 | #include <linux/uio.h> |
37 | |
38 | #include <drm/display/drm_dp_helper.h> |
39 | #include <drm/display/drm_dp_mst_helper.h> |
40 | #include <drm/drm_crtc.h> |
41 | #include <drm/drm_print.h> |
42 | |
43 | #include "drm_dp_helper_internal.h" |
44 | |
45 | struct drm_dp_aux_dev { |
46 | unsigned index; |
47 | struct drm_dp_aux *aux; |
48 | struct device *dev; |
49 | struct kref refcount; |
50 | atomic_t usecount; |
51 | }; |
52 | |
53 | #define DRM_AUX_MINORS 256 |
54 | #define AUX_MAX_OFFSET (1 << 20) |
55 | static DEFINE_IDR(aux_idr); |
56 | static DEFINE_MUTEX(aux_idr_mutex); |
57 | static struct class *drm_dp_aux_dev_class; |
58 | static int drm_dev_major = -1; |
59 | |
60 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) |
61 | { |
62 | struct drm_dp_aux_dev *aux_dev = NULL; |
63 | |
64 | mutex_lock(&aux_idr_mutex); |
65 | aux_dev = idr_find(&aux_idr, id: index); |
66 | if (aux_dev && !kref_get_unless_zero(kref: &aux_dev->refcount)) |
67 | aux_dev = NULL; |
68 | mutex_unlock(lock: &aux_idr_mutex); |
69 | |
70 | return aux_dev; |
71 | } |
72 | |
73 | static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) |
74 | { |
75 | struct drm_dp_aux_dev *aux_dev; |
76 | int index; |
77 | |
78 | aux_dev = kzalloc(size: sizeof(*aux_dev), GFP_KERNEL); |
79 | if (!aux_dev) |
80 | return ERR_PTR(error: -ENOMEM); |
81 | aux_dev->aux = aux; |
82 | atomic_set(v: &aux_dev->usecount, i: 1); |
83 | kref_init(kref: &aux_dev->refcount); |
84 | |
85 | mutex_lock(&aux_idr_mutex); |
86 | index = idr_alloc(&aux_idr, ptr: aux_dev, start: 0, DRM_AUX_MINORS, GFP_KERNEL); |
87 | mutex_unlock(lock: &aux_idr_mutex); |
88 | if (index < 0) { |
89 | kfree(objp: aux_dev); |
90 | return ERR_PTR(error: 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(objp: 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, fmt: "%s\n" , aux_dev->aux->name); |
116 | kref_put(kref: &aux_dev->refcount, release: 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(index: 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 | |
146 | static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to) |
147 | { |
148 | struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; |
149 | loff_t pos = iocb->ki_pos; |
150 | ssize_t res = 0; |
151 | |
152 | if (!atomic_inc_not_zero(v: &aux_dev->usecount)) |
153 | return -ENODEV; |
154 | |
155 | iov_iter_truncate(i: to, AUX_MAX_OFFSET - pos); |
156 | |
157 | while (iov_iter_count(i: to)) { |
158 | uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; |
159 | ssize_t todo = min(iov_iter_count(to), sizeof(buf)); |
160 | |
161 | if (signal_pending(current)) { |
162 | res = -ERESTARTSYS; |
163 | break; |
164 | } |
165 | |
166 | res = drm_dp_dpcd_read(aux: aux_dev->aux, offset: pos, buffer: buf, size: todo); |
167 | |
168 | if (res <= 0) |
169 | break; |
170 | |
171 | if (copy_to_iter(addr: buf, bytes: res, i: to) != res) { |
172 | res = -EFAULT; |
173 | break; |
174 | } |
175 | |
176 | pos += res; |
177 | } |
178 | |
179 | if (pos != iocb->ki_pos) |
180 | res = pos - iocb->ki_pos; |
181 | iocb->ki_pos = pos; |
182 | |
183 | if (atomic_dec_and_test(v: &aux_dev->usecount)) |
184 | wake_up_var(var: &aux_dev->usecount); |
185 | |
186 | return res; |
187 | } |
188 | |
189 | static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from) |
190 | { |
191 | struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; |
192 | loff_t pos = iocb->ki_pos; |
193 | ssize_t res = 0; |
194 | |
195 | if (!atomic_inc_not_zero(v: &aux_dev->usecount)) |
196 | return -ENODEV; |
197 | |
198 | iov_iter_truncate(i: from, AUX_MAX_OFFSET - pos); |
199 | |
200 | while (iov_iter_count(i: from)) { |
201 | uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; |
202 | ssize_t todo = min(iov_iter_count(from), sizeof(buf)); |
203 | |
204 | if (signal_pending(current)) { |
205 | res = -ERESTARTSYS; |
206 | break; |
207 | } |
208 | |
209 | if (!copy_from_iter_full(addr: buf, bytes: todo, i: from)) { |
210 | res = -EFAULT; |
211 | break; |
212 | } |
213 | |
214 | res = drm_dp_dpcd_write(aux: aux_dev->aux, offset: pos, buffer: buf, size: todo); |
215 | |
216 | if (res <= 0) |
217 | break; |
218 | |
219 | pos += res; |
220 | } |
221 | |
222 | if (pos != iocb->ki_pos) |
223 | res = pos - iocb->ki_pos; |
224 | iocb->ki_pos = pos; |
225 | |
226 | if (atomic_dec_and_test(v: &aux_dev->usecount)) |
227 | wake_up_var(var: &aux_dev->usecount); |
228 | |
229 | return res; |
230 | } |
231 | |
232 | static int auxdev_release(struct inode *inode, struct file *file) |
233 | { |
234 | struct drm_dp_aux_dev *aux_dev = file->private_data; |
235 | |
236 | kref_put(kref: &aux_dev->refcount, release: release_drm_dp_aux_dev); |
237 | return 0; |
238 | } |
239 | |
240 | static const struct file_operations auxdev_fops = { |
241 | .owner = THIS_MODULE, |
242 | .llseek = auxdev_llseek, |
243 | .read_iter = auxdev_read_iter, |
244 | .write_iter = auxdev_write_iter, |
245 | .open = auxdev_open, |
246 | .release = auxdev_release, |
247 | }; |
248 | |
249 | #define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) |
250 | |
251 | static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) |
252 | { |
253 | struct drm_dp_aux_dev *iter, *aux_dev = NULL; |
254 | int id; |
255 | |
256 | /* don't increase kref count here because this function should only be |
257 | * used by drm_dp_aux_unregister_devnode. Thus, it will always have at |
258 | * least one reference - the one that drm_dp_aux_register_devnode |
259 | * created |
260 | */ |
261 | mutex_lock(&aux_idr_mutex); |
262 | idr_for_each_entry(&aux_idr, iter, id) { |
263 | if (iter->aux == aux) { |
264 | aux_dev = iter; |
265 | break; |
266 | } |
267 | } |
268 | mutex_unlock(lock: &aux_idr_mutex); |
269 | return aux_dev; |
270 | } |
271 | |
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 | /* |
282 | * As some AUX adapters may exist as platform devices which outlive their respective DRM |
283 | * devices, we clear drm_dev to ensure that we never accidentally reference a stale pointer |
284 | */ |
285 | aux->drm_dev = NULL; |
286 | |
287 | mutex_lock(&aux_idr_mutex); |
288 | idr_remove(&aux_idr, id: aux_dev->index); |
289 | mutex_unlock(lock: &aux_idr_mutex); |
290 | |
291 | atomic_dec(v: &aux_dev->usecount); |
292 | wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount)); |
293 | |
294 | minor = aux_dev->index; |
295 | if (aux_dev->dev) |
296 | device_destroy(cls: drm_dp_aux_dev_class, |
297 | MKDEV(drm_dev_major, minor)); |
298 | |
299 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n" , aux->name); |
300 | kref_put(kref: &aux_dev->refcount, release: release_drm_dp_aux_dev); |
301 | } |
302 | |
303 | int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) |
304 | { |
305 | struct drm_dp_aux_dev *aux_dev; |
306 | int res; |
307 | |
308 | aux_dev = alloc_drm_dp_aux_dev(aux); |
309 | if (IS_ERR(ptr: aux_dev)) |
310 | return PTR_ERR(ptr: aux_dev); |
311 | |
312 | aux_dev->dev = device_create(cls: drm_dp_aux_dev_class, parent: aux->dev, |
313 | MKDEV(drm_dev_major, aux_dev->index), NULL, |
314 | fmt: "drm_dp_aux%d" , aux_dev->index); |
315 | if (IS_ERR(ptr: aux_dev->dev)) { |
316 | res = PTR_ERR(ptr: aux_dev->dev); |
317 | aux_dev->dev = NULL; |
318 | goto error; |
319 | } |
320 | |
321 | DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n" , |
322 | aux->name, aux_dev->index); |
323 | return 0; |
324 | error: |
325 | drm_dp_aux_unregister_devnode(aux); |
326 | return res; |
327 | } |
328 | |
329 | int drm_dp_aux_dev_init(void) |
330 | { |
331 | int res; |
332 | |
333 | drm_dp_aux_dev_class = class_create(name: "drm_dp_aux_dev" ); |
334 | if (IS_ERR(ptr: drm_dp_aux_dev_class)) { |
335 | return PTR_ERR(ptr: drm_dp_aux_dev_class); |
336 | } |
337 | drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; |
338 | |
339 | res = register_chrdev(major: 0, name: "aux" , fops: &auxdev_fops); |
340 | if (res < 0) |
341 | goto out; |
342 | drm_dev_major = res; |
343 | |
344 | return 0; |
345 | out: |
346 | class_destroy(cls: drm_dp_aux_dev_class); |
347 | return res; |
348 | } |
349 | |
350 | void drm_dp_aux_dev_exit(void) |
351 | { |
352 | unregister_chrdev(major: drm_dev_major, name: "aux" ); |
353 | class_destroy(cls: drm_dp_aux_dev_class); |
354 | } |
355 | |