blob: 9f36e0241d4ed0eafa74b6d7e98a66ed27dfd31f [file] [log] [blame]
/*
* linux/drivers/gpu/exynos/g2d/g2d_debug.c
*
* Copyright (C) 2017 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* 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/module.h>
#include <linux/kernel.h>
#include <linux/debugfs.h>
#include <linux/sched/task.h>
#include <media/exynos_tsmux.h>
#include "g2d.h"
#include "g2d_task.h"
#include "g2d_uapi.h"
#include "g2d_debug.h"
#include "g2d_regs.h"
static unsigned int g2d_debug;
#define G2D_MAX_STAMP_ID 1024
#define G2D_STAMP_CLAMP_ID(id) ((id) & (G2D_MAX_STAMP_ID - 1))
static struct g2d_stamp {
ktime_t time;
unsigned long state;
u32 job_id;
u32 stamp;
s32 val;
u8 cpu;
} g2d_stamp_list[G2D_MAX_STAMP_ID];
static atomic_t g2d_stamp_id;
enum {
G2D_STAMPTYPE_NONE,
G2D_STAMPTYPE_NUM,
G2D_STAMPTYPE_HEX,
G2D_STAMPTYPE_USEC,
G2D_STAMPTYPE_INOUT,
G2D_STAMPTYPE_ALLOCFREE,
};
static struct g2d_stamp_type {
const char *name;
int type;
bool task_specific;
} g2d_stamp_types[G2D_STAMP_STATE_NUM] = {
{"runtime_pm", G2D_STAMPTYPE_INOUT, false},
{"task_alloc", G2D_STAMPTYPE_ALLOCFREE, true},
{"task_begin", G2D_STAMPTYPE_NUM, true},
{"task_push", G2D_STAMPTYPE_USEC, true},
{"irq", G2D_STAMPTYPE_HEX, false},
{"task_done", G2D_STAMPTYPE_USEC, true},
{"fence_timeout", G2D_STAMPTYPE_HEX, true},
{"hw_timeout", G2D_STAMPTYPE_HEX, true},
{"irq_error", G2D_STAMPTYPE_HEX, true},
{"mmu_fault", G2D_STAMPTYPE_NONE, true},
{"shutdown", G2D_STAMPTYPE_INOUT, false},
{"suspend", G2D_STAMPTYPE_INOUT, false},
{"resume", G2D_STAMPTYPE_INOUT, false},
{"hwfc_job", G2D_STAMPTYPE_NUM, true},
{"pending", G2D_STAMPTYPE_NUM, false},
};
static bool g2d_stamp_show_single(struct seq_file *s, struct g2d_stamp *stamp)
{
if (stamp->time == 0)
return false;
seq_printf(s, "[%u:%12lld] %13s: ", stamp->cpu,
ktime_to_us(stamp->time),
g2d_stamp_types[stamp->stamp].name);
if (g2d_stamp_types[stamp->stamp].task_specific)
seq_printf(s, "JOB ID %2u (STATE %#05lx) - ",
stamp->job_id, stamp->state);
switch (g2d_stamp_types[stamp->stamp].type) {
case G2D_STAMPTYPE_NUM:
seq_printf(s, "%d", stamp->val);
break;
case G2D_STAMPTYPE_HEX:
seq_printf(s, "%#x", stamp->val);
break;
case G2D_STAMPTYPE_USEC:
seq_printf(s, "%d usec.", stamp->val);
break;
case G2D_STAMPTYPE_INOUT:
seq_printf(s, "%s", stamp->val ? "out" : "in");
break;
case G2D_STAMPTYPE_ALLOCFREE:
seq_printf(s, "%s", stamp->val ? "free" : "alloc");
break;
}
seq_puts(s, "\n");
return true;
}
static int g2d_stamp_show(struct seq_file *s, void *unused)
{
int idx = G2D_STAMP_CLAMP_ID(atomic_read(&g2d_stamp_id) + 1);
int i;
/* in chronological order */
for (i = idx; i < G2D_MAX_STAMP_ID; i++)
if (!g2d_stamp_show_single(s, &g2d_stamp_list[i]))
break;
for (i = 0; i < idx; i++)
if (!g2d_stamp_show_single(s, &g2d_stamp_list[i]))
break;
return 0;
}
static int g2d_debug_logs_open(struct inode *inode, struct file *file)
{
return single_open(file, g2d_stamp_show, inode->i_private);
}
static const struct file_operations g2d_debug_logs_fops = {
.open = g2d_debug_logs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int g2d_debug_contexts_show(struct seq_file *s, void *unused)
{
struct g2d_device *g2d_dev = s->private;
struct g2d_context *ctx;
seq_printf(s, "%16s %6s %4s %6s %10s %10s\n",
"task", "pid", "prio", "dev", "read_bw", "write_bw");
seq_puts(s, "------------------------------------------------------\n");
spin_lock(&g2d_dev->lock_ctx_list);
list_for_each_entry(ctx, &g2d_dev->ctx_list, node) {
task_lock(ctx->owner);
seq_printf(s, "%16s %6u %4d %6s %10llu %10llu\n",
ctx->owner->comm, ctx->owner->pid, ctx->priority,
g2d_dev->misc[(ctx->authority + 1) & 1].name,
ctx->r_bw, ctx->w_bw);
task_unlock(ctx->owner);
}
spin_unlock(&g2d_dev->lock_ctx_list);
seq_puts(s, "------------------------------------------------------\n");
seq_puts(s, "priorities:\n");
seq_printf(s, "\tlow(0) : %d\n",
atomic_read(&g2d_dev->prior_stats[G2D_LOW_PRIORITY]));
seq_printf(s, "\tmedium(1) : %d\n",
atomic_read(&g2d_dev->prior_stats[G2D_MEDIUM_PRIORITY]));
seq_printf(s, "\thigh(2) : %d\n",
atomic_read(&g2d_dev->prior_stats[G2D_HIGH_PRIORITY]));
seq_printf(s, "\thighest(3): %d\n",
atomic_read(&g2d_dev->prior_stats[G2D_HIGHEST_PRIORITY]));
return 0;
}
static int g2d_debug_contexts_open(struct inode *inode, struct file *file)
{
return single_open(file, g2d_debug_contexts_show, inode->i_private);
}
static const struct file_operations g2d_debug_contexts_fops = {
.open = g2d_debug_contexts_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int g2d_debug_tasks_show(struct seq_file *s, void *unused)
{
struct g2d_device *g2d_dev = s->private;
struct g2d_task *task;
for (task = g2d_dev->tasks; task; task = task->next) {
seq_printf(s, "TASK[%d]: state %#lx flags %#x ",
task->sec.job_id, task->state, task->flags);
seq_printf(s, "prio %d begin@%llu end@%llu nr_src %d\n",
task->sec.priority, ktime_to_us(task->ktime_begin),
ktime_to_us(task->ktime_end), task->num_source);
}
return 0;
}
static int g2d_debug_tasks_open(struct inode *inode, struct file *file)
{
return single_open(file, g2d_debug_tasks_show, inode->i_private);
}
static const struct file_operations g2d_debug_tasks_fops = {
.open = g2d_debug_tasks_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void g2d_init_debug(struct g2d_device *g2d_dev)
{
atomic_set(&g2d_stamp_id, -1);
g2d_dev->debug_root = debugfs_create_dir("g2d", NULL);
if (!g2d_dev->debug_root) {
perrdev(g2d_dev, "debugfs: failed to create root directory");
return;
}
g2d_dev->debug = debugfs_create_u32("debug",
0644, g2d_dev->debug_root, &g2d_debug);
if (!g2d_dev->debug) {
perrdev(g2d_dev, "debugfs: failed to create debug file");
return;
}
g2d_dev->debug_logs = debugfs_create_file("logs",
0444, g2d_dev->debug_root, g2d_dev, &g2d_debug_logs_fops);
if (!g2d_dev->debug_logs) {
perrdev(g2d_dev, "debugfs: failed to create logs file");
return;
}
g2d_dev->debug_contexts = debugfs_create_file("contexts",
0400, g2d_dev->debug_root, g2d_dev,
&g2d_debug_contexts_fops);
if (!g2d_dev->debug_logs) {
perrdev(g2d_dev, "debugfs: failed to create contexts file");
return;
}
g2d_dev->debug_tasks= debugfs_create_file("tasks",
0400, g2d_dev->debug_root, g2d_dev,
&g2d_debug_tasks_fops);
if (!g2d_dev->debug_logs) {
perrdev(g2d_dev, "debugfs: failed to create tasks file");
return;
}
}
void g2d_destroy_debug(struct g2d_device *g2d_dev)
{
debugfs_remove_recursive(g2d_dev->debug_root);
}
static struct regs_info g2d_reg_info[] = {
/* Start, Size, Name */
{ 0x0, 0x20, "General" },
{ 0x34, 0x10, "Secure Layer" },
{ 0xF0, 0x10, "AFBC debugging" },
{ 0x80, 0x70, "Job manager" },
{ 0x2000, 0x120, "Layer CSC Coefficient" },
{ 0x3000, 0x110, "HDR EOTF" },
{ 0x3200, 0x110, "DEGAMMA EOTF" },
{ 0x3400, 0x30, "HDR GM" },
{ 0x3500, 0x30, "DEGAMMA 2.2" },
{ 0x3600, 0x90, "HDR TM" },
{ 0x3700, 0x90, "DEGAMMA TM" },
{ 0x8000, 0x100, "HW flow control" },
{ 0x120, 0xE0, "Destination" },
{ 0x200, 0x100, "Layer0" },
{ 0x300, 0x100, "Layer1" },
{ 0x400, 0x100, "Layer2" },
{ 0x500, 0x100, "Layer3" },
{ 0x600, 0x100, "Layer4" },
{ 0x700, 0x100, "Layer5" },
{ 0x800, 0x100, "Layer6" },
{ 0x900, 0x100, "Layer7" },
{ 0xA00, 0x100, "Layer8" },
{ 0xB00, 0x100, "Layer9" },
{ 0xC00, 0x100, "Layer10" },
{ 0xD00, 0x100, "Layer11" },
{ 0xE00, 0x100, "Layer12" },
{ 0xF00, 0x100, "Layer13" },
{ 0x1000, 0x100, "Layer14" },
{ 0x1100, 0x100, "Layer15" },
};
#define G2D_COMP_DEBUG_DATA_COUNT 16
void g2d_dump_afbcdata(struct g2d_device *g2d_dev)
{
int i, cluster;
u32 cfg;
for (cluster = 0; cluster < 2; cluster++) {
for (i = 0; i < G2D_COMP_DEBUG_DATA_COUNT; i++) {
writel_relaxed(i | cluster << 5,
g2d_dev->reg + G2D_COMP_DEBUG_ADDR_REG);
cfg = readl_relaxed(
g2d_dev->reg + G2D_COMP_DEBUG_DATA_REG);
pr_err("AFBC_DEBUGGING_DATA cluster%d [%d] %#x",
cluster, i, cfg);
}
}
}
void g2d_dump_sfr(struct g2d_device *g2d_dev, struct g2d_task *task)
{
unsigned int i, num_array;
num_array = (unsigned int)ARRAY_SIZE(g2d_reg_info) - g2d_dev->max_layers
+ ((task) ? task->num_source : g2d_dev->max_layers);
for (i = 0; i < num_array; i++) {
pr_info("[%s: %04X .. %04X]\n",
g2d_reg_info[i].name, g2d_reg_info[i].start,
g2d_reg_info[i].start + g2d_reg_info[i].size);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4,
g2d_dev->reg + g2d_reg_info[i].start,
g2d_reg_info[i].size, false);
}
}
void g2d_dump_cmd(struct g2d_task *task)
{
unsigned int i;
struct g2d_reg *regs;
if (!task)
return;
regs = page_address(task->cmd_page);
for (i = 0; i < task->sec.cmd_count; i++)
pr_info("G2D: CMD[%03d] %#06x, %#010x\n",
i, regs[i].offset, regs[i].value);
}
/*
* If it happens error interrupts, mmu errors, and H/W timeouts,
* dump the SFR and job command list of task, AFBC debugging information
*/
void g2d_dump_info(struct g2d_device *g2d_dev, struct g2d_task *task)
{
g2d_dump_cmd(task);
g2d_dump_sfr(g2d_dev, task);
g2d_dump_afbcdata(g2d_dev);
}
void g2d_stamp_task(struct g2d_task *task, u32 stampid, s32 val)
{
int idx = G2D_STAMP_CLAMP_ID(atomic_inc_return(&g2d_stamp_id));
struct g2d_stamp *stamp = &g2d_stamp_list[idx];
if (task) {
stamp->state = task->state;
stamp->job_id = task->sec.job_id;
} else {
stamp->job_id = 0;
stamp->state = 0;
}
stamp->time = ktime_get();
stamp->stamp = stampid;
stamp->cpu = raw_smp_processor_id();
stamp->val = val;
if ((stamp->stamp == G2D_STAMP_STATE_DONE) && task) {
if (g2d_debug == 1) {
dev_info(task->g2d_dev->dev,
"Job %u consumed %06u usec. by H/W\n",
task->sec.job_id, val);
} else if (g2d_debug == 2) {
g2d_dump_info(task->g2d_dev, task);
}
}
/* LLWFD latency measure */
/* media/exynos_tsmux.h includes below functions */
if (task != NULL && IS_HWFC(task->flags)) {
if (stampid == G2D_STAMP_STATE_PUSH)
g2d_blending_start(task->sec.job_id);
if (stampid == G2D_STAMP_STATE_DONE)
g2d_blending_end(task->sec.job_id);
}
}