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