diff --git a/bootloader.c b/bootloader.c index 38b5651..bc79bee 100644 --- a/bootloader.c +++ b/bootloader.c @@ -119,3 +119,153 @@ int set_bootloader_message(const struct bootloader_message *in) { LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : ""); 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_root_path_unmounted(CACHE_NAME)) { + LOGE("Can't unmount %s\n", CACHE_NAME); + return -1; + } + + const MtdPartition *part = get_root_mtd_partition(CACHE_NAME); + if (part == NULL) { + LOGE("Can't find %s\n", CACHE_NAME); + return -1; + } + + MtdWriteContext *write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't open %s\n(%s)\n", CACHE_NAME, 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 %s\n(%s)\n", CACHE_NAME, 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 %s\n(%s)\n", CACHE_NAME, 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 %s\n(%s)\n", CACHE_NAME, 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 %s\n(%s)\n", CACHE_NAME, 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 %s\n(%s)\n", CACHE_NAME, strerror(errno)); + return -1; + } + + write = mtd_write_partition(part); + if (write == NULL) { + LOGE("Can't reopen %s\n(%s)\n", CACHE_NAME, strerror(errno)); + return -1; + } + + if (mtd_write_data(write, (char*) &header, header_size) != header_size) { + LOGE("Can't rewrite header to %s\n(%s)\n", CACHE_NAME, strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_erase_blocks(write, 0) != image_start_pos) { + LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno)); + mtd_write_close(write); + return -1; + } + + if (mtd_write_close(write)) { + LOGE("Can't finish header of %s\n(%s)\n", CACHE_NAME, strerror(errno)); + return -1; + } + + return 0; +} diff --git a/bootloader.h b/bootloader.h index 2e749aa..3d4302f 100644 --- a/bootloader.h +++ b/bootloader.h @@ -47,4 +47,13 @@ struct bootloader_message { int get_bootloader_message(struct bootloader_message *out); int set_bootloader_message(const struct bootloader_message *in); +/* Write an update to the cache partition for update-radio or update-hboot. + * Note, this destroys any filesystem on the cache partition! + * The expected bitmap format is 240x320, 16bpp (2Bpp), RGB 5:6:5. + */ +int write_update_for_bootloader( + const char *update, int update_len, + int bitmap_width, int bitmap_height, int bitmap_bpp, + const char *busy_bitmap, const char *error_bitmap); + #endif diff --git a/common.h b/common.h index fd8605a..81d9029 100644 --- a/common.h +++ b/common.h @@ -52,10 +52,17 @@ enum { BACKGROUND_ICON_NONE, BACKGROUND_ICON_INSTALLING, BACKGROUND_ICON_ERROR, + BACKGROUND_ICON_FIRMWARE_INSTALLING, + BACKGROUND_ICON_FIRMWARE_ERROR, NUM_BACKGROUND_ICONS }; void ui_set_background(int icon); +// Get a malloc'd copy of the screen image showing (only) the specified icon. +// Also returns the width, height, and bits per pixel of the returned image. +// TODO: Use some sort of "struct Bitmap" here instead of all these variables? +char *ui_copy_image(int icon, int *width, int *height, int *bpp); + // Show a progress bar and define the scope of the next operation: // portion - fraction of the progress bar the next operation will use // seconds - expected time interval (progress bar moves at this minimum rate) diff --git a/firmware.c b/firmware.c new file mode 100644 index 0000000..e2e4fe6 --- /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_root_device("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_root_device("CACHE:"); + return -1; + } + + reboot(RB_AUTOBOOT); + + // Can't reboot? WTF? + LOGE("Can't reboot\n"); + return -1; +} diff --git a/firmware.h b/firmware.h new file mode 100644 index 0000000..aeb8f97 --- /dev/null +++ b/firmware.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef _RECOVERY_FIRMWARE_H +#define _RECOVERY_FIRMWARE_H + +/* Save a radio or bootloader update image for later installation. + * The type should be one of "hboot" or "radio". + * Takes ownership of type and data. Returns nonzero on error. + */ +int remember_firmware_update(const char *type, const char *data, int length); + +/* Returns true if a firmware update has been saved. */ +int firmware_update_pending(); + +/* If an update was saved, reboot into the bootloader now to install it. + * Returns 0 if no radio image was defined, nonzero on error, + * doesn't return at all on success... + */ +int maybe_install_firmware_update(const char *send_intent); + +#endif diff --git a/ui.c b/ui.c index e08c4a4..4f28725 100644 --- a/ui.c +++ b/ui.c @@ -359,6 +359,23 @@ void ui_init(void) pthread_create(&t, NULL, input_thread, NULL); } +char *ui_copy_image(int icon, int *width, int *height, int *bpp) { + pthread_mutex_lock(&gUpdateMutex); + draw_background_locked(gBackgroundIcon[icon]); + *width = gr_fb_width(); + *height = gr_fb_height(); + *bpp = sizeof(gr_pixel) * 8; + int size = *width * *height * sizeof(gr_pixel); + char *ret = malloc(size); + if (ret == NULL) { + LOGE("Can't allocate %d bytes for image\n", size); + } else { + memcpy(ret, gr_fb_data(), size); + } + pthread_mutex_unlock(&gUpdateMutex); + return ret; +} + void ui_set_background(int icon) { pthread_mutex_lock(&gUpdateMutex);