diff --git a/drivers/platform/tegra/dce/Makefile b/drivers/platform/tegra/dce/Makefile index f58455de..8b759df5 100644 --- a/drivers/platform/tegra/dce/Makefile +++ b/drivers/platform/tegra/dce/Makefile @@ -32,6 +32,7 @@ tegra-dce-$(CONFIG_TEGRA_DCE) += dce-admin.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-ipc.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-ipc-signal.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-client-ipc.o +tegra-dce-$(CONFIG_TEGRA_DCE) += dce-logging.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-module.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-pm.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-os-work.o @@ -42,6 +43,7 @@ tegra-dce-$(CONFIG_TEGRA_DCE) += dce-os-ivc.o ifeq ($(CONFIG_DEBUG_FS),y) tegra-dce-$(CONFIG_TEGRA_DCE) += dce-debug.o +tegra-dce-$(CONFIG_TEGRA_DCE) += dce-debug-logging.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-admin-debug.o tegra-dce-$(CONFIG_TEGRA_DCE) += dce-debug-perf.o endif diff --git a/drivers/platform/tegra/dce/dce-admin.c b/drivers/platform/tegra/dce/dce-admin.c index 21857cfc..f034ebf0 100644 --- a/drivers/platform/tegra/dce/dce-admin.c +++ b/drivers/platform/tegra/dce/dce-admin.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -532,6 +533,12 @@ int dce_admin_init(struct tegra_dce *d) goto err_ipc_reg_alloc; } + ret = dce_log_buf_mem_init(d); + if (ret) { + dce_os_err(d, "dce log buf mem init failed"); + goto err_log_buf_alloc; + } + ret = dce_admin_channel_init(d); if (ret) { dce_os_err(d, "Channel Initialization Failed"); @@ -553,6 +560,7 @@ err_channel_init: dce_os_ipc_deinit_region_info(d); err_ipc_reg_alloc: d->boot_status |= DCE_EARLY_INIT_FAILED; +err_log_buf_alloc: return ret; } @@ -901,6 +909,56 @@ out: return ret; } +int dce_admin_get_log_info(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 == NULL) { + dce_os_err(d, "Invalid params passed in get log call"); + 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_GET_LOG_INFO; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_os_err(d, "Error in sending get log call msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + +int dce_admin_set_log_info(struct tegra_dce *d, struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + + if (msg == NULL) { + dce_os_err(d, "Invalid params passed in set log call"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_SET_LOGGING; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_os_err(d, "Error in sending set log call msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + static int dce_admin_setup_clients_ipc(struct tegra_dce *d, struct dce_ipc_message *msg) { @@ -1013,6 +1071,12 @@ int dce_start_admin_seq(struct tegra_dce *d) goto out; } + ret = dce_log_init(d, msg); + if (ret) { + dce_os_err(d, "RPC failed for DCE_ADMIN_CMD_SET_LOGGING"); + goto out; + } + ret = dce_admin_send_rm_bootstrap(d, msg); if (ret) { dce_os_err(d, "RPC failed for DCE_ADMIN_CMD_RM_BOOTSTRAP"); diff --git a/drivers/platform/tegra/dce/dce-debug-logging.c b/drivers/platform/tegra/dce/dce-debug-logging.c new file mode 100644 index 00000000..45ecb3a2 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-debug-logging.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +static int dbg_dce_log_help_fops_show(struct seq_file *s, void *data) +{ +/** + * Writing: + * '0' to /sys/kernel/debug/tegra_dce/dce_logs/logs + * - Clear the circular buffer region data. + * + * Reading: + * cat /sys/kernel/debug/tegra_dce/dce_logs/logs + * - Prints the data stored in the buffer. + * - Current size of buffer is 512KB and on overflow starts overwriting the circular + * portion of the buffer in FIFO manner + */ + seq_printf(s, " DCE logs capture\n" + "---------------------------------------------------------------\n" + " echo '0' > dce_logs/logs: Clear circular region of buffer\n" + "---------------------------------------------------------------\n" + " cat dce_logs/logs: Print buffer data\n" + "---------------------------------------------------------------\n"); + return 0; +} + +int dbg_dce_log_help_fops_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_dce_log_help_fops_show, + inode->i_private); +} + +static int dbg_dce_log_fops_show(struct seq_file *s, void *data) +{ + int ret = 0; + uint32_t offset; + uint64_t bytes_written; + uint32_t cur_buf_idx; + struct tegra_dce *d = s->private; + char *base_addr = (char *)d->dce_log_buff.cpu_base; + uint32_t log_buf_size = d->dce_log_buff.size; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_resp *resp_msg; + + msg = dce_admin_channel_client_buffer_get(d, DCE_ADMIN_CH_CL_DBG_BUFF, + 0 /* reserved flags */); + + if (!msg) { + ret = -1; + dce_os_err(d, "IPC msg allocation failed"); + goto out; + } + + /** Retrieve logging info */ + ret = dce_admin_get_log_info(d, msg); + if (ret) { + dce_os_err(d, "Failed to retrieve logging info, ret = 0x%x", ret); + goto out; + } + + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + bytes_written = resp_msg->args.log.get_log_info.bytes_written; + offset = resp_msg->args.log.get_log_info.offset; + + /** If complete buffer size is zero then buffer is invalid */ + if (log_buf_size == 0) { + seq_printf(s, "%s", "Invalid log buffer\n"); + goto out; + } + + if (bytes_written == 0U || (log_buf_size <= offset)) { + seq_printf(s, "%.*s", offset, base_addr); + goto out; + } + + /** Find total bytes written currently in circular region of buffer */ + cur_buf_idx = (bytes_written)%(log_buf_size - offset); + + /** If circular buffer is not yet wrapped around */ + if (bytes_written <= log_buf_size - offset) { + seq_printf(s, "%.*s", offset, base_addr); + seq_printf(s, "%.*s", cur_buf_idx, base_addr + offset); + goto out; + } + + /** If circular buffer region has been overwritten */ + /** Print initial logs till offset */ + seq_printf(s, "%.*s", offset, base_addr); + /** Print logs stored in circular buffer region */ + seq_printf(s, "%.*s", log_buf_size - cur_buf_idx - offset, base_addr + offset + cur_buf_idx); + seq_printf(s, "%.*s", cur_buf_idx, base_addr + offset); + +out: + if (msg) + dce_admin_channel_client_buffer_put(d, msg); + return ret; +} + +int dbg_dce_log_fops_open(struct inode *inode, struct file *file) +{ + size_t seq_buf_size = 100*PAGE_SIZE; + + return single_open_size(file, &dbg_dce_log_fops_show, inode->i_private, seq_buf_size); +} + +ssize_t dbg_dce_log_fops_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + char buf[32]; + uint32_t buf_size; + uint32_t log_op; + struct dce_ipc_message *msg = NULL; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + buf_size = min(count, (sizeof(buf)-1)); + + msg = dce_admin_channel_client_buffer_get(d, DCE_ADMIN_CH_CL_DBG_BUFF, + 0 /* reserved flags */); + if (!msg) { + dce_os_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = kstrtou32_from_user(user_buf, buf_size, 10, &log_op); + if (ret) { + dce_os_err(d, "Invalid format!"); + goto out; + } + + if (log_op == 0U) { + dce_os_err(d, "Clearing circular region of log buffer"); + ret = dce_log_clear_buffer(d, msg); + if (ret) + dce_os_err(d, "Failed to clear log buffer!"); + } + +out: + if (msg) + dce_admin_channel_client_buffer_put(d, msg); + return count; +} diff --git a/drivers/platform/tegra/dce/dce-debug.c b/drivers/platform/tegra/dce/dce-debug.c index 51a21e68..7361e286 100644 --- a/drivers/platform/tegra/dce/dce-debug.c +++ b/drivers/platform/tegra/dce/dce-debug.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2019-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -162,6 +163,21 @@ static const struct file_operations load_firmware_fops = { .write = dbg_dce_load_fw_write, }; +static const struct file_operations dbg_dce_log_help_fops = { + .open = dbg_dce_log_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations dbg_dce_log_fops = { + .open = dbg_dce_log_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_log_fops_write, +}; + static ssize_t dbg_dce_config_ast_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { @@ -764,6 +780,7 @@ void dce_init_debug(struct tegra_dce *d) struct dce_linux_device *d_dev = dce_linux_device_from_dce(d); struct dentry *debugfs_dir = NULL; struct dentry *perf_debugfs_dir = NULL; + struct dentry *dce_logging_dir = NULL; int ret = 0; ret = dce_admin_channel_client_buffers_init(d, DCE_ADMIN_CH_CL_DBG_BUFF); @@ -781,6 +798,20 @@ void dce_init_debug(struct tegra_dce *d) if (!retval) goto err_handle; + dce_logging_dir = debugfs_create_dir("dce_logs", d_dev->debugfs); + if (!dce_logging_dir) + goto err_handle; + + retval = debugfs_create_file("help", 0444, + dce_logging_dir, d, &dbg_dce_log_help_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("logs", 0644, + dce_logging_dir, d, &dbg_dce_log_fops); + if (!retval) + goto err_handle; + retval = debugfs_create_file("config_ast", 0444, d_dev->debugfs, d, &config_ast_fops); if (!retval) diff --git a/drivers/platform/tegra/dce/dce-logging.c b/drivers/platform/tegra/dce/dce-logging.c new file mode 100644 index 00000000..f79afe22 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-logging.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#include +#include +#include + +int dce_log_buf_mem_init(struct tegra_dce *d) +{ + int ret = 0; + + ret = dce_os_init_log_buffer(d); + if (ret) { + dce_os_err(d, "Failed to allocate memory for DCE log buffer"); + goto out; + } + +out: + if (ret) + dce_os_deinit_log_buffer(d); + return ret; +} + +int dce_log_init(struct tegra_dce *d, struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_log_buffer *buffer; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_set_log_info *set_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + set_info = (struct dce_admin_set_log_info *)(&req_msg->args.log.set_log_info); + + buffer = &d->dce_log_buff; + set_info->flags = DCE_ADMIN_LOG_FL_SET_IOVA_ADDR | DCE_ADMIN_LOG_FL_SET_LOG_LVL; + set_info->iova = buffer->iova_addr; + set_info->buff_size = buffer->size; + set_info->log_level = (uint32_t)DCE_LOG_DEFAULT_LOG_LVL; + set_info->stream_id = dce_os_get_dce_stream_id(d); + + ret = dce_admin_set_log_info(d, msg); + if (ret) { + dce_os_err(d, "Failed to initialize logging, ret = %d\n", ret); + goto out; + } + +out: + return ret; +} + +int dce_log_clear_buffer(struct tegra_dce *d, struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_set_log_info *ipc_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + ipc_info = (struct dce_admin_set_log_info *)(&req_msg->args.log.set_log_info); + + ipc_info->flags |= DCE_ADMIN_LOG_FL_CLEAR_BUFFER; + + ret = dce_admin_set_log_info(d, msg); + if (ret) { + dce_os_err(d, "Failed while making set admin call, ret = %d\n", ret); + goto out; + } + +out: + return ret; +} + +int dce_log_set_log_level(struct tegra_dce *d, struct dce_ipc_message *msg, + u32 log_lvl) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_set_log_info *ipc_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + ipc_info = (struct dce_admin_set_log_info *)(&req_msg->args.log.set_log_info); + + ipc_info->flags |= DCE_ADMIN_LOG_FL_SET_LOG_LVL; + ipc_info->log_level = log_lvl; + + ret = dce_admin_set_log_info(d, msg); + if (ret) { + dce_os_err(d, "Failed while making set admin call, ret = %d\n", ret); + goto out; + } + +out: + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-os-utils.c b/drivers/platform/tegra/dce/dce-os-utils.c index 5a605f02..707edc60 100644 --- a/drivers/platform/tegra/dce/dce-os-utils.c +++ b/drivers/platform/tegra/dce/dce-os-utils.c @@ -570,3 +570,32 @@ void dce_os_bitmap_clear(unsigned long *map, { bitmap_clear(map, start, (int)len); } + +int dce_os_init_log_buffer(struct tegra_dce *d) +{ + struct dce_log_buffer *buffer; + struct device *dev = dev_from_dce_linux_device(d); + + buffer = &d->dce_log_buff; + buffer->size = SZ_512K; // Allocate 512KB for log buffer + + buffer->cpu_base = dma_alloc_coherent(dev, buffer->size, &buffer->iova_addr, + GFP_KERNEL); + + if (!buffer->iova_addr) + return -ENOMEM; + + return 0; +} + +void dce_os_deinit_log_buffer(struct tegra_dce *d) +{ + struct dce_log_buffer *buffer; + struct device *dev = dev_from_dce_linux_device(d); + + buffer = &d->dce_log_buff; + if (buffer->iova_addr) { + dma_free_coherent(dev, buffer->size, (void *)buffer->cpu_base, + buffer->iova_addr); + } +} diff --git a/drivers/platform/tegra/dce/include/dce-debug-logging.h b/drivers/platform/tegra/dce/include/dce-debug-logging.h new file mode 100644 index 00000000..7bb82acf --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-debug-logging.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef DCE_DEBUG_LOGGING_H +#define DCE_DEBUG_LOGGING_H + +#include + +int dbg_dce_log_help_fops_open(struct inode *inode, struct file *file); + +int dbg_dce_log_fops_open(struct inode *inode, struct file *file); + +ssize_t dbg_dce_log_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); + +#endif /* DCE_DEBUG_LOGGING_H */ diff --git a/drivers/platform/tegra/dce/include/dce-logging.h b/drivers/platform/tegra/dce/include/dce-logging.h new file mode 100644 index 00000000..d0b8035c --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-logging.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: MIT */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. 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_LOGGING_H +#define DCE_LOGGING_H + +#include +#include + +typedef uint8_t dce_log_set_log_flags_t; + +#define DCE_LOG_FL_CLEAR_BUFFER ((dce_log_set_log_flags_t)1U) +#define DCE_LOG_FL_SET_IOVA_ADDR ((dce_log_set_log_flags_t)2U) +#define DCE_LOG_FL_SET_LOG_LVL ((dce_log_set_log_flags_t)4U) + +typedef uint32_t dce_log_set_log_lvl_t; + +#define DCE_LOG_MAX_LOG_LVL ((dce_log_set_log_lvl_t)7U) +#define DCE_LOG_DEFAULT_LOG_LVL ((dce_log_set_log_lvl_t)0xFFFFFFFFU) + +/** + * @brief Allocate memory for DCE logging buffer + */ +int dce_log_buf_mem_init(struct tegra_dce *d); + +/** + * @brief Initialize set log struct while doing log buffer initialization + */ +int dce_log_init(struct tegra_dce *d, struct dce_ipc_message *msg); + +/** + * @brief Clear circular region of log buffer + */ +int dce_log_clear_buffer(struct tegra_dce *d, struct dce_ipc_message *msg); + +/** + * @brief Set log level for logging to log buffer + */ +int dce_log_set_log_level(struct tegra_dce *d, struct dce_ipc_message *msg, + u32 log_lvl); + +#endif /* DCE_LOGGING_H */ diff --git a/drivers/platform/tegra/dce/include/dce.h b/drivers/platform/tegra/dce/include/dce.h index 1f2a4ab9..563f3105 100644 --- a/drivers/platform/tegra/dce/include/dce.h +++ b/drivers/platform/tegra/dce/include/dce.h @@ -200,6 +200,19 @@ struct dce_firmware { u64 dma_handle; }; +/** + * struct dce_log_buffer - Contains dce log buffer info + * + * @iova_addr : iova address of the buffer + * @size : size of the buffer + * @base : cpu base address of the buffer + */ +struct dce_log_buffer { + dma_addr_t iova_addr; + size_t size; + void *cpu_base; +}; + /** * struct tegra_dce - Primary OS independent tegra dce structure to hold dce * cluster's and it's element's runtime info. @@ -287,6 +300,10 @@ struct tegra_dce { * @fw_data - Stores info regardign firmware to be used runtime. */ struct dce_firmware *fw_data; + /** + * @dce_log_buffer_struct - Stores info regarding DCE log buffer + */ + struct dce_log_buffer dce_log_buff; /** * @dce_admin_ch_cl_buff - Stores admin channel client buffers. */ @@ -424,6 +441,8 @@ 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); void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type); +int dce_admin_set_log_info(struct tegra_dce *d, struct dce_ipc_message *msg); +int dce_admin_get_log_info(struct tegra_dce *d, struct dce_ipc_message *msg); bool dce_fw_boot_complete(struct tegra_dce *d); void dce_request_fw_boot_complete(struct tegra_dce *d); diff --git a/drivers/platform/tegra/dce/os/include/dce-os-utils.h b/drivers/platform/tegra/dce/os/include/dce-os-utils.h index d383abb2..6283b2f9 100644 --- a/drivers/platform/tegra/dce/os/include/dce-os-utils.h +++ b/drivers/platform/tegra/dce/os/include/dce-os-utils.h @@ -64,4 +64,8 @@ void dce_os_bitmap_clear(unsigned long *map, u8 dce_os_get_dce_stream_id(struct tegra_dce *d); +int dce_os_init_log_buffer(struct tegra_dce *d); + +void dce_os_deinit_log_buffer(struct tegra_dce *d); + #endif /* DCE_OS_UTILS_H */