blob: c091f164b73f4e2b143a7a90fe5b44c311b9d6b1 [file] [log] [blame]
/*
*
* (C) COPYRIGHT 2013-2019 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU licence.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* SPDX-License-Identifier: GPL-2.0
*
*/
/*
* Debugfs interface to dump the memory visible to the GPU
*/
#include "mali_kbase_debug_mem_view.h"
#include "mali_kbase.h"
#include <linux/list.h>
#include <linux/file.h>
#ifdef CONFIG_DEBUG_FS
#if (KERNEL_VERSION(4, 1, 0) > LINUX_VERSION_CODE)
#define get_file_rcu(x) atomic_long_inc_not_zero(&(x)->f_count)
#endif
struct debug_mem_mapping {
struct list_head node;
struct kbase_mem_phy_alloc *alloc;
unsigned long flags;
u64 start_pfn;
size_t nr_pages;
};
struct debug_mem_data {
struct list_head mapping_list;
struct kbase_context *kctx;
};
struct debug_mem_seq_off {
struct list_head *lh;
size_t offset;
};
static void *debug_mem_start(struct seq_file *m, loff_t *_pos)
{
struct debug_mem_data *mem_data = m->private;
struct debug_mem_seq_off *data;
struct debug_mem_mapping *map;
loff_t pos = *_pos;
list_for_each_entry(map, &mem_data->mapping_list, node) {
if (pos >= map->nr_pages) {
pos -= map->nr_pages;
} else {
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
data->lh = &map->node;
data->offset = pos;
return data;
}
}
/* Beyond the end */
return NULL;
}
static void debug_mem_stop(struct seq_file *m, void *v)
{
kfree(v);
}
static void *debug_mem_next(struct seq_file *m, void *v, loff_t *pos)
{
struct debug_mem_data *mem_data = m->private;
struct debug_mem_seq_off *data = v;
struct debug_mem_mapping *map;
map = list_entry(data->lh, struct debug_mem_mapping, node);
if (data->offset < map->nr_pages - 1) {
data->offset++;
++*pos;
return data;
}
if (list_is_last(data->lh, &mem_data->mapping_list)) {
kfree(data);
return NULL;
}
data->lh = data->lh->next;
data->offset = 0;
++*pos;
return data;
}
static int debug_mem_show(struct seq_file *m, void *v)
{
struct debug_mem_data *mem_data = m->private;
struct debug_mem_seq_off *data = v;
struct debug_mem_mapping *map;
int i, j;
struct page *page;
uint32_t *mapping;
pgprot_t prot = PAGE_KERNEL;
map = list_entry(data->lh, struct debug_mem_mapping, node);
kbase_gpu_vm_lock(mem_data->kctx);
if (data->offset >= map->alloc->nents) {
seq_printf(m, "%016llx: Unbacked page\n\n", (map->start_pfn +
data->offset) << PAGE_SHIFT);
goto out;
}
if (!(map->flags & KBASE_REG_CPU_CACHED))
prot = pgprot_writecombine(prot);
page = as_page(map->alloc->pages[data->offset]);
mapping = vmap(&page, 1, VM_MAP, prot);
if (!mapping)
goto out;
for (i = 0; i < PAGE_SIZE; i += 4*sizeof(*mapping)) {
seq_printf(m, "%016llx:", i + ((map->start_pfn +
data->offset) << PAGE_SHIFT));
for (j = 0; j < 4*sizeof(*mapping); j += sizeof(*mapping))
seq_printf(m, " %08x", mapping[(i+j)/sizeof(*mapping)]);
seq_putc(m, '\n');
}
vunmap(mapping);
seq_putc(m, '\n');
out:
kbase_gpu_vm_unlock(mem_data->kctx);
return 0;
}
static const struct seq_operations ops = {
.start = debug_mem_start,
.next = debug_mem_next,
.stop = debug_mem_stop,
.show = debug_mem_show,
};
static int debug_mem_zone_open(struct rb_root *rbtree,
struct debug_mem_data *mem_data)
{
int ret = 0;
struct rb_node *p;
struct kbase_va_region *reg;
struct debug_mem_mapping *mapping;
for (p = rb_first(rbtree); p; p = rb_next(p)) {
reg = rb_entry(p, struct kbase_va_region, rblink);
if (reg->gpu_alloc == NULL)
/* Empty region - ignore */
continue;
mapping = kmalloc(sizeof(*mapping), GFP_KERNEL);
if (!mapping) {
ret = -ENOMEM;
goto out;
}
mapping->alloc = kbase_mem_phy_alloc_get(reg->gpu_alloc);
mapping->start_pfn = reg->start_pfn;
mapping->nr_pages = reg->nr_pages;
mapping->flags = reg->flags;
list_add_tail(&mapping->node, &mem_data->mapping_list);
}
out:
return ret;
}
static int debug_mem_open(struct inode *i, struct file *file)
{
struct kbase_context *const kctx = i->i_private;
struct debug_mem_data *mem_data;
int ret;
if (get_file_rcu(kctx->filp) == 0)
return -ENOENT;
ret = seq_open(file, &ops);
if (ret)
goto open_fail;
mem_data = kmalloc(sizeof(*mem_data), GFP_KERNEL);
if (!mem_data) {
ret = -ENOMEM;
goto out;
}
mem_data->kctx = kctx;
INIT_LIST_HEAD(&mem_data->mapping_list);
kbase_gpu_vm_lock(kctx);
ret = debug_mem_zone_open(&kctx->reg_rbtree_same, mem_data);
if (0 != ret) {
kbase_gpu_vm_unlock(kctx);
goto out;
}
ret = debug_mem_zone_open(&kctx->reg_rbtree_custom, mem_data);
if (0 != ret) {
kbase_gpu_vm_unlock(kctx);
goto out;
}
kbase_gpu_vm_unlock(kctx);
((struct seq_file *)file->private_data)->private = mem_data;
return 0;
out:
if (mem_data) {
while (!list_empty(&mem_data->mapping_list)) {
struct debug_mem_mapping *mapping;
mapping = list_first_entry(&mem_data->mapping_list,
struct debug_mem_mapping, node);
kbase_mem_phy_alloc_put(mapping->alloc);
list_del(&mapping->node);
kfree(mapping);
}
kfree(mem_data);
}
seq_release(i, file);
open_fail:
fput(kctx->filp);
return ret;
}
static int debug_mem_release(struct inode *inode, struct file *file)
{
struct kbase_context *const kctx = inode->i_private;
struct seq_file *sfile = file->private_data;
struct debug_mem_data *mem_data = sfile->private;
struct debug_mem_mapping *mapping;
seq_release(inode, file);
while (!list_empty(&mem_data->mapping_list)) {
mapping = list_first_entry(&mem_data->mapping_list,
struct debug_mem_mapping, node);
kbase_mem_phy_alloc_put(mapping->alloc);
list_del(&mapping->node);
kfree(mapping);
}
kfree(mem_data);
fput(kctx->filp);
return 0;
}
static const struct file_operations kbase_debug_mem_view_fops = {
.owner = THIS_MODULE,
.open = debug_mem_open,
.release = debug_mem_release,
.read = seq_read,
.llseek = seq_lseek
};
void kbase_debug_mem_view_init(struct kbase_context *const kctx)
{
/* Caller already ensures this, but we keep the pattern for
* maintenance safety.
*/
if (WARN_ON(!kctx) ||
WARN_ON(IS_ERR_OR_NULL(kctx->kctx_dentry)))
return;
debugfs_create_file("mem_view", 0400, kctx->kctx_dentry, kctx,
&kbase_debug_mem_view_fops);
}
#endif