mirror of
git://nv-tegra.nvidia.com/linux-nvgpu.git
synced 2025-12-22 17:36:20 +03:00
-Currently PG task is created for both iGPU & dGPU as part PMU init sequence path, but task is not required for dGPU or can be skipped if ELPG is not supported on iGPU, made changes to create PG task only if supported else skip it, and made some functions to private as these are required by PG UNIT only. -PG instance is allocated & set to default properties as needed if support is enabled else skip it. -Made changes in dependent files as required to reflect above changes JIRA NVGPU-1972 Change-Id: I4efb7f1814a9ad48770acea2173e66f0a4c8a9c1 Signed-off-by: Mahantesh Kumbar <mkumbar@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/2094840 Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
490 lines
12 KiB
C
490 lines
12 KiB
C
/*
|
|
* Copyright (C) 2018-2019, NVIDIA Corporation. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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 <nvgpu/enabled.h>
|
|
#include <nvgpu/pmu/pmu_perfmon.h>
|
|
|
|
#include "debug_pmu.h"
|
|
#include "os_linux.h"
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
static int lpwr_debug_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
|
|
if (g->ops.pmu.pmu_pg_engines_feature_list &&
|
|
g->ops.pmu.pmu_pg_engines_feature_list(g,
|
|
PMU_PG_ELPG_ENGINE_ID_GRAPHICS) !=
|
|
NVGPU_PMU_GR_FEATURE_MASK_POWER_GATING) {
|
|
seq_printf(s, "PSTATE: %u\n"
|
|
"RPPG Enabled: %u\n"
|
|
"RPPG ref count: %u\n"
|
|
"RPPG state: %u\n"
|
|
"MSCG Enabled: %u\n"
|
|
"MSCG pstate state: %u\n"
|
|
"MSCG transition state: %u\n",
|
|
g->ops.clk_arb.get_current_pstate(g),
|
|
g->elpg_enabled, g->pmu.pg->elpg_refcnt,
|
|
g->pmu.pg->elpg_stat, g->mscg_enabled,
|
|
g->pmu.pg->mscg_stat, g->pmu.pg->mscg_transition_state);
|
|
|
|
} else
|
|
seq_printf(s, "ELPG Enabled: %u\n"
|
|
"ELPG ref count: %u\n"
|
|
"ELPG state: %u\n",
|
|
g->elpg_enabled, g->pmu.pg->elpg_refcnt,
|
|
g->pmu.pg->elpg_stat);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int lpwr_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, lpwr_debug_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations lpwr_debug_fops = {
|
|
.open = lpwr_debug_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int mscg_stat_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
u64 total_ingating, total_ungating, residency, divisor, dividend;
|
|
struct pmu_pg_stats_data pg_stat_data = { 0 };
|
|
int err;
|
|
|
|
/* Don't unnecessarily power on the device */
|
|
if (g->power_on) {
|
|
err = gk20a_busy(g);
|
|
if (err)
|
|
return err;
|
|
|
|
nvgpu_pmu_get_pg_stats(g,
|
|
PMU_PG_ELPG_ENGINE_ID_MS, &pg_stat_data);
|
|
gk20a_idle(g);
|
|
}
|
|
total_ingating = g->pg_ingating_time_us +
|
|
(u64)pg_stat_data.ingating_time;
|
|
total_ungating = g->pg_ungating_time_us +
|
|
(u64)pg_stat_data.ungating_time;
|
|
|
|
divisor = total_ingating + total_ungating;
|
|
|
|
/* We compute the residency on a scale of 1000 */
|
|
dividend = total_ingating * 1000;
|
|
|
|
if (divisor)
|
|
residency = div64_u64(dividend, divisor);
|
|
else
|
|
residency = 0;
|
|
|
|
seq_printf(s,
|
|
"Time in MSCG: %llu us\n"
|
|
"Time out of MSCG: %llu us\n"
|
|
"MSCG residency ratio: %llu\n"
|
|
"MSCG Entry Count: %u\n"
|
|
"MSCG Avg Entry latency %u\n"
|
|
"MSCG Avg Exit latency %u\n",
|
|
total_ingating, total_ungating,
|
|
residency, pg_stat_data.gating_cnt,
|
|
pg_stat_data.avg_entry_latency_us,
|
|
pg_stat_data.avg_exit_latency_us);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int mscg_stat_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, mscg_stat_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations mscg_stat_fops = {
|
|
.open = mscg_stat_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int mscg_transitions_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
struct pmu_pg_stats_data pg_stat_data = { 0 };
|
|
u32 total_gating_cnt;
|
|
int err;
|
|
|
|
if (g->power_on) {
|
|
err = gk20a_busy(g);
|
|
if (err)
|
|
return err;
|
|
|
|
nvgpu_pmu_get_pg_stats(g,
|
|
PMU_PG_ELPG_ENGINE_ID_MS, &pg_stat_data);
|
|
gk20a_idle(g);
|
|
}
|
|
total_gating_cnt = g->pg_gating_cnt + pg_stat_data.gating_cnt;
|
|
|
|
seq_printf(s, "%u\n", total_gating_cnt);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int mscg_transitions_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, mscg_transitions_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations mscg_transitions_fops = {
|
|
.open = mscg_transitions_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int elpg_stat_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
struct pmu_pg_stats_data pg_stat_data = { 0 };
|
|
u64 total_ingating, total_ungating, residency, divisor, dividend;
|
|
int err;
|
|
|
|
/* Don't unnecessarily power on the device */
|
|
if (g->power_on) {
|
|
err = gk20a_busy(g);
|
|
if (err)
|
|
return err;
|
|
|
|
nvgpu_pmu_get_pg_stats(g,
|
|
PMU_PG_ELPG_ENGINE_ID_GRAPHICS, &pg_stat_data);
|
|
gk20a_idle(g);
|
|
}
|
|
total_ingating = g->pg_ingating_time_us +
|
|
(u64)pg_stat_data.ingating_time;
|
|
total_ungating = g->pg_ungating_time_us +
|
|
(u64)pg_stat_data.ungating_time;
|
|
divisor = total_ingating + total_ungating;
|
|
|
|
/* We compute the residency on a scale of 1000 */
|
|
dividend = total_ingating * 1000;
|
|
|
|
if (divisor)
|
|
residency = div64_u64(dividend, divisor);
|
|
else
|
|
residency = 0;
|
|
|
|
seq_printf(s,
|
|
"Time in ELPG: %llu us\n"
|
|
"Time out of ELPG: %llu us\n"
|
|
"ELPG residency ratio: %llu\n"
|
|
"ELPG Entry Count: %u\n"
|
|
"ELPG Avg Entry latency %u us\n"
|
|
"ELPG Avg Exit latency %u us\n",
|
|
total_ingating, total_ungating,
|
|
residency, pg_stat_data.gating_cnt,
|
|
pg_stat_data.avg_entry_latency_us,
|
|
pg_stat_data.avg_exit_latency_us);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int elpg_stat_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, elpg_stat_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations elpg_stat_fops = {
|
|
.open = elpg_stat_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int elpg_transitions_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
struct pmu_pg_stats_data pg_stat_data = { 0 };
|
|
u32 total_gating_cnt;
|
|
int err;
|
|
|
|
if (g->power_on) {
|
|
err = gk20a_busy(g);
|
|
if (err)
|
|
return err;
|
|
|
|
nvgpu_pmu_get_pg_stats(g,
|
|
PMU_PG_ELPG_ENGINE_ID_GRAPHICS, &pg_stat_data);
|
|
gk20a_idle(g);
|
|
}
|
|
total_gating_cnt = g->pg_gating_cnt + pg_stat_data.gating_cnt;
|
|
|
|
seq_printf(s, "%u\n", total_gating_cnt);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int elpg_transitions_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, elpg_transitions_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations elpg_transitions_fops = {
|
|
.open = elpg_transitions_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int falc_trace_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
struct nvgpu_pmu *pmu = &g->pmu;
|
|
u32 i = 0, j = 0, k, l, m;
|
|
char part_str[40];
|
|
void *tracebuffer;
|
|
char *trace;
|
|
u32 *trace1;
|
|
|
|
/* allocate system memory to copy pmu trace buffer */
|
|
tracebuffer = nvgpu_kzalloc(g, GK20A_PMU_TRACE_BUFSIZE);
|
|
if (tracebuffer == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* read pmu traces into system memory buffer */
|
|
nvgpu_mem_rd_n(g, &pmu->trace_buf,
|
|
0, tracebuffer, GK20A_PMU_TRACE_BUFSIZE);
|
|
|
|
trace = (char *)tracebuffer;
|
|
trace1 = (u32 *)tracebuffer;
|
|
|
|
for (i = 0; i < GK20A_PMU_TRACE_BUFSIZE; i += 0x40) {
|
|
for (j = 0; j < 0x40; j++)
|
|
if (trace1[(i / 4) + j])
|
|
break;
|
|
if (j == 0x40)
|
|
break;
|
|
seq_printf(s, "Index %x: ", trace1[(i / 4)]);
|
|
l = 0;
|
|
m = 0;
|
|
while (nvgpu_find_hex_in_string((trace+i+20+m), g, &k)) {
|
|
if (k >= 40)
|
|
break;
|
|
(void) strncpy(part_str, (trace+i+20+m), k);
|
|
part_str[k] = 0;
|
|
seq_printf(s, "%s0x%x", part_str,
|
|
trace1[(i / 4) + 1 + l]);
|
|
l++;
|
|
m += k + 2;
|
|
}
|
|
seq_printf(s, "%s", (trace+i+20+m));
|
|
}
|
|
|
|
nvgpu_kfree(g, tracebuffer);
|
|
return 0;
|
|
}
|
|
|
|
static int falc_trace_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, falc_trace_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations falc_trace_fops = {
|
|
.open = falc_trace_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int perfmon_events_enable_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
|
|
seq_printf(s, "%u\n",
|
|
nvgpu_pmu_perfmon_get_sampling_enable_status(&(g->pmu)) ? 1 : 0);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int perfmon_events_enable_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, perfmon_events_enable_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t perfmon_events_enable_write(struct file *file,
|
|
const char __user *userbuf, size_t count, loff_t *ppos)
|
|
{
|
|
struct seq_file *s = file->private_data;
|
|
struct gk20a *g = s->private;
|
|
unsigned long val = 0;
|
|
char buf[40];
|
|
int buf_size;
|
|
int err;
|
|
bool status;
|
|
|
|
(void) memset(buf, 0, sizeof(buf));
|
|
buf_size = min(count, (sizeof(buf)-1));
|
|
|
|
if (copy_from_user(buf, userbuf, buf_size))
|
|
return -EFAULT;
|
|
|
|
if (kstrtoul(buf, 10, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
/* Don't turn on gk20a unnecessarily */
|
|
if (g->power_on) {
|
|
err = gk20a_busy(g);
|
|
if (err)
|
|
return err;
|
|
|
|
if (val && !nvgpu_pmu_perfmon_get_sampling_enable_status(&(g->pmu))
|
|
&& nvgpu_is_enabled(g, NVGPU_PMU_PERFMON)) {
|
|
nvgpu_pmu_perfmon_set_sampling_enable_status(&(g->pmu),
|
|
true);
|
|
g->ops.pmu.pmu_perfmon_start_sampling(&(g->pmu));
|
|
} else if (!val
|
|
&& nvgpu_pmu_perfmon_get_sampling_enable_status(&(g->pmu))
|
|
&& nvgpu_is_enabled(g, NVGPU_PMU_PERFMON)) {
|
|
nvgpu_pmu_perfmon_set_sampling_enable_status(&(g->pmu),
|
|
false);
|
|
g->ops.pmu.pmu_perfmon_stop_sampling(&(g->pmu));
|
|
}
|
|
gk20a_idle(g);
|
|
} else {
|
|
status = val ? true : false;
|
|
nvgpu_pmu_perfmon_set_sampling_enable_status(&(g->pmu), status);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations perfmon_events_enable_fops = {
|
|
.open = perfmon_events_enable_open,
|
|
.read = seq_read,
|
|
.write = perfmon_events_enable_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int perfmon_events_count_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
|
|
seq_printf(s, "%llu\n", nvgpu_pmu_perfmon_get_events_count(&(g->pmu)));
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int perfmon_events_count_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, perfmon_events_count_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations perfmon_events_count_fops = {
|
|
.open = perfmon_events_count_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int security_show(struct seq_file *s, void *data)
|
|
{
|
|
struct gk20a *g = s->private;
|
|
|
|
seq_printf(s, "%d\n", nvgpu_is_enabled(g, NVGPU_SEC_PRIVSECURITY));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int security_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, security_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations security_fops = {
|
|
.open = security_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
int gk20a_pmu_debugfs_init(struct gk20a *g)
|
|
{
|
|
struct dentry *d;
|
|
struct nvgpu_os_linux *l = nvgpu_os_linux_from_gk20a(g);
|
|
|
|
d = debugfs_create_file(
|
|
"lpwr_debug", S_IRUGO|S_IWUSR, l->debugfs, g,
|
|
&lpwr_debug_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"mscg_residency", S_IRUGO|S_IWUSR, l->debugfs, g,
|
|
&mscg_stat_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"mscg_transitions", S_IRUGO, l->debugfs, g,
|
|
&mscg_transitions_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"elpg_residency", S_IRUGO|S_IWUSR, l->debugfs, g,
|
|
&elpg_stat_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"elpg_transitions", S_IRUGO, l->debugfs, g,
|
|
&elpg_transitions_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"falc_trace", S_IRUGO, l->debugfs, g,
|
|
&falc_trace_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"perfmon_events_enable", S_IRUGO, l->debugfs, g,
|
|
&perfmon_events_enable_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"perfmon_events_count", S_IRUGO, l->debugfs, g,
|
|
&perfmon_events_count_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
|
|
d = debugfs_create_file(
|
|
"pmu_security", S_IRUGO, l->debugfs, g,
|
|
&security_fops);
|
|
if (!d)
|
|
goto err_out;
|
|
return 0;
|
|
err_out:
|
|
pr_err("%s: Failed to make debugfs node\n", __func__);
|
|
return -ENOMEM;
|
|
}
|