drm_dp_aux_dev.c revision 1.1
1/*	$NetBSD: drm_dp_aux_dev.c,v 1.1 2021/12/18 20:11:01 riastradh Exp $	*/
2
3/*
4 * Copyright © 2015 Intel Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 * IN THE SOFTWARE.
24 *
25 * Authors:
26 *    Rafael Antognolli <rafael.antognolli@intel.com>
27 *
28 */
29
30#include <sys/cdefs.h>
31__KERNEL_RCSID(0, "$NetBSD: drm_dp_aux_dev.c,v 1.1 2021/12/18 20:11:01 riastradh Exp $");
32
33#include <linux/device.h>
34#include <linux/fs.h>
35#include <linux/init.h>
36#include <linux/kernel.h>
37#include <linux/module.h>
38#include <linux/sched/signal.h>
39#include <linux/slab.h>
40#include <linux/uaccess.h>
41#include <linux/uio.h>
42
43#include <drm/drm_crtc.h>
44#include <drm/drm_dp_helper.h>
45#include <drm/drm_dp_mst_helper.h>
46#include <drm/drm_print.h>
47
48#include "drm_crtc_helper_internal.h"
49
50struct drm_dp_aux_dev {
51	unsigned index;
52	struct drm_dp_aux *aux;
53	struct device *dev;
54	struct kref refcount;
55	atomic_t usecount;
56};
57
58#define DRM_AUX_MINORS	256
59#define AUX_MAX_OFFSET	(1 << 20)
60static DEFINE_IDR(aux_idr);
61static DEFINE_MUTEX(aux_idr_mutex);
62static struct class *drm_dp_aux_dev_class;
63static int drm_dev_major = -1;
64
65static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index)
66{
67	struct drm_dp_aux_dev *aux_dev = NULL;
68
69	mutex_lock(&aux_idr_mutex);
70	aux_dev = idr_find(&aux_idr, index);
71	if (!kref_get_unless_zero(&aux_dev->refcount))
72		aux_dev = NULL;
73	mutex_unlock(&aux_idr_mutex);
74
75	return aux_dev;
76}
77
78static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux)
79{
80	struct drm_dp_aux_dev *aux_dev;
81	int index;
82
83	aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL);
84	if (!aux_dev)
85		return ERR_PTR(-ENOMEM);
86	aux_dev->aux = aux;
87	atomic_set(&aux_dev->usecount, 1);
88	kref_init(&aux_dev->refcount);
89
90	mutex_lock(&aux_idr_mutex);
91	index = idr_alloc(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, GFP_KERNEL);
92	mutex_unlock(&aux_idr_mutex);
93	if (index < 0) {
94		kfree(aux_dev);
95		return ERR_PTR(index);
96	}
97	aux_dev->index = index;
98
99	return aux_dev;
100}
101
102static void release_drm_dp_aux_dev(struct kref *ref)
103{
104	struct drm_dp_aux_dev *aux_dev =
105		container_of(ref, struct drm_dp_aux_dev, refcount);
106
107	kfree(aux_dev);
108}
109
110static ssize_t name_show(struct device *dev,
111			 struct device_attribute *attr, char *buf)
112{
113	ssize_t res;
114	struct drm_dp_aux_dev *aux_dev =
115		drm_dp_aux_dev_get_by_minor(MINOR(dev->devt));
116
117	if (!aux_dev)
118		return -ENODEV;
119
120	res = sprintf(buf, "%s\n", aux_dev->aux->name);
121	kref_put(&aux_dev->refcount, release_drm_dp_aux_dev);
122
123	return res;
124}
125static DEVICE_ATTR_RO(name);
126
127static struct attribute *drm_dp_aux_attrs[] = {
128	&dev_attr_name.attr,
129	NULL,
130};
131ATTRIBUTE_GROUPS(drm_dp_aux);
132
133static int auxdev_open(struct inode *inode, struct file *file)
134{
135	unsigned int minor = iminor(inode);
136	struct drm_dp_aux_dev *aux_dev;
137
138	aux_dev = drm_dp_aux_dev_get_by_minor(minor);
139	if (!aux_dev)
140		return -ENODEV;
141
142	file->private_data = aux_dev;
143	return 0;
144}
145
146static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence)
147{
148	return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET);
149}
150
151static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
152{
153	struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data;
154	loff_t pos = iocb->ki_pos;
155	ssize_t res = 0;
156
157	if (!atomic_inc_not_zero(&aux_dev->usecount))
158		return -ENODEV;
159
160	iov_iter_truncate(to, AUX_MAX_OFFSET - pos);
161
162	while (iov_iter_count(to)) {
163		uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES];
164		ssize_t todo = min(iov_iter_count(to), sizeof(buf));
165
166		if (signal_pending(current)) {
167			res = -ERESTARTSYS;
168			break;
169		}
170
171		res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo);
172
173		if (res <= 0)
174			break;
175
176		if (copy_to_iter(buf, res, to) != res) {
177			res = -EFAULT;
178			break;
179		}
180
181		pos += res;
182	}
183
184	if (pos != iocb->ki_pos)
185		res = pos - iocb->ki_pos;
186	iocb->ki_pos = pos;
187
188	if (atomic_dec_and_test(&aux_dev->usecount))
189		wake_up_var(&aux_dev->usecount);
190
191	return res;
192}
193
194static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
195{
196	struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data;
197	loff_t pos = iocb->ki_pos;
198	ssize_t res = 0;
199
200	if (!atomic_inc_not_zero(&aux_dev->usecount))
201		return -ENODEV;
202
203	iov_iter_truncate(from, AUX_MAX_OFFSET - pos);
204
205	while (iov_iter_count(from)) {
206		uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES];
207		ssize_t todo = min(iov_iter_count(from), sizeof(buf));
208
209		if (signal_pending(current)) {
210			res = -ERESTARTSYS;
211			break;
212		}
213
214		if (!copy_from_iter_full(buf, todo, from)) {
215			res = -EFAULT;
216			break;
217		}
218
219		res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo);
220
221		if (res <= 0)
222			break;
223
224		pos += res;
225	}
226
227	if (pos != iocb->ki_pos)
228		res = pos - iocb->ki_pos;
229	iocb->ki_pos = pos;
230
231	if (atomic_dec_and_test(&aux_dev->usecount))
232		wake_up_var(&aux_dev->usecount);
233
234	return res;
235}
236
237static int auxdev_release(struct inode *inode, struct file *file)
238{
239	struct drm_dp_aux_dev *aux_dev = file->private_data;
240
241	kref_put(&aux_dev->refcount, release_drm_dp_aux_dev);
242	return 0;
243}
244
245static const struct file_operations auxdev_fops = {
246	.owner		= THIS_MODULE,
247	.llseek		= auxdev_llseek,
248	.read_iter	= auxdev_read_iter,
249	.write_iter	= auxdev_write_iter,
250	.open		= auxdev_open,
251	.release	= auxdev_release,
252};
253
254#define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux)
255
256static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux)
257{
258	struct drm_dp_aux_dev *iter, *aux_dev = NULL;
259	int id;
260
261	/* don't increase kref count here because this function should only be
262	 * used by drm_dp_aux_unregister_devnode. Thus, it will always have at
263	 * least one reference - the one that drm_dp_aux_register_devnode
264	 * created
265	 */
266	mutex_lock(&aux_idr_mutex);
267	idr_for_each_entry(&aux_idr, iter, id) {
268		if (iter->aux == aux) {
269			aux_dev = iter;
270			break;
271		}
272	}
273	mutex_unlock(&aux_idr_mutex);
274	return aux_dev;
275}
276
277void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux)
278{
279	struct drm_dp_aux_dev *aux_dev;
280	unsigned int minor;
281
282	aux_dev = drm_dp_aux_dev_get_by_aux(aux);
283	if (!aux_dev) /* attach must have failed */
284		return;
285
286	mutex_lock(&aux_idr_mutex);
287	idr_remove(&aux_idr, aux_dev->index);
288	mutex_unlock(&aux_idr_mutex);
289
290	atomic_dec(&aux_dev->usecount);
291	wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount));
292
293	minor = aux_dev->index;
294	if (aux_dev->dev)
295		device_destroy(drm_dp_aux_dev_class,
296			       MKDEV(drm_dev_major, minor));
297
298	DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name);
299	kref_put(&aux_dev->refcount, release_drm_dp_aux_dev);
300}
301
302int drm_dp_aux_register_devnode(struct drm_dp_aux *aux)
303{
304	struct drm_dp_aux_dev *aux_dev;
305	int res;
306
307	aux_dev = alloc_drm_dp_aux_dev(aux);
308	if (IS_ERR(aux_dev))
309		return PTR_ERR(aux_dev);
310
311	aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev,
312				     MKDEV(drm_dev_major, aux_dev->index), NULL,
313				     "drm_dp_aux%d", aux_dev->index);
314	if (IS_ERR(aux_dev->dev)) {
315		res = PTR_ERR(aux_dev->dev);
316		aux_dev->dev = NULL;
317		goto error;
318	}
319
320	DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n",
321		  aux->name, aux_dev->index);
322	return 0;
323error:
324	drm_dp_aux_unregister_devnode(aux);
325	return res;
326}
327
328int drm_dp_aux_dev_init(void)
329{
330	int res;
331
332	drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev");
333	if (IS_ERR(drm_dp_aux_dev_class)) {
334		return PTR_ERR(drm_dp_aux_dev_class);
335	}
336	drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups;
337
338	res = register_chrdev(0, "aux", &auxdev_fops);
339	if (res < 0)
340		goto out;
341	drm_dev_major = res;
342
343	return 0;
344out:
345	class_destroy(drm_dp_aux_dev_class);
346	return res;
347}
348
349void drm_dp_aux_dev_exit(void)
350{
351	unregister_chrdev(drm_dev_major, "aux");
352	class_destroy(drm_dp_aux_dev_class);
353}
354