power: reset: Add qcom-dload-mode driver

Add a driver to manage download mode for Qualcomm SoCs. The driver can
force SoC to restart into a dump collection mode when panicking for
post-morten analysis, as controlled by
CONFIG_POWER_RESET_MSM_DOWNLOAD_MODE_DEFAULT. The driver also can reboot
into "emergency download mode," which restarts SoC into mode which
permits re-flashing storage via Qualcomm Sahara protocol.

Change-Id: I30c83bf020715b7a80c00faa19280aec7f708435
Signed-off-by: Elliot Berman <eberman@codeaurora.org>
This commit is contained in:
Elliot Berman 2019-10-18 09:38:59 -07:00
commit bc6815ab64
3 changed files with 180 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 The Linux Foundation. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/pm.h>
#include <linux/qcom_scm.h>
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");