Skip to content

Commit e94cb37

Browse files
Rafael Antognollidanvet
Rafael Antognolli
authored andcommitted
drm/dp: Add a drm_aux-dev module for reading/writing dpcd registers.
This module is heavily based on i2c-dev. Once loaded, it provides one dev node per DP AUX channel, named drm_dp_auxN, where N is an integer. It's possible to know which connector owns this aux channel by looking at the respective sysfs /sys/class/drm_aux_dev/drm_dp_auxN/connector, if the connector device pointer was correctly set in the aux helper struct. Two main operations are provided on the registers read and write. The address of the register to be read or written is given using lseek. The seek position is updated upon read or write. v2: - lseek is used to select the register to read/write - read/write are used instead of ioctl - no blocking_notifier is used, just a direct callback v3: - use drm_dp_aux_dev prefix for public functions - chardev is named drm_dp_auxN - read/write don't allocate a buffer anymore, and transfer up to 16 bytes a time - remove notifier list from the implementation - option on menuconfig is now a boolean - add inline stub functions to avoid breakage when this option is disabled v4: - fix build system changes - actually disable this module when not selected. v5: - Use kref to avoid device closing while still in use - Don't use list, use an idr for storing aux_dev - Remove "connector" attribute - set aux.dev to the connector drm_connector device, instead of drm_device v6: - Use atomic_t for usage count - Use a mutex instead of spinlock for idr lock - Destroy chardev immediately on unregister - other minor suggestions from Ville v7: - style fixes - error handling fixes v8: - more error handling fixes v9: - remove module_init and module_exit, and add drm_dp_aux_dev_init/exit to drm_kms_helper_init/exit. Signed-off-by: Rafael Antognolli <[email protected]> Reviewed-by: Ville Syrjälä <[email protected]> Signed-off-by: Daniel Vetter <[email protected]> Link: http://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent 70412cf commit e94cb37

File tree

6 files changed

+468
-2
lines changed

6 files changed

+468
-2
lines changed

drivers/gpu/drm/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ config DRM_MIPI_DSI
2525
bool
2626
depends on DRM
2727

28+
config DRM_DP_AUX_CHARDEV
29+
bool "DRM DP AUX Interface"
30+
depends on DRM
31+
help
32+
Choose this option to enable a /dev/drm_dp_auxN node that allows to
33+
read and write values to arbitrary DPCD registers on the DP aux
34+
channel.
35+
2836
config DRM_KMS_HELPER
2937
tristate
3038
depends on DRM

drivers/gpu/drm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_probe_helper.o \
2828
drm_kms_helper-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o
2929
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
3030
drm_kms_helper-$(CONFIG_DRM_KMS_CMA_HELPER) += drm_fb_cma_helper.o
31+
drm_kms_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o
3132

3233
obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o
3334

drivers/gpu/drm/drm_dp_aux_dev.c

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

0 commit comments

Comments
 (0)