blob: f17ddfd1427ddd52f1de511ae399325815ea5bb4 [file] [log] [blame]
/*
* Copyright (c) 2009, Google Inc.
* All rights reserved.
*
* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Code Aurora nor
* the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <app.h>
#include <debug.h>
#include <arch/arm.h>
#include <dev/udc.h>
#include <string.h>
#include <kernel/thread.h>
#include <arch/ops.h>
#include <dev/flash.h>
#include <lib/ptable.h>
#include <dev/keys.h>
#include <dev/fbcon.h>
#include "recovery.h"
#include "bootimg.h"
#include "fastboot.h"
#define EXPAND(NAME) #NAME
#define TARGET(NAME) EXPAND(NAME)
#define DEFAULT_CMDLINE "mem=100M console=null";
#ifdef MEMBASE
#define EMMC_BOOT_IMG_HEADER_ADDR (0xFF000+(MEMBASE))
#else
#define EMMC_BOOT_IMG_HEADER_ADDR 0xFF000
#endif
#define RECOVERY_MODE 0x77665502
#define FASTBOOT_MODE 0x77665500
static const char *emmc_cmdline = " androidboot.emmc=true";
static const char *battchg_pause = " androidboot.battchg_pause=true";
static struct udc_device surf_udc_device = {
.vendor_id = 0x18d1,
.product_id = 0xD00D,
.version_id = 0x0100,
.manufacturer = "Google",
.product = "Android",
};
struct atag_ptbl_entry
{
char name[16];
unsigned offset;
unsigned size;
unsigned flags;
};
void platform_uninit_timer(void);
unsigned* target_atag_mem(unsigned* ptr);
unsigned board_machtype(void);
unsigned check_reboot_mode(void);
void *target_get_scratch_address(void);
int target_is_emmc_boot(void);
void reboot_device(unsigned);
void target_battery_charging_enable(unsigned enable, unsigned disconnect);
unsigned int mmc_write (unsigned long long data_addr,
unsigned int data_len, unsigned int* in);
unsigned long long mmc_ptn_offset (unsigned char * name);
unsigned long long mmc_ptn_size (unsigned char * name);
void display_shutdown(void);
static void ptentry_to_tag(unsigned **ptr, struct ptentry *ptn)
{
struct atag_ptbl_entry atag_ptn;
if (ptn->type == TYPE_MODEM_PARTITION)
return;
memcpy(atag_ptn.name, ptn->name, 16);
atag_ptn.name[15] = '\0';
atag_ptn.offset = ptn->start;
atag_ptn.size = ptn->length;
atag_ptn.flags = ptn->flags;
memcpy(*ptr, &atag_ptn, sizeof(struct atag_ptbl_entry));
*ptr += sizeof(struct atag_ptbl_entry) / sizeof(unsigned);
}
void boot_linux(void *kernel, unsigned *tags,
const char *cmdline, unsigned machtype,
void *ramdisk, unsigned ramdisk_size)
{
unsigned *ptr = tags;
unsigned pcount = 0;
void (*entry)(unsigned,unsigned,unsigned*) = kernel;
struct ptable *ptable;
int cmdline_len = 0;
int have_cmdline = 0;
int pause_at_bootup = 0;
/* CORE */
*ptr++ = 2;
*ptr++ = 0x54410001;
if (ramdisk_size) {
*ptr++ = 4;
*ptr++ = 0x54420005;
*ptr++ = (unsigned)ramdisk;
*ptr++ = ramdisk_size;
}
ptr = target_atag_mem(ptr);
if (!target_is_emmc_boot()) {
/* Skip NAND partition ATAGS for eMMC boot */
if ((ptable = flash_get_ptable()) && (ptable->count != 0)) {
int i;
for(i=0; i < ptable->count; i++) {
struct ptentry *ptn;
ptn = ptable_get(ptable, i);
if (ptn->type == TYPE_APPS_PARTITION)
pcount++;
}
*ptr++ = 2 + (pcount * (sizeof(struct atag_ptbl_entry) /
sizeof(unsigned)));
*ptr++ = 0x4d534d70;
for (i = 0; i < ptable->count; ++i)
ptentry_to_tag(&ptr, ptable_get(ptable, i));
}
}
if (cmdline && cmdline[0]) {
cmdline_len = strlen(cmdline);
have_cmdline = 1;
}
if (target_is_emmc_boot()) {
cmdline_len += strlen(emmc_cmdline);
}
if (target_pause_for_battery_charge()) {
pause_at_bootup = 1;
cmdline_len += strlen(battchg_pause);
}
if (cmdline_len > 0) {
const char *src;
char *dst;
unsigned n;
/* include terminating 0 and round up to a word multiple */
n = (cmdline_len + 4) & (~3);
*ptr++ = (n / 4) + 2;
*ptr++ = 0x54410009;
dst = (char *)ptr;
if (have_cmdline) {
src = cmdline;
while ((*dst++ = *src++));
}
if (target_is_emmc_boot()) {
src = emmc_cmdline;
if (have_cmdline) --dst;
have_cmdline = 1;
while ((*dst++ = *src++));
}
if (pause_at_bootup) {
src = battchg_pause;
if (have_cmdline) --dst;
while ((*dst++ = *src++));
}
ptr += (n / 4);
}
/* END */
*ptr++ = 0;
*ptr++ = 0;
dprintf(INFO, "booting linux @ %p, ramdisk @ %p (%d)\n",
kernel, ramdisk, ramdisk_size);
if (cmdline)
dprintf(INFO, "cmdline: %s\n", cmdline);
enter_critical_section();
platform_uninit_timer();
arch_disable_cache(UCACHE);
arch_disable_mmu();
#if DISPLAY_SPLASH_SCREEN
display_shutdown();
#endif
entry(0, machtype, tags);
}
unsigned page_size = 0;
unsigned page_mask = 0;
#define ROUND_TO_PAGE(x,y) (((x) + (y)) & (~(y)))
static unsigned char buf[4096]; //Equal to max-supported pagesize
int boot_linux_from_mmc(void)
{
struct boot_img_hdr *hdr = (void*) buf;
struct boot_img_hdr *uhdr;
unsigned offset = 0;
unsigned long long ptn = 0;
unsigned n = 0;
const char *cmdline;
uhdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR;
if (!memcmp(uhdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(INFO, "Unified boot method!\n");
hdr = uhdr;
goto unified_boot;
}
if(!boot_into_recovery)
{
ptn = mmc_ptn_offset("boot");
if(ptn == 0) {
dprintf(CRITICAL, "ERROR: No boot partition found\n");
return -1;
}
}
else
{
ptn = mmc_ptn_offset("recovery");
if(ptn == 0) {
dprintf(CRITICAL, "ERROR: No recovery partition found\n");
return -1;
}
}
if (mmc_read(ptn + offset, (unsigned int *)buf, page_size)) {
dprintf(CRITICAL, "ERROR: Cannot read boot image header\n");
return -1;
}
if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(CRITICAL, "ERROR: Invaled boot image header\n");
return -1;
}
if (hdr->page_size && (hdr->page_size != page_size)) {
page_size = hdr->page_size;
page_mask = page_size - 1;
}
offset += page_size;
n = ROUND_TO_PAGE(hdr->kernel_size, page_mask);
if (mmc_read(ptn + offset, (void *)hdr->kernel_addr, n)) {
dprintf(CRITICAL, "ERROR: Cannot read kernel image\n");
return -1;
}
offset += n;
n = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);
if (mmc_read(ptn + offset, (void *)hdr->ramdisk_addr, n)) {
dprintf(CRITICAL, "ERROR: Cannot read ramdisk image\n");
return -1;
}
offset += n;
unified_boot:
dprintf(INFO, "\nkernel @ %x (%d bytes)\n", hdr->kernel_addr,
hdr->kernel_size);
dprintf(INFO, "ramdisk @ %x (%d bytes)\n", hdr->ramdisk_addr,
hdr->ramdisk_size);
if(hdr->cmdline[0]) {
cmdline = (char*) hdr->cmdline;
} else {
cmdline = DEFAULT_CMDLINE;
}
dprintf(INFO, "cmdline = '%s'\n", cmdline);
dprintf(INFO, "\nBooting Linux\n");
boot_linux((void *)hdr->kernel_addr, (void *)TAGS_ADDR,
(const char *)cmdline, board_machtype(),
(void *)hdr->ramdisk_addr, hdr->ramdisk_size);
return 0;
}
int boot_linux_from_flash(void)
{
struct boot_img_hdr *hdr = (void*) buf;
unsigned n;
struct ptentry *ptn;
struct ptable *ptable;
unsigned offset = 0;
const char *cmdline;
if (target_is_emmc_boot()) {
hdr = (struct boot_img_hdr *)EMMC_BOOT_IMG_HEADER_ADDR;
if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(CRITICAL, "ERROR: Invalid boot image header\n");
return -1;
}
goto continue_boot;
}
ptable = flash_get_ptable();
if (ptable == NULL) {
dprintf(CRITICAL, "ERROR: Partition table not found\n");
return -1;
}
if(!boot_into_recovery)
{
ptn = ptable_find(ptable, "boot");
if (ptn == NULL) {
dprintf(CRITICAL, "ERROR: No boot partition found\n");
return -1;
}
}
else
{
ptn = ptable_find(ptable, "recovery");
if (ptn == NULL) {
dprintf(CRITICAL, "ERROR: No recovery partition found\n");
return -1;
}
}
if (flash_read(ptn, offset, buf, page_size)) {
dprintf(CRITICAL, "ERROR: Cannot read boot image header\n");
return -1;
}
offset += page_size;
if (memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
dprintf(CRITICAL, "ERROR: Invaled boot image heador\n");
return -1;
}
if (hdr->page_size != page_size) {
dprintf(CRITICAL, "ERROR: Invaled boot image pagesize. Device pagesize: %d, Image pagesize: %d\n",page_size,hdr->page_size);
return -1;
}
n = ROUND_TO_PAGE(hdr->kernel_size, page_mask);
if (flash_read(ptn, offset, (void *)hdr->kernel_addr, n)) {
dprintf(CRITICAL, "ERROR: Cannot read kernel image\n");
return -1;
}
offset += n;
n = ROUND_TO_PAGE(hdr->ramdisk_size, page_mask);
if (flash_read(ptn, offset, (void *)hdr->ramdisk_addr, n)) {
dprintf(CRITICAL, "ERROR: Cannot read ramdisk image\n");
return -1;
}
offset += n;
continue_boot:
dprintf(INFO, "\nkernel @ %x (%d bytes)\n", hdr->kernel_addr,
hdr->kernel_size);
dprintf(INFO, "ramdisk @ %x (%d bytes)\n", hdr->ramdisk_addr,
hdr->ramdisk_size);
if(hdr->cmdline[0]) {
cmdline = (char*) hdr->cmdline;
} else {
cmdline = DEFAULT_CMDLINE;
}
dprintf(INFO, "cmdline = '%s'\n", cmdline);
/* TODO: create/pass atags to kernel */
dprintf(INFO, "\nBooting Linux\n");
boot_linux((void *)hdr->kernel_addr, (void *)TAGS_ADDR,
(const char *)cmdline, board_machtype(),
(void *)hdr->ramdisk_addr, hdr->ramdisk_size);
return 0;
}
void cmd_boot(const char *arg, void *data, unsigned sz)
{
unsigned kernel_actual;
unsigned ramdisk_actual;
static struct boot_img_hdr hdr;
char *ptr = ((char*) data);
if (sz < sizeof(hdr)) {
fastboot_fail("invalid bootimage header");
return;
}
memcpy(&hdr, data, sizeof(hdr));
/* ensure commandline is terminated */
hdr.cmdline[BOOT_ARGS_SIZE-1] = 0;
if(target_is_emmc_boot() && hdr.page_size) {
page_size = hdr.page_size;
page_mask = page_size - 1;
}
kernel_actual = ROUND_TO_PAGE(hdr.kernel_size, page_mask);
ramdisk_actual = ROUND_TO_PAGE(hdr.ramdisk_size, page_mask);
if (page_size + kernel_actual + ramdisk_actual < sz) {
fastboot_fail("incomplete bootimage");
return;
}
memmove((void*) KERNEL_ADDR, ptr + page_size, hdr.kernel_size);
memmove((void*) RAMDISK_ADDR, ptr + page_size + kernel_actual, hdr.ramdisk_size);
fastboot_okay("");
target_battery_charging_enable(0, 1);
udc_stop();
boot_linux((void*) KERNEL_ADDR, (void*) TAGS_ADDR,
(const char*) hdr.cmdline, board_machtype(),
(void*) RAMDISK_ADDR, hdr.ramdisk_size);
}
void cmd_erase(const char *arg, void *data, unsigned sz)
{
struct ptentry *ptn;
struct ptable *ptable;
ptable = flash_get_ptable();
if (ptable == NULL) {
fastboot_fail("partition table doesn't exist");
return;
}
ptn = ptable_find(ptable, arg);
if (ptn == NULL) {
fastboot_fail("unknown partition name");
return;
}
if (flash_erase(ptn)) {
fastboot_fail("failed to erase partition");
return;
}
fastboot_okay("");
}
void cmd_erase_mmc(const char *arg, void *data, unsigned sz)
{
unsigned long long ptn = 0;
unsigned int out[512] = {0};
ptn = mmc_ptn_offset(arg);
if(ptn == 0) {
fastboot_fail("partition table doesn't exist");
return;
}
/* Simple inefficient version of erase. Just writing
0 in first block */
if (mmc_write(ptn , 512, (unsigned int *)out)) {
fastboot_fail("failed to erase partition");
return;
}
fastboot_okay("");
}
void cmd_flash_mmc(const char *arg, void *data, unsigned sz)
{
unsigned long long ptn = 0;
unsigned long long size = 0;
ptn = mmc_ptn_offset(arg);
if(ptn == 0) {
fastboot_fail("partition table doesn't exist");
return;
}
if (!strcmp(arg, "boot") || !strcmp(arg, "recovery")) {
if (memcmp((void *)data, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
fastboot_fail("image is not a boot image");
return;
}
}
size = mmc_ptn_size(arg);
if (ROUND_TO_PAGE(sz,511) > size) {
fastboot_fail("size too large");
return;
}
if (mmc_write(ptn , sz, (unsigned int *)data)) {
fastboot_fail("flash write failure");
return;
}
fastboot_okay("");
return;
}
void cmd_flash(const char *arg, void *data, unsigned sz)
{
struct ptentry *ptn;
struct ptable *ptable;
unsigned extra = 0;
ptable = flash_get_ptable();
if (ptable == NULL) {
fastboot_fail("partition table doesn't exist");
return;
}
ptn = ptable_find(ptable, arg);
if (ptn == NULL) {
fastboot_fail("unknown partition name");
return;
}
if (!strcmp(ptn->name, "boot") || !strcmp(ptn->name, "recovery")) {
if (memcmp((void *)data, BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
fastboot_fail("image is not a boot image");
return;
}
}
if (!strcmp(ptn->name, "system") || !strcmp(ptn->name, "userdata")
|| !strcmp(ptn->name, "persist"))
extra = ((page_size >> 9) * 16);
else
sz = ROUND_TO_PAGE(sz, page_mask);
dprintf(INFO, "writing %d bytes to '%s'\n", sz, ptn->name);
if (flash_write(ptn, extra, data, sz)) {
fastboot_fail("flash write failure");
return;
}
dprintf(INFO, "partition '%s' updated\n", ptn->name);
fastboot_okay("");
}
void cmd_continue(const char *arg, void *data, unsigned sz)
{
fastboot_okay("");
target_battery_charging_enable(0, 1);
udc_stop();
if (target_is_emmc_boot())
{
boot_linux_from_mmc();
}
else
{
boot_linux_from_flash();
}
}
void cmd_reboot(const char *arg, void *data, unsigned sz)
{
dprintf(INFO, "rebooting the device\n");
fastboot_okay("");
reboot_device(0);
}
void cmd_reboot_bootloader(const char *arg, void *data, unsigned sz)
{
dprintf(INFO, "rebooting the device\n");
fastboot_okay("");
reboot_device(FASTBOOT_MODE);
}
void splash_screen ()
{
struct ptentry *ptn;
struct ptable *ptable;
struct fbcon_config *fb_display = NULL;
if (!target_is_emmc_boot())
{
ptable = flash_get_ptable();
if (ptable == NULL) {
dprintf(CRITICAL, "ERROR: Partition table not found\n");
return -1;
}
ptn = ptable_find(ptable, "splash");
if (ptn == NULL) {
dprintf(CRITICAL, "ERROR: No splash partition found\n");
} else {
fb_display = fbcon_display();
if (fb_display) {
if (flash_read(ptn, 0, fb_display->base,
(fb_display->width * fb_display->height * fb_display->bpp/8))) {
fbcon_clear();
dprintf(CRITICAL, "ERROR: Cannot read splash image\n");
}
}
}
}
}
void aboot_init(const struct app_descriptor *app)
{
unsigned reboot_mode = 0;
unsigned disp_init = 0;
unsigned usb_init = 0;
/* Setup page size information for nand/emmc reads */
if (target_is_emmc_boot())
{
page_size = 2048;
page_mask = page_size - 1;
}
else
{
page_size = flash_page_size();
page_mask = page_size - 1;
}
/* Display splash screen if enabled */
#if DISPLAY_SPLASH_SCREEN
display_init();
dprintf(INFO, "Diplay initialized\n");
disp_init = 1;
diplay_image_on_screen();
#endif
/* Check if we should do something other than booting up */
if (keys_get_state(KEY_HOME) != 0)
boot_into_recovery = 1;
if (keys_get_state(KEY_BACK) != 0)
goto fastboot;
if (keys_get_state(KEY_CLEAR) != 0)
goto fastboot;
#if NO_KEYPAD_DRIVER
/* With no keypad implementation, check the status of USB connection. */
/* If USB is connected then go into fastboot mode. */
usb_init = 1;
udc_init(&surf_udc_device);
if (usb_cable_status())
goto fastboot;
#endif
reboot_mode = check_reboot_mode();
if (reboot_mode == RECOVERY_MODE) {
boot_into_recovery = 1;
} else if(reboot_mode == FASTBOOT_MODE) {
goto fastboot;
}
if (target_is_emmc_boot())
{
boot_linux_from_mmc();
}
else
{
recovery_init();
boot_linux_from_flash();
}
dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting "
"to fastboot mode.\n");
fastboot:
if(!usb_init)
udc_init(&surf_udc_device);
fastboot_register("boot", cmd_boot);
if (target_is_emmc_boot())
{
fastboot_register("flash:", cmd_flash_mmc);
fastboot_register("erase:", cmd_erase_mmc);
}
else
{
fastboot_register("flash:", cmd_flash);
fastboot_register("erase:", cmd_erase);
}
fastboot_register("continue", cmd_continue);
fastboot_register("reboot", cmd_reboot);
fastboot_register("reboot-bootloader", cmd_reboot_bootloader);
fastboot_publish("product", TARGET(BOARD));
fastboot_publish("kernel", "lk");
fastboot_init(target_get_scratch_address(), 120 * 1024 * 1024);
udc_start();
target_battery_charging_enable(1, 0);
}
APP_START(aboot)
.init = aboot_init,
APP_END