From 873e421d95fa92e18968d855d274dd8a33d4ff34 Mon Sep 17 00:00:00 2001 From: Mahesh Kumar Date: Mon, 11 Jul 2022 15:06:14 +0530 Subject: [PATCH] platform: dce: Add Debugfs support for Perf Add debugfs nodes - start/stop perf stats. - read perf stat stats - set the format type as csv or xml Add debugfs to capture perf events - Can enable specific perf events through debugfs node. Signed-off-by: Mahesh Kumar Change-Id: I0a7833d7a8f04296ba3806f4f2a218175080d2e2 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2765513 Reviewed-by: svc_kernel_abi Reviewed-by: Arun Swain Reviewed-by: Vinod Gopalakrishnakurup Tested-by: Vinod Gopalakrishnakurup GVS: Gerrit_Virtual_Submit --- drivers/platform/tegra/dce/Makefile | 7 +- drivers/platform/tegra/dce/dce-admin-debug.c | 164 ++++++ drivers/platform/tegra/dce/dce-admin.c | 3 +- drivers/platform/tegra/dce/dce-debug-perf.c | 549 ++++++++++++++++++ drivers/platform/tegra/dce/dce-debug.c | 80 ++- .../tegra/dce/include/dce-debug-perf.h | 42 ++ drivers/platform/tegra/dce/include/dce-ipc.h | 5 +- drivers/platform/tegra/dce/include/dce.h | 14 +- .../dce/include/interface/dce-admin-cmds.h | 36 +- .../include/interface/dce-admin-perf-stats.h | 138 +++++ 10 files changed, 1015 insertions(+), 23 deletions(-) create mode 100644 drivers/platform/tegra/dce/dce-admin-debug.c create mode 100644 drivers/platform/tegra/dce/dce-debug-perf.c create mode 100644 drivers/platform/tegra/dce/include/dce-debug-perf.h create mode 100644 drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h diff --git a/drivers/platform/tegra/dce/Makefile b/drivers/platform/tegra/dce/Makefile index 93547e80..db0f788a 100644 --- a/drivers/platform/tegra/dce/Makefile +++ b/drivers/platform/tegra/dce/Makefile @@ -5,6 +5,7 @@ # GCOV_PROFILE := y +ccflags-y += -I$(srctree.nvidia)/drivers/platform/tegra/dce/include ccflags-y += -Wno-multichar ccflags-y += -Werror ccflags-y += -Wno-error=cpp @@ -39,7 +40,7 @@ tegra-dce-$(CONFIG_TEGRA_DCE) += \ ifeq ($(CONFIG_DEBUG_FS),y) tegra-dce-$(CONFIG_TEGRA_DCE) += \ - dce-debug.o + dce-debug.o \ + dce-admin-debug.o \ + dce-debug-perf.o endif - -ccflags-y += -I$(srctree.nvidia-t23x)/drivers/platform/tegra/dce/include diff --git a/drivers/platform/tegra/dce/dce-admin-debug.c b/drivers/platform/tegra/dce/dce-admin-debug.c new file mode 100644 index 00000000..e7c2985a --- /dev/null +++ b/drivers/platform/tegra/dce/dce-admin-debug.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include + +/** + * dce_admin_send_cmd_set_perf_stat - Start/stop DCE perf data collection. + * + * @d - Pointer to tegra_dce struct. + * @msg - Pointer to dce_ipc_msg struct. + * @start_perf - Bool to indicate start/stop of perf data collection + * + * Return - 0 if successful + */ +int dce_admin_send_cmd_set_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg, + bool start_perf) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + + if (!msg || !msg->tx.data || !msg->rx.data) + goto out; + + /* return if dce bootstrap not completed */ + if (!dce_is_bootstrap_done(d)) { + dce_err(d, "Admin Bootstrap not yet done"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + if (start_perf == true) + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_START; + else + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_STOP; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending set perf msg : [%d]", ret); + goto out; + } + +out: + return ret; +} +/** + * dce_admin_send_cmd_get_perf_stat - Get DCE perf data. + * + * @d - Pointer to tegra_dce struct. + * @msg - Pointer to dce_ipc_msg struct. + * + * Return - 0 if successful + */ +int dce_admin_send_cmd_get_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + + if (!msg || !msg->tx.data || !msg->rx.data) + goto out; + + /* return if dce bootstrap not completed */ + if (!dce_is_bootstrap_done(d)) { + dce_err(d, "Admin Bootstrap not yet done"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_RESULTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending get perf msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + +int dce_admin_send_cmd_get_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + + if (!msg || !msg->tx.data || !msg->rx.data) + goto out; + + /* return if dce bootstrap not completed */ + if (!dce_is_bootstrap_done(d)) { + dce_err(d, "Admin Bootstrap not yet done"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_GET_EVENTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending get perf events msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + +int dce_admin_send_cmd_clear_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + + if (!msg || !msg->tx.data || !msg->rx.data) + goto out; + + /* return if dce bootstrap not completed */ + if (!dce_is_bootstrap_done(d)) { + dce_err(d, "Admin Bootstrap not yet done"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_CLEAR_EVENTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending clear perf events msg : [%d]", ret); + goto out; + } + +out: + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-admin.c b/drivers/platform/tegra/dce/dce-admin.c index b6173ed4..274713ff 100644 --- a/drivers/platform/tegra/dce/dce-admin.c +++ b/drivers/platform/tegra/dce/dce-admin.c @@ -1,5 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, diff --git a/drivers/platform/tegra/dce/dce-debug-perf.c b/drivers/platform/tegra/dce/dce-debug-perf.c new file mode 100644 index 00000000..bd39532c --- /dev/null +++ b/drivers/platform/tegra/dce/dce-debug-perf.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 +#include +#include +#include +#include +#include +#include + +#define DCE_PERF_OUTPUT_FORMAT_CSV ((uint32_t)(0U)) +#define DCE_PERF_OUTPUT_FORMAT_XML ((uint32_t)(1U)) + +static uint32_t perf_output_format = DCE_PERF_OUTPUT_FORMAT_CSV; + +ssize_t dbg_dce_perf_stats_stats_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + bool start_perf; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + ret = kstrtobool_from_user(user_buf, 3ULL, &start_perf); + if (ret) { + dce_err(d, "Unable to parse start/stop for dce perf stats"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + /* + * Collect All Perfs + * Clear previous data and start collecting new perf data + * + * TODO: Add support to collect specific perf data + */ + req_msg->args.perf.perf_cmd.enable = DCE_ADMIN_PERF_STATS_ALL; + req_msg->args.perf.perf_cmd.clear = DCE_ADMIN_PERF_CLEAR_CLEAR; + + /* + * echo "1/y" (kstrtobool true) to start perf data collection + * echo "0/n" (kstrtobool false) to stop perf data collection + */ + ret = dce_admin_send_cmd_set_perf_stat(d, msg, start_perf); + if (ret) { + dce_err(d, "Failed to Set perf stat\n"); + goto out; + } + + dce_debug(d, "DCE perf stats collection %s", start_perf ? "started" : "stopped"); + +out: + dce_admin_free_message(d, msg); + return count; +} + +static void dbg_dce_perf_stats_sched_task_xml(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_task_stats *task_stat) +{ + /* Don't print task stats if task name is not set */ + if (task_stat->name == NULL) + return; + + seq_printf(s, "%s", task_stat->name); + seq_printf(s, "%llu%llu", + task_stat->init, task_stat->ready); + seq_printf(s, "%llu", + task_stat->ready_time); + seq_printf(s, "%llu%llu", + task_stat->dcache_misses, task_stat->inst_exec); + seq_printf(s, "%llu", + task_stat->mmio_req); + seq_printf(s, "sleep%llu", + task_stat->sleep.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->sleep.iterations, + task_stat->sleep.min, + task_stat->sleep.max); + seq_printf(s, "run%llu", + task_stat->run.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->run.iterations, + task_stat->run.min, + task_stat->run.max); + seq_printf(s, "run-context%llu", + task_stat->run_context.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->run_context.iterations, + task_stat->run_context.min, + task_stat->run_context.max); + seq_puts(s, "\n"); +} + +static void dbg_dce_perf_stats_pm_event_xml(struct seq_file *s, + const struct dce_admin_pm_event_stats *pm_evt) +{ + seq_printf(s, "%llu%llu\n", pm_evt->start, pm_evt->end); + seq_printf(s, "%llu\n", pm_evt->count); +} + +static void dbg_dce_perf_stats_show_xml(struct seq_file *s, struct tegra_dce *d, + struct dce_admin_perf_info *perf) +{ + + const struct dce_admin_pm_event_stats *pm_evt; + const struct dce_admin_sched_stats *sched = &perf->sched; + uint32_t i; + + seq_puts(s, "\n"); + seq_puts(s, ""); + seq_printf(s, "%llu\n", perf->tcm_ready); + seq_printf(s, "%llu\n", perf->sched_start); + seq_printf(s, "%llu\n", perf->ready_time); + + /* + * dCache-misses Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_DCACHE_MISSES]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * instruction execution Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_INSTR_EXEC]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * instruction execution Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_MEM_REQ]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * Print Scheduler Perf data and Per task data + */ + seq_puts(s, "\n"); + seq_printf(s, "%llu%llu", sched->start, sched->end); + seq_printf(s, "%llu\n", sched->context_switches); + + for (i = 0U; i < DCE_ADMIN_PERF_ACTIVE_TASKS_NUM; i += 1U) { + dbg_dce_perf_stats_sched_task_xml(s, d, + &sched->tasks[i]); + } + + seq_puts(s, ""); //sched perf data done + seq_puts(s, "\n"); +} + + +static void dbg_dce_perf_stats_sched_task_csv(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_task_stats *task_stat) +{ + /* Don't print if task_name is not set */ + if (task_stat->name == NULL) + return; + + seq_printf(s, "\"%s\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"sleep\",\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->name, task_stat->init, task_stat->ready, + task_stat->ready_time, task_stat->dcache_misses, + task_stat->inst_exec, task_stat->mmio_req, + task_stat->sleep.accumulate, + task_stat->sleep.iterations, task_stat->sleep.min, + task_stat->sleep.max); + seq_puts(s, "\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"run\","); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->run.accumulate, task_stat->run.iterations, + task_stat->run.min, task_stat->run.max); + seq_puts(s, "\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"run-context\","); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->run_context.accumulate, + task_stat->run_context.iterations, + task_stat->run_context.min, + task_stat->run_context.max); +} + +static void dbg_dce_perf_stats_show_csv(struct seq_file *s, struct tegra_dce *d, + struct dce_admin_perf_info *perf) +{ + const struct dce_admin_pm_event_stats *pm_evt; + const struct dce_admin_sched_stats *sched = &perf->sched; + uint32_t i; + + seq_puts(s, "\"tcm-ready\",\"sched-start\",\"signaled-ready\","); + seq_puts(s, "\"dcache:start-time\",\"dcache:end-time\",\"dcache:miss_cnt\","); + seq_puts(s, "\"inst_exec:start-time\",\"inst_exec:end-time\",\"inst_exec:count\","); + seq_puts(s, "\"mmio_req:start-time\",\"mmio_req:end-time\",\"mmio_req:count\","); + seq_puts(s, "\"sched:start-time\",\"sched:end-time\",\"sched:context_switches\"\n"); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", perf->tcm_ready, + perf->sched_start, perf->ready_time); + + /* + * Print Cache Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_DCACHE_MISSES]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + + /* + * Print Instruction exec Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_INSTR_EXEC]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + + /* + * Print Instruction exec Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_MEM_REQ]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + /* + * Print Scheduler Perf data and Per task data + */ + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\"\n", + sched->start, sched->end, + sched->context_switches); + + seq_puts(s, "\"sched:task_name\",\"sched:task-init\",\"sched:task-ready\","); + seq_puts(s, "\"sched:task-ready-time\",\"sched:task-stat:dcache-misses\","); + seq_puts(s, "\"sched:task-stat:instr_exec\",\"sched:task-stat:mem_req\","); + seq_puts(s, "\"sched:task-stat:name\","); + seq_puts(s, "\"sched:task-stat:accumulate\",\"sched:task-stat:iterations\","); + seq_puts(s, "\"sched:task-stat:min\",\"sched:task-stat:max\"\n"); + for (i = 0U; i < DCE_ADMIN_PERF_ACTIVE_TASKS_NUM; i += 1U) { + dbg_dce_perf_stats_sched_task_csv(s, d, + &sched->tasks[i]); + } +} + +static int dbg_dce_perf_stats_stats_fops_show(struct seq_file *s, void *data) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_perf_info *perf; + struct tegra_dce *d = (struct tegra_dce *)s->private; + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = dce_admin_send_cmd_get_perf_stat(d, msg); + if (ret) { + dce_err(d, "Failed to Get perf stat\n"); + goto out; + } + + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + perf = &resp_msg->args.perf.info.sched_stats; + + if (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) + dbg_dce_perf_stats_show_csv(s, d, perf); + else + dbg_dce_perf_stats_show_xml(s, d, perf); + +out: + dce_admin_free_message(d, msg); + return 0; +} + +int dbg_dce_perf_stats_stats_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_stats_stats_fops_show, + inode->i_private); +} + +/* + * Debugfs nodes for displaying a help message about perf stats capture + */ +static int dbg_dce_perf_stats_help_fops_show(struct seq_file *s, void *data) +{ +/** + * Writing + * '0' to /sys/kernel/debug/tegra_dce/perf/stats/stats + * - Clear the Perf stats data + * '1' to /sys/kernel/debug/tegra_dce/perf/stats/stats + * - Start the Perf stats data + * To capture fresh Perf stats, first clear and Start. + * + * The result can be printed in csv or xml format + * + * '0' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in CSV format + * '1' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in XML format + * Default is set for CSV + * + * cat /sys/kernel/debug/tegra_dce/perf/stats/stats + * - get the perf data in selected format + * + */ + seq_printf(s, "DCE Perf Capture\n" + "----------------------\n" + " echo '0' > perf/stats/stats : Clear perf data\n" + " cat perf/stats/stats : get perf data in selected format\n" + " echo <0/1> > perf/format : get data in CSV/XML format\n"); + return 0; +} + +int dbg_dce_perf_stats_help_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_stats_help_fops_show, + inode->i_private); +} + +ssize_t dbg_dce_perf_format_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + u32 format; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + ret = kstrtou32_from_user(user_buf, count, 10, &format); + if (ret) { + dce_err(d, "Invalid format!"); + goto done; + } + + if (format == DCE_PERF_OUTPUT_FORMAT_CSV) + perf_output_format = DCE_PERF_OUTPUT_FORMAT_CSV; + else if (format == DCE_PERF_OUTPUT_FORMAT_XML) + perf_output_format = DCE_PERF_OUTPUT_FORMAT_XML; + else + dce_err(d, "Invalid format [%u]!", format); + +done: + return count; +} + +static int dbg_dce_perf_format_fops_show(struct seq_file *s, void *data) +{ + seq_printf(s, "Supported Formats\n" + "----------------------\n" + "CSV:0 XML:1\n" + "Current format:%s\n", + (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) ? "CSV" : "XML"); + return 0; +} + +int dbg_dce_perf_format_fops_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_dce_perf_format_fops_show, + inode->i_private); +} + +/* + * Debugfs nodes for displaying a help message about perf stats capture + */ +static int dbg_dce_perf_events_help_fops_show(struct seq_file *s, void *data) +{ +/** + * Writing + * '0' to /sys/kernel/debug/tegra_dce/perf/events/events + * - Clear the Perf Events data + * 'Bit masked value' to /kernel/debug/tegra_dce/perf/events/events + * - Enable an event for the Perf Events data capture. + * Clear bit mask if the event perf is not needed to be captured. + * + * The result can be printed in csv or xml format + * + * '0' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in CSV format + * '1' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in XML format + * Default is set for CSV + * + * cat /sys/kernel/debug/tegra_dce/perf/events/events + * - get the perf event data in selected format + * - shows event name/ accumulated time/ iterations happened + * - min and max time taken to run an event iteration + */ + seq_printf(s, "DCE Perf Events Capture\n" + "----------------------\n" + " echo '0' > perf/events/events: Clear perf events data\n" + " echo 'bit masked value' > perf/events/events: Enable perf event capture\n" + " cat perf/events/events: get perf events data in selected format\n" + " echo <0/1> > perf/format: get data in CSV/XML format\n"); + return 0; +} + +int dbg_dce_perf_events_help_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_events_help_fops_show, + inode->i_private); +} + +ssize_t dbg_dce_perf_events_events_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + uint32_t events; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + /* + * Writing "0/n" will clear the events + * Write Bit Masked value to enable events + */ + ret = kstrtou32_from_user(user_buf, count, 0, &events); + if (ret) { + dce_err(d, "Unable to parse for event perf stats"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + if (events == 0x0U) { + // Clear event + req_msg->args.perf.event_cmd.clear = DCE_ADMIN_EVENT_CLEAR_CLEAR; + } else { + // Disable all Events if enable : 0x80000000 + // Elase enable only Bit masked events + req_msg->args.perf.event_cmd.clear = DCE_ADMIN_EVENT_CLEAR_RETAIN; + req_msg->args.perf.event_cmd.enable = events; + } + + ret = dce_admin_send_cmd_clear_perf_events(d, msg); + if (ret) { + dce_err(d, "Failed to Clear perf events\n"); + goto out; + } +out: + dce_admin_free_message(d, msg); + return count; +} + +static void dbg_dce_perf_events_show_csv(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_event_info *events) +{ + int i = 0; + + seq_puts(s, "\"Evt Name\",\"accumulate\",\"iterations\",\"min\",\"max\"\n"); + + for (i = 0; i < DCE_ADMIN_NUM_EVENTS; i++) { + if (events->events[i].name == NULL) + continue; + + seq_printf(s, "\"%s\",\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + events->events[i].name, events->events[i].event.accumulate, + events->events[i].event.iterations, events->events[i].event.min, + events->events[i].event.max); + } + +} + +static void dbg_dce_perf_events_show_xml(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_event_info *events) +{ + int i = 0; + + seq_puts(s, "\n"); + seq_puts(s, ""); + + for (i = 0; i < DCE_ADMIN_NUM_EVENTS; i++) { + if (events->events[i].name == NULL) + continue; + + seq_printf(s, "%s\n", events->events[i].name); + seq_printf(s, "%llu\n", + events->events[i].event.accumulate); + seq_printf(s, "%llu\n", + events->events[i].event.iterations); + seq_printf(s, "%llu\n", events->events[i].event.min); + seq_printf(s, "%llu\n", events->events[i].event.max); + } + + seq_puts(s, "\n"); +} + +static int dbg_dce_perf_events_events_fops_show(struct seq_file *s, void *data) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_event_info *events; + struct tegra_dce *d = (struct tegra_dce *)s->private; + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = dce_admin_send_cmd_get_perf_events(d, msg); + if (ret) { + dce_err(d, "Failed to Get perf stat\n"); + goto out; + } + + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + events = &resp_msg->args.perf.info.events_stats; + + if (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) + dbg_dce_perf_events_show_csv(s, d, events); + else + dbg_dce_perf_events_show_xml(s, d, events); + +out: + dce_admin_free_message(d, msg); + return 0; +} + +int dbg_dce_perf_events_events_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_events_events_fops_show, + inode->i_private); +} diff --git a/drivers/platform/tegra/dce/dce-debug.c b/drivers/platform/tegra/dce/dce-debug.c index 58eee812..edc8872b 100644 --- a/drivers/platform/tegra/dce/dce-debug.c +++ b/drivers/platform/tegra/dce/dce-debug.c @@ -1,5 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -17,6 +18,7 @@ #include #include #include +#include #include #include @@ -612,6 +614,44 @@ static const struct file_operations boot_status_fops = { .read = dbg_dce_boot_status_fops_read, }; +static const struct file_operations perf_format_fops = { + .open = dbg_dce_perf_format_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_format_fops_write, +}; + +static const struct file_operations perf_stats_stats_fops = { + .open = dbg_dce_perf_stats_stats_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_stats_stats_fops_write, +}; + +static const struct file_operations perf_stats_help_fops = { + .open = dbg_dce_perf_stats_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations perf_events_events_fops = { + .open = dbg_dce_perf_events_events_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_events_events_fops_write, +}; + +static const struct file_operations perf_events_help_fops = { + .open = dbg_dce_perf_events_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + void dce_remove_debug(struct tegra_dce *d) { struct dce_device *d_dev = dce_device_from_dce(d); @@ -699,6 +739,7 @@ void dce_init_debug(struct tegra_dce *d) struct device *dev = dev_from_dce(d); struct dce_device *d_dev = dce_device_from_dce(d); struct dentry *debugfs_dir = NULL; + struct dentry *perf_debugfs_dir = NULL; d_dev->debugfs = debugfs_create_dir("tegra_dce", NULL); if (!d_dev->debugfs) @@ -734,6 +775,43 @@ void dce_init_debug(struct tegra_dce *d) if (!retval) goto err_handle; + perf_debugfs_dir = debugfs_create_dir("perf", d_dev->debugfs); + if (!perf_debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("format", 0644, + perf_debugfs_dir, d, &perf_format_fops); + if (!retval) + goto err_handle; + + debugfs_dir = debugfs_create_dir("stats", perf_debugfs_dir); + if (!debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("stats", 0644, + debugfs_dir, d, &perf_stats_stats_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("help", 0444, + debugfs_dir, d, &perf_stats_help_fops); + if (!retval) + goto err_handle; + + debugfs_dir = debugfs_create_dir("events", perf_debugfs_dir); + if (!debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("events", 0644, + debugfs_dir, d, &perf_events_events_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("help", 0444, + debugfs_dir, d, &perf_events_help_fops); + if (!retval) + goto err_handle; + retval = debugfs_create_file("dump_hsp_regs", 0444, d_dev->debugfs, d, &dump_hsp_regs_fops); if (!retval) diff --git a/drivers/platform/tegra/dce/include/dce-debug-perf.h b/drivers/platform/tegra/dce/include/dce-debug-perf.h new file mode 100644 index 00000000..60bef8a4 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-debug-perf.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#ifndef DCE_DEBUG_PERF_H +#define DCE_DEBUG_PERF_H + +#include +#include + +int dbg_dce_perf_stats_stats_fops_open(struct inode *inode, + struct file *file); +ssize_t dbg_dce_perf_stats_stats_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); + +int dbg_dce_perf_stats_help_fops_open(struct inode *inode, + struct file *file); + +ssize_t dbg_dce_perf_format_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); +int dbg_dce_perf_format_fops_open(struct inode *inode, struct file *file); + +int dbg_dce_perf_events_events_fops_open(struct inode *inode, + struct file *file); +ssize_t dbg_dce_perf_events_events_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); +int dbg_dce_perf_events_help_fops_open(struct inode *inode, + struct file *file); +#endif diff --git a/drivers/platform/tegra/dce/include/dce-ipc.h b/drivers/platform/tegra/dce/include/dce-ipc.h index ba85a876..a54dc8a6 100644 --- a/drivers/platform/tegra/dce/include/dce-ipc.h +++ b/drivers/platform/tegra/dce/include/dce-ipc.h @@ -1,5 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -34,7 +35,7 @@ #define DCE_DISPRM_CMD_MAX_FSIZE 4096U #define DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES 4U #define DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE 4096U -#define DCE_ADMIN_CMD_MAX_FSIZE 1024U +#define DCE_ADMIN_CMD_MAX_FSIZE 2048U #define DCE_IPC_WAIT_TYPE_INVALID 0U #define DCE_IPC_WAIT_TYPE_RPC 1U diff --git a/drivers/platform/tegra/dce/include/dce.h b/drivers/platform/tegra/dce/include/dce.h index fd5fa7e9..2a0460b7 100644 --- a/drivers/platform/tegra/dce/include/dce.h +++ b/drivers/platform/tegra/dce/include/dce.h @@ -1,5 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -425,6 +426,17 @@ int dce_admin_send_enter_sc7(struct tegra_dce *d, struct dce_ipc_message *msg); int dce_admin_handle_ipc_requested_event(struct tegra_dce *d, void *params); int dce_admin_handle_ipc_received_event(struct tegra_dce *d, void *params); +int dce_admin_send_cmd_get_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_set_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg, + bool start_perf); +int dce_admin_send_cmd_stop_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_get_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_clear_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg); int dce_admin_ipc_wait(struct tegra_dce *d, u32 w_type); void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type); diff --git a/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h b/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h index d5ebffcb..3aed6230 100644 --- a/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h +++ b/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h @@ -1,5 +1,6 @@ +/* SPDX-License-Identifier: MIT */ /* - * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2018-2023 NVIDIA CORPORATION. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -24,6 +25,7 @@ #define DCE_ADMIN_CMDS_H #include +#include /* * Version of the ADMIN command interface. @@ -34,7 +36,7 @@ * To keep things simple, this value should be incremented by 1 each * time changes are made. */ -#define DCE_ADMIN_VERSION 2 +#define DCE_ADMIN_VERSION 3 #define DCE_ADMIN_CMD_SIZE sizeof(struct dce_admin_ipc_cmd) #define DCE_ADMIN_RESP_SIZE sizeof(struct dce_admin_ipc_resp) @@ -58,20 +60,22 @@ #define DCE_ADMIN_CMD_GET_LOG_INFO 0x0BU // get current log info #define DCE_ADMIN_CMD_LOCK_CHANGES 0x0CU // lock creating new channels // and changing memory areas -#define DCE_ADMIN_CMD_CODE_COVERAGE_START 0x0DU // start collecting code - // coverage data -#define DCE_ADMIN_CMD_CODE_COVERAGE_STOP 0x0EU // stop collecting code - // coverage data +#define DCE_ADMIN_CMD_CODE_COVERAGE_START 0x0DU // start collecting code + // coverage data +#define DCE_ADMIN_CMD_CODE_COVERAGE_STOP 0x0EU // stop collecting code + // coverage data #define DCE_ADMIN_CMD_PERF_START 0x0FU // start collecting perf data #define DCE_ADMIN_CMD_PERF_STOP 0x10U // stop collecting perf data -#define DCE_ADMIN_CMD_INT_TEST_START 0x11U // start internal tests -#define DCE_ADMIN_CMD_INT_TEST_STOP 0x12U // stop internal tests and return status -#define DCE_ADMIN_CMD_EXT_TEST 0x13U // run external test (blocking call) +#define DCE_ADMIN_CMD_INT_TEST_START 0x11U // start internal tests +#define DCE_ADMIN_CMD_INT_TEST_STOP 0x12U // stop internal tests and return status +#define DCE_ADMIN_CMD_EXT_TEST 0x13U // run external test (blocking call) #define DCE_ADMIN_CMD_DEBUG 0x14U // debug command - #define DCE_ADMIN_CMD_RM_BOOTSTRAP 0x15U // tell RM to "bootstrap" +#define DCE_ADMIN_CMD_PERF_RESULTS 0x16U // copy out the perf results +#define DCE_ADMIN_CMD_PERF_GET_EVENTS 0x17U // get perf events +#define DCE_ADMIN_CMD_PERF_CLEAR_EVENTS 0x18U // clear perf events -#define DCE_ADMIN_CMD_NEXT 0x16U // must be last command ID + 1 +#define DCE_ADMIN_CMD_NEXT 0x19U // must be last command ID + 1 struct dce_admin_version_info { uint32_t version; @@ -159,6 +163,7 @@ struct dce_admin_ipc_cmd { struct dce_admin_ipc_info_args ipc_info; struct dce_admin_mem_args mem_map; struct dce_admin_ipc_create_args ipc_create; + struct dce_admin_perf_args perf; } args; }; @@ -166,11 +171,12 @@ struct dce_admin_ipc_resp { uint32_t error; union { struct dce_admin_version_info version; - struct dce_admin_echo echo; - struct dce_admin_log_args log; - struct dce_admin_ipc_info ipc; - struct dce_admin_mem_args mem_info; + struct dce_admin_echo echo; + struct dce_admin_log_args log; + struct dce_admin_ipc_info ipc; + struct dce_admin_mem_args mem_info; struct dce_admin_fw_version_info fw_version; + struct dce_admin_perf_stats_info perf; } args; }; diff --git a/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h b/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h new file mode 100644 index 00000000..0c2a9c2c --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_ADMIN_PERF_STATS_H +#define DCE_ADMIN_PERF_STATS_H + +/* + * Values for stats field + */ +#define DCE_ADMIN_PERF_STATS_SCHED 0x0001 +#define DCE_ADMIN_PERF_STATS_CACHE 0x0002 +#define DCE_ADMIN_PERF_STATS_IEXEC 0x0003 +#define DCE_ADMIN_PERF_STATS_MMIO 0x0004 +#define DCE_ADMIN_PERF_STATS_ALL 0xFFFF + +/* + * Values for clear field + */ +#define DCE_ADMIN_PERF_CLEAR_RETAIN 0U +#define DCE_ADMIN_PERF_CLEAR_CLEAR 1U + +#define DCE_ADMIN_EVENT_CLEAR_RETAIN 0U +#define DCE_ADMIN_EVENT_CLEAR_CLEAR 1U + +struct dce_admin_perf_cmd_args { + uint32_t enable; // Enable stats capture through Bit-Mask + uint32_t clear; // Clear all event stats +}; + +struct dce_admin_perf_args { + struct dce_admin_perf_cmd_args perf_cmd; + struct dce_admin_perf_cmd_args event_cmd; +}; + +/* + * Task related defines for perf stats + */ +#define DCE_ADMIN_PERF_ADMIN_TASK 0U +#define DCE_ADMIN_PERF_RM_TASK 1U +#define DCE_ADMIN_PERF_PRINT_TASK 2U +#define DCE_ADMIN_PERF_TCU_TASK 3U +#define DCE_ADMIN_PERF_IDLE_TASK 4U +// Change the active task number on adding more tasks +#define DCE_ADMIN_PERF_ACTIVE_TASKS_NUM 5U +// Max number of tasks in perf stats capture +#define DCE_ADMIN_PERF_NUM_TASKS 10U +// Task name length +#define DCE_ADMIN_TASK_NAME_LEN 16U + +struct dce_admin_stat_avg { + uint64_t accumulate; // accumulated delta times + uint64_t iterations; + uint64_t min; + uint64_t max; +}; + +struct dce_admin_task_stats { + const char name[DCE_ADMIN_TASK_NAME_LEN]; // task name + uint64_t init; // how long to run init code + uint64_t ready; // how long for task to be ready + uint64_t ready_time; // time when task became ready + uint64_t dcache_misses; // dcaches missed count accumulated within this task + uint64_t inst_exec; // instruction execution accumulated + uint64_t mmio_req; // memory request accumulated + struct dce_admin_stat_avg sleep; + struct dce_admin_stat_avg run; + struct dce_admin_stat_avg run_context; // running context +}; + +struct dce_admin_sched_stats { + uint64_t start; // time collection started + uint64_t end; // time collection stopped + uint64_t context_switches; // overall context switches + struct dce_admin_task_stats tasks[DCE_ADMIN_PERF_NUM_TASKS]; +}; + +/* + * Performance monitor events and counters + */ +#define DCE_ADMIN_PM_EVT_DCACHE_MISSES 0U +#define DCE_ADMIN_PM_EVT_INSTR_EXEC 1U +#define DCE_ADMIN_PM_EVT_MEM_REQ 2U +#define DCE_ADMIN_PM_EVT_COUNTERS 3U + +struct dce_admin_pm_event_stats { + uint64_t start; // time collection started + uint64_t end; // time collection stopped + uint64_t count; // Event counter accumulated +}; + +struct dce_admin_perf_info { + uint64_t tcm_ready; // tcm is ready/copied + uint64_t sched_start; // scheduler started + uint64_t ready_time; // signaled ready + struct dce_admin_pm_event_stats pm_events[DCE_ADMIN_PM_EVT_COUNTERS]; + struct dce_admin_sched_stats sched; +}; + +#define DCE_ADMIN_EVENT_NAME_LEN 32U // max length of an event name +#define DCE_ADMIN_NUM_EVENTS 20U // s.b. same as DCE_NUM_EVENTS + +struct dce_admin_event { + char name[DCE_ADMIN_EVENT_NAME_LEN]; + struct dce_admin_stat_avg event; +}; + +struct dce_admin_event_info { + struct dce_admin_event events[DCE_ADMIN_NUM_EVENTS]; +}; + + +struct dce_admin_perf_stats_info { + union { + struct dce_admin_perf_info sched_stats; + struct dce_admin_event_info events_stats; + } info; +}; +#endif