| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2019-2022 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 <mali_kbase.h> |
| #include <linux/seq_file.h> |
| #include <linux/delay.h> |
| #include <backend/gpu/mali_kbase_pm_internal.h> |
| |
| #if IS_ENABLED(CONFIG_DEBUG_FS) |
| #include "mali_kbase_csf_tl_reader.h" |
| #include <linux/version_compat_defs.h> |
| |
| /* Wait time to be used cumulatively for all the CSG slots. |
| * Since scheduler lock is held when STATUS_UPDATE request is sent, there won't be |
| * any other Host request pending on the FW side and usually FW would be responsive |
| * to the Doorbell IRQs as it won't do any polling for a long time and also it won't |
| * have to wait for any HW state transition to complete for publishing the status. |
| * So it is reasonable to expect that handling of STATUS_UPDATE request would be |
| * relatively very quick. |
| */ |
| #define STATUS_UPDATE_WAIT_TIMEOUT 500 |
| |
| /* The bitmask of CSG slots for which the STATUS_UPDATE request completed. |
| * The access to it is serialized with scheduler lock, so at a time it would |
| * get used either for "active_groups" or per context "groups" debugfs file. |
| */ |
| static DECLARE_BITMAP(csg_slots_status_updated, MAX_SUPPORTED_CSGS); |
| |
| static bool csg_slot_status_update_finish(struct kbase_device *kbdev, u32 csg_nr) |
| { |
| struct kbase_csf_cmd_stream_group_info const *const ginfo = |
| &kbdev->csf.global_iface.groups[csg_nr]; |
| |
| return !((kbase_csf_firmware_csg_input_read(ginfo, CSG_REQ) ^ |
| kbase_csf_firmware_csg_output(ginfo, CSG_ACK)) & |
| CSG_REQ_STATUS_UPDATE_MASK); |
| } |
| |
| static bool csg_slots_status_update_finish(struct kbase_device *kbdev, |
| const unsigned long *slots_mask) |
| { |
| const u32 max_csg_slots = kbdev->csf.global_iface.group_num; |
| bool changed = false; |
| u32 csg_nr; |
| |
| lockdep_assert_held(&kbdev->csf.scheduler.lock); |
| |
| for_each_set_bit(csg_nr, slots_mask, max_csg_slots) { |
| if (csg_slot_status_update_finish(kbdev, csg_nr)) { |
| set_bit(csg_nr, csg_slots_status_updated); |
| changed = true; |
| } |
| } |
| |
| return changed; |
| } |
| |
| static void wait_csg_slots_status_update_finish(struct kbase_device *kbdev, |
| unsigned long *slots_mask) |
| { |
| const u32 max_csg_slots = kbdev->csf.global_iface.group_num; |
| long remaining = kbase_csf_timeout_in_jiffies(STATUS_UPDATE_WAIT_TIMEOUT); |
| |
| lockdep_assert_held(&kbdev->csf.scheduler.lock); |
| |
| bitmap_zero(csg_slots_status_updated, max_csg_slots); |
| |
| while (!bitmap_empty(slots_mask, max_csg_slots) && remaining) { |
| remaining = wait_event_timeout(kbdev->csf.event_wait, |
| csg_slots_status_update_finish(kbdev, slots_mask), |
| remaining); |
| if (likely(remaining)) { |
| bitmap_andnot(slots_mask, slots_mask, csg_slots_status_updated, |
| max_csg_slots); |
| } else { |
| dev_warn(kbdev->dev, "STATUS_UPDATE request timed out for slots 0x%lx", |
| slots_mask[0]); |
| } |
| } |
| } |
| |
| static void update_active_groups_status(struct kbase_device *kbdev, struct seq_file *file) |
| { |
| u32 max_csg_slots = kbdev->csf.global_iface.group_num; |
| DECLARE_BITMAP(used_csgs, MAX_SUPPORTED_CSGS) = { 0 }; |
| u32 csg_nr; |
| unsigned long flags; |
| |
| lockdep_assert_held(&kbdev->csf.scheduler.lock); |
| |
| /* Global doorbell ring for CSG STATUS_UPDATE request or User doorbell |
| * ring for Extract offset update, shall not be made when MCU has been |
| * put to sleep otherwise it will undesirably make MCU exit the sleep |
| * state. Also it isn't really needed as FW will implicitly update the |
| * status of all on-slot groups when MCU sleep request is sent to it. |
| */ |
| if (kbdev->csf.scheduler.state == SCHED_SLEEPING) { |
| bitmap_copy(csg_slots_status_updated, kbdev->csf.scheduler.csg_inuse_bitmap, |
| max_csg_slots); |
| return; |
| } |
| |
| for (csg_nr = 0; csg_nr < max_csg_slots; csg_nr++) { |
| struct kbase_queue_group *const group = |
| kbdev->csf.scheduler.csg_slots[csg_nr].resident_group; |
| if (!group) |
| continue; |
| /* Ring the User doorbell for FW to update the Extract offset */ |
| kbase_csf_ring_doorbell(kbdev, group->doorbell_nr); |
| set_bit(csg_nr, used_csgs); |
| } |
| |
| /* Return early if there are no on-slot groups */ |
| if (bitmap_empty(used_csgs, max_csg_slots)) |
| return; |
| |
| kbase_csf_scheduler_spin_lock(kbdev, &flags); |
| for_each_set_bit(csg_nr, used_csgs, max_csg_slots) { |
| struct kbase_csf_cmd_stream_group_info const *const ginfo = |
| &kbdev->csf.global_iface.groups[csg_nr]; |
| kbase_csf_firmware_csg_input_mask(ginfo, CSG_REQ, |
| ~kbase_csf_firmware_csg_output(ginfo, CSG_ACK), |
| CSG_REQ_STATUS_UPDATE_MASK); |
| } |
| |
| BUILD_BUG_ON(MAX_SUPPORTED_CSGS > (sizeof(used_csgs[0]) * BITS_PER_BYTE)); |
| kbase_csf_ring_csg_slots_doorbell(kbdev, used_csgs[0]); |
| kbase_csf_scheduler_spin_unlock(kbdev, flags); |
| wait_csg_slots_status_update_finish(kbdev, used_csgs); |
| /* Wait for the User doobell ring to take effect */ |
| msleep(100); |
| } |
| |
| #define MAX_SCHED_STATE_STRING_LEN (16) |
| static const char *scheduler_state_to_string(struct kbase_device *kbdev, |
| enum kbase_csf_scheduler_state sched_state) |
| { |
| switch (sched_state) { |
| case SCHED_BUSY: |
| return "BUSY"; |
| case SCHED_INACTIVE: |
| return "INACTIVE"; |
| case SCHED_SUSPENDED: |
| return "SUSPENDED"; |
| #ifdef KBASE_PM_RUNTIME |
| case SCHED_SLEEPING: |
| return "SLEEPING"; |
| #endif |
| default: |
| dev_warn(kbdev->dev, "Unknown Scheduler state %d", sched_state); |
| return NULL; |
| } |
| } |
| |
| /** |
| * 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 bool sb_source_supported(u32 glb_version) |
| { |
| bool supported = false; |
| |
| if (((GLB_VERSION_MAJOR_GET(glb_version) == 3) && |
| (GLB_VERSION_MINOR_GET(glb_version) >= 5)) || |
| ((GLB_VERSION_MAJOR_GET(glb_version) == 2) && |
| (GLB_VERSION_MINOR_GET(glb_version) >= 6)) || |
| ((GLB_VERSION_MAJOR_GET(glb_version) == 1) && |
| (GLB_VERSION_MINOR_GET(glb_version) >= 3))) |
| supported = true; |
| |
| return supported; |
| } |
| |
| static void kbasep_csf_scheduler_dump_active_queue_cs_status_wait( |
| struct seq_file *file, u32 glb_version, 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)); |
| if (sb_source_supported(glb_version)) |
| seq_printf(file, "SB_SOURCE: %d\n", CS_STATUS_WAIT_SB_SOURCE_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; |
| u32 glb_version; |
| |
| if (!queue) |
| return; |
| |
| glb_version = queue->kctx->kbdev->csf.global_iface.version; |
| |
| if (WARN_ON(queue->csi_index == KBASEP_IF_NR_INVALID || |
| !queue->group)) |
| return; |
| |
| 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, Size, Prio, Insert offset, Extract offset, Active, Doorbell\n" |
| |
| seq_printf(file, KBASEP_CSF_DEBUGFS_CS_HEADER_USER_IO "%8d, %16llx, %8x, %4u, %16llx, %16llx, %6u, %8d\n", |
| queue->csi_index, queue->base_addr, |
| queue->size, |
| 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) { |
| seq_printf(file, "SAVED_CMD_PTR: 0x%llx\n", queue->saved_cmd_ptr); |
| 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, glb_version, 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, glb_version, 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"); |
| } |
| |
| 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; |
| u32 ep_c, ep_r; |
| char exclusive; |
| char idle = 'N'; |
| struct kbase_csf_cmd_stream_group_info const *const ginfo = |
| &kbdev->csf.global_iface.groups[group->csg_nr]; |
| u8 slot_priority = |
| kbdev->csf.scheduler.csg_slots[group->csg_nr].priority; |
| |
| 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 (kbase_csf_firmware_csg_output(ginfo, CSG_STATUS_STATE) & |
| CSG_STATUS_STATE_IDLE_MASK) |
| idle = 'Y'; |
| |
| if (!test_bit(group->csg_nr, csg_slots_status_updated)) { |
| seq_printf(file, "*** Warn: Timed out for STATUS_UPDATE on slot %d\n", |
| group->csg_nr); |
| seq_puts(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, Idle\n"); |
| seq_printf(file, "%7d, %6d, %8d, %9d, %8d, %11d/%3d, %11d/%3d, %11d/%3d, %9c, %4c\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, |
| idle); |
| } 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); |
| if (kbdev->csf.scheduler.state == SCHED_SLEEPING) { |
| /* Wait for the MCU sleep request to complete. Please refer the |
| * update_active_groups_status() function for the explanation. |
| */ |
| kbase_pm_wait_for_desired_state(kbdev); |
| } |
| update_active_groups_status(kbdev, file); |
| 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); |
| if (kbdev->csf.scheduler.state == SCHED_SLEEPING) { |
| /* Wait for the MCU sleep request to complete. Please refer the |
| * update_active_groups_status() function for the explanation. |
| */ |
| kbase_pm_wait_for_desired_state(kbdev); |
| } |
| update_active_groups_status(kbdev, file); |
| 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; |
| const mode_t mode = 0444; |
| |
| 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_DEBUGFS_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_DEBUGFS_ATTRIBUTE(kbasep_csf_debugfs_scheduling_timer_kick_fops, NULL, |
| &kbasep_csf_debugfs_scheduling_timer_kick_set, "%llu\n"); |
| |
| /** |
| * kbase_csf_debugfs_scheduler_state_get() - Get the state of scheduler. |
| * |
| * @file: Object of the file that is being read. |
| * @user_buf: User buffer that contains the string. |
| * @count: Length of user buffer |
| * @ppos: Offset within file object |
| * |
| * This function will return the current Scheduler state to Userspace |
| * Scheduler may exit that state by the time the state string is received |
| * by the Userspace. |
| * |
| * Return: 0 if Scheduler was found in an unexpected state, or the |
| * size of the state string if it was copied successfully to the |
| * User buffer or a negative value in case of an error. |
| */ |
| static ssize_t kbase_csf_debugfs_scheduler_state_get(struct file *file, |
| char __user *user_buf, size_t count, loff_t *ppos) |
| { |
| struct kbase_device *kbdev = file->private_data; |
| struct kbase_csf_scheduler *scheduler = &kbdev->csf.scheduler; |
| const char *state_string; |
| |
| kbase_csf_scheduler_lock(kbdev); |
| state_string = scheduler_state_to_string(kbdev, scheduler->state); |
| kbase_csf_scheduler_unlock(kbdev); |
| |
| if (!state_string) |
| count = 0; |
| |
| return simple_read_from_buffer(user_buf, count, ppos, |
| state_string, strlen(state_string)); |
| } |
| |
| /** |
| * kbase_csf_debugfs_scheduler_state_set() - Set the state of scheduler. |
| * |
| * @file: Object of the file that is being written to. |
| * @ubuf: User buffer that contains the string. |
| * @count: Length of user buffer |
| * @ppos: Offset within file object |
| * |
| * This function will update the Scheduler state as per the state string |
| * passed by the Userspace. Scheduler may or may not remain in new state |
| * for long. |
| * |
| * Return: Negative value if the string doesn't correspond to a valid Scheduler |
| * state or if copy from user buffer failed, otherwise the length of |
| * the User buffer. |
| */ |
| static ssize_t kbase_csf_debugfs_scheduler_state_set(struct file *file, |
| const char __user *ubuf, size_t count, loff_t *ppos) |
| { |
| struct kbase_device *kbdev = file->private_data; |
| char buf[MAX_SCHED_STATE_STRING_LEN]; |
| ssize_t ret = count; |
| |
| CSTD_UNUSED(ppos); |
| |
| count = min_t(size_t, sizeof(buf) - 1, count); |
| if (copy_from_user(buf, ubuf, count)) |
| return -EFAULT; |
| |
| buf[count] = 0; |
| |
| if (sysfs_streq(buf, "SUSPENDED")) |
| kbase_csf_scheduler_pm_suspend(kbdev); |
| #ifdef KBASE_PM_RUNTIME |
| else if (sysfs_streq(buf, "SLEEPING")) |
| kbase_csf_scheduler_force_sleep(kbdev); |
| #endif |
| else if (sysfs_streq(buf, "INACTIVE")) |
| kbase_csf_scheduler_force_wakeup(kbdev); |
| else { |
| dev_dbg(kbdev->dev, "Bad scheduler state %s", buf); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static const struct file_operations kbasep_csf_debugfs_scheduler_state_fops = { |
| .owner = THIS_MODULE, |
| .read = kbase_csf_debugfs_scheduler_state_get, |
| .write = kbase_csf_debugfs_scheduler_state_set, |
| .open = simple_open, |
| .llseek = default_llseek, |
| }; |
| |
| 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_state", 0644, |
| kbdev->mali_debugfs_directory, kbdev, |
| &kbasep_csf_debugfs_scheduler_state_fops); |
| |
| kbase_csf_tl_reader_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 */ |