blob: e75b0aed3bf8ef9af8b38aaec2f0545efa9a045a [file] [log] [blame]
/*
* (C) Copyright 2000-2003
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* 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 <common.h>
#include <asm/io.h>
#include <mmc.h>
#include <mv_recovery.h>
#define MAGIC_NUM_ADDR (0xD1020100)
#define MAGIC_VALIDATION_ADDR (0xD1020104)
#define MAGIC_RESIDUE_ADDR (0xD1020108)
#define MISC_HEAD_BLK_ADDR (0x14C00)
#if defined(CONFIG_MACH_MK2)
#define MISC_END_BLK_ADDR (MISC_HEAD_BLK_ADDR + 0x1FFFF) /* 64M */
#else
#define MISC_END_BLK_ADDR (MISC_HEAD_BLK_ADDR + 0x9FFF) /* 20M */
#endif
#define MISC_MAX_IMAGE_SIZE (8 * 1024 * 1024) /* 8M */
//#define RECOVERY_DEBUG
#undef RECOVERY_DEBUG
#define RTC_BRN0 (0xD4010014)
#ifndef NONUSERBUILD
//Support for recovery kernel command
#define CONFIG_GO_RKERNEL_CMD "mmc dev 0 0; mmc read 0x7fc0 " \
"0xcc00 0x3000; mmc read 0x2100000 0x10c00 0x1000; bootm 0x7fc0 0x2100000"
#endif
inline void __write_recovery_reg(u8 val) {
__raw_writel(val, RTC_BRN0);
}
void write_recovery_reg(u8 val) __attribute__((weak, alias("__write_recovery_reg")));
inline int __read_recovery_reg(void) {
return __raw_readl(RTC_BRN0);
}
int read_recovery_reg(void) __attribute__((weak, alias("__read_recovery_reg")));
enum rtc_stat {
DEFAULT = 0,
KERNEL_RECOVERY,
UBOOT_RECOVERY
};
static enum uboot_stat_class {
GO_N_KERNEL = 0,
GO_R_UBOOT,
GO_R_KERNEL,
GO_UPDATE_RECOVERY,
GO_DEAD_LOOP,
} uboot_stat;
/* magic number : default value 0x54525354 */
/* magic number indicates the validation flag and the residue flag
* are correct. Wrong magic number indicates that the 2 flags are not
* trusted by themselves. */
/* validation flag */
/* validation flag indicates whether the images are trustable.
* bit[0]: ramdisk bit[1]: r_ramdisk
* bit[2]: kernel bit[3]: r_kernel
* bit[4]: uboot bit[5]: r_uboot
* bit[6]: dtim bit[7]: r_dtim
* 0: image is trusted 1: image is not trusted
*/
/* residue flag */
/* residue flag indicates whether the images are right in DDR.
* bit[0]: ramdisk bit[1]: r_ramdisk
* bit[2]: kernel bit[3]: r_kernel
* bit[4]: uboot bit[5]: r_uboot
* 0: not in DDR 1: in DDR
*/
static struct mv_magic{
unsigned int magic; /* magic number */
unsigned int validation; /* validation flag */
unsigned int residue; /* residue flag */
} magic_blk;
struct update_unit {
unsigned int idx;
unsigned int src_partition;
unsigned int src_blk_addr;
unsigned int des_partition;
unsigned int des_blk_addr;
unsigned int blk_size;
};
#if defined(CONFIG_MACH_ABILENE) || defined(CONFIG_MACH_MK2)
static struct update_unit update_table[] = {
{
/* r_dtim */
.idx = (1 << 0),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x403,
.des_partition = 0,
.des_blk_addr = 0x4400,
.blk_size = 0x800, /* 1M */
},
{
/* r_uboot */
.idx = (1 << 1),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0xC03,
.des_partition = 2,
.des_blk_addr = 0x400,
.blk_size = 0x400, /* 512K */
},
{
/* r_zImage */
.idx = (1 << 2),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x1003,
.des_partition = 0,
.des_blk_addr = 0xCC00,
.blk_size = 0x4000, /* 8M */
},
{
/* r_ramdisk */
.idx = (1 << 3),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x5003,
.des_partition = 0,
.des_blk_addr = 0x10C00,
.blk_size = 0x1000, /* 8M */
},
};
#elif defined (CONFIG_MACH_BROWNSTONE)
static struct update_unit update_table[] = {
{
/* r_uboot */
.idx = (1 << 0),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x283,
.des_partition = 1,
.des_blk_addr = 0x580,
.blk_size = 0x280, /* 320K */
},
{
/* r_zImage */
.idx = (1 << 1),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x503,
.des_partition = 0,
.des_blk_addr = 0xCC00,
.blk_size = 0x4000, /* 8M */
},
{
/* r_ramdisk */
.idx = (1 << 2),
.src_partition = 0,
.src_blk_addr = MISC_HEAD_BLK_ADDR + 0x4503,
.des_partition = 0,
.des_blk_addr = 0x10C00,
.blk_size = 0x1000, /* 8M */
},
};
#else
static struct update_unit update_table[] = {};
#endif
/* magic block should be read as soon as uboot boot up in case that
* this memory may be over-written by other program */
inline void magic_read(void)
{
#ifdef CONFIG_TRUST_BOOT
magic_blk.magic = __raw_readl(MAGIC_NUM_ADDR);
if (magic_blk.magic != CONFIG_TRUST_BOOT_MAGIC) {
magic_blk.validation = 0;
magic_blk.residue = 0;
} else {
magic_blk.validation = __raw_readl(MAGIC_VALIDATION_ADDR);
magic_blk.residue = __raw_readl(MAGIC_RESIDUE_ADDR);
}
#else
magic_blk.magic = CONFIG_TRUST_BOOT_MAGIC;
magic_blk.validation = 0;
magic_blk.residue = 0;
#endif
}
static inline void magic_write(void)
{
__raw_writel(magic_blk.magic, MAGIC_NUM_ADDR);
__raw_writel(magic_blk.validation, MAGIC_VALIDATION_ADDR);
__raw_writel(magic_blk.residue, MAGIC_RESIDUE_ADDR);
}
/* function to detect validation flag for normal flow */
static inline int normal_flow_is_trusted(void)
{
return !(magic_blk.validation & 0x55);
}
/* function to detect validation flag for recovery flow */
static inline int recovery_flow_is_trusted(void)
{
return !(magic_blk.validation & 0xaa);
}
static inline int normal_kernel_is_residue(void)
{
return (magic_blk.residue & 0x04);
}
static inline int recovery_kernel_is_residue(void)
{
return (magic_blk.residue & 0x08);
}
/* function to detect recovery command in misc partition */
static int misc_detect_recovery(int blk_addr, char *cmd)
{
int ret = 0;
#ifdef CONFIG_GENERIC_MMC
struct mmc *mmc;
char buffer[512];
mmc = find_mmc_device(0);
if (!mmc) {
printf("ERR: mmc not found\n");
return ret;
}
mmc_init(mmc);
mmc_switch_part(0, 0);
mmc->block_dev.block_read(0, blk_addr, 1, buffer);
#ifdef RECOVERY_DEBUG
printf("Misc command: %s\n", buffer);
#endif
if (strcmp(buffer, cmd) == 0)
ret = 1;
#endif
return ret;
}
static void clear_recovery_flag(int end_clear)
{
#ifdef CONFIG_GENERIC_MMC
struct mmc *mmc;
char buffer[512];
mmc = find_mmc_device(0);
if (!mmc) {
printf("ERR: mmc not found\n");
write_recovery_reg(DEFAULT);
return;
}
mmc_init(mmc);
memset(buffer, 0, sizeof(buffer));
strcpy(buffer, "reserved");
mmc_switch_part(0, 0);
mmc->block_dev.block_write(0, MISC_HEAD_BLK_ADDR, 1, buffer);
if (end_clear)
mmc->block_dev.block_write(0, MISC_END_BLK_ADDR, 1, buffer);
#endif
write_recovery_reg(DEFAULT);
}
static void recovery_flow_update(void)
{
#ifdef CONFIG_GENERIC_MMC
struct misc_end {
char cmd[32];
unsigned int idx;
char reserved[476]; /* 512 bytes align */
} me;
struct mmc *mmc;
char buffer[MISC_MAX_IMAGE_SIZE];
int i;
mmc = find_mmc_device(0);
if (!mmc) {
printf("ERR: mmc not found\n");
return;
}
mmc_init(mmc);
mmc_switch_part(0, 0);
mmc->block_dev.block_read(0, MISC_END_BLK_ADDR, 1, &me);
for (i = 0; i < ARRAY_SIZE(update_table); i++) {
if (update_table[i].idx == (me.idx & (1 << i))) {
mmc_switch_part(0, update_table[i].src_partition);
mmc->block_dev.block_read(0, update_table[i].src_blk_addr,
update_table[i].blk_size, buffer);
mmc_switch_part(0, update_table[i].des_partition);
mmc->block_dev.block_write(0, update_table[i].des_blk_addr,
update_table[i].blk_size, buffer);
}
}
clear_recovery_flag(1);
#endif
return;
}
void mv_recovery(void)
{
/* check uboot mode: normal uboot or recovery uboot */
/* recovery_reg == UBOOT_RECOVERY means this uboot is recovery uboot */
/* !normal_flow_is_trusted() means this r_uboot is loaded by OBM direrctly */
if (read_recovery_reg() == UBOOT_RECOVERY || !normal_flow_is_trusted())
uboot_stat = GO_R_KERNEL;
/* the first way used by OS to go to recovery flow */
/* set 1 to a RTC register to transfer this command */
/* it is mainly used in normal kernel */
else if (read_recovery_reg() == KERNEL_RECOVERY)
uboot_stat = GO_R_UBOOT;
/* the way used by OS to update recovery flow */
/* go normal flow after updating done */
else if (misc_detect_recovery(MISC_END_BLK_ADDR, "update_recovery"))
uboot_stat = GO_UPDATE_RECOVERY;
/* the second way used by OS to go to recovery flow */
/* write special char string to misc header to transfer the command */
/* it is mainly used in recovery kernel */
else if (misc_detect_recovery(MISC_HEAD_BLK_ADDR, "boot-recovery"))
uboot_stat = GO_R_UBOOT;
/* the way used by user to go to recovery flow */
/* go to recovery flow by pressing specific keys */
else if (magic_key_detect_recovery())
uboot_stat = GO_R_UBOOT;
/* default: go to normal flow */
else
uboot_stat = GO_N_KERNEL;
/* trust flag confirmation */
switch (uboot_stat) {
case GO_R_KERNEL:
if (!recovery_flow_is_trusted())
uboot_stat = GO_DEAD_LOOP;
break;
case GO_R_UBOOT:
if (!recovery_flow_is_trusted()) {
clear_recovery_flag(0);
if (!normal_flow_is_trusted())
uboot_stat = GO_DEAD_LOOP;
else
//uboot_stat = GO_N_KERNEL;
uboot_stat = GO_R_KERNEL;
}
break;
case GO_N_KERNEL:
case GO_UPDATE_RECOVERY:
#ifdef CONFIG_MACH_BROWNSTONE
/* both block head and block end flags indicate that it failed
* in the last power cycle when doing dtim upgrade */
if (misc_detect_recovery(MISC_HEAD_BLK_ADDR, "boot-recovery")) {
uboot_stat = GO_R_KERNEL;
break;
}
#endif
if (!normal_flow_is_trusted())
uboot_stat = GO_DEAD_LOOP;
break;
default:
break;
}
switch (uboot_stat) {
case GO_N_KERNEL:
if (normal_kernel_is_residue())
;//Do nothing setenv("bootcmd", CONFIG_RESIDUE_CMD);
printf("Normal uboot: boot normal kernel.\n");
break;
case GO_R_UBOOT:
write_recovery_reg(UBOOT_RECOVERY);
/* pass down magic blk param */
magic_write();
setenv("bootcmd", CONFIG_GO_RUBOOT_CMD);
printf("Normal uboot: boot recovery uboot.\n");
break;
case GO_R_KERNEL:
if (recovery_kernel_is_residue())
{ printf("tampered\n");
/* setenv("bootcmd", CONFIG_RESIDUE_CMD); */
setenv("bootcmd", CONFIG_GO_RKERNEL_CMD);
}
else
{ printf("magickey\n");
setenv("bootcmd", CONFIG_GO_RKERNEL_CMD);
}
printf("Recovery uboot: boot recovery kernel.\n");
break;
case GO_UPDATE_RECOVERY:
printf("Update recovery flow:\n");
printf("=========================================\n");
recovery_flow_update();
printf("=========================================\n");
break;
case GO_DEAD_LOOP:
printf("DEAD LOOP: Images are not trusted!\n");
while(1);
break;
}
}