tegrakernel/kernel/nvidia/drivers/platform/tegra/nvdumper/nvdumper.c

310 lines
7.5 KiB
C

/*
* drivers/platform/tegra/nvdumper/nvdumper.c
*
* Copyright (c) 2011-2018, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 <linux/errno.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "nvdumper.h"
#include "nvdumper-footprint.h"
#define NVDUMPER_CLEAN 0xf000caf3U
#define NVDUMPER_DIRTY 0x2badfaceU
#define NVDUMPER_DIRTY_DUMP 0xdeadbeefU
#define NVDUMPER_WDT_DUMP 0x2badbeefU
#define NVDUMPER_SET_STR_LEN 16
#define RW_MODE (S_IWUSR | S_IRUGO)
#define NV_ADDRESS_MAP_PMC_IMPL_BASE 0x0c360000
#define NV_ADDRESS_MAP_PMC_IMPL_SIZE 0x400 /* map 1k */
#define PMC_IMPL_RAMDUMP_CTL_STATUS_0 0x0000010c
#define PMC_IMPL_RAMDUMP_CTL_STATUS_0_RAMDUMP_EN 0x1
#define NVDUMPER_RESERVED_SIZE 4096UL
struct nvdumper_soc {
bool enable_hw_ram_dump_cntrl;
};
static void __iomem *nvdumper_ptr;
static u32 nvdumper_last_reboot;
static unsigned long nvdumper_reserved;
static const struct nvdumper_soc *nvdumper_soc;
static char nvdumper_set_str[NVDUMPER_SET_STR_LEN];
static struct kobject *nvdumper_kobj;
static int __init tegra_nvdumper_arg(char *options)
{
char *p = options;
nvdumper_reserved = memparse(p, &p);
pr_info("nvdumper: nvdumper_reserved is first set to 0x%lx.\n",
nvdumper_reserved);
return 0;
}
early_param("nvdumper_reserved", tegra_nvdumper_arg);
static void enable_hw_ram_dump_ctrl(int enable)
{
u32 value;
void __iomem *ram_dump_ctrl = ioremap_nocache(
NV_ADDRESS_MAP_PMC_IMPL_BASE,
NV_ADDRESS_MAP_PMC_IMPL_SIZE);
if (!ram_dump_ctrl) {
pr_info("nvdumper: failed to ioremap memory at 0x%08x\n",
NV_ADDRESS_MAP_PMC_IMPL_BASE);
return;
}
value = ioread32(ram_dump_ctrl);
value = value & ~PMC_IMPL_RAMDUMP_CTL_STATUS_0_RAMDUMP_EN;
if (enable)
value = value | PMC_IMPL_RAMDUMP_CTL_STATUS_0_RAMDUMP_EN;
iowrite32(value, ram_dump_ctrl);
iounmap(ram_dump_ctrl);
}
static uint32_t get_dirty_state(void)
{
return ioread32(nvdumper_ptr);
}
static void set_dirty_state(uint32_t state)
{
pr_info("nvdumper: set_dirty_state 0x%x\n", state);
iowrite32(state, nvdumper_ptr);
if (!nvdumper_soc->enable_hw_ram_dump_cntrl)
return;
if (state == NVDUMPER_DIRTY_DUMP || state == NVDUMPER_WDT_DUMP) {
pr_info("nvdumper: preparing wdt\n");
enable_hw_ram_dump_ctrl(1);
} else {
pr_info("nvdumper: cleaning up wdt\n");
enable_hw_ram_dump_ctrl(0);
}
}
static int nvdumper_reboot_cb(struct notifier_block *nb,
unsigned long event, void *unused)
{
pr_info("nvdumper: %s cleanly.\n",
(event == SYS_RESTART) ? "rebooting" : "shutting down");
set_dirty_state(NVDUMPER_CLEAN);
return NOTIFY_DONE;
}
static struct notifier_block nvdumper_reboot_notifier = {
.notifier_call = nvdumper_reboot_cb,
};
static ssize_t nvdumper_set_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", nvdumper_set_str);
}
static ssize_t nvdumper_set_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
if (n < 1)
return 0;
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "%s", buf);
nvdumper_set_str[n - 1] = '\0';
if (!strcmp(nvdumper_set_str, "clean"))
set_dirty_state(NVDUMPER_CLEAN);
else if (!strcmp(nvdumper_set_str, "dirty"))
set_dirty_state(NVDUMPER_DIRTY);
else if (!strcmp(nvdumper_set_str, "dirty_dump"))
set_dirty_state(NVDUMPER_DIRTY_DUMP);
else if (!strcmp(nvdumper_set_str, "wdt_dump"))
set_dirty_state(NVDUMPER_WDT_DUMP);
else
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "unknown");
pr_info("nvdumper_set was updated to %s\n", nvdumper_set_str);
return n;
}
static ssize_t nvdumper_prev_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", nvdumper_set_str);
}
static const struct kobj_attribute nvdumper_attr[] = {
__ATTR(nvdumper_set, 0644, nvdumper_set_show, nvdumper_set_store),
__ATTR(nvdumper_prev, 0444, nvdumper_prev_show, NULL),
};
static void nvdumper_sysfs_init(void)
{
int i, ret = 0;
nvdumper_kobj = kobject_create_and_add("nvdumper", kernel_kobj);
if (!nvdumper_kobj) {
pr_err("unable to create nvdumper kernel object!\n");
return;
}
/* create sysfs */
for (i = 0; i < ARRAY_SIZE(nvdumper_attr); i++) {
ret = sysfs_create_file(nvdumper_kobj, &nvdumper_attr[i].attr);
if (ret)
pr_err("failed to create %s\n",
nvdumper_attr[i].attr.name);
}
switch (nvdumper_last_reboot) {
case NVDUMPER_CLEAN:
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "clean");
break;
case NVDUMPER_DIRTY:
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "dirty");
break;
case NVDUMPER_DIRTY_DUMP:
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "dirty_dump");
break;
default:
snprintf(nvdumper_set_str, NVDUMPER_SET_STR_LEN, "dirty");
break;
}
}
static void nvdumper_sysfs_exit(void)
{
int i;
if (!nvdumper_kobj)
return;
for (i = 0; i < ARRAY_SIZE(nvdumper_attr); i++)
sysfs_remove_file(nvdumper_kobj, &nvdumper_attr[i].attr);
}
static int nvdumper_probe(struct platform_device *pdev)
{
int ret;
if (!nvdumper_reserved)
return -ENOTSUPP;
nvdumper_soc = of_device_get_match_data(&pdev->dev);
nvdumper_ptr = ioremap_nocache(nvdumper_reserved,
NVDUMPER_RESERVED_SIZE);
if (!nvdumper_ptr) {
pr_info("nvdumper: failed to ioremap memory at 0x%08lx\n",
nvdumper_reserved);
return -EIO;
}
ret = register_reboot_notifier(&nvdumper_reboot_notifier);
if (ret)
goto err_out1;
ret = nvdumper_regdump_init();
if (ret)
goto err_out2;
nvdumper_dbg_footprint_init();
nvdumper_last_reboot = get_dirty_state();
switch (nvdumper_last_reboot) {
case NVDUMPER_CLEAN:
pr_info("nvdumper: last reboot was clean\n");
break;
case NVDUMPER_DIRTY:
case NVDUMPER_DIRTY_DUMP:
case NVDUMPER_WDT_DUMP:
pr_info("nvdumper: last reboot was dirty\n");
break;
default:
pr_info("nvdumper: last reboot was unknown\n");
break;
}
nvdumper_sysfs_init();
set_dirty_state(NVDUMPER_DIRTY);
return 0;
err_out2:
unregister_reboot_notifier(&nvdumper_reboot_notifier);
err_out1:
iounmap(nvdumper_ptr);
return ret;
}
static int nvdumper_remove(struct platform_device *pdev)
{
nvdumper_sysfs_exit();
nvdumper_regdump_exit();
nvdumper_dbg_footprint_exit();
unregister_reboot_notifier(&nvdumper_reboot_notifier);
set_dirty_state(NVDUMPER_CLEAN);
iounmap(nvdumper_ptr);
return 0;
}
static const struct nvdumper_soc tegra210_nvdumper_soc = {
.enable_hw_ram_dump_cntrl = false,
};
static const struct of_device_id of_nvdumper_match[] = {
{
.compatible = "nvidia,tegra210-nvdumper",
.data = &tegra210_nvdumper_soc
},
{
.compatible = "nvidia,tegra186-nvdumper",
.data = &tegra210_nvdumper_soc
},
{ }
};
MODULE_DEVICE_TABLE(of, of_nvdumper_match);
static struct platform_driver nvdumper_driver = {
.probe = nvdumper_probe,
.remove = nvdumper_remove,
.driver = {
.name = "tegra-nvdumper",
.owner = THIS_MODULE,
.of_match_table = of_nvdumper_match,
},
};
builtin_platform_driver(nvdumper_driver);