Loading Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt 0 → 100644 +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>; }; drivers/soc/qcom/Kconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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) Loading drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading drivers/soc/qcom/core_hang_detect.c 0 → 100644 +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"); Loading
Documentation/devicetree/bindings/arm/msm/msm_hang_detect.txt 0 → 100644 +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>; };
drivers/soc/qcom/Kconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
drivers/soc/qcom/core_hang_detect.c 0 → 100644 +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");