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

Commit 1377b8d1 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "soc: qcom: core_hang: Add core hang driver"

parents 1cb62eed 6d882b54
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
* 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>;
	};
+7 −0
Original line number Diff line number Diff line
@@ -195,6 +195,13 @@ config MSM_FORCE_WDOG_BITE_ON_PANIC
	  kernel panic. On certain MSM SoCs, this provides us
	  additional debugging information.

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.

config MSM_CPU_PWR_CTL
	bool "Cpu subsystem power control"
	depends on SMP && (ARM || ARM64)
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ obj-$(CONFIG_MSM_MEMORY_DUMP_V2) += memory_dump_v2.o
obj-$(CONFIG_MSM_DDR_HEALTH) += ddr-health.o
obj-$(CONFIG_MSM_DCC) += dcc.o
obj-$(CONFIG_MSM_WATCHDOG_V2) += watchdog_v2.o
obj-$(CONFIG_MSM_CORE_HANG_DETECT) += core_hang_detect.o
obj-$(CONFIG_MSM_COMMON_LOG) += common_log.o
obj-$(CONFIG_MSM_HVC) += hvc.o
obj-$(CONFIG_MSM_CPU_PWR_CTL) += cpu_pwr_ctl.o
+340 −0
Original line number Diff line number Diff line
/* 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");