From 2296479dfd886d4cf4c145f1d3ed1ec64dc20630 Mon Sep 17 00:00:00 2001 From: Julius Werner Date: Thu, 12 May 2016 14:40:57 -0700 Subject: libpayload: cbfs: Add cbfs_handle API for more fine-grained accesses The libpayload CBFS APIs are pretty old and clunky, primarily because of the way the cbfs_media struct may or may not be passed in and may be initialized inside the API calls in a way that cannot be passed back out again. Due to this, the only real CBFS access function we have always reads a whole file with all metadata, and everything else has to build on top of that. This makes certain tasks like reading just a file attribute very inefficient on non-memory-mapped platforms (because you always have to map the whole file). This patch isn't going to fix the world, but will allow a bit more flexibility by bolting a new API on top which uses a struct cbfs_handle to represent a found but not yet read file. A cbfs_handle contains a copy of the cbfs_media needed to read the file, so it can be kept and passed around to read individual parts of it after the initial lookup. The existing (non-media) legacy API is retained for backwards compatibility, as is cbfs_file_get_contents() (which is most likely what more recent payloads would have used, and also a good convenience wrapper for the most simple use case), but they are now implemented on top of the new API. TEST=Booted Oak, made sure that firmware screens and software sync worked okay. Change-Id: I269f3979e77ae691ee9d4e1ab564eff6d45b7cbe Signed-off-by: Julius Werner Reviewed-on: https://review.coreboot.org/14810 Tested-by: build bot (Jenkins) Reviewed-by: Aaron Durbin --- payloads/libpayload/include/cbfs_core.h | 42 +++---- payloads/libpayload/libcbfs/cbfs.c | 17 ++- payloads/libpayload/libcbfs/cbfs_core.c | 189 ++++++++++++++++---------------- 3 files changed, 131 insertions(+), 117 deletions(-) (limited to 'payloads/libpayload') diff --git a/payloads/libpayload/include/cbfs_core.h b/payloads/libpayload/include/cbfs_core.h index 4cbc4c0628..f94056d185 100644 --- a/payloads/libpayload/include/cbfs_core.h +++ b/payloads/libpayload/include/cbfs_core.h @@ -168,19 +168,6 @@ struct cbfs_file_attr_hash { uint8_t hash_data[]; } __PACKED; -/* Given a cbfs_file, return the first file attribute, or NULL. */ -struct cbfs_file_attribute *cbfs_file_first_attr(struct cbfs_file *file); - -/* Given a cbfs_file and a cbfs_file_attribute, return the attribute that - * follows it, or NULL. */ -struct cbfs_file_attribute *cbfs_file_next_attr(struct cbfs_file *file, - struct cbfs_file_attribute *attr); - -/* Given a cbfs_file and an attribute tag, return the first instance of the - * attribute or NULL if none found. */ -struct cbfs_file_attribute *cbfs_file_find_attr(struct cbfs_file *file, - uint32_t tag); - /*** Component sub-headers ***/ /* Following are component sub-headers for the "standard" @@ -224,9 +211,6 @@ struct cbfs_optionrom { uint32_t len; } __attribute__((packed)); -#define CBFS_NAME(_c) (((char *) (_c)) + sizeof(struct cbfs_file)) -#define CBFS_SUBHEADER(_p) ( (void *) ((((uint8_t *) (_p)) + ntohl((_p)->offset))) ) - #define CBFS_MEDIA_INVALID_MAP_ADDRESS ((void*)(0xffffffff)) #define CBFS_DEFAULT_MEDIA ((void*)(0x0)) @@ -258,9 +242,6 @@ struct cbfs_media { int (*close)(struct cbfs_media *media); }; -/* returns pointer to a file entry inside CBFS or NULL on error */ -struct cbfs_file *cbfs_get_file(struct cbfs_media *media, const char *name); - /* * Returns pointer to a copy of the file content or NULL on error. * If the file is compressed, data will be decompressed. @@ -276,4 +257,27 @@ int cbfs_decompress(int algo, void *src, void *dst, int len); * on failure */ const struct cbfs_header *cbfs_get_header(struct cbfs_media *media); +/* Persistent handle to a CBFS file that has not yet been fully mapped. */ +struct cbfs_handle { + struct cbfs_media media; /* copy of original media object */ + u32 type; /* CBFS file type */ + u32 media_offset; /* offset from beginning of media */ + u32 attribute_offset; /* relative offset of attributes */ + u32 content_offset; /* relative offset of contents */ + u32 content_size; /* length of file contents in bytes */ +}; + +/* Returns handle to CBFS file, or NULL on error. Does not yet map contents. + * Caller is responsible to free() returned handle after use. */ +struct cbfs_handle *cbfs_get_handle(struct cbfs_media *media, const char *name); + +/* Given a cbfs_handle and an attribute tag, return a mapping for the first + * instance of the attribute or NULL if none found. */ +void *cbfs_get_attr(struct cbfs_handle *handle, uint32_t tag); + +/* Given a cbfs_handle, returns the (decompressed) file contents in a buffer, + * or NULL on error. If |size| is passed, will store amount of bytes read there. + * If |limit| is not 0, will only return up to that many bytes. */ +void *cbfs_get_contents(struct cbfs_handle *handle, size_t *size, size_t limit); + #endif diff --git a/payloads/libpayload/libcbfs/cbfs.c b/payloads/libpayload/libcbfs/cbfs.c index a1cc7e443c..38b1ff8c71 100644 --- a/payloads/libpayload/libcbfs/cbfs.c +++ b/payloads/libpayload/libcbfs/cbfs.c @@ -159,7 +159,22 @@ void *cbfs_load_payload(struct cbfs_media *media, const char *name) } struct cbfs_file *cbfs_find(const char *name) { - return cbfs_get_file(CBFS_DEFAULT_MEDIA, name); + struct cbfs_handle *handle = cbfs_get_handle(CBFS_DEFAULT_MEDIA, name); + struct cbfs_media *m = &handle->media; + void *ret; + + if (!handle) + return NULL; + + ret = m->map(m, handle->media_offset, + handle->content_offset + handle->content_size); + if (ret == CBFS_MEDIA_INVALID_MAP_ADDRESS) { + free(handle); + return NULL; + } + + free(handle); + return ret; } void *cbfs_find_file(const char *name, int type) { diff --git a/payloads/libpayload/libcbfs/cbfs_core.c b/payloads/libpayload/libcbfs/cbfs_core.c index c32d262b33..7e660ce83c 100644 --- a/payloads/libpayload/libcbfs/cbfs_core.c +++ b/payloads/libpayload/libcbfs/cbfs_core.c @@ -136,12 +136,15 @@ static int get_cbfs_range(uint32_t *offset, uint32_t *cbfs_end, } /* public API starts here*/ -struct cbfs_file *cbfs_get_file(struct cbfs_media *media, const char *name) +struct cbfs_handle *cbfs_get_handle(struct cbfs_media *media, const char *name) { const char *vardata; uint32_t offset, cbfs_end, vardata_len; - struct cbfs_file file, *file_ptr; - struct cbfs_media default_media; + struct cbfs_file file; + struct cbfs_handle *handle = malloc(sizeof(*handle)); + + if (!handle) + return NULL; if (get_cbfs_range(&offset, &cbfs_end, media)) { ERROR("Failed to find cbfs range\n"); @@ -149,11 +152,13 @@ struct cbfs_file *cbfs_get_file(struct cbfs_media *media, const char *name) } if (media == CBFS_DEFAULT_MEDIA) { - media = &default_media; + media = &handle->media; if (init_default_cbfs_media(media) != 0) { ERROR("Failed to initialize default media.\n"); return NULL; } + } else { + memcpy(&handle->media, media, sizeof(*media)); } DEBUG("CBFS location: 0x%x~0x%x\n", offset, cbfs_end); @@ -189,10 +194,14 @@ struct cbfs_file *cbfs_get_file(struct cbfs_media *media, const char *name) DEBUG("Found file (offset=0x%x, len=%d).\n", offset + file_offset, file_len); media->unmap(media, vardata); - file_ptr = media->map(media, offset, - file_offset + file_len); media->close(media); - return file_ptr; + handle->type = ntohl(file.type); + handle->media_offset = offset; + handle->content_offset = file_offset; + handle->content_size = file_len; + handle->attribute_offset = + ntohl(file.attributes_offset); + return handle; } else { DEBUG(" (unmatched file @0x%x: %s)\n", offset, vardata); @@ -209,118 +218,104 @@ struct cbfs_file *cbfs_get_file(struct cbfs_media *media, const char *name) return NULL; } -void *cbfs_get_file_content(struct cbfs_media *media, const char *name, - int type, size_t *sz) +void *cbfs_get_contents(struct cbfs_handle *handle, size_t *size, size_t limit) { - /* - * get file (possibly compressed) data. we pass through 'media' to - * cbfs_get_file (don't call init_default_cbfs_media) here so that - * cbfs_get_file can see whether the call is for CBFS_DEFAULT_MEDIA - * or not. Therefore, we don't (can't) unmap the file but it's caused - * by a pre-existing inherent problem with cbfs_get_file (and all - * callers are suffering from it). - */ - struct cbfs_file *file = cbfs_get_file(media, name); - - if (file == NULL) { - ERROR("Could not find file '%s'.\n", name); - return NULL; + struct cbfs_media *m = &handle->media; + size_t on_media_size = handle->content_size; + int algo = CBFS_COMPRESS_NONE; + void *ret = NULL; + size_t dummy_size; + + if (!size) + size = &dummy_size; + + struct cbfs_file_attr_compression *comp = + cbfs_get_attr(handle, CBFS_FILE_ATTR_TAG_COMPRESSION); + if (comp) { + algo = ntohl(comp->compression); + DEBUG("File '%s' is compressed (alg=%d)\n", name, algo); + *size = ntohl(comp->decompressed_size); + /* TODO: Implement partial decompression with |limit| */ } - if (sz) - *sz = 0; - - if (ntohl(file->type) != type) { - ERROR("File '%s' is of type %x, but we requested %x.\n", name, - ntohl(file->type), type); - return NULL; + if (algo == CBFS_COMPRESS_NONE) { + if (limit != 0 && limit < on_media_size) { + *size = limit; + on_media_size = limit; + } else { + *size = on_media_size; + } } - void *file_content = (void *)CBFS_SUBHEADER(file); - - struct cbfs_file_attribute *attr = - cbfs_file_find_attr(file, CBFS_FILE_ATTR_TAG_COMPRESSION); + void *data = m->map(m, handle->media_offset + handle->content_offset, + on_media_size); + if (data == CBFS_MEDIA_INVALID_MAP_ADDRESS) + return NULL; - size_t final_size = ntohl(file->len); - int compression_algo = CBFS_COMPRESS_NONE; - if (attr) { - struct cbfs_file_attr_compression *comp = - (struct cbfs_file_attr_compression *)attr; - compression_algo = ntohl(comp->compression); - DEBUG("File '%s' is compressed (alg=%d)\n", - name, compression_algo); - final_size = ntohl(comp->decompressed_size); + ret = malloc(*size); + if (ret != NULL && !cbfs_decompress(algo, data, ret, *size)) { + free(ret); + ret = NULL; } - void *dst = malloc(final_size); - if (dst == NULL) - goto err; - - if (!cbfs_decompress(compression_algo, file_content, dst, final_size)) - goto err; - - if (sz) - *sz = final_size; - - return dst; - -err: - free(dst); - return NULL; + m->unmap(m, data); + return ret; } -struct cbfs_file_attribute *cbfs_file_first_attr(struct cbfs_file *file) +void *cbfs_get_file_content(struct cbfs_media *media, const char *name, + int type, size_t *sz) { - /* attributes_offset should be 0 when there is no attribute, but all - * values that point into the cbfs_file header are invalid, too. */ - if (ntohl(file->attributes_offset) <= sizeof(*file)) - return NULL; + void *ret = NULL; + struct cbfs_handle *handle = cbfs_get_handle(media, name); - /* There needs to be enough space for the file header and one - * attribute header for this to make sense. */ - if (ntohl(file->offset) <= - sizeof(*file) + sizeof(struct cbfs_file_attribute)) + if (!handle) return NULL; - return (struct cbfs_file_attribute *) - (((uint8_t *)file) + ntohl(file->attributes_offset)); + if (handle->type == type) + ret = cbfs_get_contents(handle, sz, 0); + else + ERROR("File '%s' is of type %x, but we requested %x.\n", name, + handle->type, type); + + free(handle); + return ret; } -struct cbfs_file_attribute *cbfs_file_next_attr(struct cbfs_file *file, - struct cbfs_file_attribute *attr) +void *cbfs_get_attr(struct cbfs_handle *handle, uint32_t tag) { - /* ex falso sequitur quodlibet */ - if (attr == NULL) - return NULL; - - /* Is there enough space for another attribute? */ - if ((uint8_t *)attr + ntohl(attr->len) + - sizeof(struct cbfs_file_attribute) >= - (uint8_t *)file + ntohl(file->offset)) - return NULL; + struct cbfs_media *m = &handle->media; + uint32_t offset = handle->media_offset + handle->attribute_offset; + uint32_t end = handle->media_offset + handle->content_offset; + struct cbfs_file_attribute attr; + void *ret; - struct cbfs_file_attribute *next = (struct cbfs_file_attribute *) - (((uint8_t *)attr) + ntohl(attr->len)); - /* If any, "unused" attributes must come last. */ - if (ntohl(next->tag) == CBFS_FILE_ATTR_TAG_UNUSED) - return NULL; - if (ntohl(next->tag) == CBFS_FILE_ATTR_TAG_UNUSED2) + /* attribute_offset should be 0 when there is no attribute, but all + * values that point into the cbfs_file header are invalid, too. */ + if (handle->attribute_offset <= sizeof(struct cbfs_file)) return NULL; - return next; -} - -struct cbfs_file_attribute *cbfs_file_find_attr(struct cbfs_file *file, - uint32_t tag) -{ - struct cbfs_file_attribute *attr = cbfs_file_first_attr(file); - while (attr) { - if (ntohl(attr->tag) == tag) - break; - attr = cbfs_file_next_attr(file, attr); + m->open(m); + while (offset + sizeof(attr) <= end) { + if (m->read(m, &attr, offset, sizeof(attr)) != sizeof(attr)) { + ERROR("Failed to read attribute header %#x\n", offset); + m->close(m); + return NULL; + } + if (ntohl(attr.tag) != tag) { + offset += ntohl(attr.len); + continue; + } + ret = m->map(m, offset, ntohl(attr.len)); + if (ret == CBFS_MEDIA_INVALID_MAP_ADDRESS) { + ERROR("Failed to map attribute at %#x\n", offset); + m->close(m); + return NULL; + } + return ret; } - return attr; + m->close(m); + return NULL; } int cbfs_decompress(int algo, void *src, void *dst, int len) -- cgit v1.2.3