From 7f13e150cf328414920311903f3096accceece44 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 8 Sep 2011 16:55:35 -0700 Subject: [PATCH] more cwr5 work. Change-Id: I72bb14ef25a9067ccde3e40ea989c7990512687a --- Android.mk | 3 +- bootloader.c | 150 +++++++++++++++++++++++++++ firmware.c | 131 +++++++++++++++++++++++ install.c | 6 ++ recovery.c | 3 + res/images/icon_firmware_error.png | Bin 0 -> 8088 bytes res/images/icon_firmware_install.png | Bin 0 -> 12717 bytes ui.c | 2 + 8 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 firmware.c create mode 100644 res/images/icon_firmware_error.png create mode 100755 res/images/icon_firmware_install.png diff --git a/Android.mk b/Android.mk index 255ccd0..9155134 100644 --- a/Android.mk +++ b/Android.mk @@ -19,6 +19,7 @@ LOCAL_SRC_FILES := \ extendedcommands.c \ nandroid.c \ ../../system/core/toolbox/reboot.c \ + firmware.c \ edifyscripting.c \ setprop.c @@ -36,7 +37,7 @@ else RECOVERY_NAME := CWM-based Recovery endif -RECOVERY_VERSION := $(RECOVERY_NAME) v5.0.2.1 +RECOVERY_VERSION := $(RECOVERY_NAME) v5.0.2.2 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 diff --git a/bootloader.c b/bootloader.c index d455923..d4039d8 100644 --- a/bootloader.c +++ b/bootloader.c @@ -179,3 +179,153 @@ static int set_bootloader_message_block(const struct bootloader_message *in, } return 0; } + +/* Update Image + * + * - will be stored in the "cache" partition + * - bad blocks will be ignored, like boot.img and recovery.img + * - the first block will be the image header (described below) + * - the size is in BYTES, inclusive of the header + * - offsets are in BYTES from the start of the update header + * - two raw bitmaps will be included, the "busy" and "fail" bitmaps + * - for dream, the bitmaps will be 320x480x16bpp RGB565 + */ + +#define UPDATE_MAGIC "MSM-RADIO-UPDATE" +#define UPDATE_MAGIC_SIZE 16 +#define UPDATE_VERSION 0x00010000 + +struct update_header { + unsigned char MAGIC[UPDATE_MAGIC_SIZE]; + + unsigned version; + unsigned size; + + unsigned image_offset; + unsigned image_length; + + unsigned bitmap_width; + unsigned bitmap_height; + unsigned bitmap_bpp; + + unsigned busy_bitmap_offset; + unsigned busy_bitmap_length; + + unsigned fail_bitmap_offset; + unsigned fail_bitmap_length; +}; + +int write_update_for_bootloader( + const char *update, int update_length, + int bitmap_width, int bitmap_height, int bitmap_bpp, + const char *busy_bitmap, const char *fail_bitmap) { + if (ensure_path_unmounted("/cache")) { + LOGE("Can't unmount /cache\n"); + return -1; + } + + const MtdPartition *part = mtd_find_partition_by_name("cache"); + if (part == NULL) { + LOGE("Can't find cache\n"); + return -1; + } + + MtdWriteContext *write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't open cache\n(%s)\n", strerror(errno)); + return -1; + } + + /* Write an invalid (zero) header first, to disable any previous + * update and any other structured contents (like a filesystem), + * and as a placeholder for the amount of space required. + */ + + struct update_header header; + memset(&header, 0, sizeof(header)); + const ssize_t header_size = sizeof(header); + if (mtd_write_data(write, (char*) &header, header_size) != header_size) { + LOGE("Can't write header to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + /* Write each section individually block-aligned, so we can write + * each block independently without complicated buffering. + */ + + memcpy(&header.MAGIC, UPDATE_MAGIC, UPDATE_MAGIC_SIZE); + header.version = UPDATE_VERSION; + header.size = header_size; + + off_t image_start_pos = mtd_erase_blocks(write, 0); + header.image_length = update_length; + if ((int) header.image_offset == -1 || + mtd_write_data(write, update, update_length) != update_length) { + LOGE("Can't write update to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + off_t busy_start_pos = mtd_erase_blocks(write, 0); + header.image_offset = mtd_find_write_start(write, image_start_pos); + + header.bitmap_width = bitmap_width; + header.bitmap_height = bitmap_height; + header.bitmap_bpp = bitmap_bpp; + + int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height; + + header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0; + if ((int) header.busy_bitmap_offset == -1 || + mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) { + LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + off_t fail_start_pos = mtd_erase_blocks(write, 0); + header.busy_bitmap_offset = mtd_find_write_start(write, busy_start_pos); + + header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0; + if ((int) header.fail_bitmap_offset == -1 || + mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) { + LOGE("Can't write bitmap to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + mtd_erase_blocks(write, 0); + header.fail_bitmap_offset = mtd_find_write_start(write, fail_start_pos); + + /* Write the header last, after all the blocks it refers to, so that + * when the magic number is installed everything is valid. + */ + + if (mtd_write_close(write)) { + LOGE("Can't finish writing cache\n(%s)\n", strerror(errno)); + return -1; + } + + write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't reopen cache\n(%s)\n", strerror(errno)); + return -1; + } + + if (mtd_write_data(write, (char*) &header, header_size) != header_size) { + LOGE("Can't rewrite header to cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_erase_blocks(write, 0) != image_start_pos) { + LOGE("Misalignment rewriting cache\n(%s)\n", strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_write_close(write)) { + LOGE("Can't finish header of cache\n(%s)\n", strerror(errno)); + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/firmware.c b/firmware.c new file mode 100644 index 0000000..4e5c24f --- /dev/null +++ b/firmware.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bootloader.h" +#include "common.h" +#include "firmware.h" +#include "roots.h" + +#include +#include +#include + +static const char *update_type = NULL; +static const char *update_data = NULL; +static int update_length = 0; + +int remember_firmware_update(const char *type, const char *data, int length) { + if (update_type != NULL || update_data != NULL) { + LOGE("Multiple firmware images\n"); + return -1; + } + + update_type = type; + update_data = data; + update_length = length; + return 0; +} + +// Return true if there is a firmware update pending. +int firmware_update_pending() { + return update_data != NULL && update_length > 0; +} + +/* Bootloader / Recovery Flow + * + * On every boot, the bootloader will read the bootloader_message + * from flash and check the command field. The bootloader should + * deal with the command field not having a 0 terminator correctly + * (so as to not crash if the block is invalid or corrupt). + * + * The bootloader will have to publish the partition that contains + * the bootloader_message to the linux kernel so it can update it. + * + * if command == "boot-recovery" -> boot recovery.img + * else if command == "update-radio" -> update radio image (below) + * else if command == "update-hboot" -> update hboot image (below) + * else -> boot boot.img (normal boot) + * + * Radio/Hboot Update Flow + * 1. the bootloader will attempt to load and validate the header + * 2. if the header is invalid, status="invalid-update", goto #8 + * 3. display the busy image on-screen + * 4. if the update image is invalid, status="invalid-radio-image", goto #8 + * 5. attempt to update the firmware (depending on the command) + * 6. if successful, status="okay", goto #8 + * 7. if failed, and the old image can still boot, status="failed-update" + * 8. write the bootloader_message, leaving the recovery field + * unchanged, updating status, and setting command to + * "boot-recovery" + * 9. reboot + * + * The bootloader will not modify or erase the cache partition. + * It is recovery's responsibility to clean up the mess afterwards. + */ + +int maybe_install_firmware_update(const char *send_intent) { + if (update_data == NULL || update_length == 0) return 0; + + /* We destroy the cache partition to pass the update image to the + * bootloader, so all we can really do afterwards is wipe cache and reboot. + * Set up this instruction now, in case we're interrupted while writing. + */ + + struct bootloader_message boot; + memset(&boot, 0, sizeof(boot)); + strlcpy(boot.command, "boot-recovery", sizeof(boot.command)); + strlcpy(boot.recovery, "recovery\n--wipe_cache\n", sizeof(boot.command)); + if (send_intent != NULL) { + strlcat(boot.recovery, "--send_intent=", sizeof(boot.recovery)); + strlcat(boot.recovery, send_intent, sizeof(boot.recovery)); + strlcat(boot.recovery, "\n", sizeof(boot.recovery)); + } + if (set_bootloader_message(&boot)) return -1; + + int width = 0, height = 0, bpp = 0; + char *busy_image = ui_copy_image( + BACKGROUND_ICON_FIRMWARE_INSTALLING, &width, &height, &bpp); + char *fail_image = ui_copy_image( + BACKGROUND_ICON_FIRMWARE_ERROR, &width, &height, &bpp); + + ui_print("Writing %s image...\n", update_type); + if (write_update_for_bootloader( + update_data, update_length, + width, height, bpp, busy_image, fail_image)) { + LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno)); + format_volume("/cache"); // Attempt to clean cache up, at least. + return -1; + } + + free(busy_image); + free(fail_image); + + /* The update image is fully written, so now we can instruct the bootloader + * to install it. (After doing so, it will come back here, and we will + * wipe the cache and reboot into the system.) + */ + snprintf(boot.command, sizeof(boot.command), "update-%s", update_type); + if (set_bootloader_message(&boot)) { + format_volume("/cache"); + return -1; + } + + reboot(RB_AUTOBOOT); + + // Can't reboot? WTF? + LOGE("Can't reboot\n"); + return -1; +} diff --git a/install.c b/install.c index 0b35ee7..cae283c 100644 --- a/install.c +++ b/install.c @@ -92,6 +92,12 @@ handle_firmware_update(char* type, char* filename, ZipArchive* zip) { fclose(f); } + if (remember_firmware_update(type, data, data_size)) { + LOGE("Can't store %s image\n", type); + free(data); + return INSTALL_ERROR; + } + free(filename); return INSTALL_SUCCESS; diff --git a/recovery.c b/recovery.c index d0dee32..7e4ed09 100644 --- a/recovery.c +++ b/recovery.c @@ -955,6 +955,9 @@ main(int argc, char **argv) { prompt_and_wait(); } + // If there is a radio image pending, reboot now to install it. + maybe_install_firmware_update(send_intent); + // Otherwise, get ready to boot the main system... finish_recovery(send_intent); if(!poweroff) diff --git a/res/images/icon_firmware_error.png b/res/images/icon_firmware_error.png new file mode 100644 index 0000000000000000000000000000000000000000..0c32c9ede10471bf1b40f37928048ba0edef9ef4 GIT binary patch literal 8088 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 HW*hN;6<-!8 literal 0 HcmV?d00001 diff --git a/res/images/icon_firmware_install.png b/res/images/icon_firmware_install.png new file mode 100755 index 0000000000000000000000000000000000000000..2da9e5ffe6bc90d78e179f86382980586ab9f809 GIT binary patch literal 12717 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