// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2019-2021 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_csg_debugfs.h" #include #include #include #include #if IS_ENABLED(CONFIG_DEBUG_FS) #include "mali_kbase_csf_tl_reader.h" /** * blocked_reason_to_string() - Convert blocking reason id to a string * * @reason_id: blocked_reason * * Return: Suitable string */ static const char *blocked_reason_to_string(u32 reason_id) { /* possible blocking reasons of a cs */ static const char *const cs_blocked_reason[] = { [CS_STATUS_BLOCKED_REASON_REASON_UNBLOCKED] = "UNBLOCKED", [CS_STATUS_BLOCKED_REASON_REASON_WAIT] = "WAIT", [CS_STATUS_BLOCKED_REASON_REASON_PROGRESS_WAIT] = "PROGRESS_WAIT", [CS_STATUS_BLOCKED_REASON_REASON_SYNC_WAIT] = "SYNC_WAIT", [CS_STATUS_BLOCKED_REASON_REASON_DEFERRED] = "DEFERRED", [CS_STATUS_BLOCKED_REASON_REASON_RESOURCE] = "RESOURCE", [CS_STATUS_BLOCKED_REASON_REASON_FLUSH] = "FLUSH" }; if (WARN_ON(reason_id >= ARRAY_SIZE(cs_blocked_reason))) return "UNKNOWN_BLOCKED_REASON_ID"; return cs_blocked_reason[reason_id]; } static void kbasep_csf_scheduler_dump_active_queue_cs_status_wait( struct seq_file *file, u32 wait_status, u32 wait_sync_value, u64 wait_sync_live_value, u64 wait_sync_pointer, u32 sb_status, u32 blocked_reason) { #define WAITING "Waiting" #define NOT_WAITING "Not waiting" seq_printf(file, "SB_MASK: %d\n", CS_STATUS_WAIT_SB_MASK_GET(wait_status)); seq_printf(file, "PROGRESS_WAIT: %s\n", CS_STATUS_WAIT_PROGRESS_WAIT_GET(wait_status) ? WAITING : NOT_WAITING); seq_printf(file, "PROTM_PEND: %s\n", CS_STATUS_WAIT_PROTM_PEND_GET(wait_status) ? WAITING : NOT_WAITING); seq_printf(file, "SYNC_WAIT: %s\n", CS_STATUS_WAIT_SYNC_WAIT_GET(wait_status) ? WAITING : NOT_WAITING); seq_printf(file, "WAIT_CONDITION: %s\n", CS_STATUS_WAIT_SYNC_WAIT_CONDITION_GET(wait_status) ? "greater than" : "less or equal"); seq_printf(file, "SYNC_POINTER: 0x%llx\n", wait_sync_pointer); seq_printf(file, "SYNC_VALUE: %d\n", wait_sync_value); seq_printf(file, "SYNC_LIVE_VALUE: 0x%016llx\n", wait_sync_live_value); seq_printf(file, "SB_STATUS: %u\n", CS_STATUS_SCOREBOARDS_NONZERO_GET(sb_status)); seq_printf(file, "BLOCKED_REASON: %s\n", blocked_reason_to_string(CS_STATUS_BLOCKED_REASON_REASON_GET( blocked_reason))); } static void kbasep_csf_scheduler_dump_active_cs_trace(struct seq_file *file, struct kbase_csf_cmd_stream_info const *const stream) { u32 val = kbase_csf_firmware_cs_input_read(stream, CS_INSTR_BUFFER_BASE_LO); u64 addr = ((u64)kbase_csf_firmware_cs_input_read(stream, CS_INSTR_BUFFER_BASE_HI) << 32) | val; val = kbase_csf_firmware_cs_input_read(stream, CS_INSTR_BUFFER_SIZE); seq_printf(file, "CS_TRACE_BUF_ADDR: 0x%16llx, SIZE: %u\n", addr, val); /* Write offset variable address (pointer) */ val = kbase_csf_firmware_cs_input_read(stream, CS_INSTR_BUFFER_OFFSET_POINTER_LO); addr = ((u64)kbase_csf_firmware_cs_input_read(stream, CS_INSTR_BUFFER_OFFSET_POINTER_HI) << 32) | val; seq_printf(file, "CS_TRACE_BUF_OFFSET_PTR: 0x%16llx\n", addr); /* EVENT_SIZE and EVENT_STATEs */ val = kbase_csf_firmware_cs_input_read(stream, CS_INSTR_CONFIG); seq_printf(file, "TRACE_EVENT_SIZE: 0x%x, TRACE_EVENT_STAES 0x%x\n", CS_INSTR_CONFIG_EVENT_SIZE_GET(val), CS_INSTR_CONFIG_EVENT_STATE_GET(val)); } /** * kbasep_csf_scheduler_dump_active_queue() - Print GPU command queue * debug information * * @file: seq_file for printing to * @queue: Address of a GPU command queue to examine */ static void kbasep_csf_scheduler_dump_active_queue(struct seq_file *file, struct kbase_queue *queue) { u32 *addr; u64 cs_extract; u64 cs_insert; u32 cs_active; u64 wait_sync_pointer; u32 wait_status, wait_sync_value; u32 sb_status; u32 blocked_reason; struct kbase_vmap_struct *mapping; u64 *evt; u64 wait_sync_live_value; if (!queue) return; if (WARN_ON(queue->csi_index == KBASEP_IF_NR_INVALID || !queue->group)) return; /* Ring the doorbell to have firmware update CS_EXTRACT */ kbase_csf_ring_cs_user_doorbell(queue->kctx->kbdev, queue); msleep(100); addr = (u32 *)queue->user_io_addr; cs_insert = addr[CS_INSERT_LO/4] | ((u64)addr[CS_INSERT_HI/4] << 32); addr = (u32 *)(queue->user_io_addr + PAGE_SIZE); cs_extract = addr[CS_EXTRACT_LO/4] | ((u64)addr[CS_EXTRACT_HI/4] << 32); cs_active = addr[CS_ACTIVE/4]; #define KBASEP_CSF_DEBUGFS_CS_HEADER_USER_IO \ "Bind Idx, Ringbuf addr, Prio, Insert offset, Extract offset, Active, Doorbell\n" seq_printf(file, KBASEP_CSF_DEBUGFS_CS_HEADER_USER_IO "%8d, %16llx, %4u, %16llx, %16llx, %6u, %8d\n", queue->csi_index, queue->base_addr, queue->priority, cs_insert, cs_extract, cs_active, queue->doorbell_nr); /* Print status information for blocked group waiting for sync object. For on-slot queues, * if cs_trace is enabled, dump the interface's cs_trace configuration. */ if (kbase_csf_scheduler_group_get_slot(queue->group) < 0) { if (CS_STATUS_WAIT_SYNC_WAIT_GET(queue->status_wait)) { wait_status = queue->status_wait; wait_sync_value = queue->sync_value; wait_sync_pointer = queue->sync_ptr; sb_status = queue->sb_status; blocked_reason = queue->blocked_reason; evt = (u64 *)kbase_phy_alloc_mapping_get(queue->kctx, wait_sync_pointer, &mapping); if (evt) { wait_sync_live_value = evt[0]; kbase_phy_alloc_mapping_put(queue->kctx, mapping); } else { wait_sync_live_value = U64_MAX; } kbasep_csf_scheduler_dump_active_queue_cs_status_wait( file, wait_status, wait_sync_value, wait_sync_live_value, wait_sync_pointer, sb_status, blocked_reason); } } else { struct kbase_device const *const kbdev = queue->group->kctx->kbdev; struct kbase_csf_cmd_stream_group_info const *const ginfo = &kbdev->csf.global_iface.groups[queue->group->csg_nr]; struct kbase_csf_cmd_stream_info const *const stream = &ginfo->streams[queue->csi_index]; u64 cmd_ptr; u32 req_res; if (WARN_ON(!stream)) return; cmd_ptr = kbase_csf_firmware_cs_output(stream, CS_STATUS_CMD_PTR_LO); cmd_ptr |= (u64)kbase_csf_firmware_cs_output(stream, CS_STATUS_CMD_PTR_HI) << 32; req_res = kbase_csf_firmware_cs_output(stream, CS_STATUS_REQ_RESOURCE); seq_printf(file, "CMD_PTR: 0x%llx\n", cmd_ptr); seq_printf(file, "REQ_RESOURCE [COMPUTE]: %d\n", CS_STATUS_REQ_RESOURCE_COMPUTE_RESOURCES_GET(req_res)); seq_printf(file, "REQ_RESOURCE [FRAGMENT]: %d\n", CS_STATUS_REQ_RESOURCE_FRAGMENT_RESOURCES_GET(req_res)); seq_printf(file, "REQ_RESOURCE [TILER]: %d\n", CS_STATUS_REQ_RESOURCE_TILER_RESOURCES_GET(req_res)); seq_printf(file, "REQ_RESOURCE [IDVS]: %d\n", CS_STATUS_REQ_RESOURCE_IDVS_RESOURCES_GET(req_res)); wait_status = kbase_csf_firmware_cs_output(stream, CS_STATUS_WAIT); wait_sync_value = kbase_csf_firmware_cs_output(stream, CS_STATUS_WAIT_SYNC_VALUE); wait_sync_pointer = kbase_csf_firmware_cs_output(stream, CS_STATUS_WAIT_SYNC_POINTER_LO); wait_sync_pointer |= (u64)kbase_csf_firmware_cs_output(stream, CS_STATUS_WAIT_SYNC_POINTER_HI) << 32; sb_status = kbase_csf_firmware_cs_output(stream, CS_STATUS_SCOREBOARDS); blocked_reason = kbase_csf_firmware_cs_output( stream, CS_STATUS_BLOCKED_REASON); evt = (u64 *)kbase_phy_alloc_mapping_get(queue->kctx, wait_sync_pointer, &mapping); if (evt) { wait_sync_live_value = evt[0]; kbase_phy_alloc_mapping_put(queue->kctx, mapping); } else { wait_sync_live_value = U64_MAX; } kbasep_csf_scheduler_dump_active_queue_cs_status_wait( file, wait_status, wait_sync_value, wait_sync_live_value, wait_sync_pointer, sb_status, blocked_reason); /* Dealing with cs_trace */ if (kbase_csf_scheduler_queue_has_trace(queue)) kbasep_csf_scheduler_dump_active_cs_trace(file, stream); else seq_puts(file, "NO CS_TRACE\n"); } seq_puts(file, "\n"); } /* Waiting timeout for STATUS_UPDATE acknowledgment, in milliseconds */ #define CSF_STATUS_UPDATE_TO_MS (100) static void kbasep_csf_scheduler_dump_active_group(struct seq_file *file, struct kbase_queue_group *const group) { if (kbase_csf_scheduler_group_get_slot(group) >= 0) { struct kbase_device *const kbdev = group->kctx->kbdev; unsigned long flags; u32 ep_c, ep_r; char exclusive; struct kbase_csf_cmd_stream_group_info const *const ginfo = &kbdev->csf.global_iface.groups[group->csg_nr]; long remaining = kbase_csf_timeout_in_jiffies(CSF_STATUS_UPDATE_TO_MS); u8 slot_priority = kbdev->csf.scheduler.csg_slots[group->csg_nr].priority; kbase_csf_scheduler_spin_lock(kbdev, &flags); kbase_csf_firmware_csg_input_mask(ginfo, CSG_REQ, ~kbase_csf_firmware_csg_output(ginfo, CSG_ACK), CSG_REQ_STATUS_UPDATE_MASK); kbase_csf_scheduler_spin_unlock(kbdev, flags); kbase_csf_ring_csg_doorbell(kbdev, group->csg_nr); remaining = wait_event_timeout(kbdev->csf.event_wait, !((kbase_csf_firmware_csg_input_read(ginfo, CSG_REQ) ^ kbase_csf_firmware_csg_output(ginfo, CSG_ACK)) & CSG_REQ_STATUS_UPDATE_MASK), remaining); ep_c = kbase_csf_firmware_csg_output(ginfo, CSG_STATUS_EP_CURRENT); ep_r = kbase_csf_firmware_csg_output(ginfo, CSG_STATUS_EP_REQ); if (CSG_STATUS_EP_REQ_EXCLUSIVE_COMPUTE_GET(ep_r)) exclusive = 'C'; else if (CSG_STATUS_EP_REQ_EXCLUSIVE_FRAGMENT_GET(ep_r)) exclusive = 'F'; else exclusive = '0'; if (!remaining) { dev_err(kbdev->dev, "Timed out for STATUS_UPDATE on group %d on slot %d", group->handle, group->csg_nr); seq_printf(file, "*** Warn: Timed out for STATUS_UPDATE on slot %d\n", group->csg_nr); seq_printf(file, "*** The following group-record is likely stale\n"); } seq_puts(file, "GroupID, CSG NR, CSG Prio, Run State, Priority, C_EP(Alloc/Req), F_EP(Alloc/Req), T_EP(Alloc/Req), Exclusive\n"); seq_printf(file, "%7d, %6d, %8d, %9d, %8d, %11d/%3d, %11d/%3d, %11d/%3d, %9c\n", group->handle, group->csg_nr, slot_priority, group->run_state, group->priority, CSG_STATUS_EP_CURRENT_COMPUTE_EP_GET(ep_c), CSG_STATUS_EP_REQ_COMPUTE_EP_GET(ep_r), CSG_STATUS_EP_CURRENT_FRAGMENT_EP_GET(ep_c), CSG_STATUS_EP_REQ_FRAGMENT_EP_GET(ep_r), CSG_STATUS_EP_CURRENT_TILER_EP_GET(ep_c), CSG_STATUS_EP_REQ_TILER_EP_GET(ep_r), exclusive); } else { seq_puts(file, "GroupID, CSG NR, Run State, Priority\n"); seq_printf(file, "%7d, %6d, %9d, %8d\n", group->handle, group->csg_nr, group->run_state, group->priority); } if (group->run_state != KBASE_CSF_GROUP_TERMINATED) { unsigned int i; seq_puts(file, "Bound queues:\n"); for (i = 0; i < MAX_SUPPORTED_STREAMS_PER_GROUP; i++) { kbasep_csf_scheduler_dump_active_queue(file, group->bound_queues[i]); } } seq_puts(file, "\n"); } /** * kbasep_csf_queue_group_debugfs_show() - Print per-context GPU command queue * group debug 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_queue_group_debugfs_show(struct seq_file *file, void *data) { u32 gr; struct kbase_context *const kctx = file->private; struct kbase_device *const kbdev = kctx->kbdev; if (WARN_ON(!kctx)) return -EINVAL; seq_printf(file, "MALI_CSF_CSG_DEBUGFS_VERSION: v%u\n", MALI_CSF_CSG_DEBUGFS_VERSION); mutex_lock(&kctx->csf.lock); kbase_csf_scheduler_lock(kbdev); for (gr = 0; gr < MAX_QUEUE_GROUP_NUM; gr++) { struct kbase_queue_group *const group = kctx->csf.queue_groups[gr]; if (group) kbasep_csf_scheduler_dump_active_group(file, group); } kbase_csf_scheduler_unlock(kbdev); mutex_unlock(&kctx->csf.lock); return 0; } /** * kbasep_csf_scheduler_dump_active_groups() - Print debug info for active * GPU command queue groups * * @file: The seq_file for printing to * @data: The debugfs dentry private data, a pointer to kbase_device * * Return: Negative error code or 0 on success. */ static int kbasep_csf_scheduler_dump_active_groups(struct seq_file *file, void *data) { u32 csg_nr; struct kbase_device *kbdev = file->private; u32 num_groups = kbdev->csf.global_iface.group_num; seq_printf(file, "MALI_CSF_CSG_DEBUGFS_VERSION: v%u\n", MALI_CSF_CSG_DEBUGFS_VERSION); kbase_csf_scheduler_lock(kbdev); for (csg_nr = 0; csg_nr < num_groups; csg_nr++) { struct kbase_queue_group *const group = kbdev->csf.scheduler.csg_slots[csg_nr].resident_group; if (!group) continue; seq_printf(file, "\nCtx %d_%d\n", group->kctx->tgid, group->kctx->id); kbasep_csf_scheduler_dump_active_group(file, group); } kbase_csf_scheduler_unlock(kbdev); return 0; } static int kbasep_csf_queue_group_debugfs_open(struct inode *in, struct file *file) { return single_open(file, kbasep_csf_queue_group_debugfs_show, in->i_private); } static int kbasep_csf_active_queue_groups_debugfs_open(struct inode *in, struct file *file) { return single_open(file, kbasep_csf_scheduler_dump_active_groups, in->i_private); } static const struct file_operations kbasep_csf_queue_group_debugfs_fops = { .open = kbasep_csf_queue_group_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; void kbase_csf_queue_group_debugfs_init(struct kbase_context *kctx) { struct dentry *file; #if (KERNEL_VERSION(4, 7, 0) <= LINUX_VERSION_CODE) const mode_t mode = 0444; #else const mode_t mode = 0400; #endif if (WARN_ON(!kctx || IS_ERR_OR_NULL(kctx->kctx_dentry))) return; file = debugfs_create_file("groups", mode, kctx->kctx_dentry, kctx, &kbasep_csf_queue_group_debugfs_fops); if (IS_ERR_OR_NULL(file)) { dev_warn(kctx->kbdev->dev, "Unable to create per context queue groups debugfs entry"); } } static const struct file_operations kbasep_csf_active_queue_groups_debugfs_fops = { .open = kbasep_csf_active_queue_groups_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int kbasep_csf_debugfs_scheduling_timer_enabled_get( void *data, u64 *val) { struct kbase_device *const kbdev = data; *val = kbase_csf_scheduler_timer_is_enabled(kbdev); return 0; } static int kbasep_csf_debugfs_scheduling_timer_enabled_set( void *data, u64 val) { struct kbase_device *const kbdev = data; kbase_csf_scheduler_timer_set_enabled(kbdev, val != 0); return 0; } static int kbasep_csf_debugfs_scheduling_timer_kick_set( void *data, u64 val) { struct kbase_device *const kbdev = data; kbase_csf_scheduler_kick(kbdev); return 0; } DEFINE_SIMPLE_ATTRIBUTE(kbasep_csf_debugfs_scheduling_timer_enabled_fops, &kbasep_csf_debugfs_scheduling_timer_enabled_get, &kbasep_csf_debugfs_scheduling_timer_enabled_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(kbasep_csf_debugfs_scheduling_timer_kick_fops, NULL, &kbasep_csf_debugfs_scheduling_timer_kick_set, "%llu\n"); /** * kbase_csf_debugfs_scheduler_suspend_get() - get if the scheduler is suspended. * * @data: The debugfs dentry private data, a pointer to kbase_device * @val: The debugfs output value, boolean: 1 suspended, 0 otherwise * * Return: 0 */ static int kbase_csf_debugfs_scheduler_suspend_get( void *data, u64 *val) { struct kbase_device *kbdev = data; struct kbase_csf_scheduler *scheduler = &kbdev->csf.scheduler; kbase_csf_scheduler_lock(kbdev); *val = (scheduler->state == SCHED_SUSPENDED); kbase_csf_scheduler_unlock(kbdev); return 0; } /** * kbase_csf_debugfs_scheduler_suspend_set() - set the scheduler to suspended. * * @data: The debugfs dentry private data, a pointer to kbase_device * @val: The debugfs input value, boolean: 1 suspend, 0 otherwise * * Return: Negative value if already in requested state, 0 otherwise. */ static int kbase_csf_debugfs_scheduler_suspend_set( void *data, u64 val) { struct kbase_device *kbdev = data; struct kbase_csf_scheduler *scheduler = &kbdev->csf.scheduler; enum kbase_csf_scheduler_state state; kbase_csf_scheduler_lock(kbdev); state = scheduler->state; kbase_csf_scheduler_unlock(kbdev); if (val && (state != SCHED_SUSPENDED)) kbase_csf_scheduler_pm_suspend(kbdev); else if (!val && (state == SCHED_SUSPENDED)) kbase_csf_scheduler_pm_resume(kbdev); else return -1; return 0; } DEFINE_SIMPLE_ATTRIBUTE(kbasep_csf_debugfs_scheduler_suspend_fops, &kbase_csf_debugfs_scheduler_suspend_get, &kbase_csf_debugfs_scheduler_suspend_set, "%llu\n"); void kbase_csf_debugfs_init(struct kbase_device *kbdev) { debugfs_create_file("active_groups", 0444, kbdev->mali_debugfs_directory, kbdev, &kbasep_csf_active_queue_groups_debugfs_fops); debugfs_create_file("scheduling_timer_enabled", 0644, kbdev->mali_debugfs_directory, kbdev, &kbasep_csf_debugfs_scheduling_timer_enabled_fops); debugfs_create_file("scheduling_timer_kick", 0200, kbdev->mali_debugfs_directory, kbdev, &kbasep_csf_debugfs_scheduling_timer_kick_fops); debugfs_create_file("scheduler_suspend", 0644, kbdev->mali_debugfs_directory, kbdev, &kbasep_csf_debugfs_scheduler_suspend_fops); kbase_csf_tl_reader_debugfs_init(kbdev); kbase_csf_firmware_trace_buffer_debugfs_init(kbdev); } #else /* * Stub functions for when debugfs is disabled */ void kbase_csf_queue_group_debugfs_init(struct kbase_context *kctx) { } void kbase_csf_debugfs_init(struct kbase_device *kbdev) { } #endif /* CONFIG_DEBUG_FS */