|  | /* | 
|  | * 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; | 
|  | } |