drivers: qcom: Add new subsystem stats driver

This change adds subsystem sleep stats driver.
When userspace issues ioctl to read stats data, the driver
reads from smem and same passes to userspace.

Change-Id: Ia8ddb9096eb5f387922e8a7c0ad48ed71b736a85
Signed-off-by: Tushar Nimkar <tnimkar@codeaurora.org>
This commit is contained in:
Tushar Nimkar 2021-03-01 16:04:59 +05:30
commit a4b96484d0
3 changed files with 272 additions and 0 deletions

View file

@ -898,6 +898,14 @@ config QTI_RPM_STATS_LOG
the low power modes that RPM enters. The drivers outputs the message
via a sysfs node.
config QCOM_SUBSYSTEM_SLEEP_STATS
tristate "Qualcomm Technologies, Inc. Subsystem sleep stats driver"
depends on QCOM_SMEM
help
This driver is IOCTL implementation to get the subsystem stats data
from SMEM. Stats information such as sleep count, last entered at,
last exited at and accumulated duration can be read from
userspace with ioctl.
config QTI_DDR_STATS_LOG
tristate "Qualcomm Technologies Inc (QTI) DDR Stats Driver"

View file

@ -28,6 +28,7 @@ obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) += smem.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SUBSYSTEM_SLEEP_STATS) += subsystem_sleep_stats.o
obj-$(CONFIG_QSEE_IPC_IRQ_BRIDGE) += qsee_ipc_irq_bridge.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o

View file

@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
#include <linux/uaccess.h>
#define STATS_BASEMINOR 0
#define STATS_MAX_MINOR 1
#define STATS_DEVICE_NAME "stats"
#define SUBSYSTEM_STATS_MAGIC_NUM (0x9d)
#define APSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 0, \
struct subsystem_stats *)
#define MODEM_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 1, \
struct subsystem_stats *)
#define WPSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 2, \
struct subsystem_stats *)
#define ADSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 3, \
struct subsystem_stats *)
#define ADSP_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 4, \
struct subsystem_stats *)
#define CDSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 5, \
struct subsystem_stats *)
#define SLPI_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 6, \
struct subsystem_stats *)
#define GPU_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 7, \
struct subsystem_stats *)
#define DISPLAY_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 8, \
struct subsystem_stats *)
#define SLPI_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 9, \
struct subsystem_stats *)
struct subsystem_stats {
u32 version;
u32 count;
u64 last_entered_at;
u64 last_exited_at;
u64 accumulated;
};
enum subsystem_smem_id {
MPSS = 605,
ADSP,
CDSP,
SLPI,
GPU,
DISPLAY,
SLPI_ISLAND = 613,
APSS = 631,
};
enum subsystem_pid {
PID_APSS = 0,
PID_MPSS = 1,
PID_ADSP = 2,
PID_SLPI = 3,
PID_CDSP = 5,
PID_WPSS = 13,
PID_GPU = PID_APSS,
PID_DISPLAY = PID_APSS,
};
struct sleep_stats_data {
dev_t dev_no;
struct class *stats_class;
struct device *stats_device;
struct cdev stats_cdev;
};
static DEFINE_MUTEX(sleep_stats_mutex);
static long stats_data_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct subsystem_stats *temp, *subsystem_stats_data;
int ret = -ENOMEM;
unsigned int pid, smem_item;
mutex_lock(&sleep_stats_mutex);
temp = kzalloc(sizeof(struct subsystem_stats), GFP_KERNEL);
if (!temp)
goto out_unlock;
switch (cmd) {
case APSS_IOCTL:
pid = QCOM_SMEM_HOST_ANY;
smem_item = APSS;
break;
case MODEM_IOCTL:
pid = PID_MPSS;
smem_item = MPSS;
break;
case WPSS_IOCTL:
pid = PID_MPSS;
smem_item = MPSS;
break;
case ADSP_IOCTL:
pid = PID_ADSP;
smem_item = ADSP;
break;
case ADSP_ISLAND_IOCTL:
pid = PID_ADSP;
smem_item = SLPI_ISLAND;
break;
case CDSP_IOCTL:
pid = PID_CDSP;
smem_item = CDSP;
break;
case SLPI_IOCTL:
pid = PID_SLPI;
smem_item = SLPI;
break;
case GPU_IOCTL:
pid = PID_GPU;
smem_item = GPU;
break;
case DISPLAY_IOCTL:
pid = PID_DISPLAY;
smem_item = DISPLAY;
break;
case SLPI_ISLAND_IOCTL:
pid = PID_SLPI;
smem_item = SLPI_ISLAND;
break;
default:
pr_err("Incorrect command error\n");
ret = -EINVAL;
goto out_free;
}
subsystem_stats_data = qcom_smem_get(pid, smem_item, NULL);
if (IS_ERR(subsystem_stats_data)) {
ret = -ENODEV;
goto out_free;
}
temp->version = subsystem_stats_data->version;
temp->count = subsystem_stats_data->count;
temp->last_entered_at = subsystem_stats_data->last_entered_at;
temp->last_exited_at = subsystem_stats_data->last_exited_at;
temp->accumulated = subsystem_stats_data->accumulated;
/*
* 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 (temp->last_entered_at > temp->last_exited_at) {
temp->accumulated +=
(__arch_counter_get_cntvct()
- temp->last_entered_at);
}
ret = copy_to_user((void __user *)arg, temp, sizeof(struct subsystem_stats));
kfree(temp);
mutex_unlock(&sleep_stats_mutex);
return ret;
out_free:
kfree(temp);
out_unlock:
mutex_unlock(&sleep_stats_mutex);
return ret;
}
static const struct file_operations stats_data_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.unlocked_ioctl = stats_data_ioctl,
};
static int subsystem_stats_probe(struct platform_device *pdev)
{
struct sleep_stats_data *stats_data;
int ret = -ENOMEM;
stats_data = devm_kzalloc(&pdev->dev, sizeof(struct sleep_stats_data), GFP_KERNEL);
if (!stats_data)
return ret;
ret = alloc_chrdev_region(&stats_data->dev_no, STATS_BASEMINOR,
STATS_MAX_MINOR, STATS_DEVICE_NAME);
if (ret)
goto fail_alloc_chrdev;
cdev_init(&stats_data->stats_cdev, &stats_data_fops);
ret = cdev_add(&stats_data->stats_cdev, stats_data->dev_no, 1);
if (ret)
goto fail_cdev_add;
stats_data->stats_class = class_create(THIS_MODULE, STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(stats_data->stats_class)) {
ret = -EINVAL;
goto fail_class_create;
}
stats_data->stats_device = device_create(stats_data->stats_class, NULL,
stats_data->dev_no, NULL,
STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(stats_data->stats_device)) {
ret = -EINVAL;
goto fail_device_create;
}
platform_set_drvdata(pdev, stats_data);
return 0;
fail_device_create:
class_destroy(stats_data->stats_class);
fail_class_create:
cdev_del(&stats_data->stats_cdev);
fail_cdev_add:
unregister_chrdev_region(stats_data->dev_no, 1);
fail_alloc_chrdev:
return ret;
}
static int subsystem_stats_remove(struct platform_device *pdev)
{
struct sleep_stats_data *stats_data;
stats_data = platform_get_drvdata(pdev);
if (!stats_data)
return 0;
class_destroy(stats_data->stats_class);
cdev_del(&stats_data->stats_cdev);
unregister_chrdev_region(stats_data->dev_no, 1);
return 0;
}
static const struct of_device_id subsystem_stats_table[] = {
{.compatible = "qcom,subsystem-sleep-stats"},
{},
};
static struct platform_driver subsystem_sleep_stats_driver = {
.probe = subsystem_stats_probe,
.remove = subsystem_stats_remove,
.driver = {
.name = "subsystem_sleep_stats",
.of_match_table = subsystem_stats_table,
},
};
module_platform_driver(subsystem_sleep_stats_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. subsystem sleep stats driver");