diff options
author | Prasad Sodagudi <psodagud@codeaurora.org> | 2015-10-20 23:52:24 +0530 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:08:41 -0700 |
commit | 6fb5665397398c7794c49783234d0af7f06199ec (patch) | |
tree | fd669761af4ba7aacf7fb429996a0cb271473fb6 /drivers/soc | |
parent | e160d231b9990823714923a82b9187763ca069e1 (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/Kconfig | 7 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/core_hang_detect.c | 340 |
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"); |