blob: b2e03c2406393ed90472da382fcd5401ec805e94 [file] [log] [blame]
/*
* Copyright (c) 2018 MediaTek Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <arch/ops.h>
#include <boot_args.h>
#include <env.h>
#include <lib/bio.h>
#include <lib/cksum.h>
#include <lib/kcmdline.h>
#include <lib/mempool.h>
#include <lib/zlib.h>
#include <libfdt.h>
#include <malloc.h>
#include <mrdump.h>
#include <platform.h>
#include <platform/mtk_wdt.h>
#include <printf.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef MTK_PMIC_FULL_RESET
#include <platform/pmic.h>
#endif
#include "aee.h"
#include "kdump.h"
#include "ram_console.h"
#if WITH_KERNEL_VM
#include <kernel/vm.h>
#else
#include <kernel/novm.h>
#endif
#ifdef MTK_3LEVEL_PAGETABLE
#include <target.h>
#endif
#define MRDUMP_DELAY_TIME 10
extern BOOT_ARGUMENT *g_boot_arg;
static struct mrdump_control_block *mrdump_cblock = NULL;
static struct mrdump_cblock_result cblock_result;
static unsigned int log_size;
static int output_device;
void voprintf(char type, const char *msg, va_list ap)
{
char msgbuf[128], *p;
p = msgbuf;
if (msg[0] == '\r') {
*p++ = msg[0];
msg++;
}
*p++ = type;
*p++ = ':';
vsnprintf(p, sizeof(msgbuf) - (p - msgbuf), msg, ap);
switch (type) {
case 'I':
case 'W':
case 'E':
//video_printf("%s", msgbuf);
break;
}
dprintf(CRITICAL,"[%s] %s", MRDUMP_GO_DUMP, msgbuf);
/* Write log buffer */
p = msgbuf;
while ((*p != 0) && (log_size < sizeof(cblock_result.log_buf))) {
cblock_result.log_buf[log_size] = *p++;
log_size++;
}
}
void voprintf_verbose(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
voprintf('V', msg, ap);
va_end(ap);
}
void voprintf_debug(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
voprintf('D', msg, ap);
va_end(ap);
}
void voprintf_info(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
voprintf('I', msg, ap);
va_end(ap);
}
void voprintf_warning(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
voprintf('W', msg, ap);
va_end(ap);
}
void voprintf_error(const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
voprintf('E', msg, ap);
va_end(ap);
}
void vo_show_progress(int sizeM)
{
#if 0
video_set_cursor((video_get_rows() / 4) * 3, (video_get_colums() - 22)/ 2);
video_printf("=====================\n");
video_set_cursor((video_get_rows() / 4) * 3 + 1, (video_get_colums() - 22)/ 2);
video_printf(">>> Written %4dM <<<\n", sizeM);
video_set_cursor((video_get_rows() / 4) * 3 + 2, (video_get_colums() - 22)/ 2);
video_printf("=====================\n");
video_set_cursor(video_get_rows() - 1, 0);
dprintf(CRITICAL,"... Written %dM\n", sizeM);
#endif
}
static void mrdump_status(const char *status, const char *fmt, va_list ap)
{
char *dest = cblock_result.status;
dest += strlcpy(dest, status, sizeof(cblock_result.status));
*dest++ = '\n';
vsnprintf(dest, sizeof(cblock_result.status) - (dest - cblock_result.status), fmt, ap);
}
void mrdump_status_ok(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
mrdump_status("OK", fmt, ap);
va_end(ap);
}
void mrdump_status_none(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
mrdump_status("NONE", fmt, ap);
va_end(ap);
}
void mrdump_status_error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
mrdump_status("FAILED", fmt, ap);
va_end(ap);
}
uint32_t g_aee_mode = AEE_MODE_MTK_ENG;
const char *mrdump_mode2string(uint8_t mode)
{
switch (mode) {
case AEE_REBOOT_MODE_NORMAL:
return "NORMAL-BOOT";
case AEE_REBOOT_MODE_KERNEL_OOPS:
return "KERNEL-OOPS";
case AEE_REBOOT_MODE_KERNEL_PANIC:
return "KERNEL-PANIC";
case AEE_REBOOT_MODE_NESTED_EXCEPTION:
return "NESTED-CPU-EXCEPTION";
case AEE_REBOOT_MODE_WDT:
return "HWT";
case AEE_REBOOT_MODE_EXCEPTION_KDUMP:
return "MANUALDUMP";
case AEE_REBOOT_MODE_MRDUMP_KEY:
return "MRDUMP_KEY";
case AEE_REBOOT_MODE_HANG_DETECT:
return "KERNEL-HANG-DETECT";
default:
return "UNKNOWN-BOOT";
}
}
#define MRDUMP_EXPDB_OFFSET 3145728
static void mrdump_write_result(void)
{
bdev_t *bdev;
bdev = bio_open_by_label("expdb");
if (!bdev) {
dprintf(CRITICAL, "%s: no %s partition\n", __func__, "expdb");
return;
}
if (bdev->total_size < MRDUMP_EXPDB_OFFSET) {
dprintf(CRITICAL, "%s: partition size(%llx) is less then reserved (%x)\n", __func__,
bdev->total_size, MRDUMP_EXPDB_OFFSET);
return;
}
u64 part_offset = bdev->total_size - MRDUMP_EXPDB_OFFSET;
dprintf(CRITICAL, "%s: offset %lld size %lld\n", __func__, part_offset, bdev->total_size);
bio_write(bdev, (uchar *)&cblock_result, part_offset, sizeof(cblock_result));
}
#define SIZE_1MB 1048576ULL
#define SIZE_64MB 67108864ULL
static uint64_t mrdump_mem_size(void)
{
return physical_memory_size();
}
static int mrdump_output_device(void)
{
//now only support ext4
return MRDUMP_DEV_ISTORAGE_EXT4;
}
static struct kzip_addlist *mrdump_memlist_fill(void)
{
struct kzip_addlist *memlist =
mempool_alloc(sizeof(struct kzip_addlist) * 4, MEMPOOL_ANY);
if (memlist == NULL) {
return NULL;
}
void *bufp = mempool_alloc(KDUMP_CORE_HEADER_SIZE, MEMPOOL_ANY);
memset(bufp, 0, KDUMP_CORE_HEADER_SIZE);
dprintf(CRITICAL, "%s address:%p\n", __func__, bufp);
memlist[0].address = (uint64_t)(uintptr_t) bufp;
memlist[0].size = KDUMP_CORE_HEADER_SIZE;
memlist[0].type = MEM_NO_MAP;
memlist[1].address = (uint64_t)PA_TO_VA((paddr_t)mrdump_cb_addr());
memlist[1].size = mrdump_cb_size();
memlist[1].type = MEM_NO_MAP;
memlist[2].address = (uint64_t)PA_TO_VA(DRAM_BASE_PHY);
memlist[2].size = mrdump_mem_size();
memlist[2].type = MEM_NO_MAP;
memlist[3].address = 0;
memlist[3].size = 0;
memlist[3].type = MEM_NO_MAP;
return memlist;
}
static void mrdump_memlist_free(struct kzip_addlist *memlist)
{
mempool_free((void *)(uintptr_t)memlist[0].address);
mempool_free(memlist);
}
static void kdump_ui(struct mrdump_control_block *mrdump_cblock)
{
#if 0
video_clean_screen();
video_set_cursor(0, 0);
#endif
mrdump_status_error("Unknown error\n");
voprintf_info("Kdump triggerd by '%s' (address:%x, size:%lluM)\n",
mrdump_mode2string(mrdump_cblock->crash_record.reboot_mode),
DRAM_BASE_PHY, mrdump_mem_size() / 0x100000UL);
/* check machdesc crc */
uint32_t mcrc = crc32(0xffffffff, (const unsigned char *)&mrdump_cblock->machdesc,
sizeof(struct mrdump_machdesc)) ^ 0xffffffff;
if (mcrc != mrdump_cblock->machdesc_crc) {
voprintf_error("Control block machdesc field CRC error (%08x, %08x).\n",
mcrc, mrdump_cblock->machdesc_crc);
return;
}
struct kzip_addlist *memlist = mrdump_memlist_fill();
if (memlist == NULL) {
voprintf_error("Cannot allcate memlist memory.\n");
return;
}
kdump_core_header_init(mrdump_cblock, memlist);
struct aee_timer elapse_time;
aee_timer_init(&elapse_time);
aee_timer_start(&elapse_time);
int dump_ok = -1;
switch (output_device) {
case MRDUMP_DEV_NONE:
mrdump_status_none("Output to None (disabled)\n");
voprintf_info("Output to None (disabled)\n");
dump_ok = 0;
break;
case MRDUMP_DEV_NULL:
//dump_ok = kdump_null_output(mrdump_cblock, memlist);
break;
case MRDUMP_DEV_ISTORAGE_EXT4:
dump_ok = mrdump_ext4_output(mrdump_cblock, memlist, mrdump_dev_emmc_ext4());
break;
case MRDUMP_DEV_ISTORAGE_VFAT:
//dump_ok = mrdump_vfat_output(mrdump_cblock, memlist, mrdump_dev_emmc_vfat());
break;
case MRDUMP_DEV_USB:
//dump_ok = kdump_usb_output(mrdump_cblock, memlist);
break;
default:
voprintf_error("Unsupport device id %d\n", output_device);
dump_ok = -1;
}
mrdump_memlist_free(memlist);
aee_mrdump_flush_cblock(mrdump_cblock);
aee_timer_stop(&elapse_time);
voprintf_info("Dump finished.(%s, %d sec)\n", dump_ok == 0 ? "ok" : "failed",
elapse_time.acc_ms / 1000);
mtk_wdt_restart();
#if 0
video_clean_screen();
video_set_cursor(0, 0);
#endif
}
int mrdump_detection(void)
{
if (!ram_console_is_abnormal_boot()) {
dprintf(CRITICAL, "MT-RAMDUMP: No exception detected, skipped\n");
return 0;
}
mrdump_cblock = aee_mrdump_get_params();
if (mrdump_cblock == NULL) {
dprintf(CRITICAL, "MT-RAMDUMP control block not found\n");
return 0;
}
memset(&cblock_result, 0, sizeof(struct mrdump_cblock_result));
log_size = 0;
strlcpy(cblock_result.sig, MRDUMP_GO_DUMP, sizeof(cblock_result.sig));
uint8_t reboot_mode = mrdump_cblock->crash_record.reboot_mode;
if (!g_boot_arg->ddr_reserve_enable) {
voprintf_debug("DDR reserve mode disabled\n");
mrdump_status_none("DDR reserve mode disabled\n");
goto error;
}
if (!g_boot_arg->ddr_reserve_success) {
voprintf_debug("DDR reserve mode failed\n");
mrdump_status_none("DDR reserve mode failed\n");
goto error;
}
if (mrdump_cblock->enabled != MRDUMP_ENABLE_COOKIE) {
voprintf_debug("Runtime disabled %x\n", mrdump_cblock->enabled);
mrdump_status_none("Runtime disabled\n");
goto error;
}
output_device = mrdump_output_device();
voprintf_debug("sram record with mode %d\n", reboot_mode);
switch (reboot_mode) {
case AEE_REBOOT_MODE_GZ_WDT:
case AEE_REBOOT_MODE_WDT: {
goto end;
}
case AEE_REBOOT_MODE_NORMAL: {
/* MRDUMP_KEY reboot*/
if (ram_console_reboot_by_mrdump_key && ram_console_reboot_by_mrdump_key()) {
mrdump_cblock->crash_record.reboot_mode = AEE_REBOOT_MODE_MRDUMP_KEY;
goto end;
} else
return 0;
}
case AEE_REBOOT_MODE_KERNEL_OOPS:
case AEE_REBOOT_MODE_KERNEL_PANIC:
case AEE_REBOOT_MODE_NESTED_EXCEPTION:
case AEE_REBOOT_MODE_EXCEPTION_KDUMP:
case AEE_REBOOT_MODE_MRDUMP_KEY:
case AEE_REBOOT_MODE_GZ_KE:
case AEE_REBOOT_MODE_HANG_DETECT:
goto end;
}
voprintf_debug("Unsupport exception type\n");
mrdump_status_none("Unsupport exception type\n");
error:
mrdump_write_result();
return 0;
end:
if (output_device == MRDUMP_DEV_USB) {
g_boot_arg->boot_mode = 2;
//set_env("mrdump_output", "usb");
}
return 1;
}
void mrdump_reboot(void)
{
#ifdef MTK_PMIC_FULL_RESET
voprintf_debug("Ready for full pmic reset\n");
mrdump_write_result();
pmic_cold_reset();
#else
voprintf_debug("Ready for reset\n");
mrdump_write_result();
mtk_arch_reset(1);
#endif
}
int mrdump_run2(void)
{
if (mrdump_cblock != NULL) {
kdump_ui(mrdump_cblock);
#ifndef MTK_TC7_FEATURE
if (output_device != MRDUMP_DEV_USB) {
mrdump_reboot();
}
#endif
mrdump_write_result();
return 1;
}
return 0;
}
void aee_timer_init(struct aee_timer *t)
{
memset(t, 0, sizeof(struct aee_timer));
}
void aee_timer_start(struct aee_timer *t)
{
t->start_ms = current_time();
}
void aee_timer_stop(struct aee_timer *t)
{
t->acc_ms += (current_time() - t->start_ms);
t->start_ms = 0;
}
void kdump_core_header_init(const struct mrdump_control_block *kparams,
const struct kzip_addlist *memlist)
{
if (kparams->machdesc.page_offset <= 0xffffffffULL) {
voprintf_info("32b kernel detected:offset:0x%llx\n", kparams->machdesc.page_offset);
kdump_core32_header_init(kparams, memlist);
} else {
voprintf_info("64b kernel detected:offset:0x%llx\n", kparams->machdesc.page_offset);
kdump_core64_header_init(kparams, memlist);
}
}
#ifdef MTK_3LEVEL_PAGETABLE
vaddr_t scratch_addr(void)
{
return (vaddr_t)target_get_scratch_address();
}
#endif
void mrdump_setup_version(void)
{
kcmdline_append("mrdump.lk=" MRDUMP_GO_DUMP);
}