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:
parent
4bab6e79b0
commit
2296479dfd
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
return attr;
|
||||
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;
|
||||
}
|
||||
m->close(m);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cbfs_decompress(int algo, void *src, void *dst, int len)
|
||||
|
|
Loading…
Reference in New Issue