summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt54
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/core_hang_detect.c340
4 files changed, 402 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt b/Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt
new file mode 100644
index 000000000000..7f23d9a3c6e8
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt
@@ -0,0 +1,54 @@
+* Qualcomm MSM Core Hang Detection
+
+Core Hang Detection provides the three sysfs entries for configuring
+threshold, PMU event mux select and to enable hang detection.
+
+If core is hung for threshold time (value X 10ns) and no
+heart beat event from pmu to core hang monitor detection, core hang
+interrupt would be generated to reset the SOC via secure watchdog
+to collect all cores context.
+
+PMU event mux select can be programmed to one of the supported
+events, for example-
+1) Load Instruction executed,
+2) Store Instructions executed
+3) Instruction architecturally executed and etc.
+
+Writing 1 into enable sysfs entry, enables core hang detection and
+if there is no selected PMU mux event for 10ns core hang counter
+gets incremented. Once counter reaches the programmed threshold value,
+core hang interrupts generated to reset the SOC.
+
+
+The device tree parameters for the core hang detection are:
+
+Required properties:
+
+- compatible : "qcom,core-hang-detect"
+- qcom,threshold-arr :
+ Array of APCS_ALIAS*_CORE_HANG_THRESHOLD register address
+ for each core.
+- qcom,config-arr :
+ Array of APCS_ALIAS*_CORE_HANG_CONFIG register address
+ for each core.
+
+Optional properties:
+
+Example:
+ For msm8937:
+ qcom,chd {
+ compatible = "qcom,core-hang-detect";
+ qcom,threshold-arr = <0xB088094 0xB098094 0xB0A8094
+ 0xB0B8094 0xB188094 0xB198094 0xB1A8094 0xB1B8094>;
+ qcom,config-arr = <0xB08809C 0xB09809C 0xB0A809C
+ 0xB0B809C 0xB18809C 0xB19809C 0xB1A809C 0xB1B809C>;
+ };
+
+ For msmtitanium:
+ qcom,chd {
+ compatible = "qcom,core-hang-detect";
+ qcom,threshold-arr = <0xB1880B0 0xB1980B0 0xB1A80B0
+ 0xB1B80B0 0xB0880B0 0xB0980B0 0xB0A80B0 0xB0B80B0>;
+ qcom,config-arr = <0xB1880B8 0xB1980B8 0xB1A80B8
+ 0xB1B80B8 0xB0880B8 0xB0980B8 0xB0A80B8 0xB0B80B8>;
+ };
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");