diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index da5c5aad3a68..5795c1665f89 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -105,6 +105,25 @@ config POWER_RESET_MSM help Power off and restart support for Qualcomm boards. +config POWER_RESET_QCOM_DOWNLOAD_MODE + tristate "MSM download mode driver" + depends on ARCH_QCOM + help + MSM boards support booting to special download modes after a restart. + These modes could be a normal restart, restarting into a + ramdump collection mode (CrashDump), or restarting into "emergency + download mode". + +config POWER_RESET_QCOM_DOWNLOAD_MODE_DEFAULT + bool "MSM download mode enabled by default" + help + A device with "download mode" enabled will upon an unexpected + warm-restart enter a special debug mode that allows the user to + "download" memory content over USB for offline postmortem analysis. + The feature can be enabled/disabled on the kernel command line. + + Say Y here to enable "download mode" by default. + config POWER_RESET_QCOM_PON tristate "Qualcomm power-on driver" depends on ARCH_QCOM diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 85da3198e4e0..6f0ee7819909 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_QCOM_DOWNLOAD_MODE) += qcom-dload-mode.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o diff --git a/drivers/power/reset/qcom-dload-mode.c b/drivers/power/reset/qcom-dload-mode.c new file mode 100644 index 000000000000..efdec0e65137 --- /dev/null +++ b/drivers/power/reset/qcom-dload-mode.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct qcom_dload { + struct notifier_block panic_nb; + struct notifier_block reboot_nb; + + bool in_panic; +}; + +static bool enable_dump = + IS_ENABLED(CONFIG_POWER_RESET_QCOM_DOWNLOAD_MODE_DEFAULT); +static enum qcom_download_mode current_download_mode = QCOM_DOWNLOAD_NODUMP; + +static int set_download_mode(enum qcom_download_mode mode) +{ + current_download_mode = mode; + qcom_scm_set_download_mode(mode, 0); + return 0; +} + +static int param_set_download_mode(const char *val, + const struct kernel_param *kp) +{ + int ret; + + /* update enable_dump according to user input */ + ret = param_set_bool(val, kp); + if (ret) + return ret; + + set_download_mode(QCOM_DOWNLOAD_FULLDUMP); + + return 0; +} +module_param_call(download_mode, param_set_download_mode, param_get_int, + &enable_dump, 0644); + +static int qcom_dload_panic(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct qcom_dload *poweroff = container_of(this, struct qcom_dload, + panic_nb); + poweroff->in_panic = true; + if (enable_dump) + set_download_mode(QCOM_DOWNLOAD_FULLDUMP); + return NOTIFY_OK; +} + +static int qcom_dload_reboot(struct notifier_block *this, unsigned long event, + void *ptr) +{ + char *cmd = ptr; + struct qcom_dload *poweroff = container_of(this, struct qcom_dload, + reboot_nb); + + /* Clean shutdown, disable dump mode to allow normal restart */ + if (!poweroff->in_panic) + set_download_mode(QCOM_DOWNLOAD_NODUMP); + + if (cmd) { + if (!strcmp(cmd, "edl")) + set_download_mode(QCOM_DOWNLOAD_EDL); + else if (!strcmp(cmd, "qcom_dload")) + set_download_mode(QCOM_DOWNLOAD_FULLDUMP); + } + + if (current_download_mode != QCOM_DOWNLOAD_NODUMP) + reboot_mode = REBOOT_WARM; + + return NOTIFY_OK; +} + + +static int qcom_dload_probe(struct platform_device *pdev) +{ + struct qcom_dload *poweroff; + + poweroff = devm_kzalloc(&pdev->dev, sizeof(*poweroff), GFP_KERNEL); + if (!poweroff) + return -ENOMEM; + + if (enable_dump) + set_download_mode(QCOM_DOWNLOAD_FULLDUMP); + else { + set_download_mode(QCOM_DOWNLOAD_NODUMP); + qcom_scm_disable_sdi(); + } + + poweroff->panic_nb.notifier_call = qcom_dload_panic; + poweroff->panic_nb.priority = INT_MAX; + atomic_notifier_chain_register(&panic_notifier_list, + &poweroff->panic_nb); + + poweroff->reboot_nb.notifier_call = qcom_dload_reboot; + poweroff->reboot_nb.priority = 255; + register_reboot_notifier(&poweroff->reboot_nb); + + platform_set_drvdata(pdev, poweroff); + + return 0; +} + +static int qcom_dload_remove(struct platform_device *pdev) +{ + struct qcom_dload *poweroff = platform_get_drvdata(pdev); + + atomic_notifier_chain_unregister(&panic_notifier_list, + &poweroff->panic_nb); + unregister_reboot_notifier(&poweroff->reboot_nb); + + return 0; +} + +static const struct of_device_id of_qcom_dload_match[] = { + { .compatible = "qcom,dload-mode", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_qcom_dload_match); + +static struct platform_driver qcom_dload_driver = { + .probe = qcom_dload_probe, + .remove = qcom_dload_remove, + .driver = { + .name = "qcom-dload-mode", + .of_match_table = of_match_ptr(of_qcom_dload_match), + }, +}; + +static int __init qcom_dload_driver_init(void) +{ + return platform_driver_register(&qcom_dload_driver); +} +#if IS_MODULE(CONFIG_POWER_RESET_QCOM_DOWNLOAD_MODE) +module_init(qcom_dload_driver_init); +#else +pure_initcall(qcom_dload_driver_init); +#endif + +static void __exit qcom_dload_driver_exit(void) +{ + return platform_driver_unregister(&qcom_dload_driver); +} +module_exit(qcom_dload_driver_exit); + +MODULE_DESCRIPTION("MSM Download Mode Driver"); +MODULE_LICENSE("GPL v2");