diff options
Diffstat (limited to 'fs/cbfs/cbfs.c')
-rw-r--r-- | fs/cbfs/cbfs.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/fs/cbfs/cbfs.c b/fs/cbfs/cbfs.c new file mode 100644 index 0000000..cae6d56 --- /dev/null +++ b/fs/cbfs/cbfs.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. 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 + */ + +#include <cbfs.h> +#include <malloc.h> +#include <asm/byteorder.h> + +enum cbfs_result file_cbfs_result; + +const char *file_cbfs_error(void) +{ + switch (file_cbfs_result) { + case CBFS_SUCCESS: + return "Success"; + case CBFS_NOT_INITIALIZED: + return "CBFS not initialized"; + case CBFS_BAD_HEADER: + return "Bad CBFS header"; + case CBFS_BAD_FILE: + return "Bad CBFS file"; + case CBFS_FILE_NOT_FOUND: + return "File not found"; + default: + return "Unknown"; + } +} + + +static const u32 good_magic = 0x4f524243; +static const u8 good_file_magic[] = "LARCHIVE"; + + +static int initialized; +static struct cbfs_header cbfs_header; +static struct cbfs_cachenode *file_cache; + +/* Do endian conversion on the CBFS header structure. */ +static void swap_header(struct cbfs_header *dest, struct cbfs_header *src) +{ + dest->magic = be32_to_cpu(src->magic); + dest->version = be32_to_cpu(src->version); + dest->rom_size = be32_to_cpu(src->rom_size); + dest->boot_block_size = be32_to_cpu(src->boot_block_size); + dest->align = be32_to_cpu(src->align); + dest->offset = be32_to_cpu(src->offset); +} + +/* Do endian conversion on a CBFS file header. */ +static void swap_file_header(struct cbfs_fileheader *dest, + const struct cbfs_fileheader *src) +{ + memcpy(&dest->magic, &src->magic, sizeof(dest->magic)); + dest->len = be32_to_cpu(src->len); + dest->type = be32_to_cpu(src->type); + dest->checksum = be32_to_cpu(src->checksum); + dest->offset = be32_to_cpu(src->offset); +} + +/* + * Given a starting position in memory, scan forward, bounded by a size, and + * find the next valid CBFS file. No memory is allocated by this function. The + * caller is responsible for allocating space for the new file structure. + * + * @param start The location in memory to start from. + * @param size The size of the memory region to search. + * @param align The alignment boundaries to check on. + * @param newNode A pointer to the file structure to load. + * @param used A pointer to the count of of bytes scanned through, + * including the file if one is found. + * + * @return 1 if a file is found, 0 if one isn't. + */ +static int file_cbfs_next_file(u8 *start, u32 size, u32 align, + struct cbfs_cachenode *newNode, u32 *used) +{ + struct cbfs_fileheader header; + + *used = 0; + + while (size >= align) { + const struct cbfs_fileheader *fileHeader = + (const struct cbfs_fileheader *)start; + u32 name_len; + u32 step; + + /* Check if there's a file here. */ + if (memcmp(good_file_magic, &(fileHeader->magic), + sizeof(fileHeader->magic))) { + *used += align; + size -= align; + start += align; + continue; + } + + swap_file_header(&header, fileHeader); + if (header.offset < sizeof(const struct cbfs_cachenode *) || + header.offset > header.len) { + file_cbfs_result = CBFS_BAD_FILE; + return -1; + } + newNode->next = NULL; + newNode->type = header.type; + newNode->data = start + header.offset; + newNode->data_length = header.len; + name_len = header.offset - sizeof(struct cbfs_cachenode *); + newNode->name = (char *)fileHeader + + sizeof(struct cbfs_cachenode *); + newNode->name_length = name_len; + newNode->checksum = header.checksum; + + step = header.len; + if (step % align) + step = step + align - step % align; + + *used += step; + return 1; + } + return 0; +} + +/* Look through a CBFS instance and copy file metadata into regular memory. */ +static void file_cbfs_fill_cache(u8 *start, u32 size, u32 align) +{ + struct cbfs_cachenode *cache_node; + struct cbfs_cachenode *newNode; + struct cbfs_cachenode **cache_tail = &file_cache; + + /* Clear out old information. */ + cache_node = file_cache; + while (cache_node) { + struct cbfs_cachenode *oldNode = cache_node; + cache_node = cache_node->next; + free(oldNode); + } + file_cache = NULL; + + while (size >= align) { + int result; + u32 used; + + newNode = (struct cbfs_cachenode *) + malloc(sizeof(struct cbfs_cachenode)); + result = file_cbfs_next_file(start, size, align, + newNode, &used); + + if (result < 0) { + free(newNode); + return; + } else if (result == 0) { + free(newNode); + break; + } + *cache_tail = newNode; + cache_tail = &newNode->next; + + size -= used; + start += used; + } + file_cbfs_result = CBFS_SUCCESS; +} + +/* Get the CBFS header out of the ROM and do endian conversion. */ +static int file_cbfs_load_header(uintptr_t end_of_rom, + struct cbfs_header *header) +{ + struct cbfs_header *header_in_rom; + + header_in_rom = (struct cbfs_header *)(uintptr_t) + *(u32 *)(end_of_rom - 3); + swap_header(header, header_in_rom); + + if (header->magic != good_magic || header->offset > + header->rom_size - header->boot_block_size) { + file_cbfs_result = CBFS_BAD_HEADER; + return 1; + } + return 0; +} + +void file_cbfs_init(uintptr_t end_of_rom) +{ + u8 *start_of_rom; + initialized = 0; + + if (file_cbfs_load_header(end_of_rom, &cbfs_header)) + return; + + start_of_rom = (u8 *)(end_of_rom + 1 - cbfs_header.rom_size); + + file_cbfs_fill_cache(start_of_rom + cbfs_header.offset, + cbfs_header.rom_size, cbfs_header.align); + if (file_cbfs_result == CBFS_SUCCESS) + initialized = 1; +} + +const struct cbfs_header *file_cbfs_get_header(void) +{ + if (initialized) { + file_cbfs_result = CBFS_SUCCESS; + return &cbfs_header; + } else { + file_cbfs_result = CBFS_NOT_INITIALIZED; + return NULL; + } +} + +const struct cbfs_cachenode *file_cbfs_get_first(void) +{ + if (!initialized) { + file_cbfs_result = CBFS_NOT_INITIALIZED; + return NULL; + } else { + file_cbfs_result = CBFS_SUCCESS; + return file_cache; + } +} + +void file_cbfs_get_next(const struct cbfs_cachenode **file) +{ + if (!initialized) { + file_cbfs_result = CBFS_NOT_INITIALIZED; + file = NULL; + return; + } + + if (*file) + *file = (*file)->next; + file_cbfs_result = CBFS_SUCCESS; +} + +const struct cbfs_cachenode *file_cbfs_find(const char *name) +{ + struct cbfs_cachenode *cache_node = file_cache; + + if (!initialized) { + file_cbfs_result = CBFS_NOT_INITIALIZED; + return NULL; + } + + while (cache_node) { + if (!strcmp(name, cache_node->name)) + break; + cache_node = cache_node->next; + } + if (!cache_node) + file_cbfs_result = CBFS_FILE_NOT_FOUND; + else + file_cbfs_result = CBFS_SUCCESS; + + return cache_node; +} + +const struct cbfs_cachenode *file_cbfs_find_uncached(uintptr_t end_of_rom, + const char *name) +{ + u8 *start; + u32 size; + u32 align; + static struct cbfs_cachenode node; + + if (file_cbfs_load_header(end_of_rom, &cbfs_header)) + return NULL; + + start = (u8 *)(end_of_rom + 1 - cbfs_header.rom_size); + size = cbfs_header.rom_size; + align = cbfs_header.align; + + while (size >= align) { + int result; + u32 used; + + result = file_cbfs_next_file(start, size, align, &node, &used); + + if (result < 0) + return NULL; + else if (result == 0) + break; + + if (!strcmp(name, node.name)) + return &node; + + size -= used; + start += used; + } + file_cbfs_result = CBFS_FILE_NOT_FOUND; + return NULL; +} + +const char *file_cbfs_name(const struct cbfs_cachenode *file) +{ + file_cbfs_result = CBFS_SUCCESS; + return file->name; +} + +u32 file_cbfs_size(const struct cbfs_cachenode *file) +{ + file_cbfs_result = CBFS_SUCCESS; + return file->data_length; +} + +u32 file_cbfs_type(const struct cbfs_cachenode *file) +{ + file_cbfs_result = CBFS_SUCCESS; + return file->type; +} + +long file_cbfs_read(const struct cbfs_cachenode *file, void *buffer, + unsigned long maxsize) +{ + u32 size; + + size = file->data_length; + if (maxsize && size > maxsize) + size = maxsize; + + memcpy(buffer, file->data, size); + + file_cbfs_result = CBFS_SUCCESS; + return size; +} |