diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 34260bdea9bb..7256b2c3f391 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -118,6 +118,14 @@ config QCOM_RMTFS_MEM Say y here if you intend to boot the modem remoteproc. +config MSM_CORE_HANG_DETECT + tristate "MSM Core Hang Detection Support" + help + This enables the core hang detection module. It causes SoC + reset on core hang detection and collects the core context + for hang. By using sysfs entries core hang detection can be + enabled or disabled dynamically. + config QCOM_RPMH tristate "Qualcomm RPM-Hardened (RPMH) Communication" depends on ARCH_QCOM || COMPILE_TEST @@ -147,6 +155,14 @@ config QCOM_RPMPD value to RPM which then translates it into corresponding voltage for the voltage rail. +config QCOM_RUN_QUEUE_STATS + tristate "Enable collection and exporting of QTI Run Queue stats to userspace" + help + This option enables the driver to periodically collecting the statistics + of kernel run queue information and calculate the load of the system. + This information is exported to usespace via sysfs entries and userspace + algorithms uses info and decide when to turn on/off the cpu cores. + config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index a8efc014905c..91aed0ce3a02 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -30,3 +30,5 @@ obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) += kryo-l2-accessors.o obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o +obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o +obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o diff --git a/drivers/soc/qcom/core_hang_detect.c b/drivers/soc/qcom/core_hang_detect.c new file mode 100644 index 000000000000..f9ab465b7841 --- /dev/null +++ b/drivers/soc/qcom/core_hang_detect.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016, 2018, 2021 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* pmu event max value */ +#define PMU_EVENT_MAX 0x1F + +#define PMU_MUX_OFFSET 4 +#define PMU_MUX_MASK_BITS 0xF +#define ENABLE_OFFSET 1 +#define ENABLE_MASK_BITS 0x1 + +#define _VAL(z) (z##_MASK_BITS << z##_OFFSET) +#define _VALUE(_val, z) (_val<<(z##_OFFSET)) +#define _WRITE(x, y, z) (((~(_VAL(z))) & y) | _VALUE(x, z)) +#define _GET_BITS(_val, z) ((_val>>(z##_OFFSET)) & z##_MASK_BITS) + +#define MODULE_NAME "msm_hang_detect" +#define MAX_SYSFS_LEN 12 + +struct hang_detect { + phys_addr_t threshold[NR_CPUS]; + phys_addr_t config[NR_CPUS]; + uint32_t pmu_event_sel; + struct kobject kobj; +}; + +/* interface for exporting attributes */ +struct core_hang_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct attribute *attr, + char *buf); + size_t (*store)(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count); +}; + +#define CORE_HANG_ATTR(_name, _mode, _show, _store) \ + struct core_hang_attribute hang_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +#define to_core_hang_dev(kobj) \ + container_of(kobj, struct hang_detect, kobj) + +#define to_core_attr(_attr) \ + container_of(_attr, struct core_hang_attribute, attr) + +/* + * On the kernel command line specify core_hang_detect.enable=1 + * to enable the core hang detect module. + * By default core hang detect is turned on + */ +static int enable = 1; +module_param(enable, int, 0444); + +static ssize_t attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct core_hang_attribute *core_attr = to_core_attr(attr); + ssize_t ret = -EIO; + + if (core_attr->show) + ret = core_attr->show(kobj, attr, buf); + + return ret; +} + +static ssize_t attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct core_hang_attribute *core_attr = to_core_attr(attr); + ssize_t ret = -EIO; + + if (core_attr->store) + ret = core_attr->store(kobj, attr, buf, count); + + return ret; +} + +static const struct sysfs_ops core_sysfs_ops = { + .show = attr_show, + .store = attr_store, +}; + +static struct kobj_type core_ktype = { + .sysfs_ops = &core_sysfs_ops, +}; + +static ssize_t show_threshold(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct hang_detect *device = to_core_hang_dev(kobj); + u32 threshold_val; + + if (qcom_scm_io_readl(device->threshold[0], &threshold_val)) { + pr_err("%s: Failed to get threshold for core0\n", __func__); + return -EIO; + } + + return scnprintf(buf, MAX_SYSFS_LEN, "0x%x\n", threshold_val); +} + +static size_t store_threshold(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct hang_detect *hang_dev = to_core_hang_dev(kobj); + uint32_t threshold_val; + int ret, cpu; + + ret = kstrtouint(buf, 0, &threshold_val); + if (ret < 0) + return ret; + + if (threshold_val == 0) + return -EINVAL; + + for_each_possible_cpu(cpu) { + if (!hang_dev->threshold[cpu]) + continue; + + if (qcom_scm_io_writel(hang_dev->threshold[cpu], + threshold_val)) { + pr_err("%s: Failed to set threshold for core%d\n", + __func__, cpu); + return -EIO; + } + } + + return count; +} +CORE_HANG_ATTR(threshold, 0644, show_threshold, store_threshold); + +static ssize_t show_pmu_event_sel(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct hang_detect *hang_device = to_core_hang_dev(kobj); + + return scnprintf(buf, MAX_SYSFS_LEN, "0x%x\n", + hang_device->pmu_event_sel); +} + +static size_t store_pmu_event_sel(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + int cpu, ret; + uint32_t pmu_event_sel, reg_value; + struct hang_detect *hang_dev = to_core_hang_dev(kobj); + + ret = kstrtouint(buf, 0, &pmu_event_sel); + if (ret < 0) + return ret; + + if (pmu_event_sel > PMU_EVENT_MAX) + return -EINVAL; + + for_each_possible_cpu(cpu) { + if (!hang_dev->config[cpu]) + continue; + + if (qcom_scm_io_readl(hang_dev->config[cpu], ®_value)) { + pr_err("%s: Failed to get pmu config for core%d\n", + __func__, cpu); + return -EIO; + } + if (qcom_scm_io_writel(hang_dev->config[cpu], + _WRITE(pmu_event_sel, reg_value, PMU_MUX))) { + pr_err("%s: Failed to set pmu event for core%d\n", + __func__, cpu); + return -EIO; + } + } + + hang_dev->pmu_event_sel = pmu_event_sel; + return count; +} +CORE_HANG_ATTR(pmu_event_sel, 0644, show_pmu_event_sel, store_pmu_event_sel); + +static ssize_t show_enable(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + u32 enabled; + struct hang_detect *hang_dev = to_core_hang_dev(kobj); + + if (qcom_scm_io_readl(hang_dev->config[0], &enabled)) { + pr_err("%s: Failed to get pmu config for core0\n", __func__); + return -EIO; + } + enabled = _GET_BITS(enabled, ENABLE); + + return scnprintf(buf, MAX_SYSFS_LEN, "%u\n", enabled); +} + +static size_t store_enable(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct hang_detect *hang_dev = to_core_hang_dev(kobj); + uint32_t reg_value; + int cpu, ret; + bool enabled; + + ret = kstrtobool(buf, &enabled); + if (ret < 0) + return -EINVAL; + + for_each_possible_cpu(cpu) { + if (!hang_dev->config[cpu]) + continue; + + if (qcom_scm_io_readl(hang_dev->config[cpu], ®_value)) { + pr_err("%s: Failed to get pmu event for core%d\n", + __func__, cpu); + return -EIO; + } + if (qcom_scm_io_writel(hang_dev->config[cpu], + _WRITE(enabled, reg_value, ENABLE))) { + pr_err("%s: Failed to set enable for core%d\n", + __func__, cpu); + return -EIO; + } + } + + return count; +} +CORE_HANG_ATTR(enable, 0644, show_enable, store_enable); + +static struct attribute *hang_attrs[] = { + &hang_attr_threshold.attr, + &hang_attr_pmu_event_sel.attr, + &hang_attr_enable.attr, + NULL +}; + +static struct attribute_group hang_attr_group = { + .attrs = hang_attrs, +}; + +static const struct of_device_id msm_hang_detect_table[] = { + { .compatible = "qcom,core-hang-detect" }, + {}, +}; + +static int msm_hang_detect_probe(struct platform_device *pdev) +{ + struct device_node *cpu_node; + struct device_node *node = pdev->dev.of_node; + struct hang_detect *hang_det = NULL; + int cpu, ret, cpu_count = 0, num_regs = 0; + const char *name; + u32 *treg, *creg; + + if (!pdev->dev.of_node || !enable) + return -ENODEV; + + hang_det = devm_kzalloc(&pdev->dev, + sizeof(struct hang_detect), GFP_KERNEL); + + if (!hang_det) + return -ENOMEM; + + name = of_get_property(node, "label", NULL); + if (!name) { + pr_err("%s: Can't get label property\n", __func__); + return -EINVAL; + } + + num_regs = of_property_count_u32_elems(node, "qcom,threshold-arr"); + if (num_regs < 0) { + pr_err("%s: Can't get qcom,threshold-arr property\n", __func__); + return -EINVAL; + } + if (num_regs > num_possible_cpus()) { + pr_err("%s: expected <= %d elements in qcom,threshold-arr\n", + __func__, num_possible_cpus()); + return -EINVAL; + } + + ret = of_property_count_u32_elems(node, "qcom,config-arr"); + if (ret < 0) { + pr_err("%s: Can't get qcom,config-arr property\n", __func__); + return -EINVAL; + } + if (ret != num_regs) { + pr_err("%s: expected %d elements in qcom,config-arr\n", + __func__, num_regs); + return -EINVAL; + } + + treg = kzalloc(sizeof(*treg) * num_regs, GFP_KERNEL); + if (!treg) + return -ENOMEM; + ret = of_property_read_u32_array(node, "qcom,threshold-arr", + treg, num_regs); + if (ret) { + pr_err("%s: Can't get qcom,threshold-arr property\n", __func__); + kfree(treg); + return -EINVAL; + } + + creg = kzalloc(sizeof(*creg) * num_regs, GFP_KERNEL); + if (!creg) { + kfree(treg); + return -ENOMEM; + } + ret = of_property_read_u32_array(node, "qcom,config-arr", + creg, num_regs); + if (ret) { + pr_err("%s: Can't get qcom,config-arr property\n", __func__); + kfree(treg); + kfree(creg); + return -EINVAL; + } + + for_each_possible_cpu(cpu) { + cpu_node = of_get_cpu_node(cpu, NULL); + if (cpu_node != NULL) { + hang_det->threshold[cpu] = treg[cpu]; + hang_det->config[cpu] = creg[cpu]; + cpu_count++; + } + } + + kfree(treg); + kfree(creg); + + if (cpu_count == 0) { + pr_err("%s: Unable to find any CPU\n", __func__); + return -EINVAL; + } + + ret = kobject_init_and_add(&hang_det->kobj, &core_ktype, + &cpu_subsys.dev_root->kobj, "%s_%s", + "hang_detect", name); + if (ret) { + pr_err("%s: Error in creation kobject_add\n", __func__); + goto out_put_kobj; + } + + ret = sysfs_create_group(&hang_det->kobj, &hang_attr_group); + if (ret) { + pr_err("%s: Error in creation sysfs_create_group\n", __func__); + goto out_del_kobj; + } + + platform_set_drvdata(pdev, hang_det); + return 0; + +out_del_kobj: + kobject_del(&hang_det->kobj); +out_put_kobj: + kobject_put(&hang_det->kobj); + + return ret; +} + +static int msm_hang_detect_remove(struct platform_device *pdev) +{ + struct hang_detect *hang_det = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + sysfs_remove_group(&hang_det->kobj, &hang_attr_group); + kobject_del(&hang_det->kobj); + kobject_put(&hang_det->kobj); + return 0; +} + +static struct platform_driver msm_hang_detect_driver = { + .probe = msm_hang_detect_probe, + .remove = msm_hang_detect_remove, + .driver = { + .name = MODULE_NAME, + .of_match_table = msm_hang_detect_table, + }, +}; + +module_platform_driver(msm_hang_detect_driver); + +MODULE_DESCRIPTION("MSM Core Hang Detect Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rq_stats.c b/drivers/soc/qcom/rq_stats.c new file mode 100644 index 000000000000..ffb9b2354669 --- /dev/null +++ b/drivers/soc/qcom/rq_stats.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2015, 2017, 2019-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define MAX_LONG_SIZE 24 +#define DEFAULT_DEF_TIMER_JIFFIES 5 + +struct rq_data { + unsigned long def_timer_jiffies; + unsigned long def_timer_last_jiffy; + int64_t def_start_time; + struct attribute_group *attr_group; + struct kobject *kobj; + struct work_struct def_timer_work; +}; + +static struct rq_data rq_info; +static struct workqueue_struct *rq_wq; +spinlock_t rq_lock; + +static void wakeup_user(void *ignore, void *extra) +{ + unsigned long jiffy_gap; + + jiffy_gap = jiffies - rq_info.def_timer_last_jiffy; + + if (jiffy_gap >= rq_info.def_timer_jiffies) { + rq_info.def_timer_last_jiffy = jiffies; + queue_work(rq_wq, &rq_info.def_timer_work); + } +} + +static void def_work_fn(struct work_struct *work) +{ + /* Notify polling threads on change of value */ + sysfs_notify(rq_info.kobj, NULL, "def_timer_ms"); +} + +static ssize_t show_def_timer_ms(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int64_t diff; + unsigned int udiff; + + diff = ktime_to_ns(ktime_get()) - rq_info.def_start_time; + do_div(diff, 1000 * 1000); + udiff = (unsigned int) diff; + + return scnprintf(buf, MAX_LONG_SIZE, "%u\n", udiff); +} + +static ssize_t store_def_timer_ms(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + rq_info.def_timer_jiffies = msecs_to_jiffies(val); + + rq_info.def_start_time = ktime_to_ns(ktime_get()); + + return count; +} + +static struct kobj_attribute def_timer_ms_attr = + __ATTR(def_timer_ms, 0600, show_def_timer_ms, + store_def_timer_ms); + +static struct attribute *rq_attrs[] = { + &def_timer_ms_attr.attr, + NULL, +}; + +static struct attribute_group rq_attr_group = { + .attrs = rq_attrs, +}; + +static int init_rq_attribs(void) +{ + int err; + + rq_info.attr_group = &rq_attr_group; + + /* Create /sys/devices/system/cpu/cpu0/rq-stats/... */ + rq_info.kobj = kobject_create_and_add("rq-stats", + &get_cpu_device(0)->kobj); + if (!rq_info.kobj) + return -ENOMEM; + + err = sysfs_create_group(rq_info.kobj, rq_info.attr_group); + if (err) + kobject_put(rq_info.kobj); + else + kobject_uevent(rq_info.kobj, KOBJ_ADD); + + return err; +} + +static int __init msm_rq_stats_init(void) +{ + int ret; + +#ifndef CONFIG_SMP + /* Bail out if this is not an SMP Target */ + return -EPERM; +#endif + rq_wq = create_singlethread_workqueue("rq_stats"); + WARN_ON(!rq_wq); + INIT_WORK(&rq_info.def_timer_work, def_work_fn); + spin_lock_init(&rq_lock); + rq_info.def_timer_jiffies = DEFAULT_DEF_TIMER_JIFFIES; + rq_info.def_timer_last_jiffy = 0; + ret = init_rq_attribs(); + + register_trace_android_vh_jiffies_update(wakeup_user, NULL); + + return ret; +} +late_initcall(msm_rq_stats_init); + +static __exit void msm_rq_stats_exit(void) +{ + destroy_workqueue(rq_wq); + kobject_del(rq_info.kobj); +} +module_exit(msm_rq_stats_exit); + +MODULE_DESCRIPTION("QCOM Run Queue Stats"); +MODULE_LICENSE("GPL v2");