Merge "soc: qcom: add microdump collector"
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index fb9c914..45ecf84 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -57,6 +57,7 @@
obj-y += subsystem_notif.o
obj-y += subsystem_restart.o
obj-y += ramdump.o
+ obj-y += microdump_collector.o
endif
obj-$(CONFIG_QCOM_EUD) += eud.o
obj-$(CONFIG_SOC_BUS) += socinfo.o
diff --git a/drivers/soc/qcom/microdump_collector.c b/drivers/soc/qcom/microdump_collector.c
new file mode 100644
index 0000000..183f42c
--- /dev/null
+++ b/drivers/soc/qcom/microdump_collector.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/ramdump.h>
+#include <linux/soc/qcom/smem.h>
+
+#define SMEM_SSR_REASON_MSS0 421
+#define SMEM_SSR_DATA_MSS0 611
+#define SMEM_MODEM 1
+
+/*
+ * This program collects the data from SMEM regions whenever the modem crashes
+ * and stores it in /dev/ramdump_microdump_modem so as to expose it to
+ * user space.
+ */
+
+struct microdump_data {
+ struct ramdump_device *microdump_dev;
+ void *microdump_modem_notify_handler;
+ struct notifier_block microdump_modem_ssr_nb;
+};
+
+static struct microdump_data *drv;
+
+static int microdump_modem_notifier_nb(struct notifier_block *nb,
+ unsigned long code, void *data)
+{
+ int ret = 0;
+ size_t size_reason = 0, size_data = 0;
+ char *crash_reason = NULL;
+ char *crash_data = NULL;
+ struct ramdump_segment segment[2];
+
+ if (SUBSYS_RAMDUMP_NOTIFICATION != code && SUBSYS_SOC_RESET != code)
+ return NOTIFY_OK;
+
+ memset(segment, 0, sizeof(segment));
+
+ crash_reason = qcom_smem_get(QCOM_SMEM_HOST_ANY
+ , SMEM_SSR_REASON_MSS0, &size_reason);
+
+ if (IS_ERR_OR_NULL(crash_reason)) {
+ pr_info("%s: smem %d not available\n",
+ __func__, SMEM_SSR_REASON_MSS0);
+ goto out;
+ }
+
+ segment[0].v_address = crash_reason;
+ segment[0].size = size_reason;
+
+ crash_data = qcom_smem_get(SMEM_MODEM
+ , SMEM_SSR_DATA_MSS0, &size_data);
+
+ if (IS_ERR_OR_NULL(crash_data)) {
+ pr_info("%s: smem %d not available\n",
+ __func__, SMEM_SSR_DATA_MSS0);
+ goto out;
+ }
+
+ segment[1].v_address = crash_data;
+ segment[1].size = size_data;
+
+ ret = do_ramdump(drv->microdump_dev, segment, 2);
+ if (ret)
+ pr_info("%s: do_ramdump() failed\n", __func__);
+
+out:
+ return NOTIFY_OK;
+}
+
+static int microdump_modem_ssr_register_notifier(struct microdump_data *drv)
+{
+ int ret = 0;
+
+ drv->microdump_modem_ssr_nb.notifier_call = microdump_modem_notifier_nb;
+
+ drv->microdump_modem_notify_handler =
+ subsys_notif_register_notifier("modem",
+ &drv->microdump_modem_ssr_nb);
+
+ if (IS_ERR(drv->microdump_modem_notify_handler)) {
+ pr_err("Modem register notifier failed: %ld\n",
+ PTR_ERR(drv->microdump_modem_notify_handler));
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void microdump_modem_ssr_unregister_notifier(struct microdump_data *drv)
+{
+ subsys_notif_unregister_notifier(drv->microdump_modem_notify_handler,
+ &drv->microdump_modem_ssr_nb);
+ drv->microdump_modem_notify_handler = NULL;
+}
+
+/*
+ * microdump_init() - Registers kernel module for microdump collector
+ *
+ * Creates device file /dev/ramdump_microdump_modem and registers handler for
+ * modem SSR events.
+ *
+ * Returns 0 on success and negative error code in case of errors
+ */
+static int __init microdump_init(void)
+{
+ int ret = -ENOMEM;
+
+ drv = kzalloc(sizeof(struct microdump_data), GFP_KERNEL);
+ if (!drv)
+ goto out;
+
+ drv->microdump_dev = create_ramdump_device("microdump_modem", NULL);
+ if (!drv->microdump_dev) {
+ pr_err("%s: Unable to create a microdump_modem ramdump device\n"
+ , __func__);
+ ret = -ENODEV;
+ goto out_kfree;
+ }
+
+ ret = microdump_modem_ssr_register_notifier(drv);
+ if (ret) {
+ destroy_ramdump_device(drv->microdump_dev);
+ goto out_kfree;
+ }
+ return ret;
+
+out_kfree:
+ pr_err("%s: Failed to register microdump collector\n", __func__);
+ kfree(drv);
+ drv = NULL;
+out:
+ return ret;
+}
+
+static void __exit microdump_exit(void)
+{
+ microdump_modem_ssr_unregister_notifier(drv);
+ destroy_ramdump_device(drv->microdump_dev);
+ kfree(drv);
+}
+
+module_init(microdump_init);
+module_exit(microdump_exit);
+
+MODULE_DESCRIPTION("Microdump Collector");
+MODULE_LICENSE("GPL v2");