blob: 1f6a98716da0ed77381889d749f6fdddc651050d [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2019, Linaro Limited
*/
#include <kernel/panic.h>
#include <kernel/refcount.h>
#include <mm/file.h>
#include <mm/fobj.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <types_ext.h>
#include <util.h>
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);
}