/** * Copyright (C) 2011-2017 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 licence. * * A copy of the licence is included with the program, and can also be obtained from Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file mali_kernel_sysfs.c * Implementation of some sysfs data exports */ #include #include #include #include #include "mali_kernel_license.h" #include "mali_kernel_common.h" #include "mali_ukk.h" #if MALI_LICENSE_IS_GPL #include #include #include #include #include #include "mali_kernel_sysfs.h" #if defined(CONFIG_MALI400_INTERNAL_PROFILING) #include #include "mali_osk_profiling.h" #endif #include #include "mali_pm.h" #include "mali_pmu.h" #include "mali_group.h" #include "mali_gp.h" #include "mali_pp.h" #include "mali_l2_cache.h" #include "mali_hw_core.h" #include "mali_kernel_core.h" #include "mali_user_settings_db.h" #include "mali_profiling_internal.h" #include "mali_gp_job.h" #include "mali_pp_job.h" #include "mali_executor.h" #define PRIVATE_DATA_COUNTER_MAKE_GP(src) (src) #define PRIVATE_DATA_COUNTER_MAKE_PP(src) ((1 << 24) | src) #define PRIVATE_DATA_COUNTER_MAKE_PP_SUB_JOB(src, sub_job) ((1 << 24) | (1 << 16) | (sub_job << 8) | src) #define PRIVATE_DATA_COUNTER_IS_PP(a) ((((a) >> 24) & 0xFF) ? MALI_TRUE : MALI_FALSE) #define PRIVATE_DATA_COUNTER_GET_SRC(a) (a & 0xFF) #define PRIVATE_DATA_COUNTER_IS_SUB_JOB(a) ((((a) >> 16) & 0xFF) ? MALI_TRUE : MALI_FALSE) #define PRIVATE_DATA_COUNTER_GET_SUB_JOB(a) (((a) >> 8) & 0xFF) #define POWER_BUFFER_SIZE 3 static struct dentry *mali_debugfs_dir = NULL; typedef enum { _MALI_DEVICE_SUSPEND, _MALI_DEVICE_RESUME, _MALI_DEVICE_DVFS_PAUSE, _MALI_DEVICE_DVFS_RESUME, _MALI_MAX_EVENTS } _mali_device_debug_power_events; static const char *const mali_power_events[_MALI_MAX_EVENTS] = { [_MALI_DEVICE_SUSPEND] = "suspend", [_MALI_DEVICE_RESUME] = "resume", [_MALI_DEVICE_DVFS_PAUSE] = "dvfs_pause", [_MALI_DEVICE_DVFS_RESUME] = "dvfs_resume", }; static mali_bool power_always_on_enabled = MALI_FALSE; static int open_copy_private_data(struct inode *inode, struct file *filp) { filp->private_data = inode->i_private; return 0; } static ssize_t group_enabled_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int r; char buffer[64]; struct mali_group *group; group = (struct mali_group *)filp->private_data; MALI_DEBUG_ASSERT_POINTER(group); r = snprintf(buffer, 64, "%u\n", mali_executor_group_is_disabled(group) ? 0 : 1); return simple_read_from_buffer(buf, count, offp, buffer, r); } static ssize_t group_enabled_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp) { int r; char buffer[64]; unsigned long val; struct mali_group *group; group = (struct mali_group *)filp->private_data; MALI_DEBUG_ASSERT_POINTER(group); if (count >= sizeof(buffer)) { return -ENOMEM; } if (copy_from_user(&buffer[0], buf, count)) { return -EFAULT; } buffer[count] = '\0'; r = kstrtoul(&buffer[0], 10, &val); if (0 != r) { return -EINVAL; } switch (val) { case 1: mali_executor_group_enable(group); break; case 0: mali_executor_group_disable(group); break; default: return -EINVAL; break; } *offp += count; return count; } static const struct file_operations group_enabled_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = group_enabled_read, .write = group_enabled_write, }; static ssize_t hw_core_base_addr_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int r; char buffer[64]; struct mali_hw_core *hw_core; hw_core = (struct mali_hw_core *)filp->private_data; MALI_DEBUG_ASSERT_POINTER(hw_core); r = snprintf(buffer, 64, "0x%lX\n", hw_core->phys_addr); return simple_read_from_buffer(buf, count, offp, buffer, r); } static const struct file_operations hw_core_base_addr_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = hw_core_base_addr_read, }; static ssize_t profiling_counter_src_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { u32 is_pp = PRIVATE_DATA_COUNTER_IS_PP((uintptr_t)filp->private_data); u32 src_id = PRIVATE_DATA_COUNTER_GET_SRC((uintptr_t)filp->private_data); mali_bool is_sub_job = PRIVATE_DATA_COUNTER_IS_SUB_JOB((uintptr_t)filp->private_data); u32 sub_job = PRIVATE_DATA_COUNTER_GET_SUB_JOB((uintptr_t)filp->private_data); char buf[64]; int r; u32 val; if (MALI_TRUE == is_pp) { /* PP counter */ if (MALI_TRUE == is_sub_job) { /* Get counter for a particular sub job */ if (0 == src_id) { val = mali_pp_job_get_pp_counter_sub_job_src0(sub_job); } else { val = mali_pp_job_get_pp_counter_sub_job_src1(sub_job); } } else { /* Get default counter for all PP sub jobs */ if (0 == src_id) { val = mali_pp_job_get_pp_counter_global_src0(); } else { val = mali_pp_job_get_pp_counter_global_src1(); } } } else { /* GP counter */ if (0 == src_id) { val = mali_gp_job_get_gp_counter_src0(); } else { val = mali_gp_job_get_gp_counter_src1(); } } if (MALI_HW_CORE_NO_COUNTER == val) { r = snprintf(buf, 64, "-1\n"); } else { r = snprintf(buf, 64, "%u\n", val); } return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t profiling_counter_src_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { u32 is_pp = PRIVATE_DATA_COUNTER_IS_PP((uintptr_t)filp->private_data); u32 src_id = PRIVATE_DATA_COUNTER_GET_SRC((uintptr_t)filp->private_data); mali_bool is_sub_job = PRIVATE_DATA_COUNTER_IS_SUB_JOB((uintptr_t)filp->private_data); u32 sub_job = PRIVATE_DATA_COUNTER_GET_SUB_JOB((uintptr_t)filp->private_data); char buf[64]; long val; int ret; if (cnt >= sizeof(buf)) { return -EINVAL; } if (copy_from_user(&buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = 0; ret = kstrtol(buf, 10, &val); if (ret < 0) { return ret; } if (val < 0) { /* any negative input will disable counter */ val = MALI_HW_CORE_NO_COUNTER; } if (MALI_TRUE == is_pp) { /* PP counter */ if (MALI_TRUE == is_sub_job) { /* Set counter for a particular sub job */ if (0 == src_id) { mali_pp_job_set_pp_counter_sub_job_src0(sub_job, (u32)val); } else { mali_pp_job_set_pp_counter_sub_job_src1(sub_job, (u32)val); } } else { /* Set default counter for all PP sub jobs */ if (0 == src_id) { mali_pp_job_set_pp_counter_global_src0((u32)val); } else { mali_pp_job_set_pp_counter_global_src1((u32)val); } } } else { /* GP counter */ if (0 == src_id) { mali_gp_job_set_gp_counter_src0((u32)val); } else { mali_gp_job_set_gp_counter_src1((u32)val); } } *ppos += cnt; return cnt; } static const struct file_operations profiling_counter_src_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = profiling_counter_src_read, .write = profiling_counter_src_write, }; static ssize_t l2_l2x_counter_srcx_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos, u32 src_id) { char buf[64]; int r; u32 val; struct mali_l2_cache_core *l2_core = (struct mali_l2_cache_core *)filp->private_data; if (0 == src_id) { val = mali_l2_cache_core_get_counter_src0(l2_core); } else { val = mali_l2_cache_core_get_counter_src1(l2_core); } if (MALI_HW_CORE_NO_COUNTER == val) { r = snprintf(buf, 64, "-1\n"); } else { r = snprintf(buf, 64, "%u\n", val); } return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t l2_l2x_counter_srcx_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos, u32 src_id) { struct mali_l2_cache_core *l2_core = (struct mali_l2_cache_core *)filp->private_data; char buf[64]; long val; int ret; if (cnt >= sizeof(buf)) { return -EINVAL; } if (copy_from_user(&buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = 0; ret = kstrtol(buf, 10, &val); if (ret < 0) { return ret; } if (val < 0) { /* any negative input will disable counter */ val = MALI_HW_CORE_NO_COUNTER; } mali_l2_cache_core_set_counter_src(l2_core, src_id, (u32)val); *ppos += cnt; return cnt; } static ssize_t l2_all_counter_srcx_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos, u32 src_id) { char buf[64]; long val; int ret; u32 l2_id; struct mali_l2_cache_core *l2_cache; if (cnt >= sizeof(buf)) { return -EINVAL; } if (copy_from_user(&buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = 0; ret = kstrtol(buf, 10, &val); if (ret < 0) { return ret; } if (val < 0) { /* any negative input will disable counter */ val = MALI_HW_CORE_NO_COUNTER; } l2_id = 0; l2_cache = mali_l2_cache_core_get_glob_l2_core(l2_id); while (NULL != l2_cache) { mali_l2_cache_core_set_counter_src(l2_cache, src_id, (u32)val); /* try next L2 */ l2_id++; l2_cache = mali_l2_cache_core_get_glob_l2_core(l2_id); } *ppos += cnt; return cnt; } static ssize_t l2_l2x_counter_src0_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_srcx_read(filp, ubuf, cnt, ppos, 0); } static ssize_t l2_l2x_counter_src1_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_srcx_read(filp, ubuf, cnt, ppos, 1); } static ssize_t l2_l2x_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_srcx_write(filp, ubuf, cnt, ppos, 0); } static ssize_t l2_l2x_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_srcx_write(filp, ubuf, cnt, ppos, 1); } static ssize_t l2_all_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_all_counter_srcx_write(filp, ubuf, cnt, ppos, 0); } static ssize_t l2_all_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_all_counter_srcx_write(filp, ubuf, cnt, ppos, 1); } static const struct file_operations l2_l2x_counter_src0_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = l2_l2x_counter_src0_read, .write = l2_l2x_counter_src0_write, }; static const struct file_operations l2_l2x_counter_src1_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = l2_l2x_counter_src1_read, .write = l2_l2x_counter_src1_write, }; static const struct file_operations l2_all_counter_src0_fops = { .owner = THIS_MODULE, .write = l2_all_counter_src0_write, }; static const struct file_operations l2_all_counter_src1_fops = { .owner = THIS_MODULE, .write = l2_all_counter_src1_write, }; static ssize_t l2_l2x_counter_valx_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos, u32 src_id) { char buf[64]; int r; u32 src0 = 0; u32 val0 = 0; u32 src1 = 0; u32 val1 = 0; u32 val = -1; struct mali_l2_cache_core *l2_core = (struct mali_l2_cache_core *)filp->private_data; mali_l2_cache_core_get_counter_values(l2_core, &src0, &val0, &src1, &val1); if (0 == src_id) { if (MALI_HW_CORE_NO_COUNTER != val0) { val = val0; } } else { if (MALI_HW_CORE_NO_COUNTER != val1) { val = val1; } } r = snprintf(buf, 64, "%u\n", val); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t l2_l2x_counter_val0_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_valx_read(filp, ubuf, cnt, ppos, 0); } static ssize_t l2_l2x_counter_val1_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { return l2_l2x_counter_valx_read(filp, ubuf, cnt, ppos, 1); } static const struct file_operations l2_l2x_counter_val0_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = l2_l2x_counter_val0_read, }; static const struct file_operations l2_l2x_counter_val1_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = l2_l2x_counter_val1_read, }; static ssize_t power_always_on_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { unsigned long val; int ret; char buf[32]; cnt = min(cnt, sizeof(buf) - 1); if (copy_from_user(buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = '\0'; ret = kstrtoul(buf, 10, &val); if (0 != ret) { return ret; } /* Update setting (not exactly thread safe) */ if (1 == val && MALI_FALSE == power_always_on_enabled) { power_always_on_enabled = MALI_TRUE; _mali_osk_pm_dev_ref_get_sync(); } else if (0 == val && MALI_TRUE == power_always_on_enabled) { power_always_on_enabled = MALI_FALSE; _mali_osk_pm_dev_ref_put(); } *ppos += cnt; return cnt; } static ssize_t power_always_on_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { if (MALI_TRUE == power_always_on_enabled) { return simple_read_from_buffer(ubuf, cnt, ppos, "1\n", 2); } else { return simple_read_from_buffer(ubuf, cnt, ppos, "0\n", 2); } } static const struct file_operations power_always_on_fops = { .owner = THIS_MODULE, .read = power_always_on_read, .write = power_always_on_write, }; static ssize_t power_power_events_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { if (!strncmp(ubuf, mali_power_events[_MALI_DEVICE_SUSPEND], strlen(mali_power_events[_MALI_DEVICE_SUSPEND]) - 1)) { mali_pm_os_suspend(MALI_TRUE); } else if (!strncmp(ubuf, mali_power_events[_MALI_DEVICE_RESUME], strlen(mali_power_events[_MALI_DEVICE_RESUME]) - 1)) { mali_pm_os_resume(); } else if (!strncmp(ubuf, mali_power_events[_MALI_DEVICE_DVFS_PAUSE], strlen(mali_power_events[_MALI_DEVICE_DVFS_PAUSE]) - 1)) { mali_dev_pause(); } else if (!strncmp(ubuf, mali_power_events[_MALI_DEVICE_DVFS_RESUME], strlen(mali_power_events[_MALI_DEVICE_DVFS_RESUME]) - 1)) { mali_dev_resume(); } *ppos += cnt; return cnt; } static loff_t power_power_events_seek(struct file *file, loff_t offset, int orig) { file->f_pos = offset; return 0; } static const struct file_operations power_power_events_fops = { .owner = THIS_MODULE, .write = power_power_events_write, .llseek = power_power_events_seek, }; #if MALI_STATE_TRACKING static int mali_seq_internal_state_show(struct seq_file *seq_file, void *v) { u32 len = 0; u32 size; char *buf; size = seq_get_buf(seq_file, &buf); if (!size) { return -ENOMEM; } /* Create the internal state dump. */ len = snprintf(buf + len, size - len, "Mali device driver %s\n", SVN_REV_STRING); len += snprintf(buf + len, size - len, "License: %s\n\n", MALI_KERNEL_LINUX_LICENSE); len += _mali_kernel_core_dump_state(buf + len, size - len); seq_commit(seq_file, len); return 0; } static int mali_seq_internal_state_open(struct inode *inode, struct file *file) { return single_open(file, mali_seq_internal_state_show, NULL); } static const struct file_operations mali_seq_internal_state_fops = { .owner = THIS_MODULE, .open = mali_seq_internal_state_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif /* MALI_STATE_TRACKING */ #if defined(CONFIG_MALI400_INTERNAL_PROFILING) static ssize_t profiling_record_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; int r; r = snprintf(buf, 64, "%u\n", _mali_internal_profiling_is_recording() ? 1 : 0); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t profiling_record_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; unsigned long val; int ret; if (cnt >= sizeof(buf)) { return -EINVAL; } if (copy_from_user(&buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = 0; ret = kstrtoul(buf, 10, &val); if (ret < 0) { return ret; } if (val != 0) { u32 limit = MALI_PROFILING_MAX_BUFFER_ENTRIES; /* This can be made configurable at a later stage if we need to */ /* check if we are already recording */ if (MALI_TRUE == _mali_internal_profiling_is_recording()) { MALI_DEBUG_PRINT(3, ("Recording of profiling events already in progress\n")); return -EFAULT; } /* check if we need to clear out an old recording first */ if (MALI_TRUE == _mali_internal_profiling_have_recording()) { if (_MALI_OSK_ERR_OK != _mali_internal_profiling_clear()) { MALI_DEBUG_PRINT(3, ("Failed to clear existing recording of profiling events\n")); return -EFAULT; } } /* start recording profiling data */ if (_MALI_OSK_ERR_OK != _mali_internal_profiling_start(&limit)) { MALI_DEBUG_PRINT(3, ("Failed to start recording of profiling events\n")); return -EFAULT; } MALI_DEBUG_PRINT(3, ("Profiling recording started (max %u events)\n", limit)); } else { /* stop recording profiling data */ u32 count = 0; if (_MALI_OSK_ERR_OK != _mali_internal_profiling_stop(&count)) { MALI_DEBUG_PRINT(2, ("Failed to stop recording of profiling events\n")); return -EFAULT; } MALI_DEBUG_PRINT(2, ("Profiling recording stopped (recorded %u events)\n", count)); } *ppos += cnt; return cnt; } static const struct file_operations profiling_record_fops = { .owner = THIS_MODULE, .read = profiling_record_read, .write = profiling_record_write, }; static void *profiling_events_start(struct seq_file *s, loff_t *pos) { loff_t *spos; /* check if we have data avaiable */ if (MALI_TRUE != _mali_internal_profiling_have_recording()) { return NULL; } spos = kmalloc(sizeof(loff_t), GFP_KERNEL); if (NULL == spos) { return NULL; } *spos = *pos; return spos; } static void *profiling_events_next(struct seq_file *s, void *v, loff_t *pos) { loff_t *spos = v; /* check if we have data avaiable */ if (MALI_TRUE != _mali_internal_profiling_have_recording()) { return NULL; } /* check if the next entry actually is avaiable */ if (_mali_internal_profiling_get_count() <= (u32)(*spos + 1)) { return NULL; } *pos = ++*spos; return spos; } static void profiling_events_stop(struct seq_file *s, void *v) { kfree(v); } static int profiling_events_show(struct seq_file *seq_file, void *v) { loff_t *spos = v; u32 index; u64 timestamp; u32 event_id; u32 data[5]; index = (u32) * spos; /* Retrieve all events */ if (_MALI_OSK_ERR_OK == _mali_internal_profiling_get_event(index, ×tamp, &event_id, data)) { seq_printf(seq_file, "%llu %u %u %u %u %u %u\n", timestamp, event_id, data[0], data[1], data[2], data[3], data[4]); return 0; } return 0; } static int profiling_events_show_human_readable(struct seq_file *seq_file, void *v) { #define MALI_EVENT_ID_IS_HW(event_id) (((event_id & 0x00FF0000) >= MALI_PROFILING_EVENT_CHANNEL_GP0) && ((event_id & 0x00FF0000) <= MALI_PROFILING_EVENT_CHANNEL_PP7)) static u64 start_time = 0; loff_t *spos = v; u32 index; u64 timestamp; u32 event_id; u32 data[5]; index = (u32) * spos; /* Retrieve all events */ if (_MALI_OSK_ERR_OK == _mali_internal_profiling_get_event(index, ×tamp, &event_id, data)) { seq_printf(seq_file, "%llu %u %u %u %u %u %u # ", timestamp, event_id, data[0], data[1], data[2], data[3], data[4]); if (0 == index) { start_time = timestamp; } seq_printf(seq_file, "[%06u] ", index); switch (event_id & 0x0F000000) { case MALI_PROFILING_EVENT_TYPE_SINGLE: seq_printf(seq_file, "SINGLE | "); break; case MALI_PROFILING_EVENT_TYPE_START: seq_printf(seq_file, "START | "); break; case MALI_PROFILING_EVENT_TYPE_STOP: seq_printf(seq_file, "STOP | "); break; case MALI_PROFILING_EVENT_TYPE_SUSPEND: seq_printf(seq_file, "SUSPEND | "); break; case MALI_PROFILING_EVENT_TYPE_RESUME: seq_printf(seq_file, "RESUME | "); break; default: seq_printf(seq_file, "0x%01X | ", (event_id & 0x0F000000) >> 24); break; } switch (event_id & 0x00FF0000) { case MALI_PROFILING_EVENT_CHANNEL_SOFTWARE: seq_printf(seq_file, "SW | "); break; case MALI_PROFILING_EVENT_CHANNEL_GP0: seq_printf(seq_file, "GP0 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP0: seq_printf(seq_file, "PP0 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP1: seq_printf(seq_file, "PP1 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP2: seq_printf(seq_file, "PP2 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP3: seq_printf(seq_file, "PP3 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP4: seq_printf(seq_file, "PP4 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP5: seq_printf(seq_file, "PP5 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP6: seq_printf(seq_file, "PP6 | "); break; case MALI_PROFILING_EVENT_CHANNEL_PP7: seq_printf(seq_file, "PP7 | "); break; case MALI_PROFILING_EVENT_CHANNEL_GPU: seq_printf(seq_file, "GPU | "); break; default: seq_printf(seq_file, "0x%02X | ", (event_id & 0x00FF0000) >> 16); break; } if (MALI_EVENT_ID_IS_HW(event_id)) { if (((event_id & 0x0F000000) == MALI_PROFILING_EVENT_TYPE_START) || ((event_id & 0x0F000000) == MALI_PROFILING_EVENT_TYPE_STOP)) { switch (event_id & 0x0000FFFF) { case MALI_PROFILING_EVENT_REASON_START_STOP_HW_PHYSICAL: seq_printf(seq_file, "PHYSICAL | "); break; case MALI_PROFILING_EVENT_REASON_START_STOP_HW_VIRTUAL: seq_printf(seq_file, "VIRTUAL | "); break; default: seq_printf(seq_file, "0x%04X | ", event_id & 0x0000FFFF); break; } } else { seq_printf(seq_file, "0x%04X | ", event_id & 0x0000FFFF); } } else { seq_printf(seq_file, "0x%04X | ", event_id & 0x0000FFFF); } seq_printf(seq_file, "T0 + 0x%016llX\n", timestamp - start_time); return 0; } return 0; } static const struct seq_operations profiling_events_seq_ops = { .start = profiling_events_start, .next = profiling_events_next, .stop = profiling_events_stop, .show = profiling_events_show }; static int profiling_events_open(struct inode *inode, struct file *file) { return seq_open(file, &profiling_events_seq_ops); } static const struct file_operations profiling_events_fops = { .owner = THIS_MODULE, .open = profiling_events_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static const struct seq_operations profiling_events_human_readable_seq_ops = { .start = profiling_events_start, .next = profiling_events_next, .stop = profiling_events_stop, .show = profiling_events_show_human_readable }; static int profiling_events_human_readable_open(struct inode *inode, struct file *file) { return seq_open(file, &profiling_events_human_readable_seq_ops); } static const struct file_operations profiling_events_human_readable_fops = { .owner = THIS_MODULE, .open = profiling_events_human_readable_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; #endif static int memory_debugfs_show(struct seq_file *s, void *private_data) { #ifdef MALI_MEM_SWAP_TRACKING seq_printf(s, " %-25s %-10s %-10s %-15s %-15s %-10s %-10s %-10s \n"\ "=================================================================================================================================\n", "Name (:bytes)", "pid", "mali_mem", "max_mali_mem", "external_mem", "ump_mem", "dma_mem", "swap_mem"); #else seq_printf(s, " %-25s %-10s %-10s %-15s %-15s %-10s %-10s \n"\ "========================================================================================================================\n", "Name (:bytes)", "pid", "mali_mem", "max_mali_mem", "external_mem", "ump_mem", "dma_mem"); #endif mali_session_memory_tracking(s); return 0; } static int memory_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, memory_debugfs_show, inode->i_private); } static const struct file_operations memory_usage_fops = { .owner = THIS_MODULE, .open = memory_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t utilization_gp_pp_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; size_t r; u32 uval = _mali_ukk_utilization_gp_pp(); r = snprintf(buf, 64, "%u\n", uval); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t utilization_gp_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; size_t r; u32 uval = _mali_ukk_utilization_gp(); r = snprintf(buf, 64, "%u\n", uval); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static ssize_t utilization_pp_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; size_t r; u32 uval = _mali_ukk_utilization_pp(); r = snprintf(buf, 64, "%u\n", uval); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static const struct file_operations utilization_gp_pp_fops = { .owner = THIS_MODULE, .read = utilization_gp_pp_read, }; static const struct file_operations utilization_gp_fops = { .owner = THIS_MODULE, .read = utilization_gp_read, }; static const struct file_operations utilization_pp_fops = { .owner = THIS_MODULE, .read = utilization_pp_read, }; static ssize_t user_settings_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { unsigned long val; int ret; _mali_uk_user_setting_t setting; char buf[32]; cnt = min(cnt, sizeof(buf) - 1); if (copy_from_user(buf, ubuf, cnt)) { return -EFAULT; } buf[cnt] = '\0'; ret = kstrtoul(buf, 10, &val); if (0 != ret) { return ret; } /* Update setting */ setting = (_mali_uk_user_setting_t)(filp->private_data); mali_set_user_setting(setting, val); *ppos += cnt; return cnt; } static ssize_t user_settings_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { char buf[64]; size_t r; u32 value; _mali_uk_user_setting_t setting; setting = (_mali_uk_user_setting_t)(filp->private_data); value = mali_get_user_setting(setting); r = snprintf(buf, 64, "%u\n", value); return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); } static const struct file_operations user_settings_fops = { .owner = THIS_MODULE, .open = open_copy_private_data, .read = user_settings_read, .write = user_settings_write, }; static int mali_sysfs_user_settings_register(void) { struct dentry *mali_user_settings_dir = debugfs_create_dir("userspace_settings", mali_debugfs_dir); if (mali_user_settings_dir != NULL) { long i; for (i = 0; i < _MALI_UK_USER_SETTING_MAX; i++) { debugfs_create_file(_mali_uk_user_setting_descriptions[i], 0600, mali_user_settings_dir, (void *)i, &user_settings_fops); } } return 0; } static ssize_t pp_num_cores_enabled_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp) { int ret; char buffer[32]; unsigned long val; if (count >= sizeof(buffer)) { return -ENOMEM; } if (copy_from_user(&buffer[0], buf, count)) { return -EFAULT; } buffer[count] = '\0'; ret = kstrtoul(&buffer[0], 10, &val); if (0 != ret) { return -EINVAL; } ret = mali_executor_set_perf_level(val, MALI_TRUE); /* override even if core scaling is disabled */ if (ret) { return ret; } *offp += count; return count; } static ssize_t pp_num_cores_enabled_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int r; char buffer[64]; r = snprintf(buffer, 64, "%u\n", mali_executor_get_num_cores_enabled()); return simple_read_from_buffer(buf, count, offp, buffer, r); } static const struct file_operations pp_num_cores_enabled_fops = { .owner = THIS_MODULE, .write = pp_num_cores_enabled_write, .read = pp_num_cores_enabled_read, .llseek = default_llseek, }; static ssize_t pp_num_cores_total_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int r; char buffer[64]; r = snprintf(buffer, 64, "%u\n", mali_executor_get_num_cores_total()); return simple_read_from_buffer(buf, count, offp, buffer, r); } static const struct file_operations pp_num_cores_total_fops = { .owner = THIS_MODULE, .read = pp_num_cores_total_read, }; static ssize_t pp_core_scaling_enabled_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp) { int ret; char buffer[32]; unsigned long val; if (count >= sizeof(buffer)) { return -ENOMEM; } if (copy_from_user(&buffer[0], buf, count)) { return -EFAULT; } buffer[count] = '\0'; ret = kstrtoul(&buffer[0], 10, &val); if (0 != ret) { return -EINVAL; } switch (val) { case 1: mali_executor_core_scaling_enable(); break; case 0: mali_executor_core_scaling_disable(); break; default: return -EINVAL; break; } *offp += count; return count; } static ssize_t pp_core_scaling_enabled_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { return simple_read_from_buffer(buf, count, offp, mali_executor_core_scaling_is_enabled() ? "1\n" : "0\n", 2); } static const struct file_operations pp_core_scaling_enabled_fops = { .owner = THIS_MODULE, .write = pp_core_scaling_enabled_write, .read = pp_core_scaling_enabled_read, .llseek = default_llseek, }; static ssize_t version_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int r = 0; char buffer[64]; switch (mali_kernel_core_get_product_id()) { case _MALI_PRODUCT_ID_MALI200: r = snprintf(buffer, 64, "Mali-200\n"); break; case _MALI_PRODUCT_ID_MALI300: r = snprintf(buffer, 64, "Mali-300\n"); break; case _MALI_PRODUCT_ID_MALI400: r = snprintf(buffer, 64, "Mali-400 MP\n"); break; case _MALI_PRODUCT_ID_MALI450: r = snprintf(buffer, 64, "Mali-450 MP\n"); break; case _MALI_PRODUCT_ID_MALI470: r = snprintf(buffer, 64, "Mali-470 MP\n"); break; case _MALI_PRODUCT_ID_UNKNOWN: return -EINVAL; break; }; return simple_read_from_buffer(buf, count, offp, buffer, r); } static const struct file_operations version_fops = { .owner = THIS_MODULE, .read = version_read, }; #if defined(DEBUG) static int timeline_debugfs_show(struct seq_file *s, void *private_data) { struct mali_session_data *session, *tmp; u32 session_seq = 1; seq_printf(s, "timeline system info: \n=================\n\n"); mali_session_lock(); MALI_SESSION_FOREACH(session, tmp, link) { seq_printf(s, "session %d <%p> start:\n", session_seq, session); mali_timeline_debug_print_system(session->timeline_system, s); seq_printf(s, "session %d end\n\n\n", session_seq++); } mali_session_unlock(); return 0; } static int timeline_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, timeline_debugfs_show, inode->i_private); } static const struct file_operations timeline_dump_fops = { .owner = THIS_MODULE, .open = timeline_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; #endif int mali_sysfs_register(const char *mali_dev_name) { mali_debugfs_dir = debugfs_create_dir(mali_dev_name, NULL); if (ERR_PTR(-ENODEV) == mali_debugfs_dir) { /* Debugfs not supported. */ mali_debugfs_dir = NULL; } else { if (NULL != mali_debugfs_dir) { /* Debugfs directory created successfully; create files now */ struct dentry *mali_power_dir; struct dentry *mali_gp_dir; struct dentry *mali_pp_dir; struct dentry *mali_l2_dir; struct dentry *mali_profiling_dir; debugfs_create_file("version", 0400, mali_debugfs_dir, NULL, &version_fops); mali_power_dir = debugfs_create_dir("power", mali_debugfs_dir); if (mali_power_dir != NULL) { debugfs_create_file("always_on", 0600, mali_power_dir, NULL, &power_always_on_fops); debugfs_create_file("power_events", 0200, mali_power_dir, NULL, &power_power_events_fops); } mali_gp_dir = debugfs_create_dir("gp", mali_debugfs_dir); if (mali_gp_dir != NULL) { u32 num_groups; long i; num_groups = mali_group_get_glob_num_groups(); for (i = 0; i < num_groups; i++) { struct mali_group *group = mali_group_get_glob_group(i); struct mali_gp_core *gp_core = mali_group_get_gp_core(group); if (NULL != gp_core) { struct dentry *mali_gp_gpx_dir; mali_gp_gpx_dir = debugfs_create_dir("gp0", mali_gp_dir); if (NULL != mali_gp_gpx_dir) { debugfs_create_file("base_addr", 0400, mali_gp_gpx_dir, &gp_core->hw_core, &hw_core_base_addr_fops); debugfs_create_file("enabled", 0600, mali_gp_gpx_dir, group, &group_enabled_fops); } break; /* no need to look for any other GP cores */ } } } mali_pp_dir = debugfs_create_dir("pp", mali_debugfs_dir); if (mali_pp_dir != NULL) { u32 num_groups; long i; debugfs_create_file("num_cores_total", 0400, mali_pp_dir, NULL, &pp_num_cores_total_fops); debugfs_create_file("num_cores_enabled", 0600, mali_pp_dir, NULL, &pp_num_cores_enabled_fops); debugfs_create_file("core_scaling_enabled", 0600, mali_pp_dir, NULL, &pp_core_scaling_enabled_fops); num_groups = mali_group_get_glob_num_groups(); for (i = 0; i < num_groups; i++) { struct mali_group *group = mali_group_get_glob_group(i); struct mali_pp_core *pp_core = mali_group_get_pp_core(group); if (NULL != pp_core) { char buf[16]; struct dentry *mali_pp_ppx_dir; _mali_osk_snprintf(buf, sizeof(buf), "pp%u", mali_pp_core_get_id(pp_core)); mali_pp_ppx_dir = debugfs_create_dir(buf, mali_pp_dir); if (NULL != mali_pp_ppx_dir) { debugfs_create_file("base_addr", 0400, mali_pp_ppx_dir, &pp_core->hw_core, &hw_core_base_addr_fops); if (!mali_group_is_virtual(group)) { debugfs_create_file("enabled", 0600, mali_pp_ppx_dir, group, &group_enabled_fops); } } } } } mali_l2_dir = debugfs_create_dir("l2", mali_debugfs_dir); if (mali_l2_dir != NULL) { struct dentry *mali_l2_all_dir; u32 l2_id; struct mali_l2_cache_core *l2_cache; mali_l2_all_dir = debugfs_create_dir("all", mali_l2_dir); if (mali_l2_all_dir != NULL) { debugfs_create_file("counter_src0", 0200, mali_l2_all_dir, NULL, &l2_all_counter_src0_fops); debugfs_create_file("counter_src1", 0200, mali_l2_all_dir, NULL, &l2_all_counter_src1_fops); } l2_id = 0; l2_cache = mali_l2_cache_core_get_glob_l2_core(l2_id); while (NULL != l2_cache) { char buf[16]; struct dentry *mali_l2_l2x_dir; _mali_osk_snprintf(buf, sizeof(buf), "l2%u", l2_id); mali_l2_l2x_dir = debugfs_create_dir(buf, mali_l2_dir); if (NULL != mali_l2_l2x_dir) { debugfs_create_file("counter_src0", 0600, mali_l2_l2x_dir, l2_cache, &l2_l2x_counter_src0_fops); debugfs_create_file("counter_src1", 0600, mali_l2_l2x_dir, l2_cache, &l2_l2x_counter_src1_fops); debugfs_create_file("counter_val0", 0600, mali_l2_l2x_dir, l2_cache, &l2_l2x_counter_val0_fops); debugfs_create_file("counter_val1", 0600, mali_l2_l2x_dir, l2_cache, &l2_l2x_counter_val1_fops); debugfs_create_file("base_addr", 0400, mali_l2_l2x_dir, &l2_cache->hw_core, &hw_core_base_addr_fops); } /* try next L2 */ l2_id++; l2_cache = mali_l2_cache_core_get_glob_l2_core(l2_id); } } debugfs_create_file("gpu_memory", 0444, mali_debugfs_dir, NULL, &memory_usage_fops); debugfs_create_file("utilization_gp_pp", 0400, mali_debugfs_dir, NULL, &utilization_gp_pp_fops); debugfs_create_file("utilization_gp", 0400, mali_debugfs_dir, NULL, &utilization_gp_fops); debugfs_create_file("utilization_pp", 0400, mali_debugfs_dir, NULL, &utilization_pp_fops); mali_profiling_dir = debugfs_create_dir("profiling", mali_debugfs_dir); if (mali_profiling_dir != NULL) { u32 max_sub_jobs; long i; struct dentry *mali_profiling_gp_dir; struct dentry *mali_profiling_pp_dir; #if defined(CONFIG_MALI400_INTERNAL_PROFILING) struct dentry *mali_profiling_proc_dir; #endif /* * Create directory where we can set GP HW counters. */ mali_profiling_gp_dir = debugfs_create_dir("gp", mali_profiling_dir); if (mali_profiling_gp_dir != NULL) { debugfs_create_file("counter_src0", 0600, mali_profiling_gp_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_GP(0), &profiling_counter_src_fops); debugfs_create_file("counter_src1", 0600, mali_profiling_gp_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_GP(1), &profiling_counter_src_fops); } /* * Create directory where we can set PP HW counters. * Possible override with specific HW counters for a particular sub job * (Disable core scaling before using the override!) */ mali_profiling_pp_dir = debugfs_create_dir("pp", mali_profiling_dir); if (mali_profiling_pp_dir != NULL) { debugfs_create_file("counter_src0", 0600, mali_profiling_pp_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_PP(0), &profiling_counter_src_fops); debugfs_create_file("counter_src1", 0600, mali_profiling_pp_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_PP(1), &profiling_counter_src_fops); } max_sub_jobs = mali_executor_get_num_cores_total(); for (i = 0; i < max_sub_jobs; i++) { char buf[16]; struct dentry *mali_profiling_pp_x_dir; _mali_osk_snprintf(buf, sizeof(buf), "%u", i); mali_profiling_pp_x_dir = debugfs_create_dir(buf, mali_profiling_pp_dir); if (NULL != mali_profiling_pp_x_dir) { debugfs_create_file("counter_src0", 0600, mali_profiling_pp_x_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_PP_SUB_JOB(0, i), &profiling_counter_src_fops); debugfs_create_file("counter_src1", 0600, mali_profiling_pp_x_dir, (void *)PRIVATE_DATA_COUNTER_MAKE_PP_SUB_JOB(1, i), &profiling_counter_src_fops); } } #if defined(CONFIG_MALI400_INTERNAL_PROFILING) mali_profiling_proc_dir = debugfs_create_dir("proc", mali_profiling_dir); if (mali_profiling_proc_dir != NULL) { struct dentry *mali_profiling_proc_default_dir = debugfs_create_dir("default", mali_profiling_proc_dir); if (mali_profiling_proc_default_dir != NULL) { debugfs_create_file("enable", 0600, mali_profiling_proc_default_dir, (void *)_MALI_UK_USER_SETTING_SW_EVENTS_ENABLE, &user_settings_fops); } } debugfs_create_file("record", 0600, mali_profiling_dir, NULL, &profiling_record_fops); debugfs_create_file("events", 0400, mali_profiling_dir, NULL, &profiling_events_fops); debugfs_create_file("events_human_readable", 0400, mali_profiling_dir, NULL, &profiling_events_human_readable_fops); #endif } #if MALI_STATE_TRACKING debugfs_create_file("state_dump", 0400, mali_debugfs_dir, NULL, &mali_seq_internal_state_fops); #endif #if defined(DEBUG) debugfs_create_file("timeline_dump", 0400, mali_debugfs_dir, NULL, &timeline_dump_fops); #endif if (mali_sysfs_user_settings_register()) { /* Failed to create the debugfs entries for the user settings DB. */ MALI_DEBUG_PRINT(2, ("Failed to create user setting debugfs files. Ignoring...\n")); } } } /* Success! */ return 0; } int mali_sysfs_unregister(void) { if (NULL != mali_debugfs_dir) { debugfs_remove_recursive(mali_debugfs_dir); } return 0; } #else /* MALI_LICENSE_IS_GPL */ /* Dummy implementations for non-GPL */ int mali_sysfs_register(struct mali_dev *device, dev_t dev, const char *mali_dev_name) { return 0; } int mali_sysfs_unregister(void) { return 0; } #endif /* MALI_LICENSE_IS_GPL */