From a4b96484d08623ae8acc8edd1a3b6c3bd111c753 Mon Sep 17 00:00:00 2001 From: Tushar Nimkar Date: Mon, 1 Mar 2021 16:04:59 +0530 Subject: [PATCH] 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 --- drivers/soc/qcom/Kconfig | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/subsystem_sleep_stats.c | 263 +++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 drivers/soc/qcom/subsystem_sleep_stats.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9e4b3e29bf9d..ee87b7876273 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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" diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index d37792330201..9bb4144fad38 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/subsystem_sleep_stats.c b/drivers/soc/qcom/subsystem_sleep_stats.c new file mode 100644 index 000000000000..e613317bd8f1 --- /dev/null +++ b/drivers/soc/qcom/subsystem_sleep_stats.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");