/* Copyright (c) 2012, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smd_private.h" #include "modem_notifier.h" #include "ramdump.h" static struct gss_8064_data { struct miscdevice gss_dev; void *pil_handle; void *gss_ramdump_dev; void *smem_ramdump_dev; } gss_data; static int crash_shutdown; static struct subsys_device *gss_8064_dev; #define MAX_SSR_REASON_LEN 81U static void log_gss_sfr(void) { u32 size; char *smem_reason, reason[MAX_SSR_REASON_LEN]; smem_reason = smem_get_entry(SMEM_SSR_REASON_MSS0, &size); if (!smem_reason || !size) { pr_err("GSS subsystem failure reason: (unknown, smem_get_entry failed).\n"); return; } if (!smem_reason[0]) { pr_err("GSS subsystem failure reason: (unknown, init string found).\n"); return; } size = min(size, MAX_SSR_REASON_LEN-1); memcpy(reason, smem_reason, size); reason[size] = '\0'; pr_err("GSS subsystem failure reason: %s.\n", reason); smem_reason[0] = '\0'; wmb(); } static void restart_gss(void) { log_gss_sfr(); subsystem_restart_dev(gss_8064_dev); } static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state) { /* Ignore if we're the one that set SMSM_RESET */ if (crash_shutdown) return; if (new_state & SMSM_RESET) { pr_err("GSS SMSM state changed to SMSM_RESET.\n" "Probable err_fatal on the GSS. " "Calling subsystem restart...\n"); restart_gss(); } } #define Q6_FW_WDOG_ENABLE 0x08882024 #define Q6_SW_WDOG_ENABLE 0x08982024 static int gss_shutdown(const struct subsys_desc *desc) { pil_force_shutdown("gss"); disable_irq_nosync(GSS_A5_WDOG_EXPIRED); return 0; } static int gss_powerup(const struct subsys_desc *desc) { pil_force_boot("gss"); enable_irq(GSS_A5_WDOG_EXPIRED); return 0; } void gss_crash_shutdown(const struct subsys_desc *desc) { crash_shutdown = 1; smsm_reset_modem(SMSM_RESET); } /* FIXME: Get address, size from PIL */ static struct ramdump_segment gss_segments[] = { {0x89000000, 0x00D00000} }; static struct ramdump_segment smem_segments[] = { {0x80000000, 0x00200000}, }; static int gss_ramdump(int enable, const struct subsys_desc *crashed_subsys) { int ret = 0; if (enable) { ret = do_ramdump(gss_data.gss_ramdump_dev, gss_segments, ARRAY_SIZE(gss_segments)); if (ret < 0) { pr_err("Unable to dump gss memory (rc = %d).\n", ret); goto out; } ret = do_ramdump(gss_data.smem_ramdump_dev, smem_segments, ARRAY_SIZE(smem_segments)); if (ret < 0) { pr_err("Unable to dump smem memory (rc = %d).\n", ret); goto out; } } out: return ret; } static irqreturn_t gss_wdog_bite_irq(int irq, void *dev_id) { pr_err("Watchdog bite received from GSS!\n"); restart_gss(); return IRQ_HANDLED; } static struct subsys_desc gss_8064 = { .name = "gss", .shutdown = gss_shutdown, .powerup = gss_powerup, .ramdump = gss_ramdump, .crash_shutdown = gss_crash_shutdown }; static int gss_subsystem_restart_init(void) { gss_8064_dev = subsys_register(&gss_8064); if (IS_ERR(gss_8064_dev)) return PTR_ERR(gss_8064_dev); return 0; } static int gss_open(struct inode *inode, struct file *filep) { void *ret; gss_data.pil_handle = ret = pil_get("gss"); if (!ret) pr_debug("%s - pil_get returned NULL\n", __func__); return 0; } static int gss_release(struct inode *inode, struct file *filep) { pil_put(gss_data.pil_handle); pr_debug("%s pil_put called on GSS\n", __func__); return 0; } const struct file_operations gss_file_ops = { .open = gss_open, .release = gss_release, }; static int __init gss_8064_init(void) { int ret; if (!(soc_class_is_apq8064())) return -ENODEV; ret = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_RESET, smsm_state_cb, 0); if (ret < 0) pr_err("%s: Unable to register SMSM callback! (%d)\n", __func__, ret); ret = request_irq(GSS_A5_WDOG_EXPIRED, gss_wdog_bite_irq, IRQF_TRIGGER_RISING, "gss_a5_wdog", NULL); if (ret < 0) { pr_err("%s: Unable to request gss watchdog IRQ. (%d)\n", __func__, ret); disable_irq_nosync(GSS_A5_WDOG_EXPIRED); goto out; } ret = gss_subsystem_restart_init(); if (ret < 0) { pr_err("%s: Unable to reg with subsystem restart. (%d)\n", __func__, ret); goto out; } gss_data.gss_dev.minor = MISC_DYNAMIC_MINOR; gss_data.gss_dev.name = "gss"; gss_data.gss_dev.fops = &gss_file_ops; ret = misc_register(&gss_data.gss_dev); if (ret) { pr_err("%s: misc_registers failed for %s (%d)", __func__, gss_data.gss_dev.name, ret); goto out; } gss_data.gss_ramdump_dev = create_ramdump_device("gss"); if (!gss_data.gss_ramdump_dev) { pr_err("%s: Unable to create gss ramdump device. (%d)\n", __func__, -ENOMEM); ret = -ENOMEM; goto out; } gss_data.smem_ramdump_dev = create_ramdump_device("smem-gss"); if (!gss_data.smem_ramdump_dev) { pr_err("%s: Unable to create smem ramdump device. (%d)\n", __func__, -ENOMEM); ret = -ENOMEM; goto out; } pr_info("%s: gss fatal driver init'ed.\n", __func__); out: return ret; } module_init(gss_8064_init);