// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2019, Linaro Limited */ #include #include #include #include #include #include #include #include #include struct file_slice_elem { struct file_slice slice; SLIST_ENTRY(file_slice_elem) link; }; /* * struct file - file resources * @tag: Tag or hash uniquely identifying a file * @taglen: Byte length of @tag * @refc: Reference counter * @link: Linked list element * @num_slices: Number of elements in the @slices array below * @slices: Array of file slices holding the fobjs of this file * * A file is constructed of slices which may be shared in different * mappings/contexts. There may be holes in the file for ranges of the file * that can't be shared. */ struct file { uint8_t tag[FILE_TAG_SIZE]; unsigned int taglen; struct refcount refc; TAILQ_ENTRY(file) link; struct mutex mu; SLIST_HEAD(, file_slice_elem) slice_head; }; static struct mutex file_mu = MUTEX_INITIALIZER; static TAILQ_HEAD(, file) file_head = TAILQ_HEAD_INITIALIZER(file_head); static int file_tag_cmp(const struct file *f, const uint8_t *tag, unsigned int taglen) { if (f->taglen != taglen) return -1; return memcmp(tag, f->tag, taglen); } static struct file *file_find_tag_unlocked(const uint8_t *tag, unsigned int taglen) { struct file *f = NULL; TAILQ_FOREACH(f, &file_head, link) if (!file_tag_cmp(f, tag, taglen)) return f; return NULL; } static void file_free(struct file *f) { mutex_destroy(&f->mu); while (!SLIST_EMPTY(&f->slice_head)) { struct file_slice_elem *fse = SLIST_FIRST(&f->slice_head); SLIST_REMOVE_HEAD(&f->slice_head, link); fobj_put(fse->slice.fobj); free(fse); } free(f); } TEE_Result file_add_slice(struct file *f, struct fobj *fobj, unsigned int page_offset) { struct file_slice_elem *fse = NULL; unsigned int s = 0; /* Check for conflicts */ if (file_find_slice(f, page_offset)) return TEE_ERROR_BAD_PARAMETERS; fse = calloc(1, sizeof(*fse)); if (!fse) return TEE_ERROR_OUT_OF_MEMORY; fse->slice.fobj = fobj_get(fobj); if (!fse->slice.fobj || ADD_OVERFLOW(page_offset, fse->slice.fobj->num_pages, &s)) { fobj_put(fse->slice.fobj); free(fse); return TEE_ERROR_BAD_PARAMETERS; } fse->slice.page_offset = page_offset; SLIST_INSERT_HEAD(&f->slice_head, fse, link); return TEE_SUCCESS; } struct file *file_get(struct file *f) { if (f && !refcount_inc(&f->refc)) panic(); return f; } struct file *file_get_by_tag(const uint8_t *tag, unsigned int taglen) { struct file *f = NULL; if (taglen > sizeof(f->tag)) return NULL; mutex_lock(&file_mu); /* * If file is found and reference counter can be increased, we're done. * If file can't be found, it doesn't exist so it has to be added. * If it's found but reference counter is 0, the situation is * a bit complicated: * - file_put() is about to free the file as soon as it can obtain the * mutex. * - Unless there's a mismatch between file_get() and file_put() only * one thread calling file_put() is about to free the file. * * There's a window of opportunity where file_put() is called * (without a mutex being held, which is quite OK) while we're * holding the mutex here and are searching for the file and it's * found, but just after file_put() has decreased the reference * counter. * * To keep it simple we're adding a new file at the head (so new * searches finds this file instead of the old being freed) instead * of complicating file_put() by trying to rescue the file and * possibly hiding a case of mismatching file_put() and file_get(). */ f = file_find_tag_unlocked(tag, taglen); if (f && refcount_inc(&f->refc)) goto out; f = calloc(1, sizeof(*f)); if (!f) goto out; memcpy(f->tag, tag, taglen); f->taglen = taglen; refcount_set(&f->refc, 1); mutex_init(&f->mu); SLIST_INIT(&f->slice_head); TAILQ_INSERT_HEAD(&file_head, f, link); out: mutex_unlock(&file_mu); return f; } void file_put(struct file *f) { if (f && refcount_dec(&f->refc)) { mutex_lock(&file_mu); TAILQ_REMOVE(&file_head, f, link); mutex_unlock(&file_mu); file_free(f); } } struct file_slice *file_find_slice(struct file *f, unsigned int page_offset) { struct file_slice_elem *fse = NULL; assert(f->mu.state); SLIST_FOREACH(fse, &f->slice_head, link) { struct file_slice *fs = &fse->slice; if (page_offset >= fs->page_offset && page_offset < fs->page_offset + fs->fobj->num_pages) return fs; } return NULL; } void file_lock(struct file *f) { mutex_lock(&f->mu); } bool file_trylock(struct file *f) { return mutex_trylock(&f->mu); } void file_unlock(struct file *f) { mutex_unlock(&f->mu); }