blob: ab8248c915c09702cc35c19aeb36db197128a9bf [file] [log] [blame]
/**
* Copyright (C) 2011-2013 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 <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/module.h>
#include "mali_kernel_license.h"
#include "mali_kernel_common.h"
#include "mali_ukk.h"
#if MALI_LICENSE_IS_GPL
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/mali/mali_utgard.h>
#include "mali_kernel_sysfs.h"
#if defined(CONFIG_MALI400_INTERNAL_PROFILING)
#include <linux/slab.h>
#include "mali_osk_profiling.h"
#endif
#include <linux/mali/mali_utgard.h>
#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_pp_scheduler.h"
#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 = sprintf(buffer, "%u\n", mali_group_is_enabled(group) ? 1 : 0);
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 = strict_strtoul(&buffer[0], 10, &val);
if (0 != r)
{
return -EINVAL;
}
switch (val)
{
case 1:
mali_group_enable(group);
break;
case 0:
mali_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 = sprintf(buffer, "0x%08X\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 gp_gpx_counter_srcx_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *gpos, u32 src_id)
{
char buf[64];
int r;
u32 val;
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 = sprintf(buf, "-1\n");
}
else
{
r = sprintf(buf, "%u\n", val);
}
return simple_read_from_buffer(ubuf, cnt, gpos, buf, r);
}
static ssize_t gp_gpx_counter_srcx_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos, u32 src_id)
{
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 = strict_strtol(buf, 10, &val);
if (ret < 0)
{
return ret;
}
if (val < 0)
{
/* any negative input will disable counter */
val = MALI_HW_CORE_NO_COUNTER;
}
if (0 == src_id)
{
if (MALI_TRUE != mali_gp_job_set_gp_counter_src0((u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_gp_job_set_gp_counter_src1((u32)val))
{
return 0;
}
}
*gpos += cnt;
return cnt;
}
static ssize_t gp_all_counter_srcx_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos, u32 src_id)
{
char buf[64];
long val;
int ret;
u32 num_groups;
int i;
if (cnt >= sizeof(buf))
{
return -EINVAL;
}
if (copy_from_user(&buf, ubuf, cnt))
{
return -EFAULT;
}
buf[cnt] = 0;
ret = strict_strtol(buf, 10, &val);
if (ret < 0)
{
return ret;
}
if (val < 0)
{
/* any negative input will disable counter */
val = MALI_HW_CORE_NO_COUNTER;
}
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)
{
if (0 == src_id)
{
if (MALI_TRUE != mali_gp_job_set_gp_counter_src0((u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_gp_job_set_gp_counter_src1((u32)val))
{
return 0;
}
}
}
}
*gpos += cnt;
return cnt;
}
static ssize_t gp_gpx_counter_src0_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_gpx_counter_srcx_read(filp, ubuf, cnt, gpos, 0);
}
static ssize_t gp_gpx_counter_src1_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_gpx_counter_srcx_read(filp, ubuf, cnt, gpos, 1);
}
static ssize_t gp_gpx_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_gpx_counter_srcx_write(filp, ubuf, cnt, gpos, 0);
}
static ssize_t gp_gpx_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_gpx_counter_srcx_write(filp, ubuf, cnt, gpos, 1);
}
static ssize_t gp_all_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_all_counter_srcx_write(filp, ubuf, cnt, gpos, 0);
}
static ssize_t gp_all_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *gpos)
{
return gp_all_counter_srcx_write(filp, ubuf, cnt, gpos, 1);
}
static const struct file_operations gp_gpx_counter_src0_fops = {
.owner = THIS_MODULE,
.open = open_copy_private_data,
.read = gp_gpx_counter_src0_read,
.write = gp_gpx_counter_src0_write,
};
static const struct file_operations gp_gpx_counter_src1_fops = {
.owner = THIS_MODULE,
.open = open_copy_private_data,
.read = gp_gpx_counter_src1_read,
.write = gp_gpx_counter_src1_write,
};
static const struct file_operations gp_all_counter_src0_fops = {
.owner = THIS_MODULE,
.write = gp_all_counter_src0_write,
};
static const struct file_operations gp_all_counter_src1_fops = {
.owner = THIS_MODULE,
.write = gp_all_counter_src1_write,
};
static ssize_t pp_ppx_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;
if (0 == src_id)
{
val = mali_pp_job_get_pp_counter_src0();
}
else
{
val = mali_pp_job_get_pp_counter_src1();
}
if (MALI_HW_CORE_NO_COUNTER == val)
{
r = sprintf(buf, "-1\n");
}
else
{
r = sprintf(buf, "%u\n", val);
}
return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}
static ssize_t pp_ppx_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;
if (cnt >= sizeof(buf))
{
return -EINVAL;
}
if (copy_from_user(&buf, ubuf, cnt))
{
return -EFAULT;
}
buf[cnt] = 0;
ret = strict_strtol(buf, 10, &val);
if (ret < 0)
{
return ret;
}
if (val < 0)
{
/* any negative input will disable counter */
val = MALI_HW_CORE_NO_COUNTER;
}
if (0 == src_id)
{
if (MALI_TRUE != mali_pp_job_set_pp_counter_src0((u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_pp_job_set_pp_counter_src1((u32)val))
{
return 0;
}
}
*ppos += cnt;
return cnt;
}
static ssize_t pp_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 num_groups;
int i;
if (cnt >= sizeof(buf))
{
return -EINVAL;
}
if (copy_from_user(&buf, ubuf, cnt))
{
return -EFAULT;
}
buf[cnt] = 0;
ret = strict_strtol(buf, 10, &val);
if (ret < 0)
{
return ret;
}
if (val < 0)
{
/* any negative input will disable counter */
val = MALI_HW_CORE_NO_COUNTER;
}
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)
{
if (0 == src_id)
{
if (MALI_TRUE != mali_pp_job_set_pp_counter_src0((u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_pp_job_set_pp_counter_src1((u32)val))
{
return 0;
}
}
}
}
*ppos += cnt;
return cnt;
}
static ssize_t pp_ppx_counter_src0_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_ppx_counter_srcx_read(filp, ubuf, cnt, ppos, 0);
}
static ssize_t pp_ppx_counter_src1_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_ppx_counter_srcx_read(filp, ubuf, cnt, ppos, 1);
}
static ssize_t pp_ppx_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_ppx_counter_srcx_write(filp, ubuf, cnt, ppos, 0);
}
static ssize_t pp_ppx_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_ppx_counter_srcx_write(filp, ubuf, cnt, ppos, 1);
}
static ssize_t pp_all_counter_src0_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_all_counter_srcx_write(filp, ubuf, cnt, ppos, 0);
}
static ssize_t pp_all_counter_src1_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
return pp_all_counter_srcx_write(filp, ubuf, cnt, ppos, 1);
}
static const struct file_operations pp_ppx_counter_src0_fops = {
.owner = THIS_MODULE,
.open = open_copy_private_data,
.read = pp_ppx_counter_src0_read,
.write = pp_ppx_counter_src0_write,
};
static const struct file_operations pp_ppx_counter_src1_fops = {
.owner = THIS_MODULE,
.open = open_copy_private_data,
.read = pp_ppx_counter_src1_read,
.write = pp_ppx_counter_src1_write,
};
static const struct file_operations pp_all_counter_src0_fops = {
.owner = THIS_MODULE,
.write = pp_all_counter_src0_write,
};
static const struct file_operations pp_all_counter_src1_fops = {
.owner = THIS_MODULE,
.write = pp_all_counter_src1_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 = sprintf(buf, "-1\n");
}
else
{
r = sprintf(buf, "%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 = strict_strtol(buf, 10, &val);
if (ret < 0)
{
return ret;
}
if (val < 0)
{
/* any negative input will disable counter */
val = MALI_HW_CORE_NO_COUNTER;
}
if (0 == src_id)
{
if (MALI_TRUE != mali_l2_cache_core_set_counter_src0(l2_core, (u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_l2_cache_core_set_counter_src1(l2_core, (u32)val))
{
return 0;
}
}
*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 = strict_strtol(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)
{
if (0 == src_id)
{
if (MALI_TRUE != mali_l2_cache_core_set_counter_src0(l2_cache, (u32)val))
{
return 0;
}
}
else
{
if (MALI_TRUE != mali_l2_cache_core_set_counter_src1(l2_cache, (u32)val))
{
return 0;
}
}
/* 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 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 = strict_strtoul(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_add();
}
else if (0 == val && MALI_TRUE == power_always_on_enabled)
{
power_always_on_enabled = MALI_FALSE;
_mali_osk_pm_dev_ref_dec();
}
*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])))
{
mali_pm_os_suspend();
}
else if (!strncmp(ubuf,mali_power_events[_MALI_DEVICE_RESUME],strlen(mali_power_events[_MALI_DEVICE_RESUME])))
{
mali_pm_os_resume();
}
else if (!strncmp(ubuf,mali_power_events[_MALI_DEVICE_DVFS_PAUSE],strlen(mali_power_events[_MALI_DEVICE_DVFS_PAUSE])))
{
mali_dev_pause();
}
else if (!strncmp(ubuf,mali_power_events[_MALI_DEVICE_DVFS_RESUME],strlen(mali_power_events[_MALI_DEVICE_DVFS_RESUME])))
{
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 = sprintf(buf, "%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 = strict_strtoul(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, &timestamp, &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, &timestamp, &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 ssize_t memory_used_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
char buf[64];
size_t r;
u32 mem = _mali_ukk_report_memory_usage();
r = snprintf(buf, 64, "%u\n", mem);
return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}
static const struct file_operations memory_usage_fops = {
.owner = THIS_MODULE,
.read = memory_used_read,
};
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 = strict_strtoul(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)
{
int 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 pmu_power_down_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
int ret;
char buffer[32];
unsigned long val;
struct mali_pmu_core *pmu;
_mali_osk_errcode_t err;
if (count >= sizeof(buffer))
{
return -ENOMEM;
}
if (copy_from_user(&buffer[0], buf, count))
{
return -EFAULT;
}
buffer[count] = '\0';
ret = strict_strtoul(&buffer[0], 10, &val);
if (0 != ret)
{
return -EINVAL;
}
pmu = mali_pmu_get_global_pmu_core();
MALI_DEBUG_ASSERT_POINTER(pmu);
err = mali_pmu_power_down(pmu, val);
if (_MALI_OSK_ERR_OK != err)
{
return -EINVAL;
}
*offp += count;
return count;
}
static ssize_t pmu_power_up_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
int ret;
char buffer[32];
unsigned long val;
struct mali_pmu_core *pmu;
_mali_osk_errcode_t err;
if (count >= sizeof(buffer))
{
return -ENOMEM;
}
if (copy_from_user(&buffer[0], buf, count))
{
return -EFAULT;
}
buffer[count] = '\0';
ret = strict_strtoul(&buffer[0], 10, &val);
if (0 != ret)
{
return -EINVAL;
}
pmu = mali_pmu_get_global_pmu_core();
MALI_DEBUG_ASSERT_POINTER(pmu);
err = mali_pmu_power_up(pmu, val);
if (_MALI_OSK_ERR_OK != err)
{
return -EINVAL;
}
*offp += count;
return count;
}
static const struct file_operations pmu_power_down_fops = {
.owner = THIS_MODULE,
.write = pmu_power_down_write,
};
static const struct file_operations pmu_power_up_fops = {
.owner = THIS_MODULE,
.write = pmu_power_up_write,
};
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 = strict_strtoul(&buffer[0], 10, &val);
if (0 != ret)
{
return -EINVAL;
}
ret = mali_perf_set_num_pp_cores(val);
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 = sprintf(buffer, "%u\n", mali_pp_scheduler_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,
};
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 = sprintf(buffer, "%u\n", mali_pp_scheduler_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 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 = sprintf(buffer, "Mali-200\n");
break;
case _MALI_PRODUCT_ID_MALI300:
r = sprintf(buffer, "Mali-300\n");
break;
case _MALI_PRODUCT_ID_MALI400:
r = sprintf(buffer, "Mali-400 MP\n");
break;
case _MALI_PRODUCT_ID_MALI450:
r = sprintf(buffer, "Mali-450 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,
};
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_pmu_dir;
struct dentry *mali_power_dir;
struct dentry *mali_gp_dir;
struct dentry *mali_pp_dir;
struct dentry *mali_l2_dir;
#if defined(CONFIG_MALI400_INTERNAL_PROFILING)
struct dentry *mali_profiling_dir;
#endif
debugfs_create_file("version", 0400, mali_debugfs_dir, NULL, &version_fops);
mali_pmu_dir = debugfs_create_dir("pmu", mali_debugfs_dir);
if (NULL != mali_pmu_dir)
{
debugfs_create_file("power_down", 0200, mali_pmu_dir, NULL, &pmu_power_down_fops);
debugfs_create_file("power_up", 0200, mali_pmu_dir, NULL, &pmu_power_up_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)
{
struct dentry *mali_gp_all_dir;
u32 num_groups;
int i;
mali_gp_all_dir = debugfs_create_dir("all", mali_gp_dir);
if (mali_gp_all_dir != NULL)
{
debugfs_create_file("counter_src0", 0200, mali_gp_all_dir, NULL, &gp_all_counter_src0_fops);
debugfs_create_file("counter_src1", 0200, mali_gp_all_dir, NULL, &gp_all_counter_src1_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_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("counter_src0", 0600, mali_gp_gpx_dir, gp_core, &gp_gpx_counter_src0_fops);
debugfs_create_file("counter_src1", 0600, mali_gp_gpx_dir, gp_core, &gp_gpx_counter_src1_fops);
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)
{
struct dentry *mali_pp_all_dir;
u32 num_groups;
int 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);
mali_pp_all_dir = debugfs_create_dir("all", mali_pp_dir);
if (mali_pp_all_dir != NULL)
{
debugfs_create_file("counter_src0", 0200, mali_pp_all_dir, NULL, &pp_all_counter_src0_fops);
debugfs_create_file("counter_src1", 0200, mali_pp_all_dir, NULL, &pp_all_counter_src1_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("counter_src0", 0600, mali_pp_ppx_dir, pp_core, &pp_ppx_counter_src0_fops);
debugfs_create_file("counter_src1", 0600, mali_pp_ppx_dir, pp_core, &pp_ppx_counter_src1_fops);
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("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("memory_usage", 0400, 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);
#if defined(CONFIG_MALI400_INTERNAL_PROFILING)
mali_profiling_dir = debugfs_create_dir("profiling", mali_debugfs_dir);
if (mali_profiling_dir != NULL)
{
struct dentry *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 (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 */