| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2022-2023 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU license. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| */ |
| |
| #include "mali_kbase_csf_sync_debugfs.h" |
| #include "mali_kbase_csf_csg_debugfs.h" |
| #include <mali_kbase.h> |
| #include <linux/seq_file.h> |
| #include <linux/version_compat_defs.h> |
| |
| #if IS_ENABLED(CONFIG_SYNC_FILE) |
| #include "mali_kbase_sync.h" |
| #endif |
| |
| #define CQS_UNREADABLE_LIVE_VALUE "(unavailable)" |
| |
| #define CSF_SYNC_DUMP_SIZE 256 |
| |
| /** |
| * kbasep_print() - Helper function to print to either debugfs file or dmesg. |
| * |
| * @kctx: The kbase context |
| * @file: The seq_file for printing to. This is NULL if printing to dmesg. |
| * @fmt: The message to print. |
| * @...: Arguments to format the message. |
| */ |
| __attribute__((format(__printf__, 3, 4))) static void |
| kbasep_print(struct kbase_context *kctx, struct seq_file *file, const char *fmt, ...) |
| { |
| int len = 0; |
| char buffer[CSF_SYNC_DUMP_SIZE]; |
| va_list arglist; |
| |
| va_start(arglist, fmt); |
| len = vsnprintf(buffer, CSF_SYNC_DUMP_SIZE, fmt, arglist); |
| if (len <= 0) { |
| pr_err("message write to the buffer failed"); |
| goto exit; |
| } |
| |
| if (file) |
| seq_printf(file, buffer); |
| else |
| dev_warn(kctx->kbdev->dev, buffer); |
| |
| exit: |
| va_end(arglist); |
| } |
| |
| /** |
| * kbasep_csf_debugfs_get_cqs_live_u32() - Obtain live (u32) value for a CQS object. |
| * |
| * @kctx: The context of the queue. |
| * @obj_addr: Pointer to the CQS live 32-bit value. |
| * @live_val: Pointer to the u32 that will be set to the CQS object's current, live |
| * value. |
| * |
| * Return: 0 if successful or a negative error code on failure. |
| */ |
| static int kbasep_csf_debugfs_get_cqs_live_u32(struct kbase_context *kctx, u64 obj_addr, |
| u32 *live_val) |
| { |
| struct kbase_vmap_struct *mapping; |
| u32 *const cpu_ptr = (u32 *)kbase_phy_alloc_mapping_get(kctx, obj_addr, &mapping); |
| |
| if (!cpu_ptr) |
| return -1; |
| |
| *live_val = *cpu_ptr; |
| kbase_phy_alloc_mapping_put(kctx, mapping); |
| return 0; |
| } |
| |
| /** |
| * kbasep_csf_debugfs_get_cqs_live_u64() - Obtain live (u64) value for a CQS object. |
| * |
| * @kctx: The context of the queue. |
| * @obj_addr: Pointer to the CQS live value (32 or 64-bit). |
| * @live_val: Pointer to the u64 that will be set to the CQS object's current, live |
| * value. |
| * |
| * Return: 0 if successful or a negative error code on failure. |
| */ |
| static int kbasep_csf_debugfs_get_cqs_live_u64(struct kbase_context *kctx, u64 obj_addr, |
| u64 *live_val) |
| { |
| struct kbase_vmap_struct *mapping; |
| u64 *cpu_ptr = (u64 *)kbase_phy_alloc_mapping_get(kctx, obj_addr, &mapping); |
| |
| if (!cpu_ptr) |
| return -1; |
| |
| *live_val = *cpu_ptr; |
| kbase_phy_alloc_mapping_put(kctx, mapping); |
| return 0; |
| } |
| |
| /** |
| * kbasep_csf_sync_print_kcpu_fence_wait_or_signal() - Print details of a CSF SYNC Fence Wait |
| * or Fence Signal command, contained in a |
| * KCPU queue. |
| * |
| * @buffer: The buffer to write to. |
| * @length: The length of text in the buffer. |
| * @cmd: The KCPU Command to be printed. |
| * @cmd_name: The name of the command: indicates either a fence SIGNAL or WAIT. |
| */ |
| static void kbasep_csf_sync_print_kcpu_fence_wait_or_signal(char *buffer, int *length, |
| struct kbase_kcpu_command *cmd, |
| const char *cmd_name) |
| { |
| #if (KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE) |
| struct fence *fence = NULL; |
| #else |
| struct dma_fence *fence = NULL; |
| #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) */ |
| struct kbase_kcpu_command_fence_info *fence_info; |
| struct kbase_sync_fence_info info; |
| const char *timeline_name = NULL; |
| bool is_signaled = false; |
| |
| fence_info = &cmd->info.fence; |
| |
| fence = kbase_fence_get(fence_info); |
| if (WARN_ON(!fence)) |
| return; |
| |
| kbase_sync_fence_info_get(fence, &info); |
| timeline_name = fence->ops->get_timeline_name(fence); |
| is_signaled = info.status > 0; |
| |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "cmd:%s obj:0x%pK live_value:0x%.8x | ", cmd_name, fence, is_signaled); |
| |
| /* Note: fence->seqno was u32 until 5.1 kernel, then u64 */ |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "timeline_name:%s timeline_context:0x%.16llx fence_seqno:0x%.16llx", |
| timeline_name, fence->context, (u64)fence->seqno); |
| |
| kbase_fence_put(fence); |
| } |
| |
| /** |
| * kbasep_csf_sync_print_kcpu_cqs_wait() - Print details of a CSF SYNC CQS Wait command, |
| * contained in a KCPU queue. |
| * |
| * @kctx: The kbase context. |
| * @buffer: The buffer to write to. |
| * @length: The length of text in the buffer. |
| * @cmd: The KCPU Command to be printed. |
| */ |
| static void kbasep_csf_sync_print_kcpu_cqs_wait(struct kbase_context *kctx, char *buffer, |
| int *length, struct kbase_kcpu_command *cmd) |
| { |
| size_t i; |
| |
| for (i = 0; i < cmd->info.cqs_wait.nr_objs; i++) { |
| struct base_cqs_wait_info *cqs_obj = &cmd->info.cqs_wait.objs[i]; |
| |
| u32 live_val; |
| int ret = kbasep_csf_debugfs_get_cqs_live_u32(kctx, cqs_obj->addr, &live_val); |
| bool live_val_valid = (ret >= 0); |
| |
| *length += |
| snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "cmd:CQS_WAIT_OPERATION obj:0x%.16llx live_value:", cqs_obj->addr); |
| |
| if (live_val_valid) |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "0x%.16llx", (u64)live_val); |
| else |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| CQS_UNREADABLE_LIVE_VALUE); |
| |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| " | op:gt arg_value:0x%.8x", cqs_obj->val); |
| } |
| } |
| |
| /** |
| * kbasep_csf_sync_print_kcpu_cqs_set() - Print details of a CSF SYNC CQS |
| * Set command, contained in a KCPU queue. |
| * |
| * @kctx: The kbase context. |
| * @buffer: The buffer to write to. |
| * @length: The length of text in the buffer. |
| * @cmd: The KCPU Command to be printed. |
| */ |
| static void kbasep_csf_sync_print_kcpu_cqs_set(struct kbase_context *kctx, char *buffer, |
| int *length, struct kbase_kcpu_command *cmd) |
| { |
| size_t i; |
| |
| for (i = 0; i < cmd->info.cqs_set.nr_objs; i++) { |
| struct base_cqs_set *cqs_obj = &cmd->info.cqs_set.objs[i]; |
| |
| u32 live_val; |
| int ret = kbasep_csf_debugfs_get_cqs_live_u32(kctx, cqs_obj->addr, &live_val); |
| bool live_val_valid = (ret >= 0); |
| |
| *length += |
| snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "cmd:CQS_SET_OPERATION obj:0x%.16llx live_value:", cqs_obj->addr); |
| |
| if (live_val_valid) |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "0x%.16llx", (u64)live_val); |
| else |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| CQS_UNREADABLE_LIVE_VALUE); |
| |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| " | op:add arg_value:0x%.8x", 1); |
| } |
| } |
| |
| /** |
| * kbasep_csf_sync_get_wait_op_name() - Print the name of a CQS Wait Operation. |
| * |
| * @op: The numerical value of operation. |
| * |
| * Return: const static pointer to the command name, or '??' if unknown. |
| */ |
| static const char *kbasep_csf_sync_get_wait_op_name(basep_cqs_wait_operation_op op) |
| { |
| const char *string; |
| |
| switch (op) { |
| case BASEP_CQS_WAIT_OPERATION_LE: |
| string = "le"; |
| break; |
| case BASEP_CQS_WAIT_OPERATION_GT: |
| string = "gt"; |
| break; |
| default: |
| string = "??"; |
| break; |
| } |
| return string; |
| } |
| |
| /** |
| * kbasep_csf_sync_get_set_op_name() - Print the name of a CQS Set Operation. |
| * |
| * @op: The numerical value of operation. |
| * |
| * Return: const static pointer to the command name, or '??' if unknown. |
| */ |
| static const char *kbasep_csf_sync_get_set_op_name(basep_cqs_set_operation_op op) |
| { |
| const char *string; |
| |
| switch (op) { |
| case BASEP_CQS_SET_OPERATION_ADD: |
| string = "add"; |
| break; |
| case BASEP_CQS_SET_OPERATION_SET: |
| string = "set"; |
| break; |
| default: |
| string = "???"; |
| break; |
| } |
| return string; |
| } |
| |
| /** |
| * kbasep_csf_sync_print_kcpu_cqs_wait_op() - Print details of a CSF SYNC CQS |
| * Wait Operation command, contained |
| * in a KCPU queue. |
| * |
| * @kctx: The kbase context. |
| * @buffer: The buffer to write to. |
| * @length: The length of text in the buffer. |
| * @cmd: The KCPU Command to be printed. |
| */ |
| static void kbasep_csf_sync_print_kcpu_cqs_wait_op(struct kbase_context *kctx, char *buffer, |
| int *length, struct kbase_kcpu_command *cmd) |
| { |
| size_t i; |
| |
| for (i = 0; i < cmd->info.cqs_wait.nr_objs; i++) { |
| struct base_cqs_wait_operation_info *wait_op = |
| &cmd->info.cqs_wait_operation.objs[i]; |
| const char *op_name = kbasep_csf_sync_get_wait_op_name(wait_op->operation); |
| |
| u64 live_val; |
| int ret = kbasep_csf_debugfs_get_cqs_live_u64(kctx, wait_op->addr, &live_val); |
| |
| bool live_val_valid = (ret >= 0); |
| |
| *length += |
| snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "cmd:CQS_WAIT_OPERATION obj:0x%.16llx live_value:", wait_op->addr); |
| |
| if (live_val_valid) |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "0x%.16llx", live_val); |
| else |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| CQS_UNREADABLE_LIVE_VALUE); |
| |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| " | op:%s arg_value:0x%.16llx", op_name, wait_op->val); |
| } |
| } |
| |
| /** |
| * kbasep_csf_sync_print_kcpu_cqs_set_op() - Print details of a CSF SYNC CQS |
| * Set Operation command, contained |
| * in a KCPU queue. |
| * |
| * @kctx: The kbase context. |
| * @buffer: The buffer to write to. |
| * @length: The length of text in the buffer. |
| * @cmd: The KCPU Command to be printed. |
| */ |
| static void kbasep_csf_sync_print_kcpu_cqs_set_op(struct kbase_context *kctx, char *buffer, |
| int *length, struct kbase_kcpu_command *cmd) |
| { |
| size_t i; |
| |
| for (i = 0; i < cmd->info.cqs_set_operation.nr_objs; i++) { |
| struct base_cqs_set_operation_info *set_op = &cmd->info.cqs_set_operation.objs[i]; |
| const char *op_name = kbasep_csf_sync_get_set_op_name( |
| (basep_cqs_set_operation_op)set_op->operation); |
| |
| u64 live_val; |
| int ret = kbasep_csf_debugfs_get_cqs_live_u64(kctx, set_op->addr, &live_val); |
| |
| bool live_val_valid = (ret >= 0); |
| |
| *length += |
| snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "cmd:CQS_SET_OPERATION obj:0x%.16llx live_value:", set_op->addr); |
| |
| if (live_val_valid) |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| "0x%.16llx", live_val); |
| else |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| CQS_UNREADABLE_LIVE_VALUE); |
| |
| *length += snprintf(buffer + *length, CSF_SYNC_DUMP_SIZE - *length, |
| " | op:%s arg_value:0x%.16llx", op_name, set_op->val); |
| } |
| } |
| |
| /** |
| * kbasep_csf_kcpu_debugfs_print_queue() - Print debug data for a KCPU queue |
| * |
| * @kctx: The kbase context. |
| * @file: The seq_file to print to. |
| * @queue: Pointer to the KCPU queue. |
| */ |
| static void kbasep_csf_sync_kcpu_debugfs_print_queue(struct kbase_context *kctx, |
| struct seq_file *file, |
| struct kbase_kcpu_command_queue *queue) |
| { |
| char started_or_pending; |
| struct kbase_kcpu_command *cmd; |
| size_t i; |
| |
| if (WARN_ON(!queue)) |
| return; |
| |
| lockdep_assert_held(&kctx->csf.kcpu_queues.lock); |
| mutex_lock(&queue->lock); |
| |
| for (i = 0; i != queue->num_pending_cmds; ++i) { |
| char buffer[CSF_SYNC_DUMP_SIZE]; |
| int length = 0; |
| started_or_pending = ((i == 0) && queue->command_started) ? 'S' : 'P'; |
| length += snprintf(buffer, CSF_SYNC_DUMP_SIZE, "queue:KCPU-%d-%d exec:%c ", |
| kctx->id, queue->id, started_or_pending); |
| |
| cmd = &queue->commands[(u8)(queue->start_offset + i)]; |
| switch (cmd->type) { |
| #if IS_ENABLED(CONFIG_SYNC_FILE) |
| case BASE_KCPU_COMMAND_TYPE_FENCE_SIGNAL: |
| kbasep_csf_sync_print_kcpu_fence_wait_or_signal(buffer, &length, cmd, |
| "FENCE_SIGNAL"); |
| break; |
| case BASE_KCPU_COMMAND_TYPE_FENCE_WAIT: |
| kbasep_csf_sync_print_kcpu_fence_wait_or_signal(buffer, &length, cmd, |
| "FENCE_WAIT"); |
| break; |
| #endif |
| case BASE_KCPU_COMMAND_TYPE_CQS_WAIT: |
| kbasep_csf_sync_print_kcpu_cqs_wait(kctx, buffer, &length, cmd); |
| break; |
| case BASE_KCPU_COMMAND_TYPE_CQS_SET: |
| kbasep_csf_sync_print_kcpu_cqs_set(kctx, buffer, &length, cmd); |
| break; |
| case BASE_KCPU_COMMAND_TYPE_CQS_WAIT_OPERATION: |
| kbasep_csf_sync_print_kcpu_cqs_wait_op(kctx, buffer, &length, cmd); |
| break; |
| case BASE_KCPU_COMMAND_TYPE_CQS_SET_OPERATION: |
| kbasep_csf_sync_print_kcpu_cqs_set_op(kctx, buffer, &length, cmd); |
| break; |
| default: |
| length += snprintf(buffer + length, CSF_SYNC_DUMP_SIZE - length, |
| ", U, Unknown blocking command"); |
| break; |
| } |
| |
| length += snprintf(buffer + length, CSF_SYNC_DUMP_SIZE - length, "\n"); |
| kbasep_print(kctx, file, buffer); |
| } |
| |
| mutex_unlock(&queue->lock); |
| } |
| |
| int kbasep_csf_sync_kcpu_dump_locked(struct kbase_context *kctx, struct seq_file *file) |
| { |
| unsigned long queue_idx; |
| |
| lockdep_assert_held(&kctx->csf.kcpu_queues.lock); |
| |
| kbasep_print(kctx, file, "KCPU queues for ctx %d:\n", kctx->id); |
| |
| queue_idx = find_first_bit(kctx->csf.kcpu_queues.in_use, KBASEP_MAX_KCPU_QUEUES); |
| |
| while (queue_idx < KBASEP_MAX_KCPU_QUEUES) { |
| kbasep_csf_sync_kcpu_debugfs_print_queue(kctx, file, |
| kctx->csf.kcpu_queues.array[queue_idx]); |
| |
| queue_idx = find_next_bit(kctx->csf.kcpu_queues.in_use, KBASEP_MAX_KCPU_QUEUES, |
| queue_idx + 1); |
| } |
| |
| return 0; |
| } |
| |
| int kbasep_csf_sync_kcpu_dump(struct kbase_context *kctx, struct seq_file *file) |
| { |
| mutex_lock(&kctx->csf.kcpu_queues.lock); |
| kbasep_csf_sync_kcpu_dump_locked(kctx, file); |
| mutex_unlock(&kctx->csf.kcpu_queues.lock); |
| return 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_DEBUG_FS) |
| |
| /* GPU queue related values */ |
| #define GPU_CSF_MOVE_OPCODE ((u64)0x1) |
| #define GPU_CSF_MOVE32_OPCODE ((u64)0x2) |
| #define GPU_CSF_SYNC_ADD_OPCODE ((u64)0x25) |
| #define GPU_CSF_SYNC_SET_OPCODE ((u64)0x26) |
| #define GPU_CSF_SYNC_WAIT_OPCODE ((u64)0x27) |
| #define GPU_CSF_SYNC_ADD64_OPCODE ((u64)0x33) |
| #define GPU_CSF_SYNC_SET64_OPCODE ((u64)0x34) |
| #define GPU_CSF_SYNC_WAIT64_OPCODE ((u64)0x35) |
| #define GPU_CSF_CALL_OPCODE ((u64)0x20) |
| |
| #define MAX_NR_GPU_CALLS (5) |
| #define INSTR_OPCODE_MASK ((u64)0xFF << 56) |
| #define INSTR_OPCODE_GET(value) ((value & INSTR_OPCODE_MASK) >> 56) |
| #define MOVE32_IMM_MASK ((u64)0xFFFFFFFFFUL) |
| #define MOVE_DEST_MASK ((u64)0xFF << 48) |
| #define MOVE_DEST_GET(value) ((value & MOVE_DEST_MASK) >> 48) |
| #define MOVE_IMM_MASK ((u64)0xFFFFFFFFFFFFUL) |
| #define SYNC_SRC0_MASK ((u64)0xFF << 40) |
| #define SYNC_SRC1_MASK ((u64)0xFF << 32) |
| #define SYNC_SRC0_GET(value) (u8)((value & SYNC_SRC0_MASK) >> 40) |
| #define SYNC_SRC1_GET(value) (u8)((value & SYNC_SRC1_MASK) >> 32) |
| #define SYNC_WAIT_CONDITION_MASK ((u64)0xF << 28) |
| #define SYNC_WAIT_CONDITION_GET(value) (u8)((value & SYNC_WAIT_CONDITION_MASK) >> 28) |
| |
| /* Enumeration for types of GPU queue sync events for |
| * the purpose of dumping them through debugfs. |
| */ |
| enum debugfs_gpu_sync_type { |
| DEBUGFS_GPU_SYNC_WAIT, |
| DEBUGFS_GPU_SYNC_SET, |
| DEBUGFS_GPU_SYNC_ADD, |
| NUM_DEBUGFS_GPU_SYNC_TYPES |
| }; |
| |
| /** |
| * kbasep_csf_get_move_immediate_value() - Get the immediate values for sync operations |
| * from a MOVE instruction. |
| * |
| * @move_cmd: Raw MOVE instruction. |
| * @sync_addr_reg: Register identifier from SYNC_* instruction. |
| * @compare_val_reg: Register identifier from SYNC_* instruction. |
| * @sync_val: Pointer to store CQS object address for sync operation. |
| * @compare_val: Pointer to store compare value for sync operation. |
| * |
| * Return: True if value is obtained by checking for correct register identifier, |
| * or false otherwise. |
| */ |
| static bool kbasep_csf_get_move_immediate_value(u64 move_cmd, u64 sync_addr_reg, |
| u64 compare_val_reg, u64 *sync_val, |
| u64 *compare_val) |
| { |
| u64 imm_mask; |
| |
| /* Verify MOVE instruction and get immediate mask */ |
| if (INSTR_OPCODE_GET(move_cmd) == GPU_CSF_MOVE32_OPCODE) |
| imm_mask = MOVE32_IMM_MASK; |
| else if (INSTR_OPCODE_GET(move_cmd) == GPU_CSF_MOVE_OPCODE) |
| imm_mask = MOVE_IMM_MASK; |
| else |
| /* Error return */ |
| return false; |
| |
| /* Verify value from MOVE instruction and assign to variable */ |
| if (sync_addr_reg == MOVE_DEST_GET(move_cmd)) |
| *sync_val = move_cmd & imm_mask; |
| else if (compare_val_reg == MOVE_DEST_GET(move_cmd)) |
| *compare_val = move_cmd & imm_mask; |
| else |
| /* Error return */ |
| return false; |
| |
| return true; |
| } |
| |
| /** kbasep_csf_read_ringbuffer_value() - Reads a u64 from the ringbuffer at a provided |
| * offset. |
| * |
| * @queue: Pointer to the queue. |
| * @ringbuff_offset: Ringbuffer offset. |
| * |
| * Return: the u64 in the ringbuffer at the desired offset. |
| */ |
| static u64 kbasep_csf_read_ringbuffer_value(struct kbase_queue *queue, u32 ringbuff_offset) |
| { |
| u64 page_off = ringbuff_offset >> PAGE_SHIFT; |
| u64 offset_within_page = ringbuff_offset & ~PAGE_MASK; |
| struct page *page = as_page(queue->queue_reg->gpu_alloc->pages[page_off]); |
| u64 *ringbuffer = vmap(&page, 1, VM_MAP, pgprot_noncached(PAGE_KERNEL)); |
| u64 value; |
| |
| if (!ringbuffer) { |
| struct kbase_context *kctx = queue->kctx; |
| |
| dev_err(kctx->kbdev->dev, "%s failed to map the buffer page for read a command!", |
| __func__); |
| /* Return an alternative 0 for dumpping operation*/ |
| value = 0; |
| } else { |
| value = ringbuffer[offset_within_page / sizeof(u64)]; |
| vunmap(ringbuffer); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * kbasep_csf_print_gpu_sync_op() - Print sync operation info for given sync command. |
| * |
| * @file: Pointer to debugfs seq_file file struct for writing output. |
| * @kctx: Pointer to kbase context. |
| * @queue: Pointer to the GPU command queue. |
| * @ringbuff_offset: Offset to index the ring buffer with, for the given sync command. |
| * (Useful for finding preceding MOVE commands) |
| * @sync_cmd: Entire u64 of the sync command, which has both sync address and |
| * comparison-value encoded in it. |
| * @type: Type of GPU sync command (e.g. SYNC_SET, SYNC_ADD, SYNC_WAIT). |
| * @is_64bit: Bool to indicate if operation is 64 bit (true) or 32 bit (false). |
| * @follows_wait: Bool to indicate if the operation follows at least one wait |
| * operation. Used to determine whether it's pending or started. |
| */ |
| static void kbasep_csf_print_gpu_sync_op(struct seq_file *file, struct kbase_context *kctx, |
| struct kbase_queue *queue, u32 ringbuff_offset, |
| u64 sync_cmd, enum debugfs_gpu_sync_type type, |
| bool is_64bit, bool follows_wait) |
| { |
| u64 sync_addr = 0, compare_val = 0, live_val = 0; |
| u64 move_cmd; |
| u8 sync_addr_reg, compare_val_reg, wait_condition = 0; |
| int err; |
| |
| static const char *const gpu_sync_type_name[] = { "SYNC_WAIT", "SYNC_SET", "SYNC_ADD" }; |
| static const char *const gpu_sync_type_op[] = { |
| "wait", /* This should never be printed, only included to simplify indexing */ |
| "set", "add" |
| }; |
| |
| if (type >= NUM_DEBUGFS_GPU_SYNC_TYPES) { |
| dev_warn(kctx->kbdev->dev, "Expected GPU queue sync type is unknown!"); |
| return; |
| } |
| |
| /* We expect there to be at least 2 preceding MOVE instructions, and |
| * Base will always arrange for the 2 MOVE + SYNC instructions to be |
| * contiguously located, and is therefore never expected to be wrapped |
| * around the ringbuffer boundary. |
| */ |
| if (unlikely(ringbuff_offset < (2 * sizeof(u64)))) { |
| dev_warn(kctx->kbdev->dev, |
| "Unexpected wraparound detected between %s & MOVE instruction", |
| gpu_sync_type_name[type]); |
| return; |
| } |
| |
| /* 1. Get Register identifiers from SYNC_* instruction */ |
| sync_addr_reg = SYNC_SRC0_GET(sync_cmd); |
| compare_val_reg = SYNC_SRC1_GET(sync_cmd); |
| |
| /* 2. Get values from first MOVE command */ |
| ringbuff_offset -= sizeof(u64); |
| move_cmd = kbasep_csf_read_ringbuffer_value(queue, ringbuff_offset); |
| if (!kbasep_csf_get_move_immediate_value(move_cmd, sync_addr_reg, compare_val_reg, |
| &sync_addr, &compare_val)) |
| return; |
| |
| /* 3. Get values from next MOVE command */ |
| ringbuff_offset -= sizeof(u64); |
| move_cmd = kbasep_csf_read_ringbuffer_value(queue, ringbuff_offset); |
| if (!kbasep_csf_get_move_immediate_value(move_cmd, sync_addr_reg, compare_val_reg, |
| &sync_addr, &compare_val)) |
| return; |
| |
| /* 4. Get CQS object value */ |
| if (is_64bit) |
| err = kbasep_csf_debugfs_get_cqs_live_u64(kctx, sync_addr, &live_val); |
| else |
| err = kbasep_csf_debugfs_get_cqs_live_u32(kctx, sync_addr, (u32 *)(&live_val)); |
| |
| if (err) |
| return; |
| |
| /* 5. Print info */ |
| kbasep_print(kctx, file, "queue:GPU-%u-%u-%u exec:%c cmd:%s ", kctx->id, |
| queue->group->handle, queue->csi_index, |
| queue->enabled && !follows_wait ? 'S' : 'P', gpu_sync_type_name[type]); |
| |
| if (queue->group->csg_nr == KBASEP_CSG_NR_INVALID) |
| kbasep_print(kctx, file, "slot:-"); |
| else |
| kbasep_print(kctx, file, "slot:%d", (int)queue->group->csg_nr); |
| |
| kbasep_print(kctx, file, " obj:0x%.16llx live_value:0x%.16llx | ", sync_addr, live_val); |
| |
| if (type == DEBUGFS_GPU_SYNC_WAIT) { |
| wait_condition = SYNC_WAIT_CONDITION_GET(sync_cmd); |
| kbasep_print(kctx, file, "op:%s ", |
| kbasep_csf_sync_get_wait_op_name(wait_condition)); |
| } else |
| kbasep_print(kctx, file, "op:%s ", gpu_sync_type_op[type]); |
| |
| kbasep_print(kctx, file, "arg_value:0x%.16llx\n", compare_val); |
| } |
| |
| /** |
| * kbasep_csf_dump_active_queue_sync_info() - Print GPU command queue sync information. |
| * |
| * @file: seq_file for printing to. |
| * @queue: Address of a GPU command queue to examine. |
| * |
| * This function will iterate through each command in the ring buffer of the given GPU queue from |
| * CS_EXTRACT, and if is a SYNC_* instruction it will attempt to decode the sync operation and |
| * print relevant information to the debugfs file. |
| * This function will stop iterating once the CS_INSERT address is reached by the cursor (i.e. |
| * when there are no more commands to view) or a number of consumed GPU CALL commands have |
| * been observed. |
| */ |
| static void kbasep_csf_dump_active_queue_sync_info(struct seq_file *file, struct kbase_queue *queue) |
| { |
| struct kbase_context *kctx; |
| u64 *addr; |
| u64 cs_extract, cs_insert, instr, cursor; |
| bool follows_wait = false; |
| int nr_calls = 0; |
| |
| if (!queue) |
| return; |
| |
| kctx = queue->kctx; |
| |
| addr = queue->user_io_addr; |
| cs_insert = addr[CS_INSERT_LO / sizeof(*addr)]; |
| |
| addr = queue->user_io_addr + PAGE_SIZE / sizeof(*addr); |
| cs_extract = addr[CS_EXTRACT_LO / sizeof(*addr)]; |
| |
| cursor = cs_extract; |
| |
| if (!is_power_of_2(queue->size)) { |
| dev_warn(kctx->kbdev->dev, "GPU queue %u size of %u not a power of 2", |
| queue->csi_index, queue->size); |
| return; |
| } |
| |
| while ((cursor < cs_insert) && (nr_calls < MAX_NR_GPU_CALLS)) { |
| bool instr_is_64_bit = false; |
| /* Calculate offset into ringbuffer from the absolute cursor, |
| * by finding the remainder of the cursor divided by the |
| * ringbuffer size. The ringbuffer size is guaranteed to be |
| * a power of 2, so the remainder can be calculated without an |
| * explicit modulo. queue->size - 1 is the ringbuffer mask. |
| */ |
| u32 cursor_ringbuff_offset = (u32)(cursor & (queue->size - 1)); |
| |
| /* Find instruction that cursor is currently on */ |
| instr = kbasep_csf_read_ringbuffer_value(queue, cursor_ringbuff_offset); |
| |
| switch (INSTR_OPCODE_GET(instr)) { |
| case GPU_CSF_SYNC_ADD64_OPCODE: |
| case GPU_CSF_SYNC_SET64_OPCODE: |
| case GPU_CSF_SYNC_WAIT64_OPCODE: |
| instr_is_64_bit = true; |
| break; |
| default: |
| break; |
| } |
| |
| switch (INSTR_OPCODE_GET(instr)) { |
| case GPU_CSF_SYNC_ADD_OPCODE: |
| case GPU_CSF_SYNC_ADD64_OPCODE: |
| kbasep_csf_print_gpu_sync_op(file, kctx, queue, cursor_ringbuff_offset, |
| instr, DEBUGFS_GPU_SYNC_ADD, instr_is_64_bit, |
| follows_wait); |
| break; |
| case GPU_CSF_SYNC_SET_OPCODE: |
| case GPU_CSF_SYNC_SET64_OPCODE: |
| kbasep_csf_print_gpu_sync_op(file, kctx, queue, cursor_ringbuff_offset, |
| instr, DEBUGFS_GPU_SYNC_SET, instr_is_64_bit, |
| follows_wait); |
| break; |
| case GPU_CSF_SYNC_WAIT_OPCODE: |
| case GPU_CSF_SYNC_WAIT64_OPCODE: |
| kbasep_csf_print_gpu_sync_op(file, kctx, queue, cursor_ringbuff_offset, |
| instr, DEBUGFS_GPU_SYNC_WAIT, instr_is_64_bit, |
| follows_wait); |
| follows_wait = true; /* Future commands will follow at least one wait */ |
| break; |
| case GPU_CSF_CALL_OPCODE: |
| nr_calls++; |
| break; |
| default: |
| /* Unrecognized command, skip past it */ |
| break; |
| } |
| |
| cursor += sizeof(u64); |
| } |
| } |
| |
| /** |
| * kbasep_csf_dump_active_group_sync_state() - Prints SYNC commands in all GPU queues of |
| * the provided queue group. |
| * |
| * @kctx: The kbase context |
| * @file: seq_file for printing to. |
| * @group: Address of a GPU command group to iterate through. |
| * |
| * This function will iterate through each queue in the provided GPU queue group and |
| * print its SYNC related commands. |
| */ |
| static void kbasep_csf_dump_active_group_sync_state(struct kbase_context *kctx, |
| struct seq_file *file, |
| struct kbase_queue_group *const group) |
| { |
| unsigned int i; |
| |
| kbasep_print(kctx, file, "GPU queues for group %u (slot %d) of ctx %d_%d\n", group->handle, |
| group->csg_nr, kctx->tgid, kctx->id); |
| |
| for (i = 0; i < MAX_SUPPORTED_STREAMS_PER_GROUP; i++) |
| kbasep_csf_dump_active_queue_sync_info(file, group->bound_queues[i]); |
| } |
| |
| /** |
| * kbasep_csf_sync_gpu_dump() - Print CSF GPU queue sync info |
| * |
| * @kctx: The kbase context |
| * @file: The seq_file for printing to. |
| * |
| * Return: Negative error code or 0 on success. |
| */ |
| static int kbasep_csf_sync_gpu_dump(struct kbase_context *kctx, struct seq_file *file) |
| { |
| u32 gr; |
| struct kbase_device *kbdev; |
| |
| if (WARN_ON(!kctx)) |
| return -EINVAL; |
| |
| kbdev = kctx->kbdev; |
| kbase_csf_scheduler_lock(kbdev); |
| kbase_csf_debugfs_update_active_groups_status(kbdev); |
| |
| for (gr = 0; gr < kbdev->csf.global_iface.group_num; gr++) { |
| struct kbase_queue_group *const group = |
| kbdev->csf.scheduler.csg_slots[gr].resident_group; |
| if (!group || group->kctx != kctx) |
| continue; |
| kbasep_csf_dump_active_group_sync_state(kctx, file, group); |
| } |
| |
| kbase_csf_scheduler_unlock(kbdev); |
| return 0; |
| } |
| |
| /** |
| * kbasep_csf_sync_debugfs_show() - Print CSF queue sync information |
| * |
| * @file: The seq_file for printing to. |
| * @data: The debugfs dentry private data, a pointer to kbase_context. |
| * |
| * Return: Negative error code or 0 on success. |
| */ |
| static int kbasep_csf_sync_debugfs_show(struct seq_file *file, void *data) |
| { |
| struct kbase_context *kctx = file->private; |
| |
| kbasep_print(kctx, file, "MALI_CSF_SYNC_DEBUGFS_VERSION: v%u\n", |
| MALI_CSF_SYNC_DEBUGFS_VERSION); |
| |
| kbasep_csf_sync_kcpu_dump(kctx, file); |
| kbasep_csf_sync_gpu_dump(kctx, file); |
| return 0; |
| } |
| |
| static int kbasep_csf_sync_debugfs_open(struct inode *in, struct file *file) |
| { |
| return single_open(file, kbasep_csf_sync_debugfs_show, in->i_private); |
| } |
| |
| static const struct file_operations kbasep_csf_sync_debugfs_fops = { |
| .open = kbasep_csf_sync_debugfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| /** |
| * kbase_csf_sync_debugfs_init() - Initialise debugfs file. |
| * |
| * @kctx: Kernel context pointer. |
| */ |
| void kbase_csf_sync_debugfs_init(struct kbase_context *kctx) |
| { |
| struct dentry *file; |
| const mode_t mode = 0444; |
| |
| if (WARN_ON(!kctx || IS_ERR_OR_NULL(kctx->kctx_dentry))) |
| return; |
| |
| file = debugfs_create_file("csf_sync", mode, kctx->kctx_dentry, kctx, |
| &kbasep_csf_sync_debugfs_fops); |
| |
| if (IS_ERR_OR_NULL(file)) |
| dev_warn(kctx->kbdev->dev, "Unable to create CSF Sync debugfs entry"); |
| } |
| |
| #else |
| /* |
| * Stub functions for when debugfs is disabled |
| */ |
| void kbase_csf_sync_debugfs_init(struct kbase_context *kctx) |
| { |
| } |
| |
| #endif /* CONFIG_DEBUG_FS */ |