451 lines
10 KiB
C
451 lines
10 KiB
C
/* Copyright (c) 2002,2008-2011, Code Aurora Forum. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include "kgsl.h"
|
|
#include "adreno_postmortem.h"
|
|
#include "adreno.h"
|
|
|
|
#include "a200_reg.h"
|
|
|
|
unsigned int kgsl_cff_dump_enable;
|
|
int kgsl_pm_regs_enabled;
|
|
|
|
static uint32_t kgsl_ib_base;
|
|
static uint32_t kgsl_ib_size;
|
|
|
|
static struct dentry *pm_d_debugfs;
|
|
|
|
static int pm_dump_set(void *data, u64 val)
|
|
{
|
|
struct kgsl_device *device = data;
|
|
|
|
if (val) {
|
|
mutex_lock(&device->mutex);
|
|
adreno_postmortem_dump(device, 1);
|
|
mutex_unlock(&device->mutex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(pm_dump_fops,
|
|
NULL,
|
|
pm_dump_set, "%llu\n");
|
|
|
|
static int pm_regs_enabled_set(void *data, u64 val)
|
|
{
|
|
kgsl_pm_regs_enabled = val ? 1 : 0;
|
|
return 0;
|
|
}
|
|
|
|
static int pm_regs_enabled_get(void *data, u64 *val)
|
|
{
|
|
*val = kgsl_pm_regs_enabled;
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(pm_regs_enabled_fops,
|
|
pm_regs_enabled_get,
|
|
pm_regs_enabled_set, "%llu\n");
|
|
|
|
|
|
static int kgsl_cff_dump_enable_set(void *data, u64 val)
|
|
{
|
|
#ifdef CONFIG_MSM_KGSL_CFF_DUMP
|
|
kgsl_cff_dump_enable = (val != 0);
|
|
return 0;
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
static int kgsl_cff_dump_enable_get(void *data, u64 *val)
|
|
{
|
|
*val = kgsl_cff_dump_enable;
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(kgsl_cff_dump_enable_fops, kgsl_cff_dump_enable_get,
|
|
kgsl_cff_dump_enable_set, "%llu\n");
|
|
|
|
static int kgsl_dbgfs_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
|
|
file->private_data = inode->i_private;
|
|
return 0;
|
|
}
|
|
|
|
static int kgsl_dbgfs_release(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int kgsl_hex_dump(const char *prefix, int c, uint8_t *data,
|
|
int rowc, int linec, char __user *buff)
|
|
{
|
|
int ss;
|
|
/* Prefix of 20 chars max, 32 bytes per row, in groups of four - that's
|
|
* 8 groups at 8 chars per group plus a space, plus new-line, plus
|
|
* ending character */
|
|
char linebuf[20 + 64 + 1 + 1];
|
|
|
|
ss = snprintf(linebuf, sizeof(linebuf), prefix, c);
|
|
hex_dump_to_buffer(data, linec, rowc, 4, linebuf+ss,
|
|
sizeof(linebuf)-ss, 0);
|
|
strlcat(linebuf, "\n", sizeof(linebuf));
|
|
linebuf[sizeof(linebuf)-1] = 0;
|
|
ss = strlen(linebuf);
|
|
if (copy_to_user(buff, linebuf, ss+1))
|
|
return -EFAULT;
|
|
return ss;
|
|
}
|
|
|
|
static ssize_t kgsl_ib_dump_read(
|
|
struct file *file,
|
|
char __user *buff,
|
|
size_t buff_count,
|
|
loff_t *ppos)
|
|
{
|
|
int i, count = kgsl_ib_size, remaining, pos = 0, tot = 0, ss;
|
|
struct kgsl_device *device = file->private_data;
|
|
const int rowc = 32;
|
|
unsigned int pt_base, ib_memsize;
|
|
uint8_t *base_addr;
|
|
char linebuf[80];
|
|
|
|
if (!ppos || !device || !kgsl_ib_base)
|
|
return 0;
|
|
|
|
kgsl_regread(device, REG_MH_MMU_PT_BASE, &pt_base);
|
|
base_addr = kgsl_sharedmem_convertaddr(device, pt_base, kgsl_ib_base,
|
|
&ib_memsize);
|
|
|
|
if (!base_addr)
|
|
return 0;
|
|
|
|
pr_info("%s ppos=%ld, buff_count=%d, count=%d\n", __func__, (long)*ppos,
|
|
buff_count, count);
|
|
ss = snprintf(linebuf, sizeof(linebuf), "IB: base=%08x(%08x"
|
|
"), size=%d, memsize=%d\n", kgsl_ib_base,
|
|
(uint32_t)base_addr, kgsl_ib_size, ib_memsize);
|
|
if (*ppos == 0) {
|
|
if (copy_to_user(buff, linebuf, ss+1))
|
|
return -EFAULT;
|
|
tot += ss;
|
|
buff += ss;
|
|
*ppos += ss;
|
|
}
|
|
pos += ss;
|
|
remaining = count;
|
|
for (i = 0; i < count; i += rowc) {
|
|
int linec = min(remaining, rowc);
|
|
|
|
remaining -= rowc;
|
|
ss = kgsl_hex_dump("IB: %05x: ", i, base_addr, rowc, linec,
|
|
buff);
|
|
if (ss < 0)
|
|
return ss;
|
|
|
|
if (pos >= *ppos) {
|
|
if (tot+ss >= buff_count) {
|
|
ss = copy_to_user(buff, "", 1);
|
|
return tot;
|
|
}
|
|
tot += ss;
|
|
buff += ss;
|
|
*ppos += ss;
|
|
}
|
|
pos += ss;
|
|
base_addr += linec;
|
|
}
|
|
|
|
return tot;
|
|
}
|
|
|
|
static ssize_t kgsl_ib_dump_write(
|
|
struct file *file,
|
|
const char __user *buff,
|
|
size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
char local_buff[64];
|
|
|
|
if (count >= sizeof(local_buff))
|
|
return -EFAULT;
|
|
|
|
if (copy_from_user(local_buff, buff, count))
|
|
return -EFAULT;
|
|
|
|
local_buff[count] = 0; /* end of string */
|
|
sscanf(local_buff, "%x %d", &kgsl_ib_base, &kgsl_ib_size);
|
|
|
|
pr_info("%s: base=%08X size=%d\n", __func__, kgsl_ib_base,
|
|
kgsl_ib_size);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations kgsl_ib_dump_fops = {
|
|
.open = kgsl_dbgfs_open,
|
|
.release = kgsl_dbgfs_release,
|
|
.read = kgsl_ib_dump_read,
|
|
.write = kgsl_ib_dump_write,
|
|
};
|
|
|
|
static int kgsl_regread_nolock(struct kgsl_device *device,
|
|
unsigned int offsetwords, unsigned int *value)
|
|
{
|
|
unsigned int *reg;
|
|
|
|
if (offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes) {
|
|
KGSL_DRV_ERR(device, "invalid offset %d\n", offsetwords);
|
|
return -ERANGE;
|
|
}
|
|
|
|
reg = (unsigned int *)(device->regspace.mmio_virt_base
|
|
+ (offsetwords << 2));
|
|
*value = __raw_readl(reg);
|
|
return 0;
|
|
}
|
|
|
|
#define KGSL_ISTORE_START 0x5000
|
|
#define KGSL_ISTORE_LENGTH 0x600
|
|
static ssize_t kgsl_istore_read(
|
|
struct file *file,
|
|
char __user *buff,
|
|
size_t buff_count,
|
|
loff_t *ppos)
|
|
{
|
|
int i, count = KGSL_ISTORE_LENGTH, remaining, pos = 0, tot = 0;
|
|
struct kgsl_device *device = file->private_data;
|
|
const int rowc = 8;
|
|
|
|
if (!ppos || !device)
|
|
return 0;
|
|
|
|
remaining = count;
|
|
for (i = 0; i < count; i += rowc) {
|
|
unsigned int vals[rowc];
|
|
int j, ss;
|
|
int linec = min(remaining, rowc);
|
|
remaining -= rowc;
|
|
|
|
if (pos >= *ppos) {
|
|
for (j = 0; j < linec; ++j)
|
|
kgsl_regread_nolock(device,
|
|
KGSL_ISTORE_START+i+j, vals+j);
|
|
} else
|
|
memset(vals, 0, sizeof(vals));
|
|
|
|
ss = kgsl_hex_dump("IS: %04x: ", i, (uint8_t *)vals, rowc*4,
|
|
linec*4, buff);
|
|
if (ss < 0)
|
|
return ss;
|
|
|
|
if (pos >= *ppos) {
|
|
if (tot+ss >= buff_count)
|
|
return tot;
|
|
tot += ss;
|
|
buff += ss;
|
|
*ppos += ss;
|
|
}
|
|
pos += ss;
|
|
}
|
|
|
|
return tot;
|
|
}
|
|
|
|
static const struct file_operations kgsl_istore_fops = {
|
|
.open = kgsl_dbgfs_open,
|
|
.release = kgsl_dbgfs_release,
|
|
.read = kgsl_istore_read,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
typedef void (*reg_read_init_t)(struct kgsl_device *device);
|
|
typedef void (*reg_read_fill_t)(struct kgsl_device *device, int i,
|
|
unsigned int *vals, int linec);
|
|
static ssize_t kgsl_reg_read(struct kgsl_device *device, int count,
|
|
reg_read_init_t reg_read_init,
|
|
reg_read_fill_t reg_read_fill, const char *prefix, char __user *buff,
|
|
loff_t *ppos)
|
|
{
|
|
int i, remaining;
|
|
const int rowc = 8;
|
|
|
|
if (!ppos || *ppos || !device)
|
|
return 0;
|
|
|
|
mutex_lock(&device->mutex);
|
|
reg_read_init(device);
|
|
remaining = count;
|
|
for (i = 0; i < count; i += rowc) {
|
|
unsigned int vals[rowc];
|
|
int ss;
|
|
int linec = min(remaining, rowc);
|
|
remaining -= rowc;
|
|
|
|
reg_read_fill(device, i, vals, linec);
|
|
ss = kgsl_hex_dump(prefix, i, (uint8_t *)vals, rowc*4, linec*4,
|
|
buff);
|
|
if (ss < 0) {
|
|
mutex_unlock(&device->mutex);
|
|
return ss;
|
|
}
|
|
buff += ss;
|
|
*ppos += ss;
|
|
}
|
|
mutex_unlock(&device->mutex);
|
|
|
|
return *ppos;
|
|
}
|
|
|
|
|
|
static void kgsl_sx_reg_read_init(struct kgsl_device *device)
|
|
{
|
|
kgsl_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0xFF);
|
|
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
|
|
}
|
|
|
|
static void kgsl_sx_reg_read_fill(struct kgsl_device *device, int i,
|
|
unsigned int *vals, int linec)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < linec; ++j) {
|
|
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1B00 | i);
|
|
kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j);
|
|
}
|
|
}
|
|
|
|
static ssize_t kgsl_sx_debug_read(
|
|
struct file *file,
|
|
char __user *buff,
|
|
size_t buff_count,
|
|
loff_t *ppos)
|
|
{
|
|
struct kgsl_device *device = file->private_data;
|
|
return kgsl_reg_read(device, 0x1B, kgsl_sx_reg_read_init,
|
|
kgsl_sx_reg_read_fill, "SX: %02x: ", buff, ppos);
|
|
}
|
|
|
|
static const struct file_operations kgsl_sx_debug_fops = {
|
|
.open = kgsl_dbgfs_open,
|
|
.release = kgsl_dbgfs_release,
|
|
.read = kgsl_sx_debug_read,
|
|
};
|
|
|
|
static void kgsl_cp_reg_read_init(struct kgsl_device *device)
|
|
{
|
|
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
|
|
}
|
|
|
|
static void kgsl_cp_reg_read_fill(struct kgsl_device *device, int i,
|
|
unsigned int *vals, int linec)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < linec; ++j) {
|
|
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1628);
|
|
kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j);
|
|
msleep(100);
|
|
}
|
|
}
|
|
|
|
static ssize_t kgsl_cp_debug_read(
|
|
struct file *file,
|
|
char __user *buff,
|
|
size_t buff_count,
|
|
loff_t *ppos)
|
|
{
|
|
struct kgsl_device *device = file->private_data;
|
|
return kgsl_reg_read(device, 20, kgsl_cp_reg_read_init,
|
|
kgsl_cp_reg_read_fill,
|
|
"CP: %02x: ", buff, ppos);
|
|
}
|
|
|
|
static const struct file_operations kgsl_cp_debug_fops = {
|
|
.open = kgsl_dbgfs_open,
|
|
.release = kgsl_dbgfs_release,
|
|
.read = kgsl_cp_debug_read,
|
|
};
|
|
|
|
static void kgsl_mh_reg_read_init(struct kgsl_device *device)
|
|
{
|
|
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
|
|
}
|
|
|
|
static void kgsl_mh_reg_read_fill(struct kgsl_device *device, int i,
|
|
unsigned int *vals, int linec)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < linec; ++j) {
|
|
kgsl_regwrite(device, REG_MH_DEBUG_CTRL, i+j);
|
|
kgsl_regread(device, REG_MH_DEBUG_DATA, vals+j);
|
|
}
|
|
}
|
|
|
|
static ssize_t kgsl_mh_debug_read(
|
|
struct file *file,
|
|
char __user *buff,
|
|
size_t buff_count,
|
|
loff_t *ppos)
|
|
{
|
|
struct kgsl_device *device = file->private_data;
|
|
return kgsl_reg_read(device, 0x40, kgsl_mh_reg_read_init,
|
|
kgsl_mh_reg_read_fill,
|
|
"MH: %02x: ", buff, ppos);
|
|
}
|
|
|
|
static const struct file_operations kgsl_mh_debug_fops = {
|
|
.open = kgsl_dbgfs_open,
|
|
.release = kgsl_dbgfs_release,
|
|
.read = kgsl_mh_debug_read,
|
|
};
|
|
|
|
void adreno_debugfs_init(struct kgsl_device *device)
|
|
{
|
|
if (!device->d_debugfs || IS_ERR(device->d_debugfs))
|
|
return;
|
|
|
|
debugfs_create_file("ib_dump", 0600, device->d_debugfs, device,
|
|
&kgsl_ib_dump_fops);
|
|
debugfs_create_file("istore", 0400, device->d_debugfs, device,
|
|
&kgsl_istore_fops);
|
|
debugfs_create_file("sx_debug", 0400, device->d_debugfs, device,
|
|
&kgsl_sx_debug_fops);
|
|
debugfs_create_file("cp_debug", 0400, device->d_debugfs, device,
|
|
&kgsl_cp_debug_fops);
|
|
debugfs_create_file("mh_debug", 0400, device->d_debugfs, device,
|
|
&kgsl_mh_debug_fops);
|
|
debugfs_create_file("cff_dump", 0644, device->d_debugfs, device,
|
|
&kgsl_cff_dump_enable_fops);
|
|
|
|
/* Create post mortem control files */
|
|
|
|
pm_d_debugfs = debugfs_create_dir("postmortem", device->d_debugfs);
|
|
|
|
if (IS_ERR(pm_d_debugfs))
|
|
return;
|
|
|
|
debugfs_create_file("dump", 0600, pm_d_debugfs, device,
|
|
&pm_dump_fops);
|
|
debugfs_create_file("regs_enabled", 0644, pm_d_debugfs, device,
|
|
&pm_regs_enabled_fops);
|
|
}
|