| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2002,2008-2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <linux/debugfs.h> |
| #include <linux/io.h> |
| |
| #include "kgsl_debugfs.h" |
| #include "kgsl_device.h" |
| #include "kgsl_pool.h" |
| #include "kgsl_sharedmem.h" |
| |
| struct dentry *kgsl_debugfs_dir, *mempools_debugfs; |
| static struct dentry *proc_d_debugfs; |
| |
| static void kgsl_qdss_gfx_register_probe(struct kgsl_device *device) |
| { |
| struct resource *res; |
| |
| res = platform_get_resource_byname(device->pdev, IORESOURCE_MEM, |
| "qdss_gfx"); |
| |
| if (res == NULL) |
| return; |
| |
| device->qdss_gfx_virt = devm_ioremap(&device->pdev->dev, res->start, |
| resource_size(res)); |
| |
| if (device->qdss_gfx_virt == NULL) |
| dev_warn(device->dev, "qdss_gfx ioremap failed\n"); |
| } |
| |
| static int _isdb_set(void *data, u64 val) |
| { |
| struct kgsl_device *device = data; |
| |
| if (device->qdss_gfx_virt == NULL) |
| kgsl_qdss_gfx_register_probe(device); |
| |
| device->set_isdb_breakpoint = val ? true : false; |
| return 0; |
| } |
| |
| static int _isdb_get(void *data, u64 *val) |
| { |
| struct kgsl_device *device = data; |
| |
| *val = device->set_isdb_breakpoint ? 1 : 0; |
| return 0; |
| } |
| |
| DEFINE_DEBUGFS_ATTRIBUTE(_isdb_fops, _isdb_get, _isdb_set, "%llu\n"); |
| |
| static int globals_show(struct seq_file *s, void *unused) |
| { |
| struct kgsl_device *device = s->private; |
| struct kgsl_global_memdesc *md; |
| |
| list_for_each_entry(md, &device->globals, node) { |
| struct kgsl_memdesc *memdesc = &md->memdesc; |
| char flags[6]; |
| |
| flags[0] = memdesc->priv & KGSL_MEMDESC_PRIVILEGED ? 'p' : '-'; |
| flags[1] = !(memdesc->flags & KGSL_MEMFLAGS_GPUREADONLY) ? 'w' : '-'; |
| flags[2] = kgsl_memdesc_is_secured(memdesc) ? 's' : '-'; |
| flags[3] = memdesc->priv & KGSL_MEMDESC_RANDOM ? 'r' : '-'; |
| flags[4] = memdesc->priv & KGSL_MEMDESC_UCODE ? 'u' : '-'; |
| flags[5] = '\0'; |
| |
| seq_printf(s, "0x%pK-0x%pK %16llu %5s %s\n", |
| (u64 *)(uintptr_t) memdesc->gpuaddr, |
| (u64 *)(uintptr_t) (memdesc->gpuaddr + |
| memdesc->size - 1), memdesc->size, flags, |
| md->name); |
| } |
| |
| return 0; |
| } |
| |
| DEFINE_SHOW_ATTRIBUTE(globals); |
| |
| static int _pool_size_get(void *data, u64 *val) |
| { |
| *val = (u64) kgsl_pool_size_total(); |
| return 0; |
| } |
| |
| DEFINE_DEBUGFS_ATTRIBUTE(_pool_size_fops, _pool_size_get, NULL, "%llu\n"); |
| |
| DEFINE_DEBUGFS_ATTRIBUTE(_reserved_fops, |
| kgsl_pool_reserved_get, NULL, "%llu\n"); |
| DEFINE_DEBUGFS_ATTRIBUTE(_page_count_fops, |
| kgsl_pool_page_count_get, NULL, "%llu\n"); |
| |
| void kgsl_pool_init_debugfs(struct dentry *pool_debugfs, |
| char *name, void *pool) |
| { |
| struct dentry *dentry; |
| |
| pool_debugfs = debugfs_create_dir(name, mempools_debugfs); |
| |
| if (IS_ERR_OR_NULL(pool_debugfs)) { |
| WARN((pool_debugfs == NULL), |
| "Unable to create debugfs dir for %s\n", name); |
| pool_debugfs = NULL; |
| return; |
| } |
| |
| dentry = debugfs_create_file("reserved", 0444, |
| pool_debugfs, pool, &_reserved_fops); |
| |
| WARN((IS_ERR_OR_NULL(dentry)), |
| "Unable to create 'reserved' file for %s\n", name); |
| |
| dentry = debugfs_create_file("count", 0444, |
| pool_debugfs, pool, &_page_count_fops); |
| |
| WARN((IS_ERR_OR_NULL(dentry)), |
| "Unable to create 'count' file for %s\n", name); |
| } |
| |
| void kgsl_device_debugfs_init(struct kgsl_device *device) |
| { |
| struct dentry *snapshot_dir; |
| |
| if (IS_ERR_OR_NULL(kgsl_debugfs_dir)) |
| return; |
| |
| device->d_debugfs = debugfs_create_dir(device->name, |
| kgsl_debugfs_dir); |
| |
| debugfs_create_file("globals", 0444, device->d_debugfs, device, |
| &globals_fops); |
| |
| snapshot_dir = debugfs_create_dir("snapshot", kgsl_debugfs_dir); |
| debugfs_create_file("break_isdb", 0644, snapshot_dir, device, |
| &_isdb_fops); |
| } |
| |
| void kgsl_device_debugfs_close(struct kgsl_device *device) |
| { |
| debugfs_remove_recursive(device->d_debugfs); |
| } |
| |
| static const char *memtype_str(int memtype) |
| { |
| if (memtype == KGSL_MEM_ENTRY_KERNEL) |
| return "gpumem"; |
| else if (memtype == KGSL_MEM_ENTRY_USER) |
| return "usermem"; |
| else if (memtype == KGSL_MEM_ENTRY_ION) |
| return "ion"; |
| |
| return "unknown"; |
| } |
| |
| static char get_alignflag(const struct kgsl_memdesc *m) |
| { |
| int align = kgsl_memdesc_get_align(m); |
| |
| if (align >= ilog2(SZ_1M)) |
| return 'L'; |
| else if (align >= ilog2(SZ_64K)) |
| return 'l'; |
| return '-'; |
| } |
| |
| static char get_cacheflag(const struct kgsl_memdesc *m) |
| { |
| static const char table[] = { |
| [KGSL_CACHEMODE_WRITECOMBINE] = '-', |
| [KGSL_CACHEMODE_UNCACHED] = 'u', |
| [KGSL_CACHEMODE_WRITEBACK] = 'b', |
| [KGSL_CACHEMODE_WRITETHROUGH] = 't', |
| }; |
| |
| return table[kgsl_memdesc_get_cachemode(m)]; |
| } |
| |
| |
| static int print_mem_entry(void *data, void *ptr) |
| { |
| struct seq_file *s = data; |
| struct kgsl_mem_entry *entry = ptr; |
| char flags[11]; |
| char usage[16]; |
| struct kgsl_memdesc *m = &entry->memdesc; |
| unsigned int usermem_type = kgsl_memdesc_usermem_type(m); |
| int egl_surface_count = 0, egl_image_count = 0; |
| unsigned long inode_number = 0; |
| u32 map_count = atomic_read(&entry->map_count); |
| |
| flags[0] = kgsl_memdesc_is_global(m) ? 'g' : '-'; |
| flags[1] = '-'; |
| flags[2] = !(m->flags & KGSL_MEMFLAGS_GPUREADONLY) ? 'w' : '-'; |
| flags[3] = get_alignflag(m); |
| flags[4] = get_cacheflag(m); |
| flags[5] = kgsl_memdesc_use_cpu_map(m) ? 'p' : '-'; |
| /* Show Y if at least one vma has this entry mapped (could be multiple) */ |
| flags[6] = map_count ? 'Y' : 'N'; |
| flags[7] = kgsl_memdesc_is_secured(m) ? 's' : '-'; |
| flags[8] = '-'; |
| flags[9] = m->flags & KGSL_MEMFLAGS_VBO ? 'v' : '-'; |
| flags[10] = '\0'; |
| |
| kgsl_get_memory_usage(usage, sizeof(usage), m->flags); |
| |
| if (usermem_type == KGSL_MEM_ENTRY_ION) { |
| kgsl_get_egl_counts(entry, &egl_surface_count, |
| &egl_image_count); |
| inode_number = kgsl_get_dmabuf_inode_number(entry); |
| } |
| |
| seq_printf(s, "%pK %pK %16llu %5d %10s %10s %16s %5d %10d %6d %6d %10lu", |
| (uint64_t *)(uintptr_t) m->gpuaddr, |
| /* |
| * Show zero for the useraddr - we can't reliably track |
| * that value for multiple vmas anyway |
| */ |
| NULL, m->size, entry->id, flags, |
| memtype_str(usermem_type), |
| usage, (m->sgt ? m->sgt->nents : 0), map_count, |
| egl_surface_count, egl_image_count, inode_number); |
| |
| if (entry->metadata[0] != 0) |
| seq_printf(s, " %s", entry->metadata); |
| |
| seq_putc(s, '\n'); |
| |
| return 0; |
| } |
| |
| static struct kgsl_mem_entry *process_mem_seq_find(struct seq_file *s, |
| void *ptr, loff_t pos) |
| { |
| struct kgsl_mem_entry *entry = ptr; |
| struct kgsl_process_private *private = s->private; |
| int id = 0; |
| |
| loff_t temp_pos = 1; |
| |
| if (entry != SEQ_START_TOKEN) |
| id = entry->id + 1; |
| |
| spin_lock(&private->mem_lock); |
| for (entry = idr_get_next(&private->mem_idr, &id); entry; |
| id++, entry = idr_get_next(&private->mem_idr, &id), |
| temp_pos++) { |
| if (temp_pos == pos && kgsl_mem_entry_get(entry)) { |
| spin_unlock(&private->mem_lock); |
| goto found; |
| } |
| } |
| spin_unlock(&private->mem_lock); |
| |
| entry = NULL; |
| found: |
| if (ptr != SEQ_START_TOKEN) |
| kgsl_mem_entry_put(ptr); |
| |
| return entry; |
| } |
| |
| static void *process_mem_seq_start(struct seq_file *s, loff_t *pos) |
| { |
| loff_t seq_file_offset = *pos; |
| |
| if (seq_file_offset == 0) |
| return SEQ_START_TOKEN; |
| else |
| return process_mem_seq_find(s, SEQ_START_TOKEN, |
| seq_file_offset); |
| } |
| |
| static void process_mem_seq_stop(struct seq_file *s, void *ptr) |
| { |
| if (ptr && ptr != SEQ_START_TOKEN) |
| kgsl_mem_entry_put(ptr); |
| } |
| |
| static void *process_mem_seq_next(struct seq_file *s, void *ptr, |
| loff_t *pos) |
| { |
| ++*pos; |
| return process_mem_seq_find(s, ptr, 1); |
| } |
| |
| static int process_mem_seq_show(struct seq_file *s, void *ptr) |
| { |
| if (ptr == SEQ_START_TOKEN) { |
| seq_printf(s, "%16s %16s %16s %5s %10s %10s %16s %5s %10s %6s %6s %10s\n", |
| "gpuaddr", "useraddr", "size", "id", "flags", "type", |
| "usage", "sglen", "mapcnt", "eglsrf", "eglimg", "inode"); |
| return 0; |
| } else |
| return print_mem_entry(s, ptr); |
| } |
| |
| static const struct seq_operations process_mem_seq_fops = { |
| .start = process_mem_seq_start, |
| .stop = process_mem_seq_stop, |
| .next = process_mem_seq_next, |
| .show = process_mem_seq_show, |
| }; |
| |
| static int process_mem_open(struct inode *inode, struct file *file) |
| { |
| int ret; |
| pid_t pid = (pid_t) (unsigned long) inode->i_private; |
| struct seq_file *s = NULL; |
| struct kgsl_process_private *private = NULL; |
| |
| private = kgsl_process_private_find(pid); |
| |
| if (!private) |
| return -ENODEV; |
| |
| ret = seq_open(file, &process_mem_seq_fops); |
| if (ret) |
| kgsl_process_private_put(private); |
| else { |
| s = file->private_data; |
| s->private = private; |
| } |
| |
| return ret; |
| } |
| |
| static int process_mem_release(struct inode *inode, struct file *file) |
| { |
| struct kgsl_process_private *private = |
| ((struct seq_file *)file->private_data)->private; |
| |
| if (private) |
| kgsl_process_private_put(private); |
| |
| return seq_release(inode, file); |
| } |
| |
| static const struct file_operations process_mem_fops = { |
| .open = process_mem_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = process_mem_release, |
| }; |
| |
| |
| static int print_vbo_ranges(int id, void *ptr, void *data) |
| { |
| kgsl_memdesc_print_vbo_ranges(ptr, data); |
| return 0; |
| } |
| |
| static int vbo_print(struct seq_file *s, void *unused) |
| { |
| struct kgsl_process_private *private = s->private; |
| |
| seq_puts(s, "id child range\n"); |
| |
| spin_lock(&private->mem_lock); |
| idr_for_each(&private->mem_idr, print_vbo_ranges, s); |
| spin_unlock(&private->mem_lock); |
| |
| return 0; |
| } |
| |
| static int vbo_open(struct inode *inode, struct file *file) |
| { |
| pid_t pid = (pid_t) (unsigned long) inode->i_private; |
| struct kgsl_process_private *private; |
| int ret; |
| |
| private = kgsl_process_private_find(pid); |
| |
| if (!private) |
| return -ENODEV; |
| |
| ret = single_open(file, vbo_print, private); |
| if (ret) |
| kgsl_process_private_put(private); |
| |
| return ret; |
| } |
| |
| static const struct file_operations vbo_fops = { |
| .open = vbo_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| /* Reuse the same release function */ |
| .release = process_mem_release, |
| }; |
| |
| /** |
| * kgsl_process_init_debugfs() - Initialize debugfs for a process |
| * @private: Pointer to process private structure created for the process |
| * |
| * kgsl_process_init_debugfs() is called at the time of creating the |
| * process struct when a process opens kgsl device for the first time. |
| * This function is not fatal - all we do is print a warning message if |
| * the files can't be created |
| */ |
| void kgsl_process_init_debugfs(struct kgsl_process_private *private) |
| { |
| unsigned char name[16]; |
| struct dentry *dentry; |
| |
| snprintf(name, sizeof(name), "%d", pid_nr(private->pid)); |
| |
| private->debug_root = debugfs_create_dir(name, proc_d_debugfs); |
| |
| if (IS_ERR(private->debug_root)) { |
| WARN_ONCE("Unable to create debugfs dir for %s\n", name); |
| private->debug_root = NULL; |
| return; |
| } |
| |
| dentry = debugfs_create_file("mem", 0444, private->debug_root, |
| (void *) ((unsigned long) pid_nr(private->pid)), &process_mem_fops); |
| |
| if (IS_ERR(dentry)) |
| WARN_ONCE("Unable to create 'mem' file for %s\n", name); |
| |
| debugfs_create_file("vbos", 0444, private->debug_root, |
| (void *) ((unsigned long) pid_nr(private->pid)), &vbo_fops); |
| } |
| |
| void kgsl_core_debugfs_init(void) |
| { |
| struct dentry *debug_dir, *dentry; |
| |
| kgsl_debugfs_dir = debugfs_create_dir("kgsl", NULL); |
| if (IS_ERR_OR_NULL(kgsl_debugfs_dir)) |
| return; |
| |
| debug_dir = debugfs_create_dir("debug", kgsl_debugfs_dir); |
| |
| proc_d_debugfs = debugfs_create_dir("proc", kgsl_debugfs_dir); |
| |
| debugfs_create_bool("strict_memory", 0644, debug_dir, |
| &kgsl_sharedmem_noretry_flag); |
| |
| mempools_debugfs = debugfs_create_dir("mempools", kgsl_debugfs_dir); |
| |
| if (IS_ERR_OR_NULL(mempools_debugfs)) |
| return; |
| |
| dentry = debugfs_create_file("pool_size", 0444, |
| mempools_debugfs, NULL, &_pool_size_fops); |
| |
| WARN((IS_ERR_OR_NULL(dentry)), |
| "Unable to create 'pool_size' file for mempools\n"); |
| } |
| |
| void kgsl_core_debugfs_close(void) |
| { |
| debugfs_remove_recursive(kgsl_debugfs_dir); |
| } |