/** * Copyright (c) 2011 NVIDIA Corporation. All rights reserved. * * See file CREDITS for list of people who contributed to this * project. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ /* * data_layout.c - Code to manage the layout of data in the boot device. * */ #include "data_layout.h" #include "cbootimage.h" #include "crypto.h" #include "set.h" #include typedef struct blk_data_rec { u_int32_t blk_number; u_int32_t pages_used; /* pages always used starting from 0. */ u_int8_t *data; /* Pointer to ECC errors? */ struct blk_data_rec *next; } block_data; /* Function prototypes */ static block_data *new_block(u_int32_t blk_number, u_int32_t block_size); static block_data *find_block(u_int32_t blk_number, block_data *block_list); static block_data *add_block(u_int32_t blk_number, block_data **block_list, u_int32_t block_size); static int erase_block(build_image_context *context, u_int32_t blk_number); static int write_page(build_image_context *context, u_int32_t blk_number, u_int32_t page_number, u_int8_t *data); static void insert_padding(u_int8_t *data, u_int32_t length); static void write_padding(u_int8_t *data, u_int32_t length); static int write_bct(build_image_context *context, u_int32_t block, u_int32_t bct_slot); static void set_bl_data(build_image_context *context, u_int32_t instance, u_int32_t start_blk, u_int32_t start_page, u_int32_t length); static int write_bootloaders(build_image_context *context); static void find_new_journal_blk(build_image_context *context); static int begin_update (build_image_context *context); static int finish_update(build_image_context *context); static u_int32_t iceil_log2(u_int32_t a, u_int32_t b) { return (a + (1 << b) - 1) >> b; } static block_data *new_block(u_int32_t blk_number, u_int32_t block_size) { block_data *new_block = malloc(sizeof(block_data)); if (new_block == NULL) return NULL; new_block->blk_number = blk_number; new_block->pages_used = 0; new_block->data = malloc(block_size); if (new_block->data == NULL) { free(new_block); return NULL; } new_block->next = NULL; memset(new_block->data, 0, block_size); return new_block; } block_data *new_block_list(void) { return NULL; } void destroy_block_list(block_data *block_list) { block_data *next; while (block_list) { next = block_list->next; free(block_list->data); free(block_list); block_list = next; } } static block_data *find_block(u_int32_t blk_number, block_data *block_list) { while (block_list) { if (block_list->blk_number == blk_number) return block_list; block_list = block_list->next; } return NULL; } /* Returns pointer to block after adding it to block_list, if needed. */ static block_data *add_block(u_int32_t blk_number, block_data **block_list, u_int32_t block_size) { block_data *block = find_block(blk_number,*block_list); block_data *parent; if (block == NULL) { block = new_block(blk_number, block_size); if (block == NULL) return block; /* Insert block into the list */ if ((*block_list == NULL) || (blk_number < (*block_list)->blk_number)) { block->next = *block_list; *block_list = block; } else { /* Search for the correct place to insert the block. */ parent = *block_list; while (parent->next != NULL && parent->next->blk_number < blk_number) { parent = parent->next; } block->next = parent->next; parent->next = block; } } return block; } static int erase_block(build_image_context *context, u_int32_t blk_number) { block_data *block; assert(context != NULL); block = add_block(blk_number, &(context->memory), context->block_size); if (block == NULL) return -ENOMEM; if (block->data == NULL) return -ENOMEM; memset(block->data, 0, context->block_size); block->pages_used = 0; return 0; } static int write_page(build_image_context *context, u_int32_t blk_number, u_int32_t page_number, u_int8_t *data) { block_data *block; u_int8_t *page_ptr; assert(context); block = add_block(blk_number, &(context->memory), context->block_size); if (block == NULL) return -ENOMEM; if (block->data == NULL) return -ENOMEM; assert(((page_number + 1) * context->page_size) <= context->block_size); if (block->pages_used != page_number) { printf("Warning: Writing page in block out of order.\n"); printf(" block=%d page=%d\n", blk_number, page_number); } page_ptr = block->data + (page_number * context->page_size); memcpy(page_ptr, data, context->page_size); if (block->pages_used < (page_number+1)) block->pages_used = page_number+1; return 0; } static void insert_padding(u_int8_t *data, u_int32_t length) { u_int32_t aes_blks; u_int32_t remaining; aes_blks = iceil_log2(length, NVBOOT_AES_BLOCK_SIZE_LOG2); remaining = (aes_blks << NVBOOT_AES_BLOCK_SIZE_LOG2) - length; write_padding(data + length, remaining); } static void write_padding(u_int8_t *p, u_int32_t remaining) { u_int8_t value = 0x80; while (remaining) { *p++ = value; remaining--; value = 0x00; } } static int write_bct(build_image_context *context, u_int32_t block, u_int32_t bct_slot) { u_int32_t bct_size; u_int32_t pagesremaining; u_int32_t page; u_int32_t pages_per_bct; u_int8_t *buffer; u_int8_t *data; int e = 0; assert(context); /* Note: 3rd argument not used in this particular query. */ (void)context->bctlib.get_value(nvbct_lib_id_bct_size, &bct_size, context->bct); pages_per_bct = iceil_log2(bct_size, context->page_size_log2); pagesremaining = pages_per_bct; page = bct_slot * pages_per_bct; /* Create a local copy of the BCT data */ buffer = malloc(pages_per_bct * context->page_size); if (buffer == NULL) return -ENOMEM; memset(buffer, 0, pages_per_bct * context->page_size); memcpy(buffer, context->bct, bct_size); insert_padding(buffer, bct_size); /* Encrypt and compute hash */ e = sign_bct(context, buffer); if (e != 0) goto fail; /* Write the BCT data to the storage device, picking up ECC errors */ data = buffer; while (pagesremaining > 0) { e = write_page(context, block, page, data); if (e != 0) goto fail; page++; pagesremaining--; data += context->page_size; } fail: /* Cleanup */ free(buffer); return e; } #define SET_BL_FIELD(instance, field, value) \ do { \ (void)context->bctlib.setbl_param(instance, \ nvbct_lib_id_bl_##field, \ &(value), \ context->bct); \ } while (0); #define GET_BL_FIELD(instance, field, ptr) \ (void)context->bctlib.getbl_param(instance, \ nvbct_lib_id_bl_##field, \ ptr, \ context->bct); #define COPY_BL_FIELD(from, to, field) \ do { \ u_int32_t v; \ GET_BL_FIELD(from, field, &v); \ SET_BL_FIELD(to, field, v); \ } while (0); static void set_bl_data(build_image_context *context, u_int32_t instance, u_int32_t start_blk, u_int32_t start_page, u_int32_t length) { assert(context); SET_BL_FIELD(instance, version, context->version); SET_BL_FIELD(instance, start_blk, start_blk); SET_BL_FIELD(instance, start_page, start_page); SET_BL_FIELD(instance, length, length); SET_BL_FIELD(instance, load_addr, context->newbl_load_addr); SET_BL_FIELD(instance, entry_point, context->newbl_entry_point); SET_BL_FIELD(instance, attribute, context->newbl_attr); } /* * In the interest of expediency, all BL's allocated from bottom to top start * at page 0 of a block, and all BL's allocated from top to bottom end at * the end of a block. */ /* TODO: Check for partition overflow */ /* TODO: Refactor this code! */ static int write_bootloaders(build_image_context *context) { u_int32_t i; u_int32_t j; u_int32_t bl_instance; u_int32_t bl_move_count = 0; u_int32_t bl_move_remaining; u_int32_t current_blk; u_int32_t current_page; u_int32_t pages_in_bl; u_int8_t *bl_storage; /* Holds the Bl after reading */ u_int8_t *buffer; /* Holds the Bl for writing */ u_int8_t *src; /* Scans through the Bl during writing */ u_int32_t bl_length; /* In bytes */ u_int32_t bl_actual_size; /* In bytes */ u_int32_t pagesremaining; u_int32_t virtual_blk; u_int32_t pages_per_blk; u_int32_t min_offset; u_int32_t max_offset; u_int32_t bl_0_version; u_int32_t bl_used; u_int8_t *hash_buffer; u_int32_t hash_size; u_int32_t bootloaders_max; file_type bl_filetype = file_type_bl; int e = 0; assert(context); pages_per_blk = 1 << (context->block_size_log2 - context->page_size_log2); GET_VALUE(hash_size, &hash_size); GET_VALUE(bootloaders_max, &bootloaders_max); hash_buffer = malloc(hash_size); if (hash_buffer == NULL) return -ENOMEM; if (enable_debug) { printf("write_bootloaders()\n"); printf(" redundancy = %d\n", context->redundancy); } /* Make room for the Bl(s) in the BCT. */ /* Determine how many to move. * Note that this code will count Bl[0] only if there is already * a BL in the device. */ GET_BL_FIELD(0, version, &bl_0_version); GET_VALUE(bootloader_used, &bl_used); for (bl_instance = 0; bl_instance < bl_used; bl_instance++) { u_int32_t bl_version; GET_BL_FIELD(bl_instance, version, &bl_version); if (bl_version == bl_0_version) bl_move_count++; } /* Adjust the move count, if needed, to avoid overflowing the BL table. * This can happen due to too much redundancy. */ bl_move_count = MIN(bl_move_count, bootloaders_max - context->redundancy); /* Move the Bl entries down. */ bl_move_remaining = bl_move_count; while (bl_move_remaining > 0) { u_int32_t inst_from = bl_move_remaining - 1; u_int32_t inst_to = bl_move_remaining + context->redundancy - 1; COPY_BL_FIELD(inst_from, inst_to, version); COPY_BL_FIELD(inst_from, inst_to, start_blk); COPY_BL_FIELD(inst_from, inst_to, start_page); COPY_BL_FIELD(inst_from, inst_to, length); COPY_BL_FIELD(inst_from, inst_to, load_addr); COPY_BL_FIELD(inst_from, inst_to, entry_point); COPY_BL_FIELD(inst_from, inst_to, attribute); (void)context->bctlib.getbl_param(inst_from, nvbct_lib_id_bl_crypto_hash, (u_int32_t*)hash_buffer, context->bct); (void)context->bctlib.setbl_param(inst_to, nvbct_lib_id_bl_crypto_hash, (u_int32_t*)hash_buffer, context->bct); bl_move_remaining--; } /* Read the BL into memory. */ if (read_from_image(context->newbl_filename, context->page_size, &bl_storage, &bl_length, &bl_actual_size, bl_filetype) == 1) { printf("Error reading Bootloader %s.\n", context->newbl_filename); exit(1); } pages_in_bl = iceil_log2(bl_length, context->page_size_log2); current_blk = context->journal_blk+1; current_page = 0; for (bl_instance = 0; bl_instance < context->redundancy; bl_instance++) { pagesremaining = pages_in_bl; /* Advance to the next block if needed. */ if (current_page > 0) { current_blk++; current_page = 0; } virtual_blk = 0; while (pagesremaining > 0) { /* Update the bad block table with relative * bad blocks. */ min_offset = virtual_blk * context->block_size; max_offset = min_offset + context->block_size; if (virtual_blk == 0) { set_bl_data(context, bl_instance, current_blk, current_page, bl_actual_size); } if (pagesremaining > pages_per_blk) { current_blk++; virtual_blk++; pagesremaining -= pages_per_blk; } else { current_page = pagesremaining; pagesremaining = 0; } } } /* Scan forwards to write each copy. */ for (bl_instance = 0; bl_instance < context->redundancy; bl_instance++) { /* Create a local copy of the BCT data */ buffer = malloc(pages_in_bl * context->page_size); if (buffer == NULL) return -ENOMEM; memset(buffer, 0, pages_in_bl * context->page_size); memcpy(buffer, bl_storage, bl_actual_size); insert_padding(buffer, bl_actual_size); pagesremaining = pages_in_bl; GET_BL_FIELD(bl_instance, start_blk, ¤t_blk); GET_BL_FIELD(bl_instance, start_page, ¤t_page); /* Encrypt and compute hash */ sign_data_block(buffer, bl_actual_size, hash_buffer); (void)context->bctlib.setbl_param(bl_instance, nvbct_lib_id_bl_crypto_hash, (u_int32_t*)hash_buffer, context->bct); /* Write the BCT data to the storage device, * picking up ECC errors */ src = buffer; /* Write pages as we go. */ virtual_blk = 0; while (pagesremaining) { if (current_page == 0) { /* Erase the block before writing into it. */ erase_block(context, current_blk); } e = write_page(context, current_blk, current_page, src); if (e != 0) goto fail; pagesremaining--; src += context->page_size; current_page++; if (current_page >= pages_per_blk) { current_page = 0; current_blk++; virtual_blk++; } context->last_bl_blk = current_blk; } free(buffer); } (void)context->bctlib.set_value(nvbct_lib_id_bootloader_used, context->redundancy + bl_move_count, context->bct); if (enable_debug) { GET_VALUE(bootloader_used, &bl_used); for (i = 0; i < bootloaders_max; i++) { u_int32_t version; u_int32_t start_blk; u_int32_t start_page; u_int32_t length; u_int32_t load_addr; u_int32_t entry_point; GET_BL_FIELD(i, version, &version); GET_BL_FIELD(i, start_blk, &start_blk); GET_BL_FIELD(i, start_page, &start_page); GET_BL_FIELD(i, length, &length); GET_BL_FIELD(i, load_addr, &load_addr); GET_BL_FIELD(i, entry_point, &entry_point); printf("%sBL[%d]: %d %04d %04d %04d 0x%08x 0x%08x k=", i < bl_used ? " " : "**", i, version, start_blk, start_page, length, load_addr, entry_point); (void)context->bctlib.getbl_param(i, nvbct_lib_id_bl_crypto_hash, (u_int32_t*)hash_buffer, context->bct); for (j = 0; j < hash_size / 4; j++) { printf("%08x", *((u_int32_t*)(hash_buffer + 4*j))); } printf("\n"); } } free(bl_storage); free(hash_buffer); return 0; fail: /* Cleanup. */ free(buffer); free(bl_storage); free(hash_buffer); printf("Write bootloader failed, error: %d.\n", e); return e; } int update_addon_item(build_image_context *context) { u_int8_t *aoi_storage; u_int8_t *buffer=NULL; u_int8_t *src=NULL; u_int32_t aoi_length; u_int32_t aoi_actual_size; u_int32_t pages_count; u_int32_t pagesremaining; u_int32_t current_blk; u_int32_t current_page; u_int32_t pages_per_blk; u_int32_t table_length; u_int32_t hash_size; int i; u_int8_t magicid[8] = "ChromeOs"; struct addon_item_rec *current_item; file_type aoi_filetype = file_type_addon; int e = 0; /* Read the Addon item into memory. */ GET_VALUE(hash_size, &hash_size); pages_per_blk = 1 << (context->block_size_log2 - context->page_size_log2); current_blk = context->last_bl_blk; /* Get the addon table block number */ current_blk++; context->addon_tbl_blk = current_blk; /* write the addon item */ current_item = context->addon_tbl.addon_item_list; for(i = 0; i < context->addon_tbl.addon_item_no; i++) { if (read_from_image(current_item->addon_filename, context->page_size, &aoi_storage, &aoi_length, &aoi_actual_size, aoi_filetype) == 1) { printf("Error reading addon file %s.\n", context->addon_tbl.addon_item_list->addon_filename); exit(1); } pages_count = iceil_log2(aoi_length, context->page_size_log2); /* Create a local copy of the BCT data */ buffer = malloc(pages_count * context->page_size); if (buffer == NULL) return -ENOMEM; memset(buffer, 0, pages_count * context->page_size); memcpy(buffer, aoi_storage, aoi_actual_size); insert_padding(buffer, aoi_actual_size); /* Encrypt and compute hash */ sign_data_block(buffer, aoi_actual_size, (u_int8_t *)current_item->item.item_checksum); pagesremaining = pages_count; src = buffer; current_blk++; current_page = 0; current_item->item.Location = current_blk; current_item->item.size = aoi_actual_size; while (pagesremaining) { if (current_page == 0) { /* Erase the block before writing into it. */ e = erase_block(context, current_blk); if (e != 0) goto fail_on_item; } e = write_page(context, current_blk, current_page, src); if (e != 0) goto fail_on_item; pagesremaining--; src += context->page_size; current_page++; if (current_page >= pages_per_blk) { current_page = 0; current_blk++; } } current_item = current_item->next; free(aoi_storage); free(buffer); } /* write add on table */ current_blk = context->addon_tbl_blk; current_item = context->addon_tbl.addon_item_list; current_page = 0; table_length = sizeof(struct table_rec) + context->addon_tbl.addon_item_no * sizeof(struct item_rec); context->addon_tbl.table.table_size= table_length; memcpy(context->addon_tbl.table.magic_id, magicid, 8); pages_count = iceil_log2(table_length, context->page_size_log2); buffer = malloc(pages_count * context->page_size); if (buffer == NULL) return -ENOMEM; memset(buffer, 0, pages_count * context->page_size); src = buffer; memcpy(src, &context->addon_tbl, sizeof(struct table_rec)); src += sizeof(struct table_rec); for(i = 0; i < context->addon_tbl.addon_item_no; i++) { memcpy(src, current_item, sizeof(struct item_rec)); src += sizeof(struct item_rec); current_item = current_item->next; } insert_padding(buffer, table_length); pagesremaining = pages_count; src = buffer; /* Encrypt and compute hash */ e = sign_data_block(buffer, table_length, (u_int8_t *)context->addon_tbl.table.table_checksum); if (e != 0) goto fail_on_table; memcpy(src+sizeof(context->addon_tbl.table.magic_id), context->addon_tbl.table.table_checksum, hash_size); while (pagesremaining) { if (current_page == 0) { e = erase_block(context, current_blk); if (e != 0) goto fail_on_table; } e = write_page(context, current_blk, current_page, src); if(e != 0) goto fail_on_table; pagesremaining--; src += context->page_size; current_page++; } free(buffer); return 0; fail_on_item: free(aoi_storage); fail_on_table: free(buffer); return e; } void update_context(struct build_image_context_rec *context) { (void)context->bctlib.get_value(nvbct_lib_id_partition_size, &context->partition_size, context->bct); (void)context->bctlib.get_value(nvbct_lib_id_page_size_log2, &context->page_size_log2, context->bct); (void)context->bctlib.get_value(nvbct_lib_id_block_size_log2, &context->block_size_log2, context->bct); context->page_size = 1 << context->page_size_log2; context->block_size = 1 << context->block_size_log2; context->pages_per_blk = 1 << (context->block_size_log2 - context->page_size_log2); } void read_bct_file(struct build_image_context_rec *context) { u_int8_t *bct_storage; /* Holds the Bl after reading */ u_int32_t bct_length; /* In bytes */ u_int32_t bct_actual_size; /* In bytes */ u_int32_t bct_size; file_type bct_filetype = file_type_bct; bct_size = sizeof(nvboot_config_table); if (read_from_image(context->bct_filename, context->page_size, &bct_storage, &bct_length, &bct_actual_size, bct_filetype) == 1) { printf("Error reading bct file %s.\n", context->bct_filename); exit(1); } memcpy(context->bct, bct_storage, bct_size); free(bct_storage); update_context(context); } void destroy_addon_list(struct addon_item_rec *addon_list) { struct addon_item_rec *next; while (addon_list) { next = addon_list->next; free(addon_list); addon_list = next; } } static void find_new_journal_blk(build_image_context *context) { u_int32_t current_blk; u_int32_t max_bct_search_blks; assert(context); current_blk = context->journal_blk; GET_VALUE(max_bct_search_blks, &max_bct_search_blks); if (current_blk > max_bct_search_blks) { printf("Error: Unable to locate a journal block.\n"); exit(1); } context->journal_blk = current_blk; } /* * Logic for updating a BCT or BL: * - begin_update(): * - If the device is blank: * - Identify & erase a journal block. * - If the journal block has gone bad: * - Perform an UpdateBL to move the good bootloader out of the * way, if needed. * - Erase the new journal block * - Write the good BCT to slot 0 of the new journal block. * - If the journal block is full: * - Erase block 0 * - Write 0's to BCT slot 0. * - Write the good BCT to slot 1. * - Erase the journal block. * - Write the good BCT to slot 0 of the journal block. * - Erase block 0 * - If updating the BL, do so here. * - finish_update(): * - Write the new BCT to the next available of the journal block. * - Write the new BCT to slot 0 of block 0. */ /* - begin_update(): * - Test the following conditions in this order: * - If the device is blank: * - Identify & erase a journal block. * - If the journal block has gone bad: * - Perform an UpdateBL to move the good bootloader out of the * way, if needed. * - Erase the new journal block * - Write the good BCT to slot 0 of the new journal block. * - If the journal block is full: * - Erase block 0 * - Write 0's to BCT slot 0. * - Write the good BCT to slot 1. * - Erase the journal block. * - Write the good BCT to slot 0 of the journal block. * - Erase block 0 */ static int begin_update(build_image_context *context) { u_int32_t pages_per_bct; u_int32_t pages_per_blk; u_int32_t bct_size; u_int32_t hash_size; u_int32_t reserved_size; u_int32_t reserved_offset; int e = 0; assert(context); /* Ensure that the BCT block & page data is current. */ if (enable_debug) { u_int32_t block_size_log2; u_int32_t page_size_log2; GET_VALUE(block_size_log2, &block_size_log2); GET_VALUE(page_size_log2, &page_size_log2); printf("begin_update(): bct data: b=%d p=%d\n", block_size_log2, page_size_log2); } GET_VALUE(bct_size, &bct_size); GET_VALUE(hash_size, &hash_size); GET_VALUE(reserved_size, &reserved_size); GET_VALUE(reserved_offset, &reserved_offset); pages_per_bct = iceil_log2(bct_size, context->page_size_log2); pages_per_blk = (1 << (context->block_size_log2 - context->page_size_log2)); /* Fill the reserved data w/the padding pattern. */ write_padding(context->bct + reserved_offset, reserved_size); /* Device is new */ /* Find the new journal block starting at block 1. */ find_new_journal_blk(context); e = erase_block(context, context->journal_blk); if (e != 0) goto fail; e = erase_block(context, 0); if (e != 0) goto fail; return 0; fail: printf("Erase block failed, error: %d.\n", e); return e; } /* * - finish_update(): * - Write the new BCT to the next available of the journal block. * - Write the new BCT to slot 0 of block 0. * For now, ignore end state. */ static int finish_update(build_image_context *context) { int e = 0; e = write_bct(context, context->journal_blk, 0); if (e != 0) goto fail; e = write_bct(context, 0, 0); if (e != 0) /* Write to block 0, slot 0. */ goto fail; return 0; fail: printf("Write BCT failed, error: %d.\n", e); return e; } /* * For now, ignore end state. */ int update_bl(build_image_context *context) { if (enable_debug) printf("**update_bl()\n"); if (begin_update(context) != 0) return 1; if (write_bootloaders(context) != 0) return 1; if (finish_update(context) != 0) return 1; return 0; } /* * To write the current image: * - Loop over all blocks in the block data list: * - Write out the data of real blocks. * - Write out 0's for unused blocks. * - Stop on the last used page of the last used block. */ int write_block_raw(build_image_context *context) { block_data *block_list; block_data *block; u_int32_t blk_number; u_int32_t last_blk; u_int32_t pages_to_write; u_int8_t *data; u_int8_t *empty_blk = NULL; assert(context != NULL); assert(context->memory); block_list = context->memory; /* Compute the end of the image. */ block_list = context->memory; while (block_list->next) block_list = block_list->next; last_blk = block_list->blk_number; /* Loop over all the storage from block 0, page 0 to *last_blk, Lastpage */ for (blk_number = 0; blk_number <= last_blk; blk_number++) { block = find_block(blk_number, context->memory); if (block) { pages_to_write = (blk_number == last_blk) ? block->pages_used : context->pages_per_blk; data = block->data; } else { /* Allocate empty_blk if needed. */ if (empty_blk == NULL) { empty_blk = malloc(context->block_size); if (!empty_blk) return -ENOMEM; memset(empty_blk, 0, context->block_size); } pages_to_write = context->pages_per_blk; data = empty_blk; } /* Write the data */ fwrite(data, 1, pages_to_write * context->page_size, context->raw_file); } free(empty_blk); return 0; }