| 1 | /* | 
|---|
| 2 | * Created: Sun Dec 21 13:08:50 2008 by bgamari@gmail.com | 
|---|
| 3 | * | 
|---|
| 4 | * Copyright 2008 Ben Gamari <bgamari@gmail.com> | 
|---|
| 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 | * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR | 
|---|
| 21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | 
|---|
| 22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | 
|---|
| 23 | * OTHER DEALINGS IN THE SOFTWARE. | 
|---|
| 24 | */ | 
|---|
| 25 |  | 
|---|
| 26 | #include <linux/debugfs.h> | 
|---|
| 27 | #include <linux/export.h> | 
|---|
| 28 | #include <linux/seq_file.h> | 
|---|
| 29 | #include <linux/slab.h> | 
|---|
| 30 | #include <linux/uaccess.h> | 
|---|
| 31 |  | 
|---|
| 32 | #include <drm/drm_atomic.h> | 
|---|
| 33 | #include <drm/drm_auth.h> | 
|---|
| 34 | #include <drm/drm_bridge.h> | 
|---|
| 35 | #include <drm/drm_debugfs.h> | 
|---|
| 36 | #include <drm/drm_device.h> | 
|---|
| 37 | #include <drm/drm_drv.h> | 
|---|
| 38 | #include <drm/drm_edid.h> | 
|---|
| 39 | #include <drm/drm_file.h> | 
|---|
| 40 | #include <drm/drm_gem.h> | 
|---|
| 41 | #include <drm/drm_managed.h> | 
|---|
| 42 | #include <drm/drm_gpuvm.h> | 
|---|
| 43 |  | 
|---|
| 44 | #include "drm_crtc_internal.h" | 
|---|
| 45 | #include "drm_internal.h" | 
|---|
| 46 |  | 
|---|
| 47 | static struct dentry *accel_debugfs_root; | 
|---|
| 48 | static struct dentry *drm_debugfs_root; | 
|---|
| 49 |  | 
|---|
| 50 | /*************************************************** | 
|---|
| 51 | * Initialization, etc. | 
|---|
| 52 | **************************************************/ | 
|---|
| 53 |  | 
|---|
| 54 | static int drm_name_info(struct seq_file *m, void *data) | 
|---|
| 55 | { | 
|---|
| 56 | struct drm_debugfs_entry *entry = m->private; | 
|---|
| 57 | struct drm_device *dev = entry->dev; | 
|---|
| 58 | struct drm_master *master; | 
|---|
| 59 |  | 
|---|
| 60 | mutex_lock(lock: &dev->master_mutex); | 
|---|
| 61 | master = dev->master; | 
|---|
| 62 | seq_printf(m, fmt: "%s", dev->driver->name); | 
|---|
| 63 | if (dev->dev) | 
|---|
| 64 | seq_printf(m, fmt: " dev=%s", dev_name(dev: dev->dev)); | 
|---|
| 65 | if (master && master->unique) | 
|---|
| 66 | seq_printf(m, fmt: " master=%s", master->unique); | 
|---|
| 67 | if (dev->unique) | 
|---|
| 68 | seq_printf(m, fmt: " unique=%s", dev->unique); | 
|---|
| 69 | seq_printf(m, fmt: "\n"); | 
|---|
| 70 | mutex_unlock(lock: &dev->master_mutex); | 
|---|
| 71 |  | 
|---|
| 72 | return 0; | 
|---|
| 73 | } | 
|---|
| 74 |  | 
|---|
| 75 | static int drm_clients_info(struct seq_file *m, void *data) | 
|---|
| 76 | { | 
|---|
| 77 | struct drm_debugfs_entry *entry = m->private; | 
|---|
| 78 | struct drm_device *dev = entry->dev; | 
|---|
| 79 | struct drm_file *priv; | 
|---|
| 80 | kuid_t uid; | 
|---|
| 81 |  | 
|---|
| 82 | seq_printf(m, | 
|---|
| 83 | fmt: "%20s %5s %3s master a %5s %10s %*s %20s\n", | 
|---|
| 84 | "command", | 
|---|
| 85 | "tgid", | 
|---|
| 86 | "dev", | 
|---|
| 87 | "uid", | 
|---|
| 88 | "magic", | 
|---|
| 89 | DRM_CLIENT_NAME_MAX_LEN, | 
|---|
| 90 | "name", | 
|---|
| 91 | "id"); | 
|---|
| 92 |  | 
|---|
| 93 | /* dev->filelist is sorted youngest first, but we want to present | 
|---|
| 94 | * oldest first (i.e. kernel, servers, clients), so walk backwardss. | 
|---|
| 95 | */ | 
|---|
| 96 | mutex_lock(lock: &dev->filelist_mutex); | 
|---|
| 97 | list_for_each_entry_reverse(priv, &dev->filelist, lhead) { | 
|---|
| 98 | bool is_current_master = drm_is_current_master(fpriv: priv); | 
|---|
| 99 | struct task_struct *task; | 
|---|
| 100 | struct pid *pid; | 
|---|
| 101 |  | 
|---|
| 102 | mutex_lock(lock: &priv->client_name_lock); | 
|---|
| 103 | rcu_read_lock(); /* Locks priv->pid and pid_task()->comm! */ | 
|---|
| 104 | pid = rcu_dereference(priv->pid); | 
|---|
| 105 | task = pid_task(pid, PIDTYPE_TGID); | 
|---|
| 106 | uid = task ? __task_cred(task)->euid : GLOBAL_ROOT_UID; | 
|---|
| 107 | seq_printf(m, "%20s %5d %3d   %c    %c %5d %10u %*s %20llu\n", | 
|---|
| 108 | task ? task->comm : "<unknown>", | 
|---|
| 109 | pid_vnr(pid), | 
|---|
| 110 | priv->minor->index, | 
|---|
| 111 | is_current_master ? 'y' : 'n', | 
|---|
| 112 | priv->authenticated ? 'y' : 'n', | 
|---|
| 113 | from_kuid_munged(seq_user_ns(m), uid), | 
|---|
| 114 | priv->magic, | 
|---|
| 115 | DRM_CLIENT_NAME_MAX_LEN, | 
|---|
| 116 | priv->client_name ? priv->client_name : "<unset>", | 
|---|
| 117 | priv->client_id); | 
|---|
| 118 | rcu_read_unlock(); | 
|---|
| 119 | mutex_unlock(&priv->client_name_lock); | 
|---|
| 120 | } | 
|---|
| 121 | mutex_unlock(&dev->filelist_mutex); | 
|---|
| 122 | return 0; | 
|---|
| 123 | } | 
|---|
| 124 |  | 
|---|
| 125 | static int drm_gem_one_name_info(int id, void *ptr, void *data) | 
|---|
| 126 | { | 
|---|
| 127 | struct drm_gem_object *obj = ptr; | 
|---|
| 128 | struct seq_file *m = data; | 
|---|
| 129 |  | 
|---|
| 130 | seq_printf(m, fmt: "%6d %8zd %7d %8d\n", | 
|---|
| 131 | obj->name, obj->size, | 
|---|
| 132 | obj->handle_count, | 
|---|
| 133 | kref_read(kref: &obj->refcount)); | 
|---|
| 134 | return 0; | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | static int drm_gem_name_info(struct seq_file *m, void *data) | 
|---|
| 138 | { | 
|---|
| 139 | struct drm_debugfs_entry *entry = m->private; | 
|---|
| 140 | struct drm_device *dev = entry->dev; | 
|---|
| 141 |  | 
|---|
| 142 | seq_printf(m, fmt: "  name     size handles refcount\n"); | 
|---|
| 143 |  | 
|---|
| 144 | mutex_lock(lock: &dev->object_name_lock); | 
|---|
| 145 | idr_for_each(&dev->object_name_idr, fn: drm_gem_one_name_info, data: m); | 
|---|
| 146 | mutex_unlock(lock: &dev->object_name_lock); | 
|---|
| 147 |  | 
|---|
| 148 | return 0; | 
|---|
| 149 | } | 
|---|
| 150 |  | 
|---|
| 151 | static const struct drm_debugfs_info drm_debugfs_list[] = { | 
|---|
| 152 | { "name", drm_name_info, 0}, | 
|---|
| 153 | { "clients", drm_clients_info, 0}, | 
|---|
| 154 | { "gem_names", drm_gem_name_info, DRIVER_GEM}, | 
|---|
| 155 | }; | 
|---|
| 156 | #define DRM_DEBUGFS_ENTRIES ARRAY_SIZE(drm_debugfs_list) | 
|---|
| 157 |  | 
|---|
| 158 |  | 
|---|
| 159 | static int drm_debugfs_open(struct inode *inode, struct file *file) | 
|---|
| 160 | { | 
|---|
| 161 | struct drm_info_node *node = inode->i_private; | 
|---|
| 162 |  | 
|---|
| 163 | if (!device_is_registered(dev: node->minor->kdev)) | 
|---|
| 164 | return -ENODEV; | 
|---|
| 165 |  | 
|---|
| 166 | return single_open(file, node->info_ent->show, node); | 
|---|
| 167 | } | 
|---|
| 168 |  | 
|---|
| 169 | static int drm_debugfs_entry_open(struct inode *inode, struct file *file) | 
|---|
| 170 | { | 
|---|
| 171 | struct drm_debugfs_entry *entry = inode->i_private; | 
|---|
| 172 | struct drm_debugfs_info *node = &entry->file; | 
|---|
| 173 | struct drm_minor *minor = entry->dev->primary ?: entry->dev->accel; | 
|---|
| 174 |  | 
|---|
| 175 | if (!device_is_registered(dev: minor->kdev)) | 
|---|
| 176 | return -ENODEV; | 
|---|
| 177 |  | 
|---|
| 178 | return single_open(file, node->show, entry); | 
|---|
| 179 | } | 
|---|
| 180 |  | 
|---|
| 181 | static const struct file_operations drm_debugfs_entry_fops = { | 
|---|
| 182 | .owner = THIS_MODULE, | 
|---|
| 183 | .open = drm_debugfs_entry_open, | 
|---|
| 184 | .read = seq_read, | 
|---|
| 185 | .llseek = seq_lseek, | 
|---|
| 186 | .release = single_release, | 
|---|
| 187 | }; | 
|---|
| 188 |  | 
|---|
| 189 | static const struct file_operations drm_debugfs_fops = { | 
|---|
| 190 | .owner = THIS_MODULE, | 
|---|
| 191 | .open = drm_debugfs_open, | 
|---|
| 192 | .read = seq_read, | 
|---|
| 193 | .llseek = seq_lseek, | 
|---|
| 194 | .release = single_release, | 
|---|
| 195 | }; | 
|---|
| 196 |  | 
|---|
| 197 | /** | 
|---|
| 198 | * drm_debugfs_gpuva_info - dump the given DRM GPU VA space | 
|---|
| 199 | * @m: pointer to the &seq_file to write | 
|---|
| 200 | * @gpuvm: the &drm_gpuvm representing the GPU VA space | 
|---|
| 201 | * | 
|---|
| 202 | * Dumps the GPU VA mappings of a given DRM GPU VA manager. | 
|---|
| 203 | * | 
|---|
| 204 | * For each DRM GPU VA space drivers should call this function from their | 
|---|
| 205 | * &drm_info_list's show callback. | 
|---|
| 206 | * | 
|---|
| 207 | * Returns: 0 on success, -ENODEV if the &gpuvm is not initialized | 
|---|
| 208 | */ | 
|---|
| 209 | int drm_debugfs_gpuva_info(struct seq_file *m, | 
|---|
| 210 | struct drm_gpuvm *gpuvm) | 
|---|
| 211 | { | 
|---|
| 212 | struct drm_gpuva *va, *kva = &gpuvm->kernel_alloc_node; | 
|---|
| 213 |  | 
|---|
| 214 | if (!gpuvm->name) | 
|---|
| 215 | return -ENODEV; | 
|---|
| 216 |  | 
|---|
| 217 | seq_printf(m, fmt: "DRM GPU VA space (%s) [0x%016llx;0x%016llx]\n", | 
|---|
| 218 | gpuvm->name, gpuvm->mm_start, gpuvm->mm_start + gpuvm->mm_range); | 
|---|
| 219 | seq_printf(m, fmt: "Kernel reserved node [0x%016llx;0x%016llx]\n", | 
|---|
| 220 | kva->va.addr, kva->va.addr + kva->va.range); | 
|---|
| 221 | seq_puts(m, s: "\n"); | 
|---|
| 222 | seq_puts(m, s: " VAs | start              | range              | end                | object             | object offset\n"); | 
|---|
| 223 | seq_puts(m, s: "-------------------------------------------------------------------------------------------------------------\n"); | 
|---|
| 224 | drm_gpuvm_for_each_va(va, gpuvm) { | 
|---|
| 225 | if (unlikely(va == kva)) | 
|---|
| 226 | continue; | 
|---|
| 227 |  | 
|---|
| 228 | seq_printf(m, fmt: "     | 0x%016llx | 0x%016llx | 0x%016llx | 0x%016llx | 0x%016llx\n", | 
|---|
| 229 | va->va.addr, va->va.range, va->va.addr + va->va.range, | 
|---|
| 230 | (u64)(uintptr_t)va->gem.obj, va->gem.offset); | 
|---|
| 231 | } | 
|---|
| 232 |  | 
|---|
| 233 | return 0; | 
|---|
| 234 | } | 
|---|
| 235 | EXPORT_SYMBOL(drm_debugfs_gpuva_info); | 
|---|
| 236 |  | 
|---|
| 237 | /** | 
|---|
| 238 | * drm_debugfs_create_files - Initialize a given set of debugfs files for DRM | 
|---|
| 239 | * 			minor | 
|---|
| 240 | * @files: The array of files to create | 
|---|
| 241 | * @count: The number of files given | 
|---|
| 242 | * @root: DRI debugfs dir entry. | 
|---|
| 243 | * @minor: device minor number | 
|---|
| 244 | * | 
|---|
| 245 | * Create a given set of debugfs files represented by an array of | 
|---|
| 246 | * &struct drm_info_list in the given root directory. These files will be removed | 
|---|
| 247 | * automatically on drm_debugfs_dev_fini(). | 
|---|
| 248 | */ | 
|---|
| 249 | void drm_debugfs_create_files(const struct drm_info_list *files, int count, | 
|---|
| 250 | struct dentry *root, struct drm_minor *minor) | 
|---|
| 251 | { | 
|---|
| 252 | struct drm_device *dev = minor->dev; | 
|---|
| 253 | struct drm_info_node *tmp; | 
|---|
| 254 | int i; | 
|---|
| 255 |  | 
|---|
| 256 | for (i = 0; i < count; i++) { | 
|---|
| 257 | u32 features = files[i].driver_features; | 
|---|
| 258 |  | 
|---|
| 259 | if (features && !drm_core_check_all_features(dev, features)) | 
|---|
| 260 | continue; | 
|---|
| 261 |  | 
|---|
| 262 | tmp = drmm_kzalloc(dev, size: sizeof(*tmp), GFP_KERNEL); | 
|---|
| 263 | if (tmp == NULL) | 
|---|
| 264 | continue; | 
|---|
| 265 |  | 
|---|
| 266 | tmp->minor = minor; | 
|---|
| 267 | tmp->dent = debugfs_create_file(files[i].name, | 
|---|
| 268 | 0444, root, tmp, | 
|---|
| 269 | &drm_debugfs_fops); | 
|---|
| 270 | tmp->info_ent = &files[i]; | 
|---|
| 271 | } | 
|---|
| 272 | } | 
|---|
| 273 | EXPORT_SYMBOL(drm_debugfs_create_files); | 
|---|
| 274 |  | 
|---|
| 275 | int drm_debugfs_remove_files(const struct drm_info_list *files, int count, | 
|---|
| 276 | struct dentry *root, struct drm_minor *minor) | 
|---|
| 277 | { | 
|---|
| 278 | int i; | 
|---|
| 279 |  | 
|---|
| 280 | for (i = 0; i < count; i++) { | 
|---|
| 281 | struct dentry *dent = debugfs_lookup(name: files[i].name, parent: root); | 
|---|
| 282 |  | 
|---|
| 283 | if (!dent) | 
|---|
| 284 | continue; | 
|---|
| 285 |  | 
|---|
| 286 | drmm_kfree(dev: minor->dev, data: d_inode(dentry: dent)->i_private); | 
|---|
| 287 | debugfs_remove(dentry: dent); | 
|---|
| 288 | } | 
|---|
| 289 | return 0; | 
|---|
| 290 | } | 
|---|
| 291 | EXPORT_SYMBOL(drm_debugfs_remove_files); | 
|---|
| 292 |  | 
|---|
| 293 | void drm_debugfs_bridge_params(void) | 
|---|
| 294 | { | 
|---|
| 295 | drm_bridge_debugfs_params(root: drm_debugfs_root); | 
|---|
| 296 | } | 
|---|
| 297 |  | 
|---|
| 298 | void drm_debugfs_init_root(void) | 
|---|
| 299 | { | 
|---|
| 300 | drm_debugfs_root = debugfs_create_dir(name: "dri", NULL); | 
|---|
| 301 | #if IS_ENABLED(CONFIG_DRM_ACCEL) | 
|---|
| 302 | accel_debugfs_root = debugfs_create_dir( "accel", NULL); | 
|---|
| 303 | #endif | 
|---|
| 304 | } | 
|---|
| 305 |  | 
|---|
| 306 | void drm_debugfs_remove_root(void) | 
|---|
| 307 | { | 
|---|
| 308 | #if IS_ENABLED(CONFIG_DRM_ACCEL) | 
|---|
| 309 | debugfs_remove(accel_debugfs_root); | 
|---|
| 310 | #endif | 
|---|
| 311 | debugfs_remove(dentry: drm_debugfs_root); | 
|---|
| 312 | } | 
|---|
| 313 |  | 
|---|
| 314 | static int drm_debugfs_proc_info_show(struct seq_file *m, void *unused) | 
|---|
| 315 | { | 
|---|
| 316 | struct pid *pid; | 
|---|
| 317 | struct task_struct *task; | 
|---|
| 318 | struct drm_file *file = m->private; | 
|---|
| 319 |  | 
|---|
| 320 | if (!file) | 
|---|
| 321 | return -EINVAL; | 
|---|
| 322 |  | 
|---|
| 323 | rcu_read_lock(); | 
|---|
| 324 | pid = rcu_dereference(file->pid); | 
|---|
| 325 | task = pid_task(pid, PIDTYPE_TGID); | 
|---|
| 326 |  | 
|---|
| 327 | seq_printf(m, fmt: "pid: %d\n", task ? task->pid : 0); | 
|---|
| 328 | seq_printf(m, fmt: "comm: %s\n", task ? task->comm : "Unset"); | 
|---|
| 329 | rcu_read_unlock(); | 
|---|
| 330 | return 0; | 
|---|
| 331 | } | 
|---|
| 332 |  | 
|---|
| 333 | static int drm_debufs_proc_info_open(struct inode *inode, struct file *file) | 
|---|
| 334 | { | 
|---|
| 335 | return single_open(file, drm_debugfs_proc_info_show, inode->i_private); | 
|---|
| 336 | } | 
|---|
| 337 |  | 
|---|
| 338 | static const struct file_operations drm_debugfs_proc_info_fops = { | 
|---|
| 339 | .owner = THIS_MODULE, | 
|---|
| 340 | .open = drm_debufs_proc_info_open, | 
|---|
| 341 | .read = seq_read, | 
|---|
| 342 | .llseek = seq_lseek, | 
|---|
| 343 | .release = single_release, | 
|---|
| 344 | }; | 
|---|
| 345 |  | 
|---|
| 346 | /** | 
|---|
| 347 | * drm_debugfs_clients_add - Add a per client debugfs directory | 
|---|
| 348 | * @file: drm_file for a client | 
|---|
| 349 | * | 
|---|
| 350 | * Create the debugfs directory for each client. This will be used to populate | 
|---|
| 351 | * driver specific data for each client. | 
|---|
| 352 | * | 
|---|
| 353 | * Also add the process information debugfs file for each client to tag | 
|---|
| 354 | * which client belongs to which process. | 
|---|
| 355 | */ | 
|---|
| 356 | void drm_debugfs_clients_add(struct drm_file *file) | 
|---|
| 357 | { | 
|---|
| 358 | char *client; | 
|---|
| 359 |  | 
|---|
| 360 | client = kasprintf(GFP_KERNEL, fmt: "client-%llu", file->client_id); | 
|---|
| 361 | if (!client) | 
|---|
| 362 | return; | 
|---|
| 363 |  | 
|---|
| 364 | /* Create a debugfs directory for the client in root on drm debugfs */ | 
|---|
| 365 | file->debugfs_client = debugfs_create_dir(name: client, parent: drm_debugfs_root); | 
|---|
| 366 | kfree(objp: client); | 
|---|
| 367 |  | 
|---|
| 368 | debugfs_create_file( "proc_info", 0444, file->debugfs_client, file, | 
|---|
| 369 | &drm_debugfs_proc_info_fops); | 
|---|
| 370 |  | 
|---|
| 371 | client = kasprintf(GFP_KERNEL, fmt: "../%s", file->minor->dev->unique); | 
|---|
| 372 | if (!client) | 
|---|
| 373 | return; | 
|---|
| 374 |  | 
|---|
| 375 | /* Create a link from client_id to the drm device this client id belongs to */ | 
|---|
| 376 | debugfs_create_symlink(name: "device", parent: file->debugfs_client, dest: client); | 
|---|
| 377 | kfree(objp: client); | 
|---|
| 378 | } | 
|---|
| 379 |  | 
|---|
| 380 | /** | 
|---|
| 381 | * drm_debugfs_clients_remove - removes all debugfs directories and files | 
|---|
| 382 | * @file: drm_file for a client | 
|---|
| 383 | * | 
|---|
| 384 | * Removes the debugfs directories recursively from the client directory. | 
|---|
| 385 | * | 
|---|
| 386 | * There is also a possibility that debugfs files are open while the drm_file | 
|---|
| 387 | * is released. | 
|---|
| 388 | */ | 
|---|
| 389 | void drm_debugfs_clients_remove(struct drm_file *file) | 
|---|
| 390 | { | 
|---|
| 391 | debugfs_remove_recursive(dentry: file->debugfs_client); | 
|---|
| 392 | file->debugfs_client = NULL; | 
|---|
| 393 | } | 
|---|
| 394 |  | 
|---|
| 395 | /** | 
|---|
| 396 | * drm_debugfs_dev_init - create debugfs directory for the device | 
|---|
| 397 | * @dev: the device which we want to create the directory for | 
|---|
| 398 | * | 
|---|
| 399 | * Creates the debugfs directory for the device under the given root directory. | 
|---|
| 400 | */ | 
|---|
| 401 | void drm_debugfs_dev_init(struct drm_device *dev) | 
|---|
| 402 | { | 
|---|
| 403 | if (drm_core_check_feature(dev, feature: DRIVER_COMPUTE_ACCEL)) | 
|---|
| 404 | dev->debugfs_root = debugfs_create_dir(name: dev->unique, parent: accel_debugfs_root); | 
|---|
| 405 | else | 
|---|
| 406 | dev->debugfs_root = debugfs_create_dir(name: dev->unique, parent: drm_debugfs_root); | 
|---|
| 407 | } | 
|---|
| 408 |  | 
|---|
| 409 | /** | 
|---|
| 410 | * drm_debugfs_dev_fini - cleanup debugfs directory | 
|---|
| 411 | * @dev: the device to cleanup the debugfs stuff | 
|---|
| 412 | * | 
|---|
| 413 | * Remove the debugfs directory, might be called multiple times. | 
|---|
| 414 | */ | 
|---|
| 415 | void drm_debugfs_dev_fini(struct drm_device *dev) | 
|---|
| 416 | { | 
|---|
| 417 | debugfs_remove_recursive(dentry: dev->debugfs_root); | 
|---|
| 418 | dev->debugfs_root = NULL; | 
|---|
| 419 | } | 
|---|
| 420 |  | 
|---|
| 421 | void drm_debugfs_dev_register(struct drm_device *dev) | 
|---|
| 422 | { | 
|---|
| 423 | drm_debugfs_add_files(dev, files: drm_debugfs_list, DRM_DEBUGFS_ENTRIES); | 
|---|
| 424 |  | 
|---|
| 425 | if (drm_core_check_feature(dev, feature: DRIVER_MODESET)) { | 
|---|
| 426 | drm_framebuffer_debugfs_init(dev); | 
|---|
| 427 | drm_client_debugfs_init(dev); | 
|---|
| 428 | } | 
|---|
| 429 | if (drm_drv_uses_atomic_modeset(dev)) | 
|---|
| 430 | drm_atomic_debugfs_init(dev); | 
|---|
| 431 | } | 
|---|
| 432 |  | 
|---|
| 433 | int drm_debugfs_register(struct drm_minor *minor, int minor_id) | 
|---|
| 434 | { | 
|---|
| 435 | struct drm_device *dev = minor->dev; | 
|---|
| 436 | char name[64]; | 
|---|
| 437 |  | 
|---|
| 438 | sprintf(buf: name, fmt: "%d", minor_id); | 
|---|
| 439 | minor->debugfs_symlink = debugfs_create_symlink(name, parent: drm_debugfs_root, | 
|---|
| 440 | dest: dev->unique); | 
|---|
| 441 |  | 
|---|
| 442 | /* TODO: Only for compatibility with drivers */ | 
|---|
| 443 | minor->debugfs_root = dev->debugfs_root; | 
|---|
| 444 |  | 
|---|
| 445 | if (dev->driver->debugfs_init && dev->render != minor) | 
|---|
| 446 | dev->driver->debugfs_init(minor); | 
|---|
| 447 |  | 
|---|
| 448 | return 0; | 
|---|
| 449 | } | 
|---|
| 450 |  | 
|---|
| 451 | void drm_debugfs_unregister(struct drm_minor *minor) | 
|---|
| 452 | { | 
|---|
| 453 | debugfs_remove(dentry: minor->debugfs_symlink); | 
|---|
| 454 | minor->debugfs_symlink = NULL; | 
|---|
| 455 | } | 
|---|
| 456 |  | 
|---|
| 457 | /** | 
|---|
| 458 | * drm_debugfs_add_file - Add a given file to the DRM device debugfs file list | 
|---|
| 459 | * @dev: drm device for the ioctl | 
|---|
| 460 | * @name: debugfs file name | 
|---|
| 461 | * @show: show callback | 
|---|
| 462 | * @data: driver-private data, should not be device-specific | 
|---|
| 463 | * | 
|---|
| 464 | * Add a given file entry to the DRM device debugfs file list to be created on | 
|---|
| 465 | * drm_debugfs_init. | 
|---|
| 466 | */ | 
|---|
| 467 | void drm_debugfs_add_file(struct drm_device *dev, const char *name, | 
|---|
| 468 | int (*show)(struct seq_file*, void*), void *data) | 
|---|
| 469 | { | 
|---|
| 470 | struct drm_debugfs_entry *entry = drmm_kzalloc(dev, size: sizeof(*entry), GFP_KERNEL); | 
|---|
| 471 |  | 
|---|
| 472 | if (!entry) | 
|---|
| 473 | return; | 
|---|
| 474 |  | 
|---|
| 475 | entry->file.name = name; | 
|---|
| 476 | entry->file.show = show; | 
|---|
| 477 | entry->file.data = data; | 
|---|
| 478 | entry->dev = dev; | 
|---|
| 479 |  | 
|---|
| 480 | debugfs_create_file(name, 0444, dev->debugfs_root, entry, | 
|---|
| 481 | &drm_debugfs_entry_fops); | 
|---|
| 482 | } | 
|---|
| 483 | EXPORT_SYMBOL(drm_debugfs_add_file); | 
|---|
| 484 |  | 
|---|
| 485 | /** | 
|---|
| 486 | * drm_debugfs_add_files - Add an array of files to the DRM device debugfs file list | 
|---|
| 487 | * @dev: drm device for the ioctl | 
|---|
| 488 | * @files: The array of files to create | 
|---|
| 489 | * @count: The number of files given | 
|---|
| 490 | * | 
|---|
| 491 | * Add a given set of debugfs files represented by an array of | 
|---|
| 492 | * &struct drm_debugfs_info in the DRM device debugfs file list. | 
|---|
| 493 | */ | 
|---|
| 494 | void drm_debugfs_add_files(struct drm_device *dev, const struct drm_debugfs_info *files, int count) | 
|---|
| 495 | { | 
|---|
| 496 | int i; | 
|---|
| 497 |  | 
|---|
| 498 | for (i = 0; i < count; i++) | 
|---|
| 499 | drm_debugfs_add_file(dev, files[i].name, files[i].show, files[i].data); | 
|---|
| 500 | } | 
|---|
| 501 | EXPORT_SYMBOL(drm_debugfs_add_files); | 
|---|
| 502 |  | 
|---|
| 503 | static int connector_show(struct seq_file *m, void *data) | 
|---|
| 504 | { | 
|---|
| 505 | struct drm_connector *connector = m->private; | 
|---|
| 506 |  | 
|---|
| 507 | seq_printf(m, fmt: "%s\n", drm_get_connector_force_name(force: connector->force)); | 
|---|
| 508 |  | 
|---|
| 509 | return 0; | 
|---|
| 510 | } | 
|---|
| 511 |  | 
|---|
| 512 | static int connector_open(struct inode *inode, struct file *file) | 
|---|
| 513 | { | 
|---|
| 514 | struct drm_connector *dev = inode->i_private; | 
|---|
| 515 |  | 
|---|
| 516 | return single_open(file, connector_show, dev); | 
|---|
| 517 | } | 
|---|
| 518 |  | 
|---|
| 519 | static ssize_t connector_write(struct file *file, const char __user *ubuf, | 
|---|
| 520 | size_t len, loff_t *offp) | 
|---|
| 521 | { | 
|---|
| 522 | struct seq_file *m = file->private_data; | 
|---|
| 523 | struct drm_connector *connector = m->private; | 
|---|
| 524 | char buf[12]; | 
|---|
| 525 |  | 
|---|
| 526 | if (len > sizeof(buf) - 1) | 
|---|
| 527 | return -EINVAL; | 
|---|
| 528 |  | 
|---|
| 529 | if (copy_from_user(to: buf, from: ubuf, n: len)) | 
|---|
| 530 | return -EFAULT; | 
|---|
| 531 |  | 
|---|
| 532 | buf[len] = '\0'; | 
|---|
| 533 |  | 
|---|
| 534 | if (sysfs_streq(s1: buf, s2: "on")) | 
|---|
| 535 | connector->force = DRM_FORCE_ON; | 
|---|
| 536 | else if (sysfs_streq(s1: buf, s2: "digital")) | 
|---|
| 537 | connector->force = DRM_FORCE_ON_DIGITAL; | 
|---|
| 538 | else if (sysfs_streq(s1: buf, s2: "off")) | 
|---|
| 539 | connector->force = DRM_FORCE_OFF; | 
|---|
| 540 | else if (sysfs_streq(s1: buf, s2: "unspecified")) | 
|---|
| 541 | connector->force = DRM_FORCE_UNSPECIFIED; | 
|---|
| 542 | else | 
|---|
| 543 | return -EINVAL; | 
|---|
| 544 |  | 
|---|
| 545 | return len; | 
|---|
| 546 | } | 
|---|
| 547 |  | 
|---|
| 548 | static int edid_show(struct seq_file *m, void *data) | 
|---|
| 549 | { | 
|---|
| 550 | return drm_edid_override_show(connector: m->private, m); | 
|---|
| 551 | } | 
|---|
| 552 |  | 
|---|
| 553 | static int edid_open(struct inode *inode, struct file *file) | 
|---|
| 554 | { | 
|---|
| 555 | struct drm_connector *dev = inode->i_private; | 
|---|
| 556 |  | 
|---|
| 557 | return single_open(file, edid_show, dev); | 
|---|
| 558 | } | 
|---|
| 559 |  | 
|---|
| 560 | static ssize_t edid_write(struct file *file, const char __user *ubuf, | 
|---|
| 561 | size_t len, loff_t *offp) | 
|---|
| 562 | { | 
|---|
| 563 | struct seq_file *m = file->private_data; | 
|---|
| 564 | struct drm_connector *connector = m->private; | 
|---|
| 565 | char *buf; | 
|---|
| 566 | int ret; | 
|---|
| 567 |  | 
|---|
| 568 | buf = memdup_user(ubuf, len); | 
|---|
| 569 | if (IS_ERR(ptr: buf)) | 
|---|
| 570 | return PTR_ERR(ptr: buf); | 
|---|
| 571 |  | 
|---|
| 572 | if (len == 5 && !strncmp(buf, "reset", 5)) | 
|---|
| 573 | ret = drm_edid_override_reset(connector); | 
|---|
| 574 | else | 
|---|
| 575 | ret = drm_edid_override_set(connector, edid: buf, size: len); | 
|---|
| 576 |  | 
|---|
| 577 | kfree(objp: buf); | 
|---|
| 578 |  | 
|---|
| 579 | return ret ? ret : len; | 
|---|
| 580 | } | 
|---|
| 581 |  | 
|---|
| 582 | /* | 
|---|
| 583 | * Returns the min and max vrr vfreq through the connector's debugfs file. | 
|---|
| 584 | * Example usage: cat /sys/kernel/debug/dri/0/DP-1/vrr_range | 
|---|
| 585 | */ | 
|---|
| 586 | static int vrr_range_show(struct seq_file *m, void *data) | 
|---|
| 587 | { | 
|---|
| 588 | struct drm_connector *connector = m->private; | 
|---|
| 589 |  | 
|---|
| 590 | if (connector->status != connector_status_connected) | 
|---|
| 591 | return -ENODEV; | 
|---|
| 592 |  | 
|---|
| 593 | seq_printf(m, fmt: "Min: %u\n", connector->display_info.monitor_range.min_vfreq); | 
|---|
| 594 | seq_printf(m, fmt: "Max: %u\n", connector->display_info.monitor_range.max_vfreq); | 
|---|
| 595 |  | 
|---|
| 596 | return 0; | 
|---|
| 597 | } | 
|---|
| 598 | DEFINE_SHOW_ATTRIBUTE(vrr_range); | 
|---|
| 599 |  | 
|---|
| 600 | /* | 
|---|
| 601 | * Returns Connector's max supported bpc through debugfs file. | 
|---|
| 602 | * Example usage: cat /sys/kernel/debug/dri/0/DP-1/output_bpc | 
|---|
| 603 | */ | 
|---|
| 604 | static int output_bpc_show(struct seq_file *m, void *data) | 
|---|
| 605 | { | 
|---|
| 606 | struct drm_connector *connector = m->private; | 
|---|
| 607 |  | 
|---|
| 608 | if (connector->status != connector_status_connected) | 
|---|
| 609 | return -ENODEV; | 
|---|
| 610 |  | 
|---|
| 611 | seq_printf(m, fmt: "Maximum: %u\n", connector->display_info.bpc); | 
|---|
| 612 |  | 
|---|
| 613 | return 0; | 
|---|
| 614 | } | 
|---|
| 615 | DEFINE_SHOW_ATTRIBUTE(output_bpc); | 
|---|
| 616 |  | 
|---|
| 617 | static const struct file_operations drm_edid_fops = { | 
|---|
| 618 | .owner = THIS_MODULE, | 
|---|
| 619 | .open = edid_open, | 
|---|
| 620 | .read = seq_read, | 
|---|
| 621 | .llseek = seq_lseek, | 
|---|
| 622 | .release = single_release, | 
|---|
| 623 | .write = edid_write | 
|---|
| 624 | }; | 
|---|
| 625 |  | 
|---|
| 626 |  | 
|---|
| 627 | static const struct file_operations drm_connector_fops = { | 
|---|
| 628 | .owner = THIS_MODULE, | 
|---|
| 629 | .open = connector_open, | 
|---|
| 630 | .read = seq_read, | 
|---|
| 631 | .llseek = seq_lseek, | 
|---|
| 632 | .release = single_release, | 
|---|
| 633 | .write = connector_write | 
|---|
| 634 | }; | 
|---|
| 635 |  | 
|---|
| 636 | static ssize_t | 
|---|
| 637 | audio_infoframe_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos) | 
|---|
| 638 | { | 
|---|
| 639 | struct drm_connector_hdmi_infoframe *infoframe; | 
|---|
| 640 | struct drm_connector *connector; | 
|---|
| 641 | union hdmi_infoframe *frame; | 
|---|
| 642 | u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)]; | 
|---|
| 643 | ssize_t len = 0; | 
|---|
| 644 |  | 
|---|
| 645 | connector = filp->private_data; | 
|---|
| 646 | mutex_lock(lock: &connector->hdmi.infoframes.lock); | 
|---|
| 647 |  | 
|---|
| 648 | infoframe = &connector->hdmi.infoframes.audio; | 
|---|
| 649 | if (!infoframe->set) | 
|---|
| 650 | goto out; | 
|---|
| 651 |  | 
|---|
| 652 | frame = &infoframe->data; | 
|---|
| 653 | len = hdmi_infoframe_pack(frame, buffer: buf, size: sizeof(buf)); | 
|---|
| 654 | if (len < 0) | 
|---|
| 655 | goto out; | 
|---|
| 656 |  | 
|---|
| 657 | len = simple_read_from_buffer(to: ubuf, count, ppos, from: buf, available: len); | 
|---|
| 658 |  | 
|---|
| 659 | out: | 
|---|
| 660 | mutex_unlock(lock: &connector->hdmi.infoframes.lock); | 
|---|
| 661 | return len; | 
|---|
| 662 | } | 
|---|
| 663 |  | 
|---|
| 664 | static const struct file_operations audio_infoframe_fops = { | 
|---|
| 665 | .owner   = THIS_MODULE, | 
|---|
| 666 | .open    = simple_open, | 
|---|
| 667 | .read    = audio_infoframe_read, | 
|---|
| 668 | }; | 
|---|
| 669 |  | 
|---|
| 670 | static int create_hdmi_audio_infoframe_file(struct drm_connector *connector, | 
|---|
| 671 | struct dentry *parent) | 
|---|
| 672 | { | 
|---|
| 673 | struct dentry *file; | 
|---|
| 674 |  | 
|---|
| 675 | file = debugfs_create_file( "audio", 0400, parent, connector, &audio_infoframe_fops); | 
|---|
| 676 | if (IS_ERR(ptr: file)) | 
|---|
| 677 | return PTR_ERR(ptr: file); | 
|---|
| 678 |  | 
|---|
| 679 | return 0; | 
|---|
| 680 | } | 
|---|
| 681 |  | 
|---|
| 682 | #define DEFINE_INFOFRAME_FILE(_f) \ | 
|---|
| 683 | static ssize_t _f##_read_infoframe(struct file *filp, \ | 
|---|
| 684 | char __user *ubuf, \ | 
|---|
| 685 | size_t count,      \ | 
|---|
| 686 | loff_t *ppos)      \ | 
|---|
| 687 | { \ | 
|---|
| 688 | struct drm_connector_hdmi_infoframe *infoframe; \ | 
|---|
| 689 | struct drm_connector_state *conn_state; \ | 
|---|
| 690 | struct drm_connector *connector; \ | 
|---|
| 691 | union hdmi_infoframe *frame; \ | 
|---|
| 692 | struct drm_device *dev; \ | 
|---|
| 693 | u8 buf[HDMI_INFOFRAME_SIZE(MAX)]; \ | 
|---|
| 694 | ssize_t len = 0; \ | 
|---|
| 695 | \ | 
|---|
| 696 | connector = filp->private_data; \ | 
|---|
| 697 | dev = connector->dev; \ | 
|---|
| 698 | \ | 
|---|
| 699 | drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); \ | 
|---|
| 700 | \ | 
|---|
| 701 | conn_state = connector->state; \ | 
|---|
| 702 | infoframe = &conn_state->hdmi.infoframes._f; \ | 
|---|
| 703 | if (!infoframe->set) \ | 
|---|
| 704 | goto out; \ | 
|---|
| 705 | \ | 
|---|
| 706 | frame = &infoframe->data; \ | 
|---|
| 707 | len = hdmi_infoframe_pack(frame, buf, sizeof(buf)); \ | 
|---|
| 708 | if (len < 0) \ | 
|---|
| 709 | goto out; \ | 
|---|
| 710 | \ | 
|---|
| 711 | len = simple_read_from_buffer(ubuf, count, ppos, buf, len); \ | 
|---|
| 712 | \ | 
|---|
| 713 | out: \ | 
|---|
| 714 | drm_modeset_unlock(&dev->mode_config.connection_mutex); \ | 
|---|
| 715 | return len; \ | 
|---|
| 716 | } \ | 
|---|
| 717 | \ | 
|---|
| 718 | static const struct file_operations _f##_infoframe_fops = { \ | 
|---|
| 719 | .owner = THIS_MODULE, \ | 
|---|
| 720 | .open = simple_open, \ | 
|---|
| 721 | .read = _f##_read_infoframe, \ | 
|---|
| 722 | }; \ | 
|---|
| 723 | \ | 
|---|
| 724 | static int create_hdmi_## _f ## _infoframe_file(struct drm_connector *connector, \ | 
|---|
| 725 | struct dentry *parent) \ | 
|---|
| 726 | { \ | 
|---|
| 727 | struct dentry *file; \ | 
|---|
| 728 | \ | 
|---|
| 729 | file = debugfs_create_file(#_f, 0400, parent, connector, &_f ## _infoframe_fops); \ | 
|---|
| 730 | if (IS_ERR(file)) \ | 
|---|
| 731 | return PTR_ERR(file); \ | 
|---|
| 732 | \ | 
|---|
| 733 | return 0; \ | 
|---|
| 734 | } | 
|---|
| 735 |  | 
|---|
| 736 | DEFINE_INFOFRAME_FILE(avi); | 
|---|
| 737 | DEFINE_INFOFRAME_FILE(hdmi); | 
|---|
| 738 | DEFINE_INFOFRAME_FILE(hdr_drm); | 
|---|
| 739 | DEFINE_INFOFRAME_FILE(spd); | 
|---|
| 740 |  | 
|---|
| 741 | static int create_hdmi_infoframe_files(struct drm_connector *connector, | 
|---|
| 742 | struct dentry *parent) | 
|---|
| 743 | { | 
|---|
| 744 | int ret; | 
|---|
| 745 |  | 
|---|
| 746 | ret = create_hdmi_audio_infoframe_file(connector, parent); | 
|---|
| 747 | if (ret) | 
|---|
| 748 | return ret; | 
|---|
| 749 |  | 
|---|
| 750 | ret = create_hdmi_avi_infoframe_file(connector, parent); | 
|---|
| 751 | if (ret) | 
|---|
| 752 | return ret; | 
|---|
| 753 |  | 
|---|
| 754 | ret = create_hdmi_hdmi_infoframe_file(connector, parent); | 
|---|
| 755 | if (ret) | 
|---|
| 756 | return ret; | 
|---|
| 757 |  | 
|---|
| 758 | ret = create_hdmi_hdr_drm_infoframe_file(connector, parent); | 
|---|
| 759 | if (ret) | 
|---|
| 760 | return ret; | 
|---|
| 761 |  | 
|---|
| 762 | ret = create_hdmi_spd_infoframe_file(connector, parent); | 
|---|
| 763 | if (ret) | 
|---|
| 764 | return ret; | 
|---|
| 765 |  | 
|---|
| 766 | return 0; | 
|---|
| 767 | } | 
|---|
| 768 |  | 
|---|
| 769 | static void hdmi_debugfs_add(struct drm_connector *connector) | 
|---|
| 770 | { | 
|---|
| 771 | struct dentry *dir; | 
|---|
| 772 |  | 
|---|
| 773 | if (!(connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || | 
|---|
| 774 | connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)) | 
|---|
| 775 | return; | 
|---|
| 776 |  | 
|---|
| 777 | dir = debugfs_create_dir(name: "infoframes", parent: connector->debugfs_entry); | 
|---|
| 778 | if (IS_ERR(ptr: dir)) | 
|---|
| 779 | return; | 
|---|
| 780 |  | 
|---|
| 781 | create_hdmi_infoframe_files(connector, parent: dir); | 
|---|
| 782 | } | 
|---|
| 783 |  | 
|---|
| 784 | void drm_debugfs_connector_add(struct drm_connector *connector) | 
|---|
| 785 | { | 
|---|
| 786 | struct drm_device *dev = connector->dev; | 
|---|
| 787 | struct dentry *root; | 
|---|
| 788 |  | 
|---|
| 789 | if (!dev->debugfs_root) | 
|---|
| 790 | return; | 
|---|
| 791 |  | 
|---|
| 792 | root = debugfs_create_dir(name: connector->name, parent: dev->debugfs_root); | 
|---|
| 793 | connector->debugfs_entry = root; | 
|---|
| 794 |  | 
|---|
| 795 | /* force */ | 
|---|
| 796 | debugfs_create_file( "force", 0644, root, connector, | 
|---|
| 797 | &drm_connector_fops); | 
|---|
| 798 |  | 
|---|
| 799 | /* edid */ | 
|---|
| 800 | debugfs_create_file( "edid_override", 0644, root, connector, | 
|---|
| 801 | &drm_edid_fops); | 
|---|
| 802 |  | 
|---|
| 803 | /* vrr range */ | 
|---|
| 804 | debugfs_create_file( "vrr_range", 0444, root, connector, | 
|---|
| 805 | &vrr_range_fops); | 
|---|
| 806 |  | 
|---|
| 807 | /* max bpc */ | 
|---|
| 808 | debugfs_create_file( "output_bpc", 0444, root, connector, | 
|---|
| 809 | &output_bpc_fops); | 
|---|
| 810 |  | 
|---|
| 811 | hdmi_debugfs_add(connector); | 
|---|
| 812 |  | 
|---|
| 813 | if (connector->funcs->debugfs_init) | 
|---|
| 814 | connector->funcs->debugfs_init(connector, root); | 
|---|
| 815 | } | 
|---|
| 816 |  | 
|---|
| 817 | void drm_debugfs_connector_remove(struct drm_connector *connector) | 
|---|
| 818 | { | 
|---|
| 819 | if (!connector->debugfs_entry) | 
|---|
| 820 | return; | 
|---|
| 821 |  | 
|---|
| 822 | debugfs_remove_recursive(dentry: connector->debugfs_entry); | 
|---|
| 823 |  | 
|---|
| 824 | connector->debugfs_entry = NULL; | 
|---|
| 825 | } | 
|---|
| 826 |  | 
|---|
| 827 | void drm_debugfs_crtc_add(struct drm_crtc *crtc) | 
|---|
| 828 | { | 
|---|
| 829 | struct drm_device *dev = crtc->dev; | 
|---|
| 830 | struct dentry *root; | 
|---|
| 831 | char *name; | 
|---|
| 832 |  | 
|---|
| 833 | name = kasprintf(GFP_KERNEL, fmt: "crtc-%d", crtc->index); | 
|---|
| 834 | if (!name) | 
|---|
| 835 | return; | 
|---|
| 836 |  | 
|---|
| 837 | root = debugfs_create_dir(name, parent: dev->debugfs_root); | 
|---|
| 838 | kfree(objp: name); | 
|---|
| 839 |  | 
|---|
| 840 | crtc->debugfs_entry = root; | 
|---|
| 841 |  | 
|---|
| 842 | drm_debugfs_crtc_crc_add(crtc); | 
|---|
| 843 | } | 
|---|
| 844 |  | 
|---|
| 845 | void drm_debugfs_crtc_remove(struct drm_crtc *crtc) | 
|---|
| 846 | { | 
|---|
| 847 | debugfs_remove_recursive(dentry: crtc->debugfs_entry); | 
|---|
| 848 | crtc->debugfs_entry = NULL; | 
|---|
| 849 | } | 
|---|
| 850 |  | 
|---|
| 851 | void drm_debugfs_encoder_add(struct drm_encoder *encoder) | 
|---|
| 852 | { | 
|---|
| 853 | struct drm_minor *minor = encoder->dev->primary; | 
|---|
| 854 | struct dentry *root; | 
|---|
| 855 | char *name; | 
|---|
| 856 |  | 
|---|
| 857 | name = kasprintf(GFP_KERNEL, fmt: "encoder-%d", encoder->index); | 
|---|
| 858 | if (!name) | 
|---|
| 859 | return; | 
|---|
| 860 |  | 
|---|
| 861 | root = debugfs_create_dir(name, parent: minor->debugfs_root); | 
|---|
| 862 | kfree(objp: name); | 
|---|
| 863 |  | 
|---|
| 864 | encoder->debugfs_entry = root; | 
|---|
| 865 |  | 
|---|
| 866 | drm_bridge_debugfs_encoder_params(root, encoder); | 
|---|
| 867 |  | 
|---|
| 868 | if (encoder->funcs && encoder->funcs->debugfs_init) | 
|---|
| 869 | encoder->funcs->debugfs_init(encoder, root); | 
|---|
| 870 | } | 
|---|
| 871 |  | 
|---|
| 872 | void drm_debugfs_encoder_remove(struct drm_encoder *encoder) | 
|---|
| 873 | { | 
|---|
| 874 | debugfs_remove_recursive(dentry: encoder->debugfs_entry); | 
|---|
| 875 | encoder->debugfs_entry = NULL; | 
|---|
| 876 | } | 
|---|
| 877 |  | 
|---|