| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * 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. |
| * |
| * 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 AND FITNESS |
| * FOR A PARTICULAR PURPOSE 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 <boot/boot.h> |
| #include <boot/flash.h> |
| #include <boot/board.h> |
| #include <boot/usb.h> |
| |
| #include <boot/bootimg.h> |
| #include <boot/tags.h> |
| |
| #include <boot/gpio.h> |
| |
| #define VERSION "0.5" |
| |
| #define REQUIRE_SIGNATURE 0 |
| |
| #if REQUIRE_SIGNATURE |
| unsigned key_engineering[2 + 64 + 64] = { |
| 64,0x5b022317,-60769447,648742897,-13657530,585562035,591851935, |
| 454860199,-1809625305,1868200692,-155297008,-1688439840,-1333607631, |
| -483027189,-2051438457,1030069735,819944365,2133377257,-1978924214, |
| 2109678622,1974978919,-1811463608,765849268,1984092281,921245328, |
| -1055062768,1487475997,1209618652,871985152,-611178965,-2057018571, |
| 335641539,-1196119550,1550548229,-356223887,1909799623,1281016007, |
| 957001635,1005656532,-1027634024,-1576447610,-1917246637,589192795, |
| -1137386186,-1958135372,1933245070,64958951,-1820428322,-1577697840, |
| 1824253519,555306239,-1588272058,-1925773018,1205934271,-836584444, |
| -1140961670,-185198349,1293769947,37045923,1516796974,-297288651, |
| 651582073,-1337054592,-543971216,-1706823885,-1040652818,-594113104, |
| 260093481,-1277656496,56493468,1577037283,773995876,244894933, |
| -2075797967,783894843,880611008,-1433369702,380946504,-2081431477, |
| 1377832804,2089455451,-410001201,1245307237,-1228170341,-2062569137, |
| -1327614308,-1671042654,1242248660,-418803721,40890010,-1806767460, |
| -1468529145,-1058158532,1243817302,-527795003,175453645,-210650325, |
| -827053868,-571422860,886300657,2129677324,846504590,-1413102805, |
| -1287448511,-1991140134,56194155,1375685594,-129884114,1393568535, |
| -1098719620,-935279550,1717137954,-1782544741,272581921,-669183778, |
| 584824755,1434974827,-1122387971,-810584927,-2147338547,-937541680, |
| -313561073,5506366,-1594059648,-1744451574,1896015834,1496367069, |
| 1742853908,508461291,1905056764 |
| }; |
| #endif |
| |
| const char *get_fastboot_version(void) |
| { |
| return VERSION; |
| } |
| |
| unsigned linux_type = 0; |
| unsigned linux_tags = 0; |
| |
| unsigned ramdisk_addr = 0x10400000; |
| unsigned ramdisk_size = 0; |
| unsigned kernel_addr = 0x10008000; |
| unsigned kernel_size = 0; |
| |
| static void fixup_tags(unsigned *tags, unsigned *out, const char *cmdline) |
| { |
| unsigned *newtags = (unsigned *) 0x10004000; |
| unsigned *np = newtags; |
| unsigned n; |
| char *oldcmdline = ""; |
| |
| if(cmdline == 0) cmdline = ""; |
| |
| /* CORE */ |
| *np++ = 2; |
| *np++ = 0x54410001; |
| |
| if(tags != 0) { |
| while(*tags) { |
| if(tags[1] == 0x54410001) { |
| /* skip core tag */ |
| tags += tags[0]; |
| } else if((tags[1] == 0x54420005) && (ramdisk_size != 0)) { |
| /* skip ramdisk if we have one of our own */ |
| tags += tags[0]; |
| } else if((tags[1] == 0x54410009) && (cmdline[0])) { |
| /* skip existing cmdline so we can replace it */ |
| oldcmdline = (char*) (tags + 2); |
| tags += tags[0]; |
| } else { |
| /* copy any unknown tags */ |
| n = tags[0]; |
| while(n-- > 0) { |
| *np++ = *tags++; |
| } |
| } |
| } |
| } |
| |
| /* create a ramdisk tag if we need to */ |
| if(ramdisk_size) { |
| *np++ = 4; |
| *np++ = 0x54420005; |
| *np++ = ramdisk_addr; |
| *np++ = ramdisk_size; |
| } |
| |
| dprintf("cmdline: '%s'\n", oldcmdline); |
| dprintf("cmdline: '%s'\n", cmdline); |
| |
| /* create a cmdline tag if we need to */ |
| if(cmdline[0]) { |
| int len; |
| char *str = (char*) (np + 2); |
| |
| len = strlen(oldcmdline); |
| if(len) { |
| memcpy(str, oldcmdline, len); |
| str += len; |
| *str++ = ' '; |
| } |
| |
| len = strlen(cmdline); |
| memcpy(str, cmdline, len); |
| str += len; |
| *str++ = 0; |
| |
| /* length in words */ |
| len = ((str - ((char*) (np + 2))) + 3) / 4; |
| |
| dprintf("CMDLINE: '%s'\n", ((char*) (np + 2))); |
| |
| *np++ = 2 + len; |
| *np++ = 0x54410009; |
| |
| np += len; |
| } |
| |
| /* add footer tag */ |
| *np++ = 0; |
| *np++ = 0; |
| |
| /* copy it all back to the original tags area */ |
| while(newtags < np) { |
| *out++ = *newtags++; |
| } |
| } |
| |
| static char cmdline[BOOT_ARGS_SIZE]; |
| |
| static void boot_linux(void) |
| { |
| unsigned *tags = (unsigned*) 0x10000100; |
| void (*entry)(unsigned,unsigned,unsigned) = (void*) kernel_addr; |
| |
| if(linux_type == 0) { |
| linux_type = board_machtype(); |
| } |
| |
| fixup_tags((unsigned*) linux_tags, tags, cmdline); |
| |
| entry(0, linux_type, tags); |
| |
| for(;;); |
| } |
| |
| /* convert a boot_image at kernel_addr into a kernel + ramdisk + tags */ |
| static int init_boot_linux(void) |
| { |
| boot_img_hdr *hdr = (void*) kernel_addr; |
| unsigned page_mask = 2047; |
| unsigned kernel_actual; |
| unsigned ramdisk_actual; |
| unsigned second_actual; |
| |
| if((kernel_size < 2048) || memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)){ |
| dprintf("bootimg: bad header\n"); |
| return -1; |
| } |
| |
| if(hdr->page_size != 2048) { |
| dprintf("bootimg: invalid page size\n"); |
| return -1; |
| } |
| |
| kernel_actual = (hdr->kernel_size + page_mask) & (~page_mask); |
| ramdisk_actual = (hdr->ramdisk_size + page_mask) & (~page_mask); |
| second_actual = (hdr->second_size + page_mask) & (~page_mask); |
| |
| if(kernel_size != (kernel_actual + ramdisk_actual + second_actual + 2048)) { |
| dprintf("bootimg: invalid image size"); |
| return -1; |
| } |
| |
| /* XXX process commandline here */ |
| if(hdr->cmdline[0]){ |
| hdr->cmdline[BOOT_ARGS_SIZE - 1] = 0; |
| memcpy(cmdline, hdr->cmdline, BOOT_ARGS_SIZE); |
| } |
| |
| /* XXX how to validate addresses? */ |
| ramdisk_addr = hdr->ramdisk_addr; |
| ramdisk_size = hdr->ramdisk_size; |
| |
| kernel_addr = hdr->kernel_addr; |
| kernel_size = hdr->kernel_size; |
| |
| dprintf("bootimg: kernel addr=%x size=%x\n", |
| kernel_addr, kernel_size); |
| dprintf("bootimg: ramdisk addr=%x size=%x\n", |
| ramdisk_addr, ramdisk_size); |
| |
| memcpy((void*) ramdisk_addr, |
| hdr->magic + 2048 + kernel_actual, |
| ramdisk_size); |
| |
| memcpy((void*) kernel_addr, |
| hdr->magic + 2048, |
| kernel_size); |
| |
| return 0; |
| } |
| |
| static struct usb_endpoint *ep1in, *ep1out; |
| static struct usb_request *rx_req, *tx_req; |
| static unsigned rx_addr; |
| static unsigned rx_length; |
| |
| static char *cmdbuf; |
| |
| static void usb_rx_cmd_complete(struct usb_request *req, unsigned actual, int status); |
| static void usb_rx_data_complete(struct usb_request *req, unsigned actual, int status); |
| |
| static void rx_cmd(void) |
| { |
| struct usb_request *req = rx_req; |
| req->buf = cmdbuf; |
| req->length = 4096; |
| req->complete = usb_rx_cmd_complete; |
| usb_queue_req(ep1out, req); |
| } |
| |
| static void rx_data(void) |
| { |
| struct usb_request *req = rx_req; |
| req->buf = (void*) rx_addr; |
| req->length = (rx_length > 4096) ? 4096 : rx_length; |
| req->complete = usb_rx_data_complete; |
| usb_queue_req(ep1out, req); |
| } |
| |
| static void tx_status(const char *status) |
| { |
| struct usb_request *req = tx_req; |
| int len = strlen(status); |
| // dprintf("tx_status('%s')\n", status); |
| memcpy(req->buf, status, len); |
| req->length = len; |
| req->complete = 0; |
| usb_queue_req(ep1in, req); |
| } |
| |
| static void usb_rx_data_complete(struct usb_request *req, unsigned actual, int status) |
| { |
| if(status != 0) return; |
| |
| if(actual > rx_length) { |
| actual = rx_length; |
| } |
| |
| rx_addr += actual; |
| rx_length -= actual; |
| |
| if(rx_length > 0) { |
| rx_data(); |
| } else { |
| tx_status("OKAY"); |
| rx_cmd(); |
| } |
| } |
| |
| static unsigned hex2unsigned(char *x) |
| { |
| unsigned n = 0; |
| |
| while(*x) { |
| switch(*x) { |
| case '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': |
| n = (n << 4) | (*x - '0'); |
| break; |
| case 'a': case 'b': case 'c': |
| case 'd': case 'e': case 'f': |
| n = (n << 4) | (*x - 'a' + 10); |
| break; |
| case 'A': case 'B': case 'C': |
| case 'D': case 'E': case 'F': |
| n = (n << 4) | (*x - 'A' + 10); |
| break; |
| default: |
| return n; |
| } |
| x++; |
| } |
| |
| return n; |
| } |
| |
| static void num_to_hex8(unsigned n, char *out) |
| { |
| static char tohex[16] = "0123456789abcdef"; |
| int i; |
| for(i = 7; i >= 0; i--) { |
| out[i] = tohex[n & 15]; |
| n >>= 4; |
| } |
| out[8] = 0; |
| } |
| |
| extern char serialno[]; |
| |
| static char signature[SIGNATURE_SIZE]; |
| |
| static void usb_rx_cmd_complete(struct usb_request *req, unsigned actual, int status) |
| { |
| if(status != 0) return; |
| |
| if(actual > 4095) actual = 4095; |
| cmdbuf[actual] = 0; |
| |
| dprintf("\n> %s\n",cmdbuf); |
| |
| // dprintf("usb_rx_cmd_complete() '%s'\n", cmdbuf); |
| |
| if(memcmp(cmdbuf, "reboot", 6) == 0) { |
| tx_status("OKAY"); |
| rx_cmd(); |
| mdelay(100); |
| board_reboot(); |
| } |
| #if 0 |
| if(memcmp(cmdbuf, "debug:", 6) == 0) { |
| void debug(char *cmd, char *resp); |
| memcpy(cmdbuf, "OKAY", 5); |
| tx_status(cmdbuf); |
| rx_cmd(); |
| mdelay(5000); |
| dprintf("NOW!\n"); |
| debug(cmdbuf + 6, cmdbuf + 4); |
| return; |
| } |
| #endif |
| if(memcmp(cmdbuf, "getvar:", 7) == 0) { |
| char response[64]; |
| strcpy(response,"OKAY"); |
| |
| if(!strcmp(cmdbuf + 7, "version")) { |
| strcpy(response + 4, VERSION); |
| } else if(!strcmp(cmdbuf + 7, "product")) { |
| strcpy(response + 4, PRODUCTNAME); |
| } else if(!strcmp(cmdbuf + 7, "serialno")) { |
| strcpy(response + 4, serialno); |
| } else { |
| board_getvar(cmdbuf + 7, response + 4); |
| } |
| tx_status(response); |
| rx_cmd(); |
| return; |
| } |
| |
| if(memcmp(cmdbuf, "download:", 9) == 0) { |
| char status[16]; |
| rx_addr = kernel_addr; |
| rx_length = hex2unsigned(cmdbuf + 9); |
| if (rx_length > (64*1024*1024)) { |
| tx_status("FAILdata too large"); |
| rx_cmd(); |
| return; |
| } |
| kernel_size = rx_length; |
| dprintf("recv data addr=%x size=%x\n", rx_addr, rx_length); |
| strcpy(status,"DATA"); |
| num_to_hex8(rx_length, status + 4); |
| tx_status(status); |
| rx_data(); |
| return; |
| } |
| |
| if(memcmp(cmdbuf, "erase:", 6) == 0){ |
| struct ptentry *ptn; |
| ptn = flash_find_ptn(cmdbuf + 6); |
| if(ptn == 0) { |
| tx_status("FAILpartition does not exist"); |
| rx_cmd(); |
| return; |
| } |
| dprintf("erasing '%s'\n", ptn->name); |
| cprintf("erasing '%s'", ptn->name); |
| if(flash_erase(ptn)) { |
| tx_status("FAILfailed to erase partition"); |
| rx_cmd(); |
| cprintf(" - FAIL\n"); |
| return; |
| } else { |
| dprintf("partition '%s' erased\n", ptn->name); |
| cprintf(" - OKAY\n"); |
| } |
| tx_status("OKAY"); |
| rx_cmd(); |
| return; |
| } |
| |
| if(memcmp(cmdbuf, "flash:", 6) == 0){ |
| struct ptentry *ptn; |
| int extra = 0; |
| ptn = flash_find_ptn(cmdbuf + 6); |
| if(kernel_size == 0) { |
| tx_status("FAILno image downloaded"); |
| rx_cmd(); |
| return; |
| } |
| if(ptn == 0) { |
| tx_status("FAILpartition does not exist"); |
| rx_cmd(); |
| return; |
| } |
| if(!strcmp(ptn->name,"boot") || !strcmp(ptn->name,"recovery")) { |
| if(memcmp((void*) kernel_addr, BOOT_MAGIC, BOOT_MAGIC_SIZE)) { |
| tx_status("FAILimage is not a boot image"); |
| rx_cmd(); |
| return; |
| } |
| } |
| #if REQUIRE_SIGNATURE |
| { |
| unsigned char digest[DIGEST_SIZE]; |
| compute_digest((void*) kernel_addr, kernel_size, digest); |
| if (is_signature_okay(digest, signature, key_engineering)) { |
| dprintf("verified by engineering key\n"); |
| } else { |
| tx_status("FAILsignature did not verify"); |
| rx_cmd(); |
| return; |
| } |
| } |
| #endif |
| if(!strcmp(ptn->name,"system") || !strcmp(ptn->name,"userdata")) { |
| extra = 64; |
| } else { |
| kernel_size = (kernel_size + 2047) & (~2047); |
| } |
| dprintf("writing %d bytes to '%s'\n", |
| kernel_size, ptn->name); |
| cprintf("writing '%s' (%d bytes)", ptn->name, kernel_size); |
| if(flash_write(ptn, extra, (void*) kernel_addr, kernel_size)) { |
| tx_status("FAILflash write failure"); |
| rx_cmd(); |
| cprintf(" - FAIL\n"); |
| return; |
| } else { |
| dprintf("partition '%s' updated\n", ptn->name); |
| cprintf(" - OKAY\n"); |
| } |
| tx_status("OKAY"); |
| rx_cmd(); |
| return; |
| } |
| if(memcmp(cmdbuf, "boot", 4) == 0) { |
| if(init_boot_linux()) { |
| tx_status("FAILinvalid boot image"); |
| rx_cmd(); |
| return; |
| } |
| dprintf("booting linux...\n"); |
| cprintf("\nbooting linux...\n"); |
| tx_status("OKAY"); |
| mdelay(10); |
| usb_shutdown(); |
| boot_linux(); |
| return; |
| } |
| if(memcmp(cmdbuf, "signature", 9) == 0) { |
| if (kernel_size != SIGNATURE_SIZE) { |
| tx_status("FAILsignature not 256 bytes long"); |
| rx_cmd(); |
| return; |
| } |
| memcpy(signature, (void*)kernel_addr, SIGNATURE_SIZE); |
| tx_status("OKAY"); |
| rx_cmd(); |
| return; |
| } |
| |
| tx_status("FAILinvalid command"); |
| rx_cmd(); |
| } |
| |
| void usb_status(unsigned online, unsigned highspeed) |
| { |
| if(online) { |
| dprintf("usb: online (%s)\n", highspeed ? "highspeed" : "fullspeed"); |
| rx_cmd(); |
| } |
| } |
| |
| void usbloader_init(void) |
| { |
| usb_init(); |
| |
| ep1out = usb_endpoint_alloc(1, 0, 512); |
| ep1in = usb_endpoint_alloc(1, 1, 512); |
| rx_req = usb_request_alloc(4096); |
| tx_req = usb_request_alloc(4096); |
| cmdbuf = rx_req->buf; |
| |
| boot_register_poll_func(usb_poll); |
| } |