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 000000000..cae6d56db --- /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; +} |