/* Copyright (c) 2013-2015, 2017, 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 "esoc.h" #include "mdm-dbg.h" enum { PWR_OFF = 0x1, PWR_ON, BOOT, RUN, CRASH, IN_DEBUG, SHUTDOWN, RESET, PEER_CRASH, }; struct mdm_drv { unsigned mode; struct esoc_eng cmd_eng; struct completion boot_done; struct completion req_eng_wait; struct esoc_clink *esoc_clink; bool boot_fail; struct workqueue_struct *mdm_queue; struct work_struct ssr_work; struct notifier_block esoc_restart; }; #define to_mdm_drv(d) container_of(d, struct mdm_drv, cmd_eng) static int esoc_msm_restart_handler(struct notifier_block *nb, unsigned long action, void *data) { struct mdm_drv *mdm_drv = container_of(nb, struct mdm_drv, esoc_restart); struct esoc_clink *esoc_clink = mdm_drv->esoc_clink; const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; if (action == SYS_RESTART) { if (mdm_dbg_stall_notify(ESOC_PRIMARY_REBOOT)) return NOTIFY_OK; dev_dbg(&esoc_clink->dev, "Notifying esoc of cold reboot\n"); clink_ops->notify(ESOC_PRIMARY_REBOOT, esoc_clink); } return NOTIFY_OK; } static void mdm_handle_clink_evt(enum esoc_evt evt, struct esoc_eng *eng) { struct mdm_drv *mdm_drv = to_mdm_drv(eng); switch (evt) { case ESOC_INVALID_STATE: mdm_drv->boot_fail = true; complete(&mdm_drv->boot_done); break; case ESOC_RUN_STATE: mdm_drv->boot_fail = false; mdm_drv->mode = RUN, complete(&mdm_drv->boot_done); break; case ESOC_UNEXPECTED_RESET: case ESOC_ERR_FATAL: /* * Modem can crash while we are waiting for boot_done during * a subsystem_get(). Setting mode to CRASH will prevent a * subsequent subsystem_get() from entering poweron ops. Avoid * this by seting mode to CRASH only if device was up and * running. */ if (mdm_drv->mode == CRASH || mdm_drv->mode != RUN) return; mdm_drv->mode = CRASH; queue_work(mdm_drv->mdm_queue, &mdm_drv->ssr_work); break; case ESOC_REQ_ENG_ON: complete(&mdm_drv->req_eng_wait); break; default: break; } } static void mdm_ssr_fn(struct work_struct *work) { struct mdm_drv *mdm_drv = container_of(work, struct mdm_drv, ssr_work); /* * If restarting esoc fails, the SSR framework triggers a kernel panic */ esoc_clink_request_ssr(mdm_drv->esoc_clink); return; } static void mdm_crash_shutdown(const struct subsys_desc *mdm_subsys) { struct esoc_clink *esoc_clink = container_of(mdm_subsys, struct esoc_clink, subsys); const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; if (mdm_dbg_stall_notify(ESOC_PRIMARY_CRASH)) return; clink_ops->notify(ESOC_PRIMARY_CRASH, esoc_clink); } static int mdm_subsys_shutdown(const struct subsys_desc *crashed_subsys, bool force_stop) { int ret; struct esoc_clink *esoc_clink = container_of(crashed_subsys, struct esoc_clink, subsys); struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink); const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; if (mdm_drv->mode == CRASH || mdm_drv->mode == PEER_CRASH) { if (mdm_dbg_stall_cmd(ESOC_PREPARE_DEBUG)) /* We want to mask debug command. * In this case return success * to move to next stage */ return 0; ret = clink_ops->cmd_exe(ESOC_PREPARE_DEBUG, esoc_clink); if (ret) { dev_err(&esoc_clink->dev, "failed to enter debug\n"); return ret; } mdm_drv->mode = IN_DEBUG; } else if (!force_stop) { if (esoc_clink->subsys.sysmon_shutdown_ret) ret = clink_ops->cmd_exe(ESOC_FORCE_PWR_OFF, esoc_clink); else { if (mdm_dbg_stall_cmd(ESOC_PWR_OFF)) /* Since power off command is masked * we return success, and leave the state * of the command engine as is. */ return 0; ret = clink_ops->cmd_exe(ESOC_PWR_OFF, esoc_clink); } if (ret) { dev_err(&esoc_clink->dev, "failed to exe power off\n"); return ret; } mdm_drv->mode = PWR_OFF; } return 0; } static int mdm_subsys_powerup(const struct subsys_desc *crashed_subsys) { int ret; struct esoc_clink *esoc_clink = container_of(crashed_subsys, struct esoc_clink, subsys); struct mdm_drv *mdm_drv = esoc_get_drv_data(esoc_clink); const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; int timeout = INT_MAX; if (!esoc_clink->auto_boot && !esoc_req_eng_enabled(esoc_clink)) { dev_dbg(&esoc_clink->dev, "Wait for req eng registration\n"); wait_for_completion(&mdm_drv->req_eng_wait); } if (mdm_drv->mode == PWR_OFF) { if (mdm_dbg_stall_cmd(ESOC_PWR_ON)) return -EBUSY; ret = clink_ops->cmd_exe(ESOC_PWR_ON, esoc_clink); if (ret) { dev_err(&esoc_clink->dev, "pwr on fail\n"); return ret; } } else if (mdm_drv->mode == IN_DEBUG) { ret = clink_ops->cmd_exe(ESOC_EXIT_DEBUG, esoc_clink); if (ret) { dev_err(&esoc_clink->dev, "cannot exit debug mode\n"); return ret; } mdm_drv->mode = PWR_OFF; ret = clink_ops->cmd_exe(ESOC_PWR_ON, esoc_clink); if (ret) { dev_err(&esoc_clink->dev, "pwr on fail\n"); return ret; } } /* * In autoboot case, it is possible that we can forever wait for * boot completion, when esoc fails to boot. This is because there * is no helper application which can alert esoc driver about boot * failure. Prevent going to wait forever in such case. */ if (esoc_clink->auto_boot) timeout = 10 * HZ; ret = wait_for_completion_timeout(&mdm_drv->boot_done, timeout); if (mdm_drv->boot_fail || ret <= 0) { dev_err(&esoc_clink->dev, "booting failed\n"); return -EIO; } return 0; } static int mdm_subsys_ramdumps(int want_dumps, const struct subsys_desc *crashed_subsys) { int ret; struct esoc_clink *esoc_clink = container_of(crashed_subsys, struct esoc_clink, subsys); const struct esoc_clink_ops const *clink_ops = esoc_clink->clink_ops; if (want_dumps) { ret = clink_ops->cmd_exe(ESOC_EXE_DEBUG, esoc_clink); if (ret) { dev_err(&esoc_clink->dev, "debugging failed\n"); return ret; } } return 0; } static int mdm_register_ssr(struct esoc_clink *esoc_clink) { struct subsys_desc *subsys = &esoc_clink->subsys; subsys->shutdown = mdm_subsys_shutdown; subsys->ramdump = mdm_subsys_ramdumps; subsys->powerup = mdm_subsys_powerup; subsys->crash_shutdown = mdm_crash_shutdown; return esoc_clink_register_ssr(esoc_clink); } int esoc_ssr_probe(struct esoc_clink *esoc_clink, struct esoc_drv *drv) { int ret; struct mdm_drv *mdm_drv; struct esoc_eng *esoc_eng; mdm_drv = devm_kzalloc(&esoc_clink->dev, sizeof(*mdm_drv), GFP_KERNEL); if (IS_ERR_OR_NULL(mdm_drv)) return PTR_ERR(mdm_drv); esoc_eng = &mdm_drv->cmd_eng; esoc_eng->handle_clink_evt = mdm_handle_clink_evt; ret = esoc_clink_register_cmd_eng(esoc_clink, esoc_eng); if (ret) { dev_err(&esoc_clink->dev, "failed to register cmd engine\n"); return ret; } ret = mdm_register_ssr(esoc_clink); if (ret) goto ssr_err; mdm_drv->mdm_queue = alloc_workqueue("mdm_drv_queue", 0, 0); if (!mdm_drv->mdm_queue) { dev_err(&esoc_clink->dev, "could not create mdm_queue\n"); goto queue_err; } esoc_set_drv_data(esoc_clink, mdm_drv); init_completion(&mdm_drv->boot_done); init_completion(&mdm_drv->req_eng_wait); INIT_WORK(&mdm_drv->ssr_work, mdm_ssr_fn); mdm_drv->esoc_clink = esoc_clink; mdm_drv->mode = PWR_OFF; mdm_drv->boot_fail = false; mdm_drv->esoc_restart.notifier_call = esoc_msm_restart_handler; ret = register_reboot_notifier(&mdm_drv->esoc_restart); if (ret) dev_err(&esoc_clink->dev, "register for reboot failed\n"); ret = mdm_dbg_eng_init(drv, esoc_clink); if (ret) { debug_init_done = false; dev_err(&esoc_clink->dev, "dbg engine failure\n"); } else { dev_dbg(&esoc_clink->dev, "dbg engine initialized\n"); debug_init_done = true; } return 0; queue_err: esoc_clink_unregister_ssr(esoc_clink); ssr_err: esoc_clink_unregister_cmd_eng(esoc_clink, esoc_eng); return ret; } static struct esoc_compat compat_table[] = { { .name = "MDM9x25", .data = NULL, }, { .name = "MDM9x35", .data = NULL, }, { .name = "MDM9x55", .data = NULL, }, { .name = "MDM9x45", .data = NULL, }, { .name = "APQ8096", .data = NULL, }, }; static struct esoc_drv esoc_ssr_drv = { .owner = THIS_MODULE, .probe = esoc_ssr_probe, .compat_table = compat_table, .compat_entries = ARRAY_SIZE(compat_table), .driver = { .name = "mdm-4x", }, }; int __init esoc_ssr_init(void) { return esoc_drv_register(&esoc_ssr_drv); } module_init(esoc_ssr_init); MODULE_LICENSE("GPL v2");