mirror of
git://nv-tegra.nvidia.com/linux-nvgpu.git
synced 2025-12-22 17:36:20 +03:00
Enable nvriscv debug buffer feature in NVGPU. Debug buffer is a feature to print the debug log from ucode onto console in real time. Debug buffer feature uses the DMEM, queue and SWGEN1 interrupt to share ucode debug data with NVGPU. Ucode writes debug message to DMEM and updates offset in queue to trigger interrupt to NVGPU. NVGPU copies the debug message from DMEM to local buffer to process and print onto console. Debug buffer feature is added under falcon unit and required engine can utilize the feature by providing required param through public functions. Currently GA10B NVRISCV NS/LS PMU ucode has support for this feature and enabled support on NVGPU side by adding required changes, with this feature enabled, it is now possible to see prints in real time. JIRA NVGPU-6959 Change-Id: I9d46020470285b490b6bc876204f62698055b1ec Signed-off-by: mkumbar <mkumbar@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvgpu/+/2548951 Reviewed-by: svc-mobile-coverity <svc-mobile-coverity@nvidia.com> Reviewed-by: svc-mobile-cert <svc-mobile-cert@nvidia.com> Reviewed-by: svc-mobile-misra <svc-mobile-misra@nvidia.com> Reviewed-by: svc_kernel_abi <svc_kernel_abi@nvidia.com> Reviewed-by: Vijayakumar Subbu <vsubbu@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com> GVS: Gerrit_Virtual_Submit Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
378 lines
12 KiB
C
378 lines
12 KiB
C
/*
|
|
* Copyright (c) 2021, 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.
|
|
*/
|
|
|
|
#include <nvgpu/gk20a.h>
|
|
#include <nvgpu/timers.h>
|
|
#include <nvgpu/falcon.h>
|
|
#include <nvgpu/io.h>
|
|
#include <nvgpu/static_analysis.h>
|
|
#include <nvgpu/string.h>
|
|
|
|
#include "falcon_debug.h"
|
|
|
|
#define NV_NVRISCV_DEBUG_BUFFER_MAGIC 0xf007ba11
|
|
|
|
#define FLCN_DMEM_ACCESS_ALIGNMENT (4)
|
|
|
|
#define NV_ALIGN_DOWN(v, g) ((v) & ~((g) - 1))
|
|
|
|
#define NV_IS_ALIGNED(addr, align) ((addr & (align - 1U)) == 0U)
|
|
|
|
void nvgpu_falcon_dbg_buf_destroy(struct nvgpu_falcon *flcn)
|
|
{
|
|
struct gk20a *g = flcn->g;
|
|
struct nvgpu_falcon_dbg_buf *debug_buffer = &flcn->debug_buffer;
|
|
|
|
if (debug_buffer->local_buf != NULL) {
|
|
nvgpu_kfree(g, debug_buffer->local_buf);
|
|
debug_buffer->local_buf = NULL;
|
|
}
|
|
|
|
debug_buffer->first_msg_received = false;
|
|
debug_buffer->read_offset = 0;
|
|
debug_buffer->buffer_size = 0;
|
|
debug_buffer->dmem_offset = g->ops.falcon.get_mem_size(flcn, MEM_DMEM);
|
|
}
|
|
|
|
int nvgpu_falcon_dbg_buf_init(struct nvgpu_falcon *flcn,
|
|
u32 debug_buffer_max_size, u32 write_reg_addr, u32 read_reg_addr)
|
|
{
|
|
struct gk20a *g = flcn->g;
|
|
struct nvgpu_falcon_dbg_buf *debug_buffer = &flcn->debug_buffer;
|
|
int status = 0;
|
|
|
|
/*
|
|
* Set the debugBufferSize to it's initial value of max size.
|
|
* We will refine it later once ucode informs us of the size it wants
|
|
* the debug buffer to be.
|
|
*/
|
|
debug_buffer->buffer_size = debug_buffer_max_size;
|
|
debug_buffer->first_msg_received = false;
|
|
debug_buffer->read_offset = 0;
|
|
|
|
if (debug_buffer->local_buf == NULL) {
|
|
/*
|
|
* Allocate memory for nvgpu-side debug buffer, used for copies
|
|
* from nvriscv dmem. we make it 1 byte larger than the actual debug
|
|
* buffer to keep a null character at the end for ease of printing.
|
|
*/
|
|
debug_buffer->local_buf = nvgpu_kzalloc(g, debug_buffer_max_size + 1);
|
|
|
|
if (debug_buffer->local_buf == NULL) {
|
|
nvgpu_err(g, "Failed to alloc memory for flcn debug buffer");
|
|
nvgpu_err(g, "status=0x%08x", status);
|
|
status = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
/* Zero out memory in the local debug buffer. */
|
|
memset(debug_buffer->local_buf, 0, debug_buffer_max_size + 1);
|
|
|
|
/*
|
|
* Debug buffer is located at the very end of available DMEM.
|
|
* NVGPU don't know the exact size until the ucode informs us of
|
|
* the size it wants, so only make it as large as the metadata
|
|
* at the end of the buffer.
|
|
*/
|
|
debug_buffer->dmem_offset = g->ops.falcon.get_mem_size(flcn, MEM_DMEM) -
|
|
sizeof(struct nvgpu_falcon_dbg_buf_metadata);
|
|
|
|
/* The DMEM offset must be 4-byte aligned */
|
|
if (!NV_IS_ALIGNED(debug_buffer->dmem_offset, FLCN_DMEM_ACCESS_ALIGNMENT)) {
|
|
nvgpu_err(g, "metadata DMEM offset is not 4-byte aligned.");
|
|
nvgpu_err(g, "dmem_offset=0x%08x", debug_buffer->dmem_offset);
|
|
status = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* The DMEM buffer size must be 4-byte aligned */
|
|
if (!NV_IS_ALIGNED(sizeof(struct nvgpu_falcon_dbg_buf_metadata),
|
|
FLCN_DMEM_ACCESS_ALIGNMENT)) {
|
|
nvgpu_err(g, "The debug buffer metadata size is not 4-byte aligned");
|
|
status = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
debug_buffer->read_offset_address = read_reg_addr;
|
|
debug_buffer->write_offset_address = write_reg_addr;
|
|
|
|
exit:
|
|
if (status != 0) {
|
|
nvgpu_falcon_dbg_buf_destroy(flcn);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Copy new data from the nvriscv debug buffer to the local buffer.
|
|
* Get all data from the last read offset to the current write offset.
|
|
*
|
|
* @return '0' if data fetched successfully, error otherwise.
|
|
*/
|
|
static int falcon_update_debug_buffer_from_dmem(struct nvgpu_falcon *flcn,
|
|
u32 write_offset)
|
|
{
|
|
struct gk20a *g = flcn->g;
|
|
struct nvgpu_falcon_dbg_buf *debug_buffer = &flcn->debug_buffer;
|
|
u32 first_read_size = 0;
|
|
u32 second_read_size = 0;
|
|
|
|
/*
|
|
* Align read offset, since reading DMEM only works with 32-bit words.
|
|
* We only need to align the offset since dmem_offset is already aligned.
|
|
* We don't need to align the write offset since nvgpu_falcon_copy_from_dmem
|
|
* handles unaligned-size reads.
|
|
*/
|
|
u32 read_offset_aligned = NV_ALIGN_DOWN(debug_buffer->read_offset,
|
|
FLCN_DMEM_ACCESS_ALIGNMENT);
|
|
|
|
if (write_offset >= debug_buffer->read_offset) {
|
|
first_read_size = write_offset - read_offset_aligned;
|
|
second_read_size = 0;
|
|
} else {
|
|
/* Write offset has wrapped around, need two reads */
|
|
first_read_size = debug_buffer->buffer_size - read_offset_aligned;
|
|
second_read_size = write_offset;
|
|
}
|
|
|
|
if (first_read_size > 0) {
|
|
if (read_offset_aligned + first_read_size >
|
|
debug_buffer->buffer_size) {
|
|
nvgpu_err(g,
|
|
"Invalid read (first read) from print buffer attempted!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (nvgpu_falcon_copy_from_dmem(flcn,
|
|
debug_buffer->dmem_offset + read_offset_aligned,
|
|
debug_buffer->local_buf + read_offset_aligned,
|
|
first_read_size,
|
|
0) != 0) {
|
|
nvgpu_err(g, "Failed to copy debug buffer contents from DMEM");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (second_read_size > 0) {
|
|
if (second_read_size > debug_buffer->buffer_size) {
|
|
nvgpu_err(g,
|
|
"Invalid read (second read) from print buffer attempted!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Wrap around, read from start
|
|
* Assume dmem_offset is always aligned.
|
|
*/
|
|
if (nvgpu_falcon_copy_from_dmem(flcn, debug_buffer->dmem_offset,
|
|
debug_buffer->local_buf, second_read_size,
|
|
0) != 0) {
|
|
nvgpu_err(g,
|
|
"Failed to copy debug buffer contents from nvriscv DMEM");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (first_read_size == 0 && second_read_size == 0) {
|
|
nvgpu_err(g, "Debug buffer empty, can't read any data!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* There is a metadata buffer at the end of the DMEM buffer in nvriscv.
|
|
* It sets the buffer size, the magic number for identification etc.
|
|
*
|
|
*/
|
|
static int falcon_fetch_debug_buffer_metadata(struct nvgpu_falcon *flcn)
|
|
{
|
|
struct gk20a *g = flcn->g;
|
|
struct nvgpu_falcon_dbg_buf *debug_buffer = &flcn->debug_buffer;
|
|
struct nvgpu_falcon_dbg_buf_metadata buffer_metadata_copy;
|
|
|
|
/* DMEM offset will point to metadata initially */
|
|
if (nvgpu_falcon_copy_from_dmem(flcn, debug_buffer->dmem_offset,
|
|
(u8 *)&buffer_metadata_copy, sizeof(buffer_metadata_copy),
|
|
0) != 0) {
|
|
nvgpu_err(g, "Failed to copy debug buffer metadata from nvriscv DMEM");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nvgpu_info(g, "metadata magic - 0x%x", buffer_metadata_copy.magic);
|
|
nvgpu_info(g, "metadata buffer size - 0x%x",
|
|
buffer_metadata_copy.buffer_size);
|
|
nvgpu_info(g, "metadata write offset - 0x%x",
|
|
buffer_metadata_copy.write_offset);
|
|
nvgpu_info(g, "metadata read offset - 0x%x",
|
|
buffer_metadata_copy.read_offset);
|
|
|
|
if (buffer_metadata_copy.magic != NV_NVRISCV_DEBUG_BUFFER_MAGIC) {
|
|
nvgpu_err(g, "Failed to verify magic number in debug buffer");
|
|
nvgpu_err(g, " metadata copied from nvriscv DMEM");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (buffer_metadata_copy.buffer_size >= debug_buffer->buffer_size) {
|
|
nvgpu_err(g, "Debug buffer size requested by ucode too big!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
debug_buffer->buffer_size = buffer_metadata_copy.buffer_size;
|
|
|
|
/* The DMEM buffer size must be 4-byte aligned */
|
|
if (!NV_IS_ALIGNED(debug_buffer->buffer_size, FLCN_DMEM_ACCESS_ALIGNMENT)) {
|
|
nvgpu_err(g, "The debug buffer size is not 4-byte aligned");
|
|
nvgpu_err(g, "buffer_size=0x%08x", debug_buffer->buffer_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* NVGPU don't want to overwrite the metadata since NVGPU might want to use
|
|
* it to pass read and write offsets if no registers are available.
|
|
*/
|
|
debug_buffer->dmem_offset -= buffer_metadata_copy.buffer_size;
|
|
|
|
/* The DMEM offset must be 4-byte aligned */
|
|
if (!NV_IS_ALIGNED(debug_buffer->dmem_offset, FLCN_DMEM_ACCESS_ALIGNMENT)) {
|
|
nvgpu_err(g, "The debug buffer DMEM offset is not 4-byte aligned.");
|
|
nvgpu_err(g, " dmem_offset=0x%08x", debug_buffer->dmem_offset);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nvgpu_falcon_dbg_buf_display(struct nvgpu_falcon *flcn)
|
|
{
|
|
struct gk20a *g = flcn->g;
|
|
struct nvgpu_falcon_dbg_buf *debug_buffer = &flcn->debug_buffer;
|
|
u8 *buffer_data = debug_buffer->local_buf;
|
|
u32 write_offset = nvgpu_readl(g, debug_buffer->write_offset_address);
|
|
u32 itr_Offset = debug_buffer->read_offset;
|
|
|
|
bool is_line_split = false;
|
|
|
|
if (debug_buffer->local_buf == NULL) {
|
|
nvgpu_err(g, "Local Debug Buffer not allocated!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!debug_buffer->first_msg_received) {
|
|
if (falcon_fetch_debug_buffer_metadata(flcn) != 0) {
|
|
nvgpu_err(g, "Failed to process debug buffer metadata!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
debug_buffer->first_msg_received = true;
|
|
}
|
|
|
|
if (write_offset >= debug_buffer->buffer_size) {
|
|
nvgpu_err(g, "Invalid write offset (%u >= %u)",
|
|
write_offset, debug_buffer->buffer_size);
|
|
nvgpu_err(g, "abort Debug buffer display");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (falcon_update_debug_buffer_from_dmem(flcn, write_offset) != 0) {
|
|
nvgpu_err(g, "Failed to fetch debug buffer contents");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Buffer is empty when read_offset == write_offset */
|
|
while (itr_Offset != write_offset) {
|
|
/* Null character is the newline marker in falcon firmware logs */
|
|
if (buffer_data[itr_Offset] != '\0') {
|
|
itr_Offset = (itr_Offset + 1) % debug_buffer->buffer_size;
|
|
if (itr_Offset == 0) {
|
|
is_line_split = true;
|
|
}
|
|
} else {
|
|
int status = 0;
|
|
u8 *tmp_buf = NULL;
|
|
u8 *curr_data = NULL;
|
|
u32 buf_size = 0;
|
|
|
|
if (is_line_split) {
|
|
/* Logic to concat the split line into a temp buffer */
|
|
u32 first_chunk_len =
|
|
strlen((char *)&buffer_data[debug_buffer->read_offset]);
|
|
u32 second_chunk_len = strlen((char *)&buffer_data[0]);
|
|
|
|
buf_size = first_chunk_len + second_chunk_len + 1;
|
|
tmp_buf = nvgpu_kzalloc(g, buf_size);
|
|
|
|
if (tmp_buf == NULL) {
|
|
status = -ENOMEM;
|
|
nvgpu_err(g,
|
|
"Failed to alloc tmp buf for line-split print %d",
|
|
status);
|
|
return status;
|
|
}
|
|
|
|
nvgpu_memcpy(tmp_buf, &buffer_data[debug_buffer->read_offset],
|
|
first_chunk_len + 1);
|
|
strcat((char *)tmp_buf, (char *)&buffer_data[0]);
|
|
|
|
/* Set the byte array that gets printed as a string */
|
|
curr_data = tmp_buf;
|
|
|
|
/* Reset line-split flag */
|
|
is_line_split = false;
|
|
} else {
|
|
buf_size =
|
|
strlen((char *)&buffer_data[debug_buffer->read_offset]) + 1;
|
|
|
|
/* Set the byte array that gets printed as a string */
|
|
curr_data = &buffer_data[debug_buffer->read_offset];
|
|
}
|
|
|
|
if (curr_data == NULL) {
|
|
status = -EINVAL;
|
|
nvgpu_err(g, "Debug buffer - no data to print %d", status);
|
|
|
|
if (tmp_buf != NULL) {
|
|
nvgpu_kfree(g, tmp_buf);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
nvgpu_info(g, "Flcn-%d Async: %s", flcn->flcn_id, curr_data);
|
|
|
|
/* Cleanup in case we had to allocate a temp buffer */
|
|
if (tmp_buf != NULL) {
|
|
nvgpu_kfree(g, tmp_buf);
|
|
}
|
|
|
|
itr_Offset = (itr_Offset + 1) % debug_buffer->buffer_size;
|
|
debug_buffer->read_offset = itr_Offset;
|
|
}
|
|
}
|
|
|
|
nvgpu_writel(g, debug_buffer->read_offset_address,
|
|
debug_buffer->read_offset);
|
|
|
|
return 0;
|
|
}
|