From 80fc3aba43f0e8e04b0255ee198b40818835997e Mon Sep 17 00:00:00 2001 From: Huang Yiwei Date: Thu, 31 Mar 2022 16:39:20 +0800 Subject: [PATCH] power: reset: Add snapshot of qcom-dload-mode driver This is a snapshot of the qcom-dload-mode driver as of msm-5.15 commit <908a517b050d> ("Merge "sched/walt: remove duplicate definitions of rt_task_arrival_time""). Change-Id: I267e2ef3d4d86aba285e38d8b03ce5f3037d3c13 Signed-off-by: Huang Yiwei --- drivers/power/reset/Kconfig | 19 ++ drivers/power/reset/Makefile | 1 + drivers/power/reset/qcom-dload-mode.c | 385 ++++++++++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 drivers/power/reset/qcom-dload-mode.c diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index bef35bdfbba6..69585f2ffedc 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -123,6 +123,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 8e632799abb5..b748ff23e187 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.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_QCOM_REBOOT_REASON) += qcom-reboot-reason.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.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..2feb576f561f --- /dev/null +++ b/drivers/power/reset/qcom-dload-mode.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020, 2021 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum qcom_download_dest { + QCOM_DOWNLOAD_DEST_UNKNOWN = -1, + QCOM_DOWNLOAD_DEST_QPST = 0, + QCOM_DOWNLOAD_DEST_EMMC = 2, +}; + +struct qcom_dload { + struct notifier_block panic_nb; + struct notifier_block reboot_nb; + struct kobject kobj; + + bool in_panic; + void __iomem *dload_dest_addr; +}; + +#define to_qcom_dload(o) container_of(o, struct qcom_dload, kobj) + +#define QCOM_DOWNLOAD_BOTHDUMP (QCOM_DOWNLOAD_FULLDUMP | QCOM_DOWNLOAD_MINIDUMP) + +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 enum qcom_download_mode dump_mode = + QCOM_DOWNLOAD_FULLDUMP | QCOM_DOWNLOAD_MINIDUMP; + +static int set_download_mode(enum qcom_download_mode mode) +{ + if ((mode & QCOM_DOWNLOAD_MINIDUMP) && !msm_minidump_enabled()) { + mode &= ~QCOM_DOWNLOAD_MINIDUMP; + pr_warn("Minidump not enabled.\n"); + if (!mode) + return -ENODEV; + } + current_download_mode = mode; + qcom_scm_set_download_mode(mode, 0); + return 0; +} + +static int set_dump_mode(enum qcom_download_mode mode) +{ + int ret = 0; + + if (enable_dump) { + ret = set_download_mode(mode); + if (likely(!ret)) + dump_mode = mode; + } else + dump_mode = mode; + return ret; +} + +static void msm_enable_dump_mode(bool enable) +{ + if (enable) + set_download_mode(dump_mode); + else + set_download_mode(QCOM_DOWNLOAD_NODUMP); +} + +static void set_download_dest(struct qcom_dload *poweroff, + enum qcom_download_dest dest) +{ + if (poweroff->dload_dest_addr) + __raw_writel(dest, poweroff->dload_dest_addr); +} +static enum qcom_download_dest get_download_dest(struct qcom_dload *poweroff) +{ + if (poweroff->dload_dest_addr) + return __raw_readl(poweroff->dload_dest_addr); + else + return QCOM_DOWNLOAD_DEST_UNKNOWN; +} + +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; + + msm_enable_dump_mode(true); + + return 0; +} +module_param_call(download_mode, param_set_download_mode, param_get_int, + &enable_dump, 0644); + +/* interface for exporting attributes */ +struct reset_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct attribute *attr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count); +}; +#define to_reset_attr(_attr) \ + container_of(_attr, struct reset_attribute, attr) + +static ssize_t attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct reset_attribute *reset_attr = to_reset_attr(attr); + ssize_t ret = -EIO; + + if (reset_attr->show) + ret = reset_attr->show(kobj, attr, buf); + + return ret; +} + +static ssize_t attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct reset_attribute *reset_attr = to_reset_attr(attr); + ssize_t ret = -EIO; + + if (reset_attr->store) + ret = reset_attr->store(kobj, attr, buf, count); + + return ret; +} + +static const struct sysfs_ops reset_sysfs_ops = { + .show = attr_show, + .store = attr_store, +}; + +static struct kobj_type qcom_dload_kobj_type = { + .sysfs_ops = &reset_sysfs_ops, +}; + +static ssize_t emmc_dload_show(struct kobject *kobj, + struct attribute *this, + char *buf) +{ + struct qcom_dload *poweroff = to_qcom_dload(kobj); + + if (!poweroff->dload_dest_addr) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%u\n", + get_download_dest(poweroff) == QCOM_DOWNLOAD_DEST_EMMC); +} +static ssize_t emmc_dload_store(struct kobject *kobj, + struct attribute *this, + const char *buf, size_t count) +{ + int ret; + bool enabled; + struct qcom_dload *poweroff = to_qcom_dload(kobj); + + if (!poweroff->dload_dest_addr) + return -ENODEV; + + ret = kstrtobool(buf, &enabled); + + if (ret < 0) + return ret; + + if (enabled) + set_download_dest(poweroff, QCOM_DOWNLOAD_DEST_EMMC); + else + set_download_dest(poweroff, QCOM_DOWNLOAD_DEST_QPST); + + return count; +} +static struct reset_attribute attr_emmc_dload = __ATTR_RW(emmc_dload); + +static ssize_t dload_mode_show(struct kobject *kobj, + struct attribute *this, + char *buf) +{ + const char *mode; + + switch ((unsigned int)dump_mode) { + case QCOM_DOWNLOAD_FULLDUMP: + mode = "full"; + break; + case QCOM_DOWNLOAD_MINIDUMP: + mode = "mini"; + break; + case QCOM_DOWNLOAD_BOTHDUMP: + mode = "both"; + break; + default: + mode = "unknown"; + break; + } + return scnprintf(buf, PAGE_SIZE, "DLOAD dump type: %s\n", mode); +} +static ssize_t dload_mode_store(struct kobject *kobj, + struct attribute *this, + const char *buf, size_t count) +{ + enum qcom_download_mode mode; + + if (sysfs_streq(buf, "full")) + mode = QCOM_DOWNLOAD_FULLDUMP; + else if (sysfs_streq(buf, "mini")) + mode = QCOM_DOWNLOAD_MINIDUMP; + else if (sysfs_streq(buf, "both")) + mode = QCOM_DOWNLOAD_BOTHDUMP; + else { + pr_err("Invalid dump mode request...\n"); + pr_err("Supported dumps: 'full', 'mini', or 'both'\n"); + return -EINVAL; + } + + return set_dump_mode(mode) ? : count; +} +static struct reset_attribute attr_dload_mode = __ATTR_RW(dload_mode); + +static struct attribute *qcom_dload_attrs[] = { + &attr_emmc_dload.attr, + &attr_dload_mode.attr, + NULL +}; +static struct attribute_group qcom_dload_attr_group = { + .attrs = qcom_dload_attrs, +}; + +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) + msm_enable_dump_mode(true); + 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")) + msm_enable_dump_mode(true); + } + + if (current_download_mode != QCOM_DOWNLOAD_NODUMP) + reboot_mode = REBOOT_WARM; + + return NOTIFY_OK; +} + +static void __iomem *map_prop_mem(const char *propname) +{ + struct device_node *np = of_find_compatible_node(NULL, NULL, propname); + void __iomem *addr; + + if (!np) { + pr_err("Unable to find DT property: %s\n", propname); + return NULL; + } + + addr = of_iomap(np, 0); + if (!addr) + pr_err("Unable to map memory for DT property: %s\n", propname); + return addr; +} + +static int qcom_dload_probe(struct platform_device *pdev) +{ + struct qcom_dload *poweroff; + int ret; + + poweroff = devm_kzalloc(&pdev->dev, sizeof(*poweroff), GFP_KERNEL); + if (!poweroff) + return -ENOMEM; + + ret = kobject_init_and_add(&poweroff->kobj, &qcom_dload_kobj_type, + kernel_kobj, "dload"); + if (ret) { + pr_err("%s: Error in creation kobject_add\n", __func__); + kobject_put(&poweroff->kobj); + return ret; + } + + ret = sysfs_create_group(&poweroff->kobj, &qcom_dload_attr_group); + if (ret) { + pr_err("%s: Error in creation sysfs_create_group\n", __func__); + kobject_del(&poweroff->kobj); + return ret; + } + + poweroff->dload_dest_addr = map_prop_mem("qcom,msm-imem-dload-type"); + + msm_enable_dump_mode(enable_dump); + if (!enable_dump) + 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); + + if (poweroff->dload_dest_addr) + iounmap(poweroff->dload_dest_addr); + + 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 +fs_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");