soc: qcom: qcom_stats: Add ioctl support for stats

Add ioctl support by which userspace can know the various stats.

Change-Id: I916232f7eeb0efd7ca5a8d99afff547fdb9ad594
Signed-off-by: Maulik Shah <quic_mkshah@quicinc.com>
This commit is contained in:
Maulik Shah 2022-09-15 11:53:12 +05:30
parent 6d0045aaf5
commit 321af67c34

View File

@ -4,14 +4,18 @@
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/soc/qcom/smem.h>
#include <clocksource/arm_arch_timer.h>
@ -35,6 +39,45 @@
#define DDR_STATS_COUNT_ADDR 0x4
#define DDR_STATS_DURATION_ADDR 0x8
#define STATS_BASEMINOR 0
#define STATS_MAX_MINOR 1
#define STATS_DEVICE_NAME "stats"
#define SUBSYSTEM_STATS_MAGIC_NUM (0x9d)
#define SUBSYSTEM_STATS_OTHERS_NUM (-2)
#define APSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 0, \
struct sleep_stats *)
#define MODEM_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 1, \
struct sleep_stats *)
#define WPSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 2, \
struct sleep_stats *)
#define ADSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 3, \
struct sleep_stats *)
#define ADSP_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 4, \
struct sleep_stats *)
#define CDSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 5, \
struct sleep_stats *)
#define SLPI_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 6, \
struct sleep_stats *)
#define GPU_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 7, \
struct sleep_stats *)
#define DISPLAY_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 8, \
struct sleep_stats *)
#define SLPI_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 9, \
struct sleep_stats *)
#define AOSD_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 10, \
struct sleep_stats *)
#define CXSD_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 11, \
struct sleep_stats *)
#define DDR_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 12, \
struct sleep_stats *)
#define DDR_STATS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 13, \
struct sleep_stats *)
struct subsystem_data {
const char *name;
u32 smem_item;
@ -63,17 +106,23 @@ struct stats_config {
bool subsystem_stats_in_smem;
};
struct ddr_stats_entry {
uint32_t name;
uint32_t count;
uint64_t duration;
};
struct stats_data {
bool appended_stats_avail;
void __iomem *base;
};
struct stats_drvdata {
void __iomem *base;
const struct stats_config *config;
struct stats_data *d;
struct dentry *root;
dev_t dev_no;
struct class *stats_class;
struct device *stats_device;
struct cdev stats_cdev;
struct mutex lock;
};
struct sleep_stats {
u32 stat_type;
u32 count;
@ -87,6 +136,191 @@ struct appended_stats {
u32 reserved[3];
};
static inline int qcom_stats_copy_to_user(unsigned long arg, struct sleep_stats *stats,
unsigned long size)
{
return copy_to_user((void __user *)arg, stats, size);
}
static inline void qcom_stats_update_accumulated_duration(struct sleep_stats *stats)
{
/*
* If a subsystem is in sleep when reading the sleep stats from SMEM
* adjust the accumulated sleep duration to show actual sleep time.
* This ensures that the displayed stats are real when used for
* the purpose of computing battery utilization.
*/
if (stats->last_entered_at > stats->last_exited_at)
stats->accumulated += (__arch_counter_get_cntvct() - stats->last_entered_at);
}
static inline void qcom_stats_copy(struct sleep_stats *src, struct sleep_stats *dst)
{
dst->stat_type = src->stat_type;
dst->count = src->count;
dst->last_entered_at = src->last_entered_at;
dst->last_exited_at = src->last_exited_at;
dst->accumulated = src->accumulated;
}
static u64 qcom_stats_fill_ddr_stats(void __iomem *reg, struct sleep_stats *data, u32 *entry_count)
{
u64 accumulated_duration = 0;
int i;
*entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR);
if (*entry_count > DDR_STATS_MAX_NUM_MODES) {
pr_err("Invalid entry count\n");
return 0;
}
reg += DDR_STATS_NUM_MODES_ADDR + 0x4;
for (i = 0; i < *entry_count; i++) {
data[i].stat_type = readl_relaxed(reg + DDR_STATS_NAME_ADDR);
data[i].count = readl_relaxed(reg + DDR_STATS_COUNT_ADDR);
data[i].last_entered_at = 0xDEADDEAD;
data[i].last_exited_at = 0xDEADDEAD;
data[i].accumulated = readq_relaxed(reg + DDR_STATS_DURATION_ADDR);
accumulated_duration += data[i].accumulated;
reg += sizeof(struct sleep_stats) - 2 * sizeof(u64);
}
return accumulated_duration;
}
static int qcom_stats_device_open(struct inode *inode, struct file *file)
{
struct stats_drvdata *drv = NULL;
if (!inode || !inode->i_cdev || !file)
return -EINVAL;
drv = container_of(inode->i_cdev, struct stats_drvdata, stats_cdev);
file->private_data = drv;
return 0;
}
static long qcom_stats_device_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct stats_drvdata *drv = file->private_data;
const struct subsystem_data *subsystem = NULL;
struct sleep_stats *stat;
struct sleep_stats *temp;
void __iomem *reg = NULL;
unsigned long size = sizeof(struct sleep_stats);
u32 stats_id, entry_count;
int ret;
mutex_lock(&drv->lock);
if (cmd != DDR_STATS_IOCTL)
stat = kzalloc(sizeof(struct sleep_stats), GFP_KERNEL);
else
stat = kcalloc(DDR_STATS_MAX_NUM_MODES, sizeof(struct sleep_stats), GFP_KERNEL);
if (!stat) {
mutex_unlock(&drv->lock);
return -ENOMEM;
}
switch (cmd) {
case MODEM_IOCTL:
subsystem = &subsystems[0];
break;
case WPSS_IOCTL:
subsystem = &subsystems[1];
break;
case ADSP_IOCTL:
subsystem = &subsystems[2];
break;
case CDSP_IOCTL:
subsystem = &subsystems[3];
break;
case SLPI_IOCTL:
subsystem = &subsystems[4];
break;
case GPU_IOCTL:
subsystem = &subsystems[5];
break;
case DISPLAY_IOCTL:
subsystem = &subsystems[6];
break;
case ADSP_ISLAND_IOCTL:
subsystem = &subsystems[7];
break;
case SLPI_ISLAND_IOCTL:
subsystem = &subsystems[8];
break;
case APSS_IOCTL:
subsystem = &subsystems[9];
break;
case AOSD_IOCTL:
stats_id = 0;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case CXSD_IOCTL:
stats_id = 1;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case DDR_IOCTL:
stats_id = 2;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case DDR_STATS_IOCTL:
break;
default:
pr_err("Incorrect command error\n");
ret = -EINVAL;
goto exit;
}
if (subsystem) {
/* Items are allocated lazily, so lookup pointer each time */
temp = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL);
if (IS_ERR(temp)) {
ret = -EIO;
goto exit;
}
qcom_stats_copy(temp, stat);
qcom_stats_update_accumulated_duration(stat);
ret = qcom_stats_copy_to_user(arg, stat, size);
} else if (reg) {
memcpy_fromio(stat, reg, sizeof(*stat));
qcom_stats_update_accumulated_duration(stat);
ret = qcom_stats_copy_to_user(arg, stat, size);
} else {
int modes = DDR_STATS_MAX_NUM_MODES;
reg = drv->base + drv->config->ddr_stats_offset;
qcom_stats_fill_ddr_stats(reg, stat, &entry_count);
ret = qcom_stats_copy_to_user(arg, stat, modes * size);
}
exit:
kfree(stat);
mutex_unlock(&drv->lock);
return ret;
}
static const struct file_operations qcom_stats_device_fops = {
.owner = THIS_MODULE,
.open = qcom_stats_device_open,
.unlocked_ioctl = qcom_stats_device_ioctl,
};
static void qcom_print_stats(struct seq_file *s, const struct sleep_stats *stat)
{
u64 accumulated = stat->accumulated;
@ -137,62 +371,46 @@ static int qcom_soc_sleep_stats_show(struct seq_file *s, void *unused)
return 0;
}
static void print_ddr_stats(struct seq_file *s, int *count,
struct ddr_stats_entry *data, u64 accumulated_duration)
static void print_ddr_stats(struct seq_file *s, int *count,
struct sleep_stats *data, u64 accumulated_duration)
{
u32 cp_idx = 0;
u32 name, duration;
if (accumulated_duration)
duration = (data->duration * 100) / accumulated_duration;
duration = (data->accumulated * 100) / accumulated_duration;
name = (data->name >> 8) & 0xFF;
name = (data->stat_type >> 8) & 0xFF;
if (name == 0x0) {
name = (data->name) & 0xFF;
name = (data->stat_type) & 0xFF;
*count = *count + 1;
seq_printf(s,
"LPM %d:\tName:0x%x\tcount:%u\tDuration (ticks):%ld (~%d%%)\n",
*count, name, data->count, data->duration, duration);
*count, name, data->count, data->accumulated, duration);
} else if (name == 0x1) {
cp_idx = data->name & 0x1F;
name = data->name >> 16;
cp_idx = data->stat_type & 0x1F;
name = data->stat_type >> 16;
if (!name || !data->count)
return;
seq_printf(s,
"Freq %dMhz:\tCP IDX:%u\tcount:%u\tDuration (ticks):%ld (~%d%%)\n",
name, cp_idx, data->count, data->duration, duration);
name, cp_idx, data->count, data->accumulated, duration);
}
}
static int ddr_stats_show(struct seq_file *s, void *d)
{
struct ddr_stats_entry data[DDR_STATS_MAX_NUM_MODES];
struct sleep_stats data[DDR_STATS_MAX_NUM_MODES];
void __iomem *reg = s->private;
u32 entry_count;
u64 accumulated_duration = 0;
int i, lpm_count = 0;
entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR);
if (entry_count > DDR_STATS_MAX_NUM_MODES) {
pr_err("Invalid entry count\n");
accumulated_duration = qcom_stats_fill_ddr_stats(reg, data, &entry_count);
if (!accumulated_duration)
return 0;
}
reg += DDR_STATS_NUM_MODES_ADDR + 0x4;
for (i = 0; i < entry_count; i++) {
data[i].count = readl_relaxed(reg + DDR_STATS_COUNT_ADDR);
data[i].name = readl_relaxed(reg + DDR_STATS_NAME_ADDR);
data[i].duration = readq_relaxed(reg + DDR_STATS_DURATION_ADDR);
accumulated_duration += data[i].duration;
reg += sizeof(struct ddr_stats_entry);
}
for (i = 0; i < entry_count; i++)
print_ddr_stats(s, &lpm_count, &data[i], accumulated_duration);
@ -204,6 +422,41 @@ DEFINE_SHOW_ATTRIBUTE(qcom_soc_sleep_stats);
DEFINE_SHOW_ATTRIBUTE(qcom_subsystem_sleep_stats);
DEFINE_SHOW_ATTRIBUTE(ddr_stats);
static int qcom_create_stats_device(struct stats_drvdata *drv)
{
int ret;
ret = alloc_chrdev_region(&drv->dev_no, STATS_BASEMINOR, STATS_MAX_MINOR,
STATS_DEVICE_NAME);
if (ret)
return ret;
cdev_init(&drv->stats_cdev, &qcom_stats_device_fops);
ret = cdev_add(&drv->stats_cdev, drv->dev_no, 1);
if (ret) {
unregister_chrdev_region(drv->dev_no, 1);
return ret;
}
drv->stats_class = class_create(THIS_MODULE, STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(drv->stats_class)) {
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
return PTR_ERR(drv->stats_class);
}
drv->stats_device = device_create(drv->stats_class, NULL, drv->dev_no, NULL,
STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(drv->stats_device)) {
class_destroy(drv->stats_class);
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
return PTR_ERR(drv->stats_device);
}
return ret;
}
static void qcom_create_ddr_stat_files(struct dentry *root, void __iomem *reg,
struct stats_data *d,
const struct stats_config *config)
@ -301,7 +554,9 @@ static int qcom_stats_probe(struct platform_device *pdev)
struct dentry *root;
const struct stats_config *config;
struct stats_data *d;
struct stats_drvdata *drv;
int i;
int ret;
config = device_get_match_data(&pdev->dev);
if (!config)
@ -325,16 +580,35 @@ static int qcom_stats_probe(struct platform_device *pdev)
qcom_create_soc_sleep_stat_files(root, reg, d, config);
qcom_create_ddr_stat_files(root, reg, d, config);
platform_set_drvdata(pdev, root);
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
drv->d = d;
drv->config = config;
drv->base = reg;
drv->root = root;
mutex_init(&drv->lock);
ret = qcom_create_stats_device(drv);
if (ret)
return ret;
platform_set_drvdata(pdev, drv);
return 0;
}
static int qcom_stats_remove(struct platform_device *pdev)
{
struct dentry *root = platform_get_drvdata(pdev);
struct stats_drvdata *drv = platform_get_drvdata(pdev);
debugfs_remove_recursive(root);
device_destroy(drv->stats_class, drv->dev_no);
class_destroy(drv->stats_class);
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
debugfs_remove_recursive(drv->root);
return 0;
}