diff --git a/src/commonlib/bsd/include/commonlib/bsd/cbfs_mdata.h b/src/commonlib/bsd/include/commonlib/bsd/cbfs_mdata.h new file mode 100644 index 0000000000..df13427c5f --- /dev/null +++ b/src/commonlib/bsd/include/commonlib/bsd/cbfs_mdata.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */ + +#ifndef _COMMONLIB_BSD_CBFS_MDATA_H_ +#define _COMMONLIB_BSD_CBFS_MDATA_H_ + +#include +#include +#include + +/* + * Helper structure to allocate space for a blob of metadata on the stack. + * NOTE: The fields in any union cbfs_mdata or any of its substructures from cbfs_serialized.h + * should always remain in the same byte order as they are stored on flash (= big endian). To + * avoid byte-order confusion, fields should always and only be converted to host byte order at + * exactly the time they are read from one of these structures into their own separate variable. + */ +union cbfs_mdata { + struct cbfs_file h; + uint8_t raw[CBFS_METADATA_MAX_SIZE]; +}; + +/* Finds a CBFS attribute in a metadata block. Attribute returned as-is (still big-endian). + If |size| is not 0, will check that it matches the length of the attribute (if found)... + else caller is responsible for checking the |len| field to avoid reading out-of-bounds. */ +const void *cbfs_find_attr(const union cbfs_mdata *mdata, uint32_t attr_tag, size_t size_check); + +#endif /* _COMMONLIB_BSD_CBFS_MDATA_H_ */ diff --git a/src/commonlib/bsd/include/commonlib/bsd/cbfs_private.h b/src/commonlib/bsd/include/commonlib/bsd/cbfs_private.h index df5014355c..fc2d0d0457 100644 --- a/src/commonlib/bsd/include/commonlib/bsd/cbfs_private.h +++ b/src/commonlib/bsd/include/commonlib/bsd/cbfs_private.h @@ -5,7 +5,7 @@ #include -#include +#include #include #include #include @@ -41,18 +41,6 @@ */ #include -/* - * Helper structure to allocate space for a blob of metadata on the stack. - * NOTE: The fields in any union cbfs_mdata or any of its substructures from cbfs_serialized.h - * should always remain in the same byte order as they are stored on flash (= big endian). To - * avoid byte-order confusion, fields should always and only be converted to host byte order at - * exactly the time they are read from one of these structures into their own separate variable. - */ -union cbfs_mdata { - struct cbfs_file h; - uint8_t raw[CBFS_METADATA_MAX_SIZE]; -}; - /* Flags that modify behavior of cbfs_walk(). */ enum cbfs_walk_flags { /* Write the calculated hash back out to |metadata_hash->hash| rather than comparing it. @@ -130,9 +118,4 @@ cb_err_t cbfs_mcache_lookup(const void *mcache, size_t mcache_size, const char * /* Returns the amount of bytes actually used by the CBFS metadata cache in |mcache|. */ size_t cbfs_mcache_real_size(const void *mcache, size_t mcache_size); -/* Finds a CBFS attribute in a metadata block. Attribute returned as-is (still big-endian). - If |size| is not 0, will check that it matches the length of the attribute (if found)... - else caller is responsible for checking the |len| field to avoid reading out-of-bounds. */ -const void *cbfs_find_attr(const union cbfs_mdata *mdata, uint32_t attr_tag, size_t size_check); - #endif /* _COMMONLIB_BSD_CBFS_PRIVATE_H_ */ diff --git a/src/commonlib/bsd/include/commonlib/bsd/cbfs_serialized.h b/src/commonlib/bsd/include/commonlib/bsd/cbfs_serialized.h index dd504695b3..dc14cd5f49 100644 --- a/src/commonlib/bsd/include/commonlib/bsd/cbfs_serialized.h +++ b/src/commonlib/bsd/include/commonlib/bsd/cbfs_serialized.h @@ -13,6 +13,9 @@ enum cbfs_compression { }; enum cbfs_type { + /* QUERY is an alias for DELETED that can be passed to CBFS APIs to + inquire about the type of a file, rather than constrain it. */ + CBFS_TYPE_QUERY = 0, CBFS_TYPE_DELETED = 0x00000000, CBFS_TYPE_NULL = 0xffffffff, CBFS_TYPE_BOOTBLOCK = 0x01, diff --git a/src/include/cbfs.h b/src/include/cbfs.h index 426757d3f5..b89a13ddd3 100644 --- a/src/include/cbfs.h +++ b/src/include/cbfs.h @@ -4,6 +4,7 @@ #define _CBFS_H_ #include +#include #include #include #include @@ -15,27 +16,91 @@ * CBFS FILE ACCESS APIs * **********************************************************************************************/ -/* Map file into memory, returning a pointer to the mapping or NULL on error. If |size_out| is - not NULL, it will pass out the size of the mapped file. - NOTE: Since this may return a direct pointer to memory-mapped hardware, compressed files are - NOT transparently decompressed (unlike cbfs_load()). */ -static inline void *cbfs_map(const char *name, size_t *size_out); +/* + * These are the APIs used to access files in CBFS. In order to keep the calls simple and free + * of clutter in the common cases, but still offer all advanced functionality when needed, there + * are many different variations that are implemented by wrapping the same underlying API with + * static inlines. All accessors have in common that they look up files by name, and will + * transparently decompress files that are compressed. + * + * There are three main flavors of CBFS accessors: + * + * size_t cbfs_load(char *name, void *buf, size_t size): Loads the contents of a CBFS file into + * a buffer provided by the caller (by providing pointer and size to it). Will return the + * amount of bytes loaded on success, or 0 on error. + * + * void *cbfs_map(char *name, size_t *size_out): Maps a file into the address space. If the file + * is not compressed and the platform supports direct memory-mapping for the boot medium, + * a pointer to the platform mapping is returned directly. In all other cases, memory will + * be allocated from the cbfs_cache and file data will be loaded into there. Returns a + * pointer to the mapping on success, or NULL on error. If an optional size_out parameter + * is passed in, it will be filled out with the size of the mapped data. Caller should call + * cbfs_unmap() after it is done using the mapping to free up the cbfs_cache if possible. + * + * void *cbfs_alloc(char *name, cbfs_allocator_t allocator, void *arg, size_t *size_out): Loads + * file data into memory provided by a custom allocator function that the caller passes in. + * The caller may pass an argument that is passed through verbatim to the allocator. + * Returns the pointer returned by the allocator (where the file data was loaded to) on + * success, or NULL on error. If an optional size_out parameter is passed in, it will be + * filled out with the size of the loaded data. + * + * void *cbfs_cbmem_alloc(char *name, uint32_t cbmem_id, size_t *size_out): Wrapper around + * cbfs_alloc() that will provide an allocator function for allocating space for the file + * data in CBMEM, with the provided CBMEM ID. + * + * All of these flavors have variations with any of the following optional parameters added: + * + * ..._ro_...: Will force looking up the CBFS file in the read-only CBFS (the "COREBOOT" FMAP + * section), even when running in an RW stage from one of the RW CBFSs. Only relevant if + * CONFIG(VBOOT) is set. + * + * ..._type_...: May pass in an extra enum cbfs_type *type parameter. If the value it points to + * is CBFS_TYPE_QUERY, it will be replaced with the actual CBFS type of the found file. If + * it is anything else, the type will be compared with the actually found type, and the + * operation will fail if they don't match. + */ -/* Like cbfs_map(), except that it will always read from the read-only CBFS (the "COREBOOT" FMAP - region), even when CONFIG(VBOOT) is enabled. */ +/* + * An allocator function for passing to cbfs_alloc(). Takes the argument that was originally + * passed to cbfs_alloc(), the size of the file to be loaded, and a pointer to the already + * loaded and verified file metadata (for rare cases where the allocator needs to check custom + * attributes). Must return a pointer to space of the requested size where the file data should + * be loaded, or NULL to make the operation fail. + */ +typedef void *(*cbfs_allocator_t)(void *arg, size_t size, union cbfs_mdata *mdata); + +static inline size_t cbfs_load(const char *name, void *buf, size_t size); +static inline size_t cbfs_ro_load(const char *name, void *buf, size_t size); +static inline size_t cbfs_type_load(const char *name, void *buf, size_t size, + enum cbfs_type *type); +static inline size_t cbfs_ro_type_load(const char *name, void *buf, size_t size, + enum cbfs_type *type); + +static inline void *cbfs_map(const char *name, size_t *size_out); static inline void *cbfs_ro_map(const char *name, size_t *size_out); +static inline void *cbfs_type_map(const char *name, size_t *size_out, enum cbfs_type *type); +static inline void *cbfs_ro_type_map(const char *name, size_t *size_out, enum cbfs_type *type); + +static inline void *cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out); +static inline void *cbfs_ro_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out); +static inline void *cbfs_type_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, enum cbfs_type *type); +static inline void *cbfs_ro_type_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, enum cbfs_type *type); + +static inline void *cbfs_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out); +static inline void *cbfs_ro_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out); +static inline void *cbfs_type_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out, + enum cbfs_type *type); +static inline void *cbfs_ro_type_cbmem_alloc(const char *name, uint32_t cbmem_id, + size_t *size_out, enum cbfs_type *type); /* Removes a previously allocated CBFS mapping. Should try to unmap mappings in strict LIFO order where possible, since mapping backends often don't support more complicated cases. */ void cbfs_unmap(void *mapping); -/* Load a file from CBFS into a buffer. Returns amount of loaded bytes on success or 0 on error. - File will get decompressed as necessary. */ -static inline size_t cbfs_load(const char *name, void *buf, size_t buf_size); -/* Like cbfs_load(), except that it will always read from the read-only CBFS (the "COREBOOT" - FMAP region), even when CONFIG(VBOOT) is enabled. */ -static inline size_t cbfs_ro_load(const char *name, void *buf, size_t buf_size); - /* Load stage into memory filling in prog. Return 0 on success. < 0 on error. */ int cbfs_prog_stage_load(struct prog *prog); @@ -111,31 +176,118 @@ size_t cbfs_load_and_decompress(const struct region_device *rdev, size_t offset, /********************************************************************************************** * INTERNAL HELPERS FOR INLINES, DO NOT USE. * **********************************************************************************************/ -size_t _cbfs_load(const char *name, void *buf, size_t buf_size, bool force_ro); -void *_cbfs_map(const char *name, size_t *size_out, bool force_ro); +void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, bool force_ro, enum cbfs_type *type); +struct _cbfs_default_allocator_arg { + void *buf; + size_t buf_size; +}; +void *_cbfs_default_allocator(void *arg, size_t size, union cbfs_mdata *unused); + +void *_cbfs_cbmem_allocator(void *arg, size_t size, union cbfs_mdata *unused); /********************************************************************************************** * INLINE IMPLEMENTATIONS * **********************************************************************************************/ +static inline void *cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out) +{ + return cbfs_type_alloc(name, allocator, arg, size_out, NULL); +} + +static inline void *cbfs_ro_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out) +{ + return cbfs_ro_type_alloc(name, allocator, arg, size_out, NULL); +} + +static inline void *cbfs_type_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, enum cbfs_type *type) +{ + return _cbfs_alloc(name, allocator, arg, size_out, false, type); +} + +static inline void *cbfs_ro_type_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, enum cbfs_type *type) +{ + return _cbfs_alloc(name, allocator, arg, size_out, true, type); +} + static inline void *cbfs_map(const char *name, size_t *size_out) { - return _cbfs_map(name, size_out, false); + return cbfs_type_map(name, size_out, NULL); } static inline void *cbfs_ro_map(const char *name, size_t *size_out) { - return _cbfs_map(name, size_out, true); + return cbfs_ro_type_map(name, size_out, NULL); } -static inline size_t cbfs_load(const char *name, void *buf, size_t buf_size) +static inline void *cbfs_type_map(const char *name, size_t *size_out, enum cbfs_type *type) { - return _cbfs_load(name, buf, buf_size, false); + return cbfs_type_alloc(name, NULL, NULL, size_out, type); } -static inline size_t cbfs_ro_load(const char *name, void *buf, size_t buf_size) +static inline void *cbfs_ro_type_map(const char *name, size_t *size_out, enum cbfs_type *type) { - return _cbfs_load(name, buf, buf_size, true); + return cbfs_ro_type_alloc(name, NULL, NULL, size_out, type); +} + +static inline size_t _cbfs_load(const char *name, void *buf, size_t size, bool force_ro, + enum cbfs_type *type) +{ + struct _cbfs_default_allocator_arg arg = { .buf = buf, .buf_size = size }; + if (_cbfs_alloc(name, _cbfs_default_allocator, &arg, &size, force_ro, type)) + return size; + else + return 0; +} + +static inline size_t cbfs_load(const char *name, void *buf, size_t size) +{ + return cbfs_type_load(name, buf, size, NULL); +} + +static inline size_t cbfs_type_load(const char *name, void *buf, size_t size, + enum cbfs_type *type) +{ + return _cbfs_load(name, buf, size, false, type); +} + +static inline size_t cbfs_ro_load(const char *name, void *buf, size_t size) +{ + return cbfs_ro_type_load(name, buf, size, NULL); +} + +static inline size_t cbfs_ro_type_load(const char *name, void *buf, size_t size, + enum cbfs_type *type) +{ + return _cbfs_load(name, buf, size, true, type); +} + +static inline void *cbfs_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out) +{ + return cbfs_type_cbmem_alloc(name, cbmem_id, size_out, NULL); +} + +static inline void *cbfs_ro_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out) +{ + return cbfs_ro_type_cbmem_alloc(name, cbmem_id, size_out, NULL); +} + +static inline void *cbfs_type_cbmem_alloc(const char *name, uint32_t cbmem_id, size_t *size_out, + enum cbfs_type *type) +{ + return cbfs_type_alloc(name, _cbfs_cbmem_allocator, (void *)(uintptr_t)cbmem_id, + size_out, type); +} + +static inline void *cbfs_ro_type_cbmem_alloc(const char *name, uint32_t cbmem_id, + size_t *size_out, enum cbfs_type *type) +{ + return cbfs_ro_type_alloc(name, _cbfs_cbmem_allocator, (void *)(uintptr_t)cbmem_id, + size_out, type); } #endif diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c index 93dbb681aa..7df9dc671e 100644 --- a/src/lib/cbfs.c +++ b/src/lib/cbfs.c @@ -102,48 +102,6 @@ int cbfs_boot_locate(struct cbfsf *fh, const char *name, uint32_t *type) return 0; } -void *_cbfs_map(const char *name, size_t *size_out, bool force_ro) -{ - struct region_device rdev; - union cbfs_mdata mdata; - - if (cbfs_boot_lookup(name, force_ro, &mdata, &rdev)) - return NULL; - - if (size_out != NULL) - *size_out = region_device_sz(&rdev); - - uint32_t compression = CBFS_COMPRESS_NONE; - const struct cbfs_file_attr_compression *attr = cbfs_find_attr(&mdata, - CBFS_FILE_ATTR_TAG_COMPRESSION, sizeof(*attr)); - if (attr) - compression = be32toh(attr->compression); - - if (compression == CBFS_COMPRESS_NONE) - return rdev_mmap_full(&rdev); - - if (!CBFS_CACHE_AVAILABLE) { - ERROR("Cannot map compressed file %s on x86\n", mdata.h.filename); - return NULL; - } - - size_t size = be32toh(attr->decompressed_size); - void *buf = mem_pool_alloc(&cbfs_cache, size); - if (!buf) { - ERROR("CBFS cache out of memory when mapping %s\n", mdata.h.filename); - return NULL; - } - - size = cbfs_load_and_decompress(&rdev, 0, region_device_sz(&rdev), buf, size, - compression); - if (!size) - return NULL; - - if (size_out != NULL) - *size_out = size; - return buf; -} - void cbfs_unmap(void *mapping) { /* @@ -234,6 +192,8 @@ size_t cbfs_load_and_decompress(const struct region_device *rdev, size_t offset, size_t out_size; void *map; + DEBUG("Decompressing %zu bytes to %p with algo %d\n", buffer_size, buffer, compression); + switch (compression) { case CBFS_COMPRESS_NONE: if (buffer_size < in_size) @@ -348,25 +308,78 @@ void *cbfs_boot_map_optionrom_revision(uint16_t vendor, uint16_t device, uint8_t return cbfs_map(name, NULL); } -size_t _cbfs_load(const char *name, void *buf, size_t buf_size, bool force_ro) +void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg, + size_t *size_out, bool force_ro, enum cbfs_type *type) { struct region_device rdev; union cbfs_mdata mdata; + void *loc; + + DEBUG("%s(name='%s', alloc=%p(%p), force_ro=%s, type=%d)\n", __func__, name, allocator, + arg, force_ro ? "true" : "false", type ? *type : -1); if (cbfs_boot_lookup(name, force_ro, &mdata, &rdev)) - return 0; + return NULL; - uint32_t compression = CBFS_COMPRESS_NONE; - const struct cbfs_file_attr_compression *attr = cbfs_find_attr(&mdata, - CBFS_FILE_ATTR_TAG_COMPRESSION, sizeof(*attr)); - if (attr) { - compression = be32toh(attr->compression); - if (buf_size < be32toh(attr->decompressed_size)) - return 0; + if (type) { + const enum cbfs_type real_type = be32toh(mdata.h.type); + if (*type == CBFS_TYPE_QUERY) + *type = real_type; + else if (*type != real_type) { + ERROR("'%s' type mismatch (is %u, expected %u)\n", + mdata.h.filename, real_type, *type); + return NULL; + } } - return cbfs_load_and_decompress(&rdev, 0, region_device_sz(&rdev), - buf, buf_size, compression); + size_t size = region_device_sz(&rdev); + uint32_t compression = CBFS_COMPRESS_NONE; + const struct cbfs_file_attr_compression *cattr = cbfs_find_attr(&mdata, + CBFS_FILE_ATTR_TAG_COMPRESSION, sizeof(*cattr)); + if (cattr) { + compression = be32toh(cattr->compression); + size = be32toh(cattr->decompressed_size); + } + + if (size_out) + *size_out = size; + + /* allocator == NULL means do a cbfs_map() */ + if (allocator) { + loc = allocator(arg, size, &mdata); + } else if (compression == CBFS_COMPRESS_NONE) { + return rdev_mmap_full(&rdev); + } else if (!CBFS_CACHE_AVAILABLE) { + ERROR("Cannot map compressed file %s on x86\n", mdata.h.filename); + return NULL; + } else { + loc = mem_pool_alloc(&cbfs_cache, size); + } + + if (!loc) { + ERROR("'%s' allocation failure\n", mdata.h.filename); + return NULL; + } + + size = cbfs_load_and_decompress(&rdev, 0, region_device_sz(&rdev), + loc, size, compression); + if (!size) + return NULL; + + return loc; +} + +void *_cbfs_default_allocator(void *arg, size_t size, union cbfs_mdata *unused) +{ + struct _cbfs_default_allocator_arg *darg = arg; + if (size > darg->buf_size) + return NULL; + return darg->buf; +} + +void *_cbfs_cbmem_allocator(void *arg, size_t size, union cbfs_mdata *unused) +{ + return cbmem_add((uintptr_t)arg, size); } int cbfs_prog_stage_load(struct prog *pstage)