/* Copyright (c) 2011-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. */ #define pr_fmt(fmt) "subsys-restart: %s(): " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smd_private.h" struct subsys_soc_restart_order { const char * const *subsystem_list; int count; struct mutex shutdown_lock; struct mutex powerup_lock; struct subsys_device *subsys_ptrs[]; }; struct restart_log { struct timeval time; struct subsys_device *dev; struct list_head list; }; struct subsys_device { struct subsys_desc *desc; struct list_head list; struct wake_lock wake_lock; char wlname[64]; struct work_struct work; spinlock_t restart_lock; int restart_count; void *notify; struct mutex shutdown_lock; struct mutex powerup_lock; void *restart_order; }; static int enable_ramdumps; module_param(enable_ramdumps, int, S_IRUGO | S_IWUSR); struct workqueue_struct *ssr_wq; static LIST_HEAD(restart_log_list); static LIST_HEAD(subsystem_list); static DEFINE_MUTEX(subsystem_list_lock); static DEFINE_MUTEX(soc_order_reg_lock); static DEFINE_MUTEX(restart_log_mutex); /* SOC specific restart orders go here */ #define DEFINE_SINGLE_RESTART_ORDER(name, order) \ static struct subsys_soc_restart_order __##name = { \ .subsystem_list = order, \ .count = ARRAY_SIZE(order), \ .subsys_ptrs = {[ARRAY_SIZE(order)] = NULL} \ }; \ static struct subsys_soc_restart_order *name[] = { \ &__##name, \ } /* MSM 8x60 restart ordering info */ static const char * const _order_8x60_all[] = { "external_modem", "modem", "lpass" }; DEFINE_SINGLE_RESTART_ORDER(orders_8x60_all, _order_8x60_all); static const char * const _order_8x60_modems[] = {"external_modem", "modem"}; DEFINE_SINGLE_RESTART_ORDER(orders_8x60_modems, _order_8x60_modems); /*SGLTE restart ordering info*/ static const char * const order_8960_sglte[] = {"external_modem", "modem"}; static struct subsys_soc_restart_order restart_orders_8960_fusion_sglte = { .subsystem_list = order_8960_sglte, .count = ARRAY_SIZE(order_8960_sglte), .subsys_ptrs = {[ARRAY_SIZE(order_8960_sglte)] = NULL} }; static struct subsys_soc_restart_order *restart_orders_8960_sglte[] = { &restart_orders_8960_fusion_sglte, }; /* These will be assigned to one of the sets above after * runtime SoC identification. */ static struct subsys_soc_restart_order **restart_orders; static int n_restart_orders; static int restart_level = RESET_SOC; int get_restart_level() { return restart_level; } EXPORT_SYMBOL(get_restart_level); static int restart_level_set(const char *val, struct kernel_param *kp) { int ret; int old_val = restart_level; if (cpu_is_msm9615()) { pr_err("Only Phase 1 subsystem restart is supported\n"); return -EINVAL; } ret = param_set_int(val, kp); if (ret) return ret; switch (restart_level) { case RESET_SUBSYS_INDEPENDENT: if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) { pr_info("Phase 3 is currently unsupported. Using phase 2 instead.\n"); restart_level = RESET_SUBSYS_COUPLED; } case RESET_SUBSYS_COUPLED: case RESET_SOC: pr_info("Phase %d behavior activated.\n", restart_level); break; default: restart_level = old_val; return -EINVAL; } return 0; } module_param_call(restart_level, restart_level_set, param_get_int, &restart_level, 0644); static struct subsys_soc_restart_order * update_restart_order(struct subsys_device *dev) { int i, j; struct subsys_soc_restart_order *order; const char *name = dev->desc->name; int len = SUBSYS_NAME_MAX_LENGTH; mutex_lock(&soc_order_reg_lock); for (j = 0; j < n_restart_orders; j++) { order = restart_orders[j]; for (i = 0; i < order->count; i++) { if (!strncmp(order->subsystem_list[i], name, len)) { order->subsys_ptrs[i] = dev; goto found; } } } order = NULL; found: mutex_unlock(&soc_order_reg_lock); return order; } static int max_restarts; module_param(max_restarts, int, 0644); static long max_history_time = 3600; module_param(max_history_time, long, 0644); static void do_epoch_check(struct subsys_device *dev) { int n = 0; struct timeval *time_first = NULL, *curr_time; struct restart_log *r_log, *temp; static int max_restarts_check; static long max_history_time_check; mutex_lock(&restart_log_mutex); max_restarts_check = max_restarts; max_history_time_check = max_history_time; /* Check if epoch checking is enabled */ if (!max_restarts_check) goto out; r_log = kmalloc(sizeof(struct restart_log), GFP_KERNEL); if (!r_log) goto out; r_log->dev = dev; do_gettimeofday(&r_log->time); curr_time = &r_log->time; INIT_LIST_HEAD(&r_log->list); list_add_tail(&r_log->list, &restart_log_list); list_for_each_entry_safe(r_log, temp, &restart_log_list, list) { if ((curr_time->tv_sec - r_log->time.tv_sec) > max_history_time_check) { pr_debug("Deleted node with restart_time = %ld\n", r_log->time.tv_sec); list_del(&r_log->list); kfree(r_log); continue; } if (!n) { time_first = &r_log->time; pr_debug("Time_first: %ld\n", time_first->tv_sec); } n++; pr_debug("Restart_time: %ld\n", r_log->time.tv_sec); } if (time_first && n >= max_restarts_check) { if ((curr_time->tv_sec - time_first->tv_sec) < max_history_time_check) { panic("Subsystems have crashed %d times in less than "\ "%ld seconds!", max_restarts_check, max_history_time_check); } } out: mutex_unlock(&restart_log_mutex); } static void for_each_subsys_device(struct subsys_device **list, unsigned count, void *data, void (*fn)(struct subsys_device *, void *)) { while (count--) { struct subsys_device *dev = *list++; if (!dev) continue; fn(dev, data); } } static void __send_notification_to_order(struct subsys_device *dev, void *data) { enum subsys_notif_type type = (enum subsys_notif_type)data; subsys_notif_queue_notification(dev->notify, type); } static void send_notification_to_order(struct subsys_device **l, unsigned n, enum subsys_notif_type t) { for_each_subsys_device(l, n, (void *)t, __send_notification_to_order); } static void subsystem_shutdown(struct subsys_device *dev, void *data) { const char *name = dev->desc->name; pr_info("[%p]: Shutting down %s\n", current, name); if (dev->desc->shutdown(dev->desc) < 0) { panic("subsys-restart: [%p]: Failed to shutdown %s!", current, name); } } static void subsystem_ramdump(struct subsys_device *dev, void *data) { const char *name = dev->desc->name; if (dev->desc->ramdump) if (dev->desc->ramdump(enable_ramdumps, dev->desc) < 0) pr_warn("%s[%p]: Ramdump failed.\n", name, current); } static void subsystem_powerup(struct subsys_device *dev, void *data) { const char *name = dev->desc->name; pr_info("[%p]: Powering up %s\n", current, name); if (dev->desc->powerup(dev->desc) < 0) panic("[%p]: Failed to powerup %s!", current, name); } static void subsystem_restart_wq_func(struct work_struct *work) { struct subsys_device *dev = container_of(work, struct subsys_device, work); struct subsys_device **list; struct subsys_desc *desc = dev->desc; struct subsys_soc_restart_order *soc_restart_order = NULL; struct mutex *powerup_lock; struct mutex *shutdown_lock; unsigned count; unsigned long flags; if (restart_level != RESET_SUBSYS_INDEPENDENT) soc_restart_order = dev->restart_order; /* * It's OK to not take the registration lock at this point. * This is because the subsystem list inside the relevant * restart order is not being traversed. */ if (!soc_restart_order) { list = &dev; count = 1; powerup_lock = &dev->powerup_lock; shutdown_lock = &dev->shutdown_lock; } else { list = soc_restart_order->subsys_ptrs; count = soc_restart_order->count; powerup_lock = &soc_restart_order->powerup_lock; shutdown_lock = &soc_restart_order->shutdown_lock; } pr_debug("[%p]: Attempting to get shutdown lock!\n", current); /* * Try to acquire shutdown_lock. If this fails, these subsystems are * already being restarted - return. */ if (!mutex_trylock(shutdown_lock)) goto out; pr_debug("[%p]: Attempting to get powerup lock!\n", current); /* * Now that we've acquired the shutdown lock, either we're the first to * restart these subsystems or some other thread is doing the powerup * sequence for these subsystems. In the latter case, panic and bail * out, since a subsystem died in its powerup sequence. */ if (!mutex_trylock(powerup_lock)) { panic("%s[%p]: Subsystem died during powerup!", __func__, current); } do_epoch_check(dev); /* * It's necessary to take the registration lock because the subsystem * list in the SoC restart order will be traversed and it shouldn't be * changed until _this_ restart sequence completes. */ mutex_lock(&soc_order_reg_lock); pr_debug("[%p]: Starting restart sequence for %s\n", current, desc->name); send_notification_to_order(list, count, SUBSYS_BEFORE_SHUTDOWN); for_each_subsys_device(list, count, NULL, subsystem_shutdown); send_notification_to_order(list, count, SUBSYS_AFTER_SHUTDOWN); /* * Now that we've finished shutting down these subsystems, release the * shutdown lock. If a subsystem restart request comes in for a * subsystem in _this_ restart order after the unlock below, and * before the powerup lock is released, panic and bail out. */ mutex_unlock(shutdown_lock); /* Collect ram dumps for all subsystems in order here */ for_each_subsys_device(list, count, NULL, subsystem_ramdump); send_notification_to_order(list, count, SUBSYS_BEFORE_POWERUP); for_each_subsys_device(list, count, NULL, subsystem_powerup); send_notification_to_order(list, count, SUBSYS_AFTER_POWERUP); pr_info("[%p]: Restart sequence for %s completed.\n", current, desc->name); mutex_unlock(powerup_lock); mutex_unlock(&soc_order_reg_lock); pr_debug("[%p]: Released powerup lock!\n", current); out: spin_lock_irqsave(&dev->restart_lock, flags); dev->restart_count--; if (!dev->restart_count) wake_unlock(&dev->wake_lock); spin_unlock_irqrestore(&dev->restart_lock, flags); } static void __subsystem_restart_dev(struct subsys_device *dev) { struct subsys_desc *desc = dev->desc; unsigned long flags; pr_debug("Restarting %s [level=%d]!\n", desc->name, restart_level); spin_lock_irqsave(&dev->restart_lock, flags); if (!dev->restart_count) wake_lock(&dev->wake_lock); dev->restart_count++; spin_unlock_irqrestore(&dev->restart_lock, flags); if (!queue_work(ssr_wq, &dev->work)) { spin_lock_irqsave(&dev->restart_lock, flags); dev->restart_count--; if (!dev->restart_count) wake_unlock(&dev->wake_lock); spin_unlock_irqrestore(&dev->restart_lock, flags); } } int subsystem_restart_dev(struct subsys_device *dev) { const char *name = dev->desc->name; /* * If a system reboot/shutdown is underway, ignore subsystem errors. * However, print a message so that we know that a subsystem behaved * unexpectedly here. */ if (system_state == SYSTEM_RESTART || system_state == SYSTEM_POWER_OFF) { pr_err("%s crashed during a system poweroff/shutdown.\n", name); return -EBUSY; } pr_info("Restart sequence requested for %s, restart_level = %d.\n", name, restart_level); switch (restart_level) { case RESET_SUBSYS_COUPLED: case RESET_SUBSYS_INDEPENDENT: __subsystem_restart_dev(dev); break; case RESET_SOC: panic("subsys-restart: Resetting the SoC - %s crashed.", name); break; default: panic("subsys-restart: Unknown restart level!\n"); break; } return 0; } EXPORT_SYMBOL(subsystem_restart_dev); int subsystem_restart(const char *name) { struct subsys_device *dev; mutex_lock(&subsystem_list_lock); list_for_each_entry(dev, &subsystem_list, list) if (!strncmp(dev->desc->name, name, SUBSYS_NAME_MAX_LENGTH)) goto found; dev = NULL; found: mutex_unlock(&subsystem_list_lock); if (dev) return subsystem_restart_dev(dev); return -ENODEV; } EXPORT_SYMBOL(subsystem_restart); struct subsys_device *subsys_register(struct subsys_desc *desc) { struct subsys_device *dev; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return ERR_PTR(-ENOMEM); dev->desc = desc; dev->notify = subsys_notif_add_subsys(desc->name); dev->restart_order = update_restart_order(dev); snprintf(dev->wlname, sizeof(dev->wlname), "ssr(%s)", desc->name); wake_lock_init(&dev->wake_lock, WAKE_LOCK_SUSPEND, dev->wlname); INIT_WORK(&dev->work, subsystem_restart_wq_func); spin_lock_init(&dev->restart_lock); mutex_init(&dev->shutdown_lock); mutex_init(&dev->powerup_lock); mutex_lock(&subsystem_list_lock); list_add(&dev->list, &subsystem_list); mutex_unlock(&subsystem_list_lock); return dev; } EXPORT_SYMBOL(subsys_register); void subsys_unregister(struct subsys_device *dev) { if (IS_ERR_OR_NULL(dev)) return; mutex_lock(&subsystem_list_lock); list_del(&dev->list); mutex_unlock(&subsystem_list_lock); wake_lock_destroy(&dev->wake_lock); kfree(dev); } EXPORT_SYMBOL(subsys_unregister); static int ssr_panic_handler(struct notifier_block *this, unsigned long event, void *ptr) { struct subsys_device *dev; list_for_each_entry(dev, &subsystem_list, list) if (dev->desc->crash_shutdown) dev->desc->crash_shutdown(dev->desc); return NOTIFY_DONE; } static struct notifier_block panic_nb = { .notifier_call = ssr_panic_handler, }; static int __init ssr_init_soc_restart_orders(void) { int i; atomic_notifier_chain_register(&panic_notifier_list, &panic_nb); if (cpu_is_msm8x60()) { for (i = 0; i < ARRAY_SIZE(orders_8x60_all); i++) { mutex_init(&orders_8x60_all[i]->powerup_lock); mutex_init(&orders_8x60_all[i]->shutdown_lock); } for (i = 0; i < ARRAY_SIZE(orders_8x60_modems); i++) { mutex_init(&orders_8x60_modems[i]->powerup_lock); mutex_init(&orders_8x60_modems[i]->shutdown_lock); } restart_orders = orders_8x60_all; n_restart_orders = ARRAY_SIZE(orders_8x60_all); } if (socinfo_get_platform_subtype() == PLATFORM_SUBTYPE_SGLTE) { restart_orders = restart_orders_8960_sglte; n_restart_orders = ARRAY_SIZE(restart_orders_8960_sglte); } for (i = 0; i < n_restart_orders; i++) { mutex_init(&restart_orders[i]->powerup_lock); mutex_init(&restart_orders[i]->shutdown_lock); } if (restart_orders == NULL || n_restart_orders < 1) { WARN_ON(1); } return 0; } static int __init subsys_restart_init(void) { restart_level = RESET_SUBSYS_INDEPENDENT; ssr_wq = alloc_workqueue("ssr_wq", WQ_CPU_INTENSIVE, 0); if (!ssr_wq) panic("%s: out of memory\n", __func__); return ssr_init_soc_restart_orders(); } arch_initcall(subsys_restart_init); MODULE_DESCRIPTION("Subsystem Restart Driver"); MODULE_LICENSE("GPL v2");