// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2014-2024, NVIDIA CORPORATION. All rights reserved.

#include <linux/platform_device.h>
#include <linux/tegra_nvadsp.h>
#include <linux/dma-mapping.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/firmware.h>
#include <linux/dma-buf.h>
#include <linux/slab.h>
#include <linux/elf.h>

#include "aram_manager.h"
#include "os.h"
#include "dev.h"
#include "adsp_shared_struct.h"

#define DYN_APP_EXTN	".elf"
#define ADSP_APP_INIT_TIMEOUT 2000 /* in ms */

/*
 * structure to hold the list of app binaries loaded and
 * its associated instances.
*/
struct nvadsp_app_service {
	char name[NVADSP_NAME_SZ];
	struct list_head node;
	int instance;
	struct mutex lock;
	struct list_head app_head;
	const uint32_t token;
	const struct app_mem_size *mem_size;
	int generated_instance_id;
	struct adsp_module *mod;
#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs;
#endif
};

/* nvadsp app loader private structure */
struct nvadsp_app_priv_struct {
	struct platform_device *pdev;
	struct completion os_load_complete;
	struct nvadsp_mbox mbox;
	struct list_head service_list;
	struct mutex service_lock_list;
#ifdef CONFIG_DEBUG_FS
	struct dentry *adsp_app_debugfs_root;
#endif
};

static struct nvadsp_app_priv_struct priv;

static void delete_app_instance(nvadsp_app_info_t *);

#ifdef CONFIG_DEBUG_FS
static int dump_binary_in_2bytes_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;
	u32 adsp_ptr;
	u16 *ptr;
	int i;

	adsp_ptr = mod->adsp_module_ptr;
	ptr = (u16 *)mod->module_ptr;
	for (i = 0; i < mod->size; i += 2)
		seq_printf(s, "0x%x : 0x%04x\n", adsp_ptr + i, *(ptr + i));

	return 0;
}


static int dump_binary_in_words_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;
	u32 adsp_ptr;
	u32 *ptr;
	int i;

	adsp_ptr = mod->adsp_module_ptr;
	ptr = (u32 *)mod->module_ptr;
	for (i = 0; i < mod->size; i += 4)
		seq_printf(s, "0x%x : 0x%08x\n", adsp_ptr + i, *(ptr + i));

	return 0;
}

static int host_load_addr_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;

	seq_printf(s, "%p\n", mod->module_ptr);

	return 0;
}

static int adsp_load_addr_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;

	seq_printf(s, "0x%x\n", mod->adsp_module_ptr);

	return 0;
}

static int size_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;

	seq_printf(s, "%lu\n", mod->size);

	return 0;
}

static int version_app_file_node(struct seq_file *s, void *data)
{
	struct nvadsp_app_service *ser = s->private;
	struct adsp_module *mod = ser->mod;

	seq_printf(s, "%s\n", strcmp(mod->version, "") ? mod->version : "unavailable");

	return 0;
}

static int dram_app_file_node(struct seq_file *s, void *data)
{
	const struct app_mem_size *mem_size = s->private;

	seq_printf(s, "%llu\n", mem_size->dram);

	return 0;
}

static int dram_shared_app_file_node(struct seq_file *s, void *data)
{
	const struct app_mem_size *mem_size = s->private;

	seq_printf(s, "%llu\n", mem_size->dram_shared);

	return 0;
}

static int dram_shared_wc_app_file_node(struct seq_file *s, void *data)
{
	const struct app_mem_size *mem_size = s->private;

	seq_printf(s, "%llu\n", mem_size->dram_shared_wc);

	return 0;
}

static int aram_app_file_node(struct seq_file *s, void *data)
{
	const struct app_mem_size *mem_size = s->private;

	seq_printf(s, "%llu\n", mem_size->aram);

	return 0;
}

static int aram_exclusive_app_file_node(struct seq_file *s, void *data)
{
	const struct app_mem_size *mem_size = s->private;

	seq_printf(s, "%llu\n", mem_size->aram_x);

	return 0;
}

#define ADSP_APP_CREATE_FOLDER(x, root) \
	do  {\
		x = debugfs_create_dir(#x, root); \
		if (IS_ERR_OR_NULL(x)) { \
			dev_err(dev, "unable to create app %s folder\n", #x); \
			ret = -ENOENT; \
			goto rm_debug_root; \
		} \
	} while (0)

#define ADSP_APP_CREATE_FILE(x, priv, root) \
	do { \
		if (IS_ERR_OR_NULL(debugfs_create_file(#x, S_IRUSR, root, \
			priv, &x##_node_operations))) { \
			dev_err(dev, "unable tp create app %s file\n", #x); \
			ret = -ENOENT; \
			goto rm_debug_root; \
		} \
	} while (0)

#define ADSP_APP_FILE_OPERATION(x) \
static int x##_open(struct inode *inode, struct file *file) \
{ \
	return single_open(file, x##_app_file_node, inode->i_private); \
} \
\
static const struct file_operations x##_node_operations = { \
	.open = x##_open, \
	.read = seq_read, \
	.llseek = seq_lseek, \
	.release = single_release, \
};

ADSP_APP_FILE_OPERATION(dump_binary_in_2bytes);
ADSP_APP_FILE_OPERATION(dump_binary_in_words);
ADSP_APP_FILE_OPERATION(host_load_addr);
ADSP_APP_FILE_OPERATION(adsp_load_addr);
ADSP_APP_FILE_OPERATION(size);
ADSP_APP_FILE_OPERATION(version);

ADSP_APP_FILE_OPERATION(dram);
ADSP_APP_FILE_OPERATION(dram_shared);
ADSP_APP_FILE_OPERATION(dram_shared_wc);
ADSP_APP_FILE_OPERATION(aram);
ADSP_APP_FILE_OPERATION(aram_exclusive);

static int create_adsp_app_debugfs(struct nvadsp_app_service *ser)
{

	struct app_mem_size *mem_size = (struct app_mem_size *)ser->mem_size;
	struct device *dev = &priv.pdev->dev;
	struct dentry *instance_mem_sizes;
	struct dentry *root;
	int ret = 0;

	root = debugfs_create_dir(ser->name,
			priv.adsp_app_debugfs_root);
	if (IS_ERR_OR_NULL(root)) {
		ret = -EINVAL;
		goto err_out;
	}

	ADSP_APP_CREATE_FILE(dump_binary_in_2bytes, ser, root);
	ADSP_APP_CREATE_FILE(dump_binary_in_words, ser, root);
	ADSP_APP_CREATE_FILE(host_load_addr, ser, root);
	ADSP_APP_CREATE_FILE(adsp_load_addr, ser, root);
	ADSP_APP_CREATE_FILE(size, ser, root);
	ADSP_APP_CREATE_FILE(version, ser, root);
	ADSP_APP_CREATE_FOLDER(instance_mem_sizes, root);
	ADSP_APP_CREATE_FILE(dram, mem_size, instance_mem_sizes);
	ADSP_APP_CREATE_FILE(dram_shared, mem_size, instance_mem_sizes);
	ADSP_APP_CREATE_FILE(dram_shared_wc, mem_size, instance_mem_sizes);
	ADSP_APP_CREATE_FILE(aram, mem_size, instance_mem_sizes);
	ADSP_APP_CREATE_FILE(aram_exclusive, mem_size, instance_mem_sizes);

	root = ser->debugfs;
	return 0;
rm_debug_root:
	debugfs_remove_recursive(root);
err_out:
	return ret;
}

static int __init adsp_app_debug_init(struct dentry *root)
{
	priv.adsp_app_debugfs_root = debugfs_create_dir("adsp_apps", root);
	return IS_ERR_OR_NULL(priv.adsp_app_debugfs_root) ? -ENOMEM : 0;
}
#endif /* CONFIG_DEBUG_FS */

static struct nvadsp_app_service *get_loaded_service(const char *appfile)
{
	struct device *dev = &priv.pdev->dev;
	struct nvadsp_app_service *ser;

	list_for_each_entry(ser, &priv.service_list, node) {
		if (!strcmp(appfile, ser->name)) {
			dev_dbg(dev, "module %s already loaded\n", appfile);
			return ser;
		}
	}
	dev_dbg(dev, "module %s will be loaded\n", appfile);
	return NULL;
}

static inline void extract_appname(char *appname, const char *appfile)
{
	char *token = strstr(appfile, DYN_APP_EXTN);
	int len = token ? token - appfile : strlen(appfile);

	strncpy(appname, appfile, len);
	appname[len] = '\0';
}

static nvadsp_app_handle_t app_load(const char *appfile,
	struct adsp_shared_app *shared_app, bool dynamic)
{
	struct nvadsp_drv_data *drv_data;
	struct device *dev = &priv.pdev->dev;
	char appname[NVADSP_NAME_SZ] = { };
	struct nvadsp_app_service *ser;

	drv_data = platform_get_drvdata(priv.pdev);
	extract_appname(appname, appfile);
	mutex_lock(&priv.service_lock_list);
	ser = get_loaded_service(appname);
	if (!ser) {

		/* dynamic loading is disabled when running in secure mode */
		if (drv_data->adsp_os_secload && dynamic)
			goto err;
		dev_dbg(dev, "loading app %s %s\n", appfile, appname);
		ser = devm_kzalloc(dev, sizeof(*ser), GFP_KERNEL);
		if (!ser)
			goto err;
		strscpy(ser->name, appname, NVADSP_NAME_SZ);

		/*load the module in to memory */
		ser->mod = dynamic ?
			load_adsp_dynamic_module(appfile, appfile, dev) :
			load_adsp_static_module(appfile, shared_app, dev);
		if (IS_ERR_OR_NULL(ser->mod))
			goto err_free_service;
		ser->mem_size = &ser->mod->mem_size;

		mutex_init(&ser->lock);
		INIT_LIST_HEAD(&ser->app_head);

		/* add the app instance service to the list */
		list_add_tail(&ser->node, &priv.service_list);
#ifdef CONFIG_DEBUG_FS
		create_adsp_app_debugfs(ser);
#endif
		dev_dbg(dev, "loaded app %s\n", ser->name);
	}
	mutex_unlock(&priv.service_lock_list);

	return ser;

err_free_service:
	devm_kfree(dev, ser);
err:
	mutex_unlock(&priv.service_lock_list);
	return NULL;
}


nvadsp_app_handle_t nvadsp_app_load(const char *appname, const char *appfile)
{
	struct nvadsp_drv_data *drv_data;

	if (IS_ERR_OR_NULL(priv.pdev)) {
		pr_err("ADSP Driver is not initialized\n");
		return NULL;
	}

	drv_data = platform_get_drvdata(priv.pdev);

	if (!drv_data->adsp_os_running)
		return NULL;

	return app_load(appfile, NULL, true);
}
EXPORT_SYMBOL(nvadsp_app_load);

static void free_instance_memory(nvadsp_app_info_t *app,
		const struct app_mem_size *sz)
{
	adsp_app_mem_t *mem = &app->mem;
	adsp_app_iova_mem_t *iova_mem = &app->iova_mem;

	if (mem->dram) {
		nvadsp_free_coherent(sz->dram, mem->dram, iova_mem->dram);
		mem->dram = NULL;
		iova_mem->dram = 0;
	}

	if (mem->shared) {
		nvadsp_free_coherent(sz->dram_shared, mem->shared,
				iova_mem->shared);
		mem->shared = NULL;
		iova_mem->shared = 0;
	}

	if (mem->shared_wc) {
		nvadsp_free_coherent(sz->dram_shared_wc, mem->shared_wc,
				iova_mem->shared_wc);
		mem->shared_wc = NULL;
		iova_mem->shared_wc = 0;
	}

	if (mem->aram_flag)
		nvadsp_aram_release(mem->aram);
	else if (mem->aram)
		nvadsp_free_coherent(sz->aram, mem->aram, iova_mem->aram);
	mem->aram = NULL;
	iova_mem->aram = 0;
	mem->aram_flag = 0;

	if (mem->aram_x_flag) {
		nvadsp_aram_release(mem->aram_x);
		mem->aram_x = NULL;
		iova_mem->aram_x = 0;
		mem->aram_flag = 0;
	}

}

static int create_instance_memory(nvadsp_app_info_t *app,
		const struct app_mem_size *sz)
{
	adsp_app_iova_mem_t *iova_mem = &app->iova_mem;
	struct device *dev = &priv.pdev->dev;
	adsp_app_mem_t *mem = &app->mem;
	char name[NVADSP_NAME_SZ];
	void *aram_handle;
	dma_addr_t da;
	int ret;

	ret = snprintf(name, NVADSP_NAME_SZ, "%s:%d", app->name, app->instance_id);
	if (ret < 0 || ret >= NVADSP_NAME_SZ) {
		dev_err(dev, "Invalid App name %s\n", app->name);
		return -EINVAL;
	}

	if (sz->dram) {
		mem->dram = nvadsp_alloc_coherent(sz->dram, &da, GFP_KERNEL);
		iova_mem->dram = (uint32_t)da;
		if (!mem->dram) {
			dev_err(dev, "app %s dram alloc failed\n",
				name);
			goto end;
		}
		dev_dbg(dev, "%s :: mem.dram %p 0x%x\n", name,
			mem->dram, iova_mem->dram);
	}

	if (sz->dram_shared) {
		mem->shared = nvadsp_alloc_coherent(sz->dram_shared,
				&da, GFP_KERNEL);
		if (!mem->shared) {
			dev_err(dev, "app %s shared dram alloc failed\n",
				name);
			goto end;
		}
		iova_mem->shared = (uint32_t)da;
		dev_dbg(dev, "%s :: mem.shared %p 0x%x\n", name,
			mem->shared, iova_mem->shared);
	}

	if (sz->dram_shared_wc) {
		mem->shared_wc = nvadsp_alloc_coherent(sz->dram_shared_wc,
					&da, GFP_KERNEL);
		if (!mem->shared_wc) {
			dev_err(dev, "app %s shared dram wc alloc failed\n",
				name);
			goto end;
		}
		iova_mem->shared_wc = (uint32_t)da;
		dev_dbg(dev, "%s :: mem.shared_wc %p 0x%x\n", name,
			mem->shared_wc, iova_mem->shared_wc);
	}

	if (sz->aram) {
		aram_handle = nvadsp_aram_request(name, sz->aram);
		if (!IS_ERR_OR_NULL(aram_handle)) {
			iova_mem->aram = nvadsp_aram_get_address(aram_handle);
			mem->aram = aram_handle;
			iova_mem->aram_flag = mem->aram_flag = 1;
			dev_dbg(dev, "%s aram %x\n", name, iova_mem->aram);
		} else {
			dev_dbg(dev, "app %s no ARAM memory ! using DRAM\n",
				name);
			mem->aram = nvadsp_alloc_coherent(sz->aram,
					&da, GFP_KERNEL);
			if (!mem->aram) {
				iova_mem->aram_flag = mem->aram_flag = 0;
				dev_err(dev,
					"app %s aram memory alloc failed\n",
					name);
				goto end;
			}
			iova_mem->aram = (uint32_t)da;
			dev_dbg(dev, "%s :: mem.aram %p 0x%x\n", name,
				mem->aram, iova_mem->aram);
		}
	}

	if (sz->aram_x) {
		aram_handle = nvadsp_aram_request(name, sz->aram);
		if (!IS_ERR_OR_NULL(aram_handle)) {
			iova_mem->aram_x = nvadsp_aram_get_address(aram_handle);
			mem->aram_x = aram_handle;
			iova_mem->aram_x_flag = mem->aram_x_flag = 1;
			dev_dbg(dev, "aram_x %x\n", iova_mem->aram_x);
		} else {
			iova_mem->aram_x = 0;
			iova_mem->aram_x_flag = mem->aram_x_flag = 0;
			dev_err(dev, "app %s aram x memory alloc failed\n",
				name);
		}
	}
	return 0;

end:
	free_instance_memory(app, sz);
	return -ENOMEM;
}

static void fill_app_instance_data(nvadsp_app_info_t *app,
	struct nvadsp_app_service *ser, nvadsp_app_args_t *app_args,
	struct run_app_instance_data *data, uint32_t stack_sz)
{
	adsp_app_iova_mem_t *iova_mem = &app->iova_mem;

	data->adsp_mod_ptr = ser->mod->adsp_module_ptr;
	/* copy the iova address to adsp so that adsp can access the memory */
	data->dram_data_ptr = iova_mem->dram;
	data->dram_shared_ptr = iova_mem->shared;
	data->dram_shared_wc_ptr = iova_mem->shared_wc;
	data->aram_ptr = iova_mem->aram;
	data->aram_flag = iova_mem->aram_flag;
	data->aram_x_ptr = iova_mem->aram_x;
	data->aram_x_flag = iova_mem->aram_x_flag;

	if (app_args)
		memcpy(&data->app_args, app_args, sizeof(nvadsp_app_args_t));
	/*
	 * app on adsp holds the reference of host app instance to communicate
	 * back when completed. This way we do not need to iterate through the
	 * list to find the instance.
	 */
	data->host_ref = (uint64_t)app;

	/* copy instance mem_size */
	memcpy(&data->mem_size, ser->mem_size, sizeof(struct app_mem_size));
}

static nvadsp_app_info_t *create_app_instance(nvadsp_app_handle_t handle,
	nvadsp_app_args_t *app_args, struct run_app_instance_data *data,
	app_complete_status_notifier notifier, uint32_t stack_size,
	uint32_t core_id)
{
	struct nvadsp_app_service *ser = (void *)handle;
	struct device *dev = &priv.pdev->dev;
	nvadsp_app_info_t *app;
	int *state;
	int *id;

	app = kzalloc(sizeof(*app), GFP_KERNEL);
	if (unlikely(!app)) {
		dev_err(dev, "cannot allocate memory for app %s instance\n",
				ser->name);
		goto err_value;
	}
	/* set the instance name with the app name */
	app->name = ser->name;
	/* associate a unique id */
	id = (int *)&app->instance_id;
	*id = ser->generated_instance_id++;
	/*
	 * hold the pointer to the service, to dereference later during deinit
	 */
	app->handle = ser;

	/* create the instance memory required by the app instance */
	if (create_instance_memory(app, ser->mem_size)) {
		dev_err(dev, "instance creation failed for app %s:%d\n",
				app->name, app->instance_id);
		goto free_app;
	}

	/* assign the stack that is needed by the app */
	data->stack_size  = stack_size;
	/* assign the core that is needed by the app */
	data->core_id  = core_id;

	/* set the state to INITIALIZED. No need to do it in a spin lock */
	state = (int *)&app->state;
	*state = NVADSP_APP_STATE_INITIALIZED;

	/* increment instance count and add the app instance to service list */
	mutex_lock(&ser->lock);
	list_add_tail(&app->node, &ser->app_head);
	ser->instance++;
	mutex_unlock(&ser->lock);

	fill_app_instance_data(app, ser, app_args, data, stack_size);

	init_completion(&app->wait_for_app_start);
	init_completion(&app->wait_for_app_complete);
	set_app_complete_notifier(app, notifier);

	dev_dbg(dev, "app %s instance %d initilized\n",
			app->name, app->instance_id);
	dev_dbg(dev, "app %s has %d instances\n", ser->name, ser->instance);
	goto end;

free_app:
	kfree(app);
err_value:
	app = ERR_PTR(-ENOMEM);
end:
	return app;
}

nvadsp_app_info_t __must_check *nvadsp_app_init(nvadsp_app_handle_t handle,
	nvadsp_app_args_t *args)
{
	struct nvadsp_app_shared_msg_pool *msg_pool;
	struct nvadsp_shared_mem *shared_mem;
	union app_loader_message *message;
	struct nvadsp_drv_data *drv_data;
	struct app_loader_data *data;
	nvadsp_app_info_t *app;
	msgq_t *msgq_send;
	int *state;
	unsigned long flags, ret = 0;

	if (IS_ERR_OR_NULL(priv.pdev)) {
		pr_err("ADSP Driver is not initialized\n");
		goto err;
	}

	drv_data = platform_get_drvdata(priv.pdev);

	if (!drv_data->adsp_os_running) {
		pr_err("ADSP is not running\n");
		goto err;
	}

	if (IS_ERR_OR_NULL(handle)) {
		pr_err("ADSP APP handle is NULL\n");
		goto err;
	}

	message = kzalloc(sizeof(*message), GFP_KERNEL);
	if (!message) {
		pr_err("Failed to allocate memory for ADSP msg\n");
		goto err;
	}

	shared_mem = drv_data->shared_adsp_os_data;
	msg_pool = &shared_mem->app_shared_msg_pool;
	msgq_send = &msg_pool->app_loader_send_message.msgq;
	data = &message->data;

	/* Pinning app to core 0 by default */
	app = create_app_instance(handle, args, &data->app_init, NULL, 0, 0);
	if (IS_ERR_OR_NULL(app)) {
		pr_err("Failed to create APP instance\n");
		kfree(message);
		goto err;
	}
	app->priv = data;
	data->app_init.message = ADSP_APP_INIT;

	message->msgq_msg.size = MSGQ_MSG_PAYLOAD_WSIZE(*message);

	spin_lock_irqsave(&drv_data->mbox_lock, flags);
	msgq_queue_message(msgq_send, &message->msgq_msg);
	spin_unlock_irqrestore(&drv_data->mbox_lock, flags);

	if (app->return_status) {
		state = (int *)&app->state;
		*state = NVADSP_APP_STATE_STARTED;
	}

	nvadsp_mbox_send(&priv.mbox, 0, NVADSP_MBOX_SMSG, false, 0);

	ret = wait_for_completion_timeout(&app->wait_for_app_start,
			msecs_to_jiffies(ADSP_APP_INIT_TIMEOUT));
	if (!ret) {
		delete_app_instance(app);
		return NULL;
	}
	init_completion(&app->wait_for_app_start);
	return app;
err:
	return ERR_PTR(-ENOMEM);
}
EXPORT_SYMBOL(nvadsp_app_init);

static int start_app_on_adsp(nvadsp_app_info_t *app,
	union app_loader_message *message, bool block)
{
	struct nvadsp_app_shared_msg_pool *msg_pool;
	struct device *dev = &priv.pdev->dev;
	struct nvadsp_shared_mem *shared_mem;
	struct nvadsp_drv_data *drv_data;
	msgq_t *msgq_send;
	int *state;
	unsigned long flags;

	drv_data = platform_get_drvdata(priv.pdev);
	shared_mem = drv_data->shared_adsp_os_data;
	msg_pool = &shared_mem->app_shared_msg_pool;
	msgq_send = &msg_pool->app_loader_send_message.msgq;

	message->msgq_msg.size = MSGQ_MSG_PAYLOAD_WSIZE(*message);

	spin_lock_irqsave(&drv_data->mbox_lock, flags);
	msgq_queue_message(msgq_send, &message->msgq_msg);
	spin_unlock_irqrestore(&drv_data->mbox_lock, flags);

	state = (int *)&app->state;
	*state = NVADSP_APP_STATE_STARTED;

	nvadsp_mbox_send(&priv.mbox, 0, NVADSP_MBOX_SMSG, false, 0);

	if (block) {
		wait_for_completion(&app->wait_for_app_start);
		if (app->return_status) {
			dev_err(dev, "%s app instance %d failed to start\n",
				app->name, app->instance_id);
			state = (int *)&app->state;
			*state = NVADSP_APP_STATE_INITIALIZED;
		}
	}

	return app->return_status;
}

int nvadsp_app_start(nvadsp_app_info_t *app)
{
	union app_loader_message *message = app->priv;
	struct app_loader_data *data = &message->data;
	struct nvadsp_drv_data *drv_data;
	int ret = -EINVAL;

	if (IS_ERR_OR_NULL(app))
		return -EINVAL;

	if (IS_ERR_OR_NULL(priv.pdev)) {
		pr_err("ADSP Driver is not initialized\n");
		goto err;
	}

	drv_data = platform_get_drvdata(priv.pdev);

	if (!drv_data->adsp_os_running)
		goto err;

	data->app_init.message = ADSP_APP_START;
	data->app_init.adsp_ref = app->token;
	data->app_init.stack_size = app->stack_size;
	ret = start_app_on_adsp(app, app->priv, true);
err:
	return ret;
}
EXPORT_SYMBOL(nvadsp_app_start);

nvadsp_app_info_t *nvadsp_run_app(nvadsp_os_handle_t os_handle,
	const char *appfile, nvadsp_app_args_t *app_args,
	app_complete_status_notifier notifier, uint32_t stack_sz,
	uint32_t core_id, bool block)
{
	union app_loader_message message = {};
	nvadsp_app_handle_t service_handle;
	struct nvadsp_drv_data *drv_data;
	nvadsp_app_info_t *info =  NULL;
	struct app_loader_data *data;
	struct device *dev;
	int ret;

	if (IS_ERR_OR_NULL(priv.pdev)) {
		pr_err("ADSP Driver is not initialized\n");
		info = ERR_PTR(-EINVAL);
		goto end;
	}

	drv_data = platform_get_drvdata(priv.pdev);
	dev = &priv.pdev->dev;

	if (!drv_data->adsp_os_running)
		goto end;

	if (IS_ERR_OR_NULL(appfile))
		goto end;

	data = &message.data;
	service_handle = app_load(appfile, NULL, true);
	if (!service_handle) {
		dev_err(dev, "unable to load the app %s\n", appfile);
		goto end;
	}

	info = create_app_instance(service_handle, app_args,
		&data->app_init, notifier, stack_sz, core_id);
	if (IS_ERR_OR_NULL(info)) {
		dev_err(dev, "unable to create instance for app %s\n", appfile);
		goto end;
	}
	data->app_init.message = RUN_ADSP_APP;

	ret = start_app_on_adsp(info, &message, block);
	if (ret) {
		delete_app_instance(info);
		info = NULL;
	}
end:
	return info;
}
EXPORT_SYMBOL(nvadsp_run_app);

static void delete_app_instance(nvadsp_app_info_t *app)
{
	struct nvadsp_app_service *ser =
		(struct nvadsp_app_service *)app->handle;
	struct device *dev = &priv.pdev->dev;

	dev_dbg(dev, "%s:freeing app %s:%d\n",
		__func__, app->name, app->instance_id);

	/* update the service app instance manager atomically */
	mutex_lock(&ser->lock);
	ser->instance--;
	list_del(&app->node);
	mutex_unlock(&ser->lock);

	/* free instance memory */
	free_instance_memory(app, ser->mem_size);
	kfree(app->priv);
	kfree(app);
}

void nvadsp_exit_app(nvadsp_app_info_t *app, bool terminate)
{
	int *state;

	if (IS_ERR_OR_NULL(priv.pdev)) {
		pr_err("ADSP Driver is not initialized\n");
		return;
	}

	if (IS_ERR_OR_NULL(app))
		return;

	/* TODO: add termination if possible to kill thread on adsp */
	if (app->state == NVADSP_APP_STATE_STARTED) {
		wait_for_completion(&app->wait_for_app_complete);
		state = (int *)&app->state;
		*state = NVADSP_APP_STATE_INITIALIZED;
	}
	delete_app_instance(app);
}
EXPORT_SYMBOL(nvadsp_exit_app);

int nvadsp_app_deinit(nvadsp_app_info_t *app)
{
	nvadsp_exit_app(app, false);
	return 0;
}
EXPORT_SYMBOL(nvadsp_app_deinit);

int nvadsp_app_stop(nvadsp_app_info_t *app)
{
	return -ENOENT;
}
EXPORT_SYMBOL(nvadsp_app_stop);

void nvadsp_app_unload(nvadsp_app_handle_t handle)
{
	struct nvadsp_drv_data *drv_data;
	struct nvadsp_app_service *ser;
	struct device *dev;

	if (!priv.pdev) {
		pr_err("ADSP Driver is not initialized\n");
		return;
	}

	drv_data = platform_get_drvdata(priv.pdev);
	dev = &priv.pdev->dev;

	if (!drv_data->adsp_os_running)
		return;

	if (IS_ERR_OR_NULL(handle))
		return;

	ser = (struct nvadsp_app_service *)handle;
	if (!ser->mod->dynamic)
		return;

	mutex_lock(&priv.service_lock_list);
	if (ser->instance) {
		dev_err(dev, "cannot unload app %s, has instances %d\n",
				ser->name, ser->instance);
		return;
	}

	list_del(&ser->node);
#ifdef CONFIG_DEBUG_FS
	debugfs_remove_recursive(ser->debugfs);
#endif
	unload_adsp_module(ser->mod);
	devm_kfree(dev, ser);
	mutex_unlock(&priv.service_lock_list);
}
EXPORT_SYMBOL(nvadsp_app_unload);

static status_t nvadsp_app_receive_handler(uint32_t msg, void *hdata)
{
	union app_complete_status_message message = { };
	struct nvadsp_app_shared_msg_pool *msg_pool;
	struct app_complete_status_data *data;
	struct nvadsp_shared_mem *shared_mem;
	struct nvadsp_drv_data *drv_data;
	struct platform_device *pdev;
	nvadsp_app_info_t *app;
	struct device *dev;
	msgq_t *msgq_recv;
	uint32_t *token;

	pdev = hdata;
	dev = &pdev->dev;
	drv_data = platform_get_drvdata(pdev);
	shared_mem = drv_data->shared_adsp_os_data;
	msg_pool = &shared_mem->app_shared_msg_pool;
	msgq_recv = &msg_pool->app_loader_recv_message.msgq;
	data = &message.complete_status_data;

	message.msgq_msg.size = MSGQ_MSG_PAYLOAD_WSIZE(*data);
	if (msgq_dequeue_message(msgq_recv, &message.msgq_msg)) {
		dev_err(dev, "unable to dequeue app status message\n");
		return 0;
	}

	app = (nvadsp_app_info_t *)data->host_ref;
	app->return_status = data->status;
	app->status_msg = data->header.message;
	token = (uint32_t *)&app->token;
	*token = data->adsp_ref;

	if (app->complete_status_notifier) {
		app->complete_status_notifier(app,
			app->status_msg, app->return_status);
	}

	switch (data->header.message) {
	case ADSP_APP_START_STATUS:
		complete_all(&app->wait_for_app_start);
		break;
	case ADSP_APP_COMPLETE_STATUS:
		complete_all(&app->wait_for_app_complete);
		break;
	}

	return 0;
}

int load_adsp_static_apps(void)
{
	struct nvadsp_app_shared_msg_pool *msg_pool;
	struct nvadsp_shared_mem *shared_mem;
	struct nvadsp_drv_data *drv_data;
	struct platform_device *pdev;
	struct device *dev;
	msgq_t *msgq_recv;

	pdev = priv.pdev;
	dev = &pdev->dev;
	drv_data = platform_get_drvdata(pdev);

	shared_mem = drv_data->shared_adsp_os_data;
	if (!shared_mem)
		return 0;

	msg_pool = &shared_mem->app_shared_msg_pool;
	msgq_recv = &msg_pool->app_loader_recv_message.msgq;

	while (msgq_recv->size) {
		union app_complete_status_message message = { };
		struct adsp_static_app_data *data;
		struct adsp_shared_app *shared_app;
		char *name;

		data = &message.static_app_data;
		message.msgq_msg.size = MSGQ_MSG_PAYLOAD_WSIZE(*data);
		if (msgq_dequeue_message(msgq_recv, &message.msgq_msg)) {
			dev_err(dev, "dequeue of static apps failed\n");
			return -EINVAL;
		}
		shared_app = &data->shared_app;
		name = shared_app->name;
		if (!shared_app->mod_ptr)
			break;
		/* Skip Start on boot apps */
		if (shared_app->flags & ADSP_APP_FLAG_START_ON_BOOT)
			continue;
		app_load(name, shared_app, false);
	}
	return 0;
}

int __init nvadsp_app_module_probe(struct platform_device *pdev)
{
#ifdef CONFIG_DEBUG_FS
	struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
#endif
	uint16_t mbox_id = APP_LOADER_MBOX_ID;
	struct device *dev = &pdev->dev;
	int ret;

	dev_info(dev, "%s\n", __func__);

	ret = nvadsp_mbox_open(&priv.mbox, &mbox_id,
		"app_service", nvadsp_app_receive_handler, pdev);
	if (ret) {
		dev_err(dev, "unable to open mailbox\n");
		goto end;
	}
	priv.pdev = pdev;
	INIT_LIST_HEAD(&priv.service_list);
	init_completion(&priv.os_load_complete);
	mutex_init(&priv.service_lock_list);

#ifdef CONFIG_DEBUG_FS
	if (adsp_app_debug_init(drv_data->adsp_debugfs_root))
		dev_err(&pdev->dev, "unable to create adsp apps debugfs\n");
#endif
end:
	return ret;
}
