summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
authorPrasad Sodagudi <psodagud@codeaurora.org>2015-10-20 23:52:24 +0530
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:08:41 -0700
commit6fb5665397398c7794c49783234d0af7f06199ec (patch)
treefd669761af4ba7aacf7fb429996a0cb271473fb6 /drivers/soc
parente160d231b9990823714923a82b9187763ca069e1 (diff)
soc: qcom: core_hang: Add core hang driver
Add driver for core hang detection. This drivers provides sysfs entries to configure threshold, pmu event select and enable parameters for core hang detection feature. Change-Id: Ieb19b309238fc11f1a631842564a7e43b16651dc Signed-off-by: Prasad Sodagudi <psodagud@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/core_hang_detect.c340
3 files changed, 348 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index b2b7a54dc78b..590b83e62cc2 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -331,6 +331,13 @@ config MSM_GLADIATOR_HANG_DETECT
gladiator hang detection and collects the context for the
gladiator hang.
+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.
+
endif # ARCH_QCOM
config MSM_SUBSYSTEM_RESTART
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index f7764f7263fd..c722ee78edbc 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -52,4 +52,5 @@ obj-$(CONFIG_MSM_SECURE_BUFFER) += secure_buffer.o
obj-$(CONFIG_MSM_MPM_OF) += mpm-of.o
obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o
obj-$(CONFIG_MSM_GLADIATOR_ERP) += gladiator_erp.o
+obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_MSM_GLADIATOR_HANG_DETECT) += gladiator_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..40ddbc447936
--- /dev/null
+++ b/drivers/soc/qcom/core_hang_detect.c
@@ -0,0 +1,340 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/cpu.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <soc/qcom/scm.h>
+#include <linux/platform_device.h>
+
+/* pmu event min and max value */
+#define PMU_EVENT_MIN 0
+#define PMU_EVENT_MAX 7
+
+#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 MODULE_NAME "msm_hang_detect"
+
+struct hang_detect {
+ phys_addr_t threshold[NR_CPUS];
+ phys_addr_t config[NR_CPUS];
+ uint32_t enabled;
+ uint32_t pmu_event_sel;
+ uint32_t threshold_val;
+ 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);
+
+ return snprintf(buf, sizeof(device->threshold_val),
+ "%u\n", device->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 (scm_io_write(hang_dev->threshold[cpu], threshold_val)) {
+ pr_err("%s: Failed to set threshold for core%d\n",
+ __func__, cpu);
+ return -EIO;
+ }
+ }
+
+ hang_dev->threshold_val = threshold_val;
+ 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 snprintf(buf, sizeof(hang_device->pmu_event_sel),
+ "%u\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_MIN || pmu_event_sel > PMU_EVENT_MAX)
+ return -EINVAL;
+
+ for_each_possible_cpu(cpu) {
+ if (!hang_dev->config[cpu])
+ continue;
+
+ reg_value = scm_io_read(hang_dev->config[cpu]);
+ if (scm_io_write(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)
+{
+ struct hang_detect *hang_device = to_core_hang_dev(kobj);
+
+ return snprintf(buf, sizeof(hang_device->enabled),
+ "%u\n", hang_device->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 enabled, reg_value;
+ int cpu, ret;
+
+ ret = kstrtouint(buf, 0, &enabled);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (!(enabled == 0 || enabled == 1))
+ return -EINVAL;
+
+ for_each_possible_cpu(cpu) {
+ if (!hang_dev->config[cpu])
+ continue;
+
+ reg_value = scm_io_read(hang_dev->config[cpu]);
+ if (scm_io_write(hang_dev->config[cpu],
+ _WRITE(enabled, reg_value, ENABLE))) {
+ pr_err("%s: Failed to set enable for core%d\n",
+ __func__, cpu);
+ return -EIO;
+ }
+ }
+
+ hang_dev->enabled = enabled;
+ 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;
+ u32 treg[NR_CPUS], creg[NR_CPUS];
+
+ if (!pdev->dev.of_node || !enable)
+ return -ENODEV;
+
+ hang_det = devm_kzalloc(&pdev->dev,
+ sizeof(struct hang_detect), GFP_KERNEL);
+
+ if (!hang_det) {
+ pr_err("Can't allocate hang_detect memory\n");
+ return -ENOMEM;
+ }
+
+ ret = of_property_read_u32_array(node, "qcom,threshold-arr",
+ treg, num_possible_cpus());
+ if (ret) {
+ pr_err("Can't get threshold-arr property\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(node, "qcom,config-arr",
+ creg, num_possible_cpus());
+ if (ret) {
+ pr_err("Can't get config-arr property\n");
+ return -EINVAL;
+ }
+
+ for_each_possible_cpu(cpu) {
+ cpu_node = of_get_cpu_node(cpu, NULL);
+ if (cpu_node == NULL)
+ continue;
+ else {
+ hang_det->threshold[cpu] = treg[cpu];
+ hang_det->config[cpu] = creg[cpu];
+ cpu_count++;
+ }
+ }
+
+ if (cpu_count == 0) {
+ pr_err("%s:core-hang-arr prop is missing %d\n" , __func__, ret);
+ return -EINVAL;
+ }
+
+ ret = kobject_init_and_add(&hang_det->kobj, &core_ktype,
+ &cpu_subsys.dev_root->kobj, "%s", "hang_detect");
+ 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,
+ .owner = THIS_MODULE,
+ .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");