| /* |
| * (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; |
| } |
| } |
| |