Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5257427 authored by Rishabh Bhatnagar's avatar Rishabh Bhatnagar Committed by Venkata Narendra Kumar Gutta
Browse files

soc: qcom: core_hang: Add snapshot of core hang driver



This is a snapshot of the core hang driver as of msm-4.14 commit
<02ff79e5>.(Merge "perf: don't zombie certain events in
perf_event_release_kernel").

Change-Id: Id5627736553d1ea11a5a8d76f4ce27d6ef3af118
Signed-off-by: default avatarRishabh Bhatnagar <rishabhb@codeaurora.org>
Signed-off-by: default avatarVenkata Narendra Kumar Gutta <vnkgutta@codeaurora.org>
parent 7efe19f7
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
* QTI 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"
- label: unique name used to created sysfs entry
- 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>;
	};
+9 −0
Original line number Diff line number Diff line
@@ -231,4 +231,13 @@ config QCOM_APR
          application processor and QDSP6. APR is
          used by audio driver to configure QDSP6
          ASM, ADM and AFE modules.

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.

endmenu
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ obj-$(CONFIG_QCOM_APR) += apr.o
CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
obj-$(CONFIG_QCOM_SCM)  +=      scm.o
obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_MSM_SERVICE_NOTIFIER) += service-notifier.o
obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o
obj-$(CONFIG_MSM_SYSMON_GLINK_COMM) += sysmon-glink.o sysmon-qmi.o
+347 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2015-2016, 2018 The Linux Foundation. All rights reserved.
 */

#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 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;

	threshold_val = scm_io_read(device->threshold[0]);

	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 (scm_io_write(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;

		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_dev = to_core_hang_dev(kobj);
	u32 enabled;

	enabled = scm_io_read(hang_dev->config[0]);
	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;

		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;
		}
	}

	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;
	const char *name;
	u32 treg[NR_CPUS] = {0}, creg[NR_CPUS] = {0};
	u32 num_reg = 0;

	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_reg = of_property_count_u32_elems(node,
			"qcom,threshold-arr");
	if (num_reg < 0) {
		pr_err("%s: Can't get qcom,threshold-arr property\n", __func__);
		return -EINVAL;
	}

	ret = of_property_read_u32_array(node, "qcom,threshold-arr",
				treg, num_reg);
	if (ret) {
		pr_err("%s: Can't get qcom,threshold-arr property\n", __func__);
		return -EINVAL;
	}

	ret = of_property_read_u32_array(node, "qcom,config-arr",
				creg, num_reg);
	if (ret) {
		pr_err("%s: Can't get qcom,config-arr property\n", __func__);
		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: 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,
		.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");