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 <jwerner@chromium.org>
Reviewed-on: https://review.coreboot.org/14810
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
This commit is contained in:
Julius Werner 2016-05-12 14:40:57 -07:00
parent 4bab6e79b0
commit 2296479dfd
3 changed files with 137 additions and 123 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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_contents(struct cbfs_handle *handle, size_t *size, size_t limit)
{
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 (algo == CBFS_COMPRESS_NONE) {
if (limit != 0 && limit < on_media_size) {
*size = limit;
on_media_size = limit;
} else {
*size = on_media_size;
}
}
void *data = m->map(m, handle->media_offset + handle->content_offset,
on_media_size);
if (data == CBFS_MEDIA_INVALID_MAP_ADDRESS)
return NULL;
ret = malloc(*size);
if (ret != NULL && !cbfs_decompress(algo, data, ret, *size)) {
free(ret);
ret = NULL;
}
m->unmap(m, data);
return ret;
}
void *cbfs_get_file_content(struct cbfs_media *media, const char *name,
int type, size_t *sz)
{
/*
* 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);
void *ret = NULL;
struct cbfs_handle *handle = cbfs_get_handle(media, name);
if (file == NULL) {
ERROR("Could not find file '%s'.\n", name);
if (!handle)
return NULL;
}
if (sz)
*sz = 0;
if (ntohl(file->type) != type) {
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,
ntohl(file->type), type);
return NULL;
}
handle->type, type);
void *file_content = (void *)CBFS_SUBHEADER(file);
struct cbfs_file_attribute *attr =
cbfs_file_find_attr(file, CBFS_FILE_ATTR_TAG_COMPRESSION);
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);
}
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;
free(handle);
return ret;
}
struct cbfs_file_attribute *cbfs_file_first_attr(struct cbfs_file *file)
void *cbfs_get_attr(struct cbfs_handle *handle, uint32_t tag)
{
/* attributes_offset should be 0 when there is no attribute, but all
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;
/* attribute_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))
if (handle->attribute_offset <= sizeof(struct cbfs_file))
return NULL;
/* 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))
return NULL;
return (struct cbfs_file_attribute *)
(((uint8_t *)file) + ntohl(file->attributes_offset));
}
struct cbfs_file_attribute *cbfs_file_next_attr(struct cbfs_file *file,
struct cbfs_file_attribute *attr)
{
/* 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_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)
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)