/*
 * core.c - Implementation of core module of MOST Linux driver stack
 *
 * Copyright (C) 2013-2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * This file is licensed under GPLv2.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/kobject.h>
#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/sysfs.h>
#include <linux/kthread.h>
#include <linux/dma-mapping.h>
#include <linux/idr.h>
#include "mostcore.h"

#define MAX_CHANNELS	64
#define STRING_SIZE	80

static struct class *most_class;
static struct device *core_dev;
static struct ida mdev_id;
static int dummy_num_buffers;

struct most_c_aim_obj {
	struct most_aim *ptr;
	int refs;
	int num_buffers;
};

struct most_c_obj {
	struct kobject kobj;
	struct completion cleanup;
	atomic_t mbo_ref;
	atomic_t mbo_nq_level;
	u16 channel_id;
	bool is_poisoned;
	struct mutex start_mutex;
	struct mutex nq_mutex; /* nq thread synchronization */
	int is_starving;
	struct most_interface *iface;
	struct most_inst_obj *inst;
	struct most_channel_config cfg;
	bool keep_mbo;
	bool enqueue_halt;
	struct list_head fifo;
	spinlock_t fifo_lock;
	struct list_head halt_fifo;
	struct list_head list;
	struct most_c_aim_obj aim0;
	struct most_c_aim_obj aim1;
	struct list_head trash_fifo;
	struct task_struct *hdm_enqueue_task;
	wait_queue_head_t hdm_fifo_wq;
};

#define to_c_obj(d) container_of(d, struct most_c_obj, kobj)

struct most_inst_obj {
	int dev_id;
	struct most_interface *iface;
	struct list_head channel_list;
	struct most_c_obj *channel[MAX_CHANNELS];
	struct kobject kobj;
	struct list_head list;
};

static const struct {
	int most_ch_data_type;
	const char *name;
} ch_data_type[] = {
	{ MOST_CH_CONTROL, "control\n" },
	{ MOST_CH_ASYNC, "async\n" },
	{ MOST_CH_SYNC, "sync\n" },
	{ MOST_CH_ISOC, "isoc\n"},
	{ MOST_CH_ISOC, "isoc_avp\n"},
};

#define to_inst_obj(d) container_of(d, struct most_inst_obj, kobj)

/**
 * list_pop_mbo - retrieves the first MBO of the list and removes it
 * @ptr: the list head to grab the MBO from.
 */
#define list_pop_mbo(ptr)						\
({									\
	struct mbo *_mbo = list_first_entry(ptr, struct mbo, list);	\
	list_del(&_mbo->list);						\
	_mbo;								\
})

/*		     ___	     ___
 *		     ___C H A N N E L___
 */

/**
 * struct most_c_attr - to access the attributes of a channel object
 * @attr: attributes of a channel
 * @show: pointer to the show function
 * @store: pointer to the store function
 */
struct most_c_attr {
	struct attribute attr;
	ssize_t (*show)(struct most_c_obj *d,
			struct most_c_attr *attr,
			char *buf);
	ssize_t (*store)(struct most_c_obj *d,
			 struct most_c_attr *attr,
			 const char *buf,
			 size_t count);
};

#define to_channel_attr(a) container_of(a, struct most_c_attr, attr)

/**
 * channel_attr_show - show function of channel object
 * @kobj: pointer to its kobject
 * @attr: pointer to its attributes
 * @buf: buffer
 */
static ssize_t channel_attr_show(struct kobject *kobj, struct attribute *attr,
				 char *buf)
{
	struct most_c_attr *channel_attr = to_channel_attr(attr);
	struct most_c_obj *c_obj = to_c_obj(kobj);

	if (!channel_attr->show)
		return -EIO;

	return channel_attr->show(c_obj, channel_attr, buf);
}

/**
 * channel_attr_store - store function of channel object
 * @kobj: pointer to its kobject
 * @attr: pointer to its attributes
 * @buf: buffer
 * @len: length of buffer
 */
static ssize_t channel_attr_store(struct kobject *kobj,
				  struct attribute *attr,
				  const char *buf,
				  size_t len)
{
	struct most_c_attr *channel_attr = to_channel_attr(attr);
	struct most_c_obj *c_obj = to_c_obj(kobj);

	if (!channel_attr->store)
		return -EIO;
	return channel_attr->store(c_obj, channel_attr, buf, len);
}

static const struct sysfs_ops most_channel_sysfs_ops = {
	.show = channel_attr_show,
	.store = channel_attr_store,
};

/**
 * most_free_mbo_coherent - free an MBO and its coherent buffer
 * @mbo: buffer to be released
 *
 */
static void most_free_mbo_coherent(struct mbo *mbo)
{
	struct most_c_obj *c = mbo->context;
	u16 const coherent_buf_size = c->cfg.buffer_size + c->cfg.extra_len;

	if (c->iface->dma_free)
		c->iface->dma_free(mbo, coherent_buf_size);
	else
		kfree(mbo->virt_address);
	kfree(mbo);
	if (atomic_sub_and_test(1, &c->mbo_ref))
		complete(&c->cleanup);
}

/**
 * flush_channel_fifos - clear the channel fifos
 * @c: pointer to channel object
 */
static void flush_channel_fifos(struct most_c_obj *c)
{
	unsigned long flags, hf_flags;
	struct mbo *mbo, *tmp;

	if (list_empty(&c->fifo) && list_empty(&c->halt_fifo))
		return;

	spin_lock_irqsave(&c->fifo_lock, flags);
	list_for_each_entry_safe(mbo, tmp, &c->fifo, list) {
		list_del(&mbo->list);
		spin_unlock_irqrestore(&c->fifo_lock, flags);
		most_free_mbo_coherent(mbo);
		spin_lock_irqsave(&c->fifo_lock, flags);
	}
	spin_unlock_irqrestore(&c->fifo_lock, flags);

	spin_lock_irqsave(&c->fifo_lock, hf_flags);
	list_for_each_entry_safe(mbo, tmp, &c->halt_fifo, list) {
		list_del(&mbo->list);
		spin_unlock_irqrestore(&c->fifo_lock, hf_flags);
		most_free_mbo_coherent(mbo);
		spin_lock_irqsave(&c->fifo_lock, hf_flags);
	}
	spin_unlock_irqrestore(&c->fifo_lock, hf_flags);

	if (unlikely((!list_empty(&c->fifo) || !list_empty(&c->halt_fifo))))
		pr_info("WARN: fifo | trash fifo not empty\n");
}

/**
 * flush_trash_fifo - clear the trash fifo
 * @c: pointer to channel object
 */
static int flush_trash_fifo(struct most_c_obj *c)
{
	struct mbo *mbo, *tmp;
	unsigned long flags;

	spin_lock_irqsave(&c->fifo_lock, flags);
	list_for_each_entry_safe(mbo, tmp, &c->trash_fifo, list) {
		list_del(&mbo->list);
		spin_unlock_irqrestore(&c->fifo_lock, flags);
		most_free_mbo_coherent(mbo);
		spin_lock_irqsave(&c->fifo_lock, flags);
	}
	spin_unlock_irqrestore(&c->fifo_lock, flags);
	return 0;
}

/**
 * most_channel_release - release function of channel object
 * @kobj: pointer to channel's kobject
 */
static void most_channel_release(struct kobject *kobj)
{
	struct most_c_obj *c = to_c_obj(kobj);

	kfree(c);
}

static ssize_t available_directions_show(struct most_c_obj *c,
					 struct most_c_attr *attr,
					 char *buf)
{
	unsigned int i = c->channel_id;

	strcpy(buf, "");
	if (c->iface->channel_vector[i].direction & MOST_CH_RX)
		strcat(buf, "rx ");
	if (c->iface->channel_vector[i].direction & MOST_CH_TX)
		strcat(buf, "tx ");
	strcat(buf, "\n");
	return strlen(buf);
}

static ssize_t available_datatypes_show(struct most_c_obj *c,
					struct most_c_attr *attr,
					char *buf)
{
	unsigned int i = c->channel_id;

	strcpy(buf, "");
	if (c->iface->channel_vector[i].data_type & MOST_CH_CONTROL)
		strcat(buf, "control ");
	if (c->iface->channel_vector[i].data_type & MOST_CH_ASYNC)
		strcat(buf, "async ");
	if (c->iface->channel_vector[i].data_type & MOST_CH_SYNC)
		strcat(buf, "sync ");
	if (c->iface->channel_vector[i].data_type & MOST_CH_ISOC)
		strcat(buf, "isoc ");
	strcat(buf, "\n");
	return strlen(buf);
}

static ssize_t number_of_packet_buffers_show(struct most_c_obj *c,
					     struct most_c_attr *attr,
					     char *buf)
{
	unsigned int i = c->channel_id;

	return snprintf(buf, PAGE_SIZE, "%d\n",
			c->iface->channel_vector[i].num_buffers_packet);
}

static ssize_t number_of_stream_buffers_show(struct most_c_obj *c,
					     struct most_c_attr *attr,
					     char *buf)
{
	unsigned int i = c->channel_id;

	return snprintf(buf, PAGE_SIZE, "%d\n",
			c->iface->channel_vector[i].num_buffers_streaming);
}

static ssize_t size_of_packet_buffer_show(struct most_c_obj *c,
					  struct most_c_attr *attr,
					  char *buf)
{
	unsigned int i = c->channel_id;

	return snprintf(buf, PAGE_SIZE, "%d\n",
			c->iface->channel_vector[i].buffer_size_packet);
}

static ssize_t size_of_stream_buffer_show(struct most_c_obj *c,
					  struct most_c_attr *attr,
					  char *buf)
{
	unsigned int i = c->channel_id;

	return snprintf(buf, PAGE_SIZE, "%d\n",
			c->iface->channel_vector[i].buffer_size_streaming);
}

static ssize_t channel_starving_show(struct most_c_obj *c,
				     struct most_c_attr *attr,
				     char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->is_starving);
}

static ssize_t set_number_of_buffers_show(struct most_c_obj *c,
					  struct most_c_attr *attr,
					  char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.num_buffers);
}

static ssize_t set_number_of_buffers_store(struct most_c_obj *c,
					   struct most_c_attr *attr,
					   const char *buf,
					   size_t count)
{
	int ret = kstrtou16(buf, 0, &c->cfg.num_buffers);

	if (ret)
		return ret;
	return count;
}

static ssize_t set_buffer_size_show(struct most_c_obj *c,
				    struct most_c_attr *attr,
				    char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.buffer_size);
}

static ssize_t set_buffer_size_store(struct most_c_obj *c,
				     struct most_c_attr *attr,
				     const char *buf,
				     size_t count)
{
	int ret = kstrtou16(buf, 0, &c->cfg.buffer_size);

	if (ret)
		return ret;
	return count;
}

static ssize_t set_direction_show(struct most_c_obj *c,
				  struct most_c_attr *attr,
				  char *buf)
{
	if (c->cfg.direction & MOST_CH_TX)
		return snprintf(buf, PAGE_SIZE, "tx\n");
	else if (c->cfg.direction & MOST_CH_RX)
		return snprintf(buf, PAGE_SIZE, "rx\n");
	return snprintf(buf, PAGE_SIZE, "unconfigured\n");
}

static ssize_t set_direction_store(struct most_c_obj *c,
				   struct most_c_attr *attr,
				   const char *buf,
				   size_t count)
{
	if (!strcmp(buf, "dir_rx\n")) {
		c->cfg.direction = MOST_CH_RX;
	} else if (!strcmp(buf, "rx\n")) {
		c->cfg.direction = MOST_CH_RX;
	} else if (!strcmp(buf, "dir_tx\n")) {
		c->cfg.direction = MOST_CH_TX;
	} else if (!strcmp(buf, "tx\n")) {
		c->cfg.direction = MOST_CH_TX;
	} else {
		pr_info("WARN: invalid attribute settings\n");
		return -EINVAL;
	}
	return count;
}

static ssize_t set_datatype_show(struct most_c_obj *c,
				 struct most_c_attr *attr,
				 char *buf)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ch_data_type); i++) {
		if (c->cfg.data_type & ch_data_type[i].most_ch_data_type)
			return snprintf(buf, PAGE_SIZE, ch_data_type[i].name);
	}
	return snprintf(buf, PAGE_SIZE, "unconfigured\n");
}

static ssize_t set_datatype_store(struct most_c_obj *c,
				  struct most_c_attr *attr,
				  const char *buf,
				  size_t count)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ch_data_type); i++) {
		if (!strcmp(buf, ch_data_type[i].name)) {
			c->cfg.data_type = ch_data_type[i].most_ch_data_type;
			break;
		}
	}

	if (i == ARRAY_SIZE(ch_data_type)) {
		pr_info("WARN: invalid attribute settings\n");
		return -EINVAL;
	}
	return count;
}

static ssize_t set_subbuffer_size_show(struct most_c_obj *c,
				       struct most_c_attr *attr,
				       char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.subbuffer_size);
}

static ssize_t set_subbuffer_size_store(struct most_c_obj *c,
					struct most_c_attr *attr,
					const char *buf,
					size_t count)
{
	int ret = kstrtou16(buf, 0, &c->cfg.subbuffer_size);

	if (ret)
		return ret;
	return count;
}

static ssize_t set_packets_per_xact_show(struct most_c_obj *c,
					 struct most_c_attr *attr,
					 char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.packets_per_xact);
}

static ssize_t set_packets_per_xact_store(struct most_c_obj *c,
					  struct most_c_attr *attr,
					  const char *buf,
					  size_t count)
{
	int ret = kstrtou16(buf, 0, &c->cfg.packets_per_xact);

	if (ret)
		return ret;
	return count;
}

static ssize_t set_dbr_size_show(struct most_c_obj *c,
				 struct most_c_attr *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", c->cfg.dbr_size);
}

static ssize_t set_dbr_size_store(struct most_c_obj *c,
				  struct most_c_attr *attr, const char *buf,
				  size_t count)
{
	int ret = kstrtou16(buf, 0, &c->cfg.dbr_size);

	if (ret)
		return ret;
	return count;
}

static struct most_c_attr common_c_attrs[] = {
	__ATTR_RO(available_directions),
	__ATTR_RO(available_datatypes),
	__ATTR_RO(number_of_packet_buffers),
	__ATTR_RO(number_of_stream_buffers),
	__ATTR_RO(size_of_stream_buffer),
	__ATTR_RO(size_of_packet_buffer),
	__ATTR_RO(channel_starving),
	__ATTR_RW(set_buffer_size),
	__ATTR_RW(set_number_of_buffers),
	__ATTR_RW(set_direction),
	__ATTR_RW(set_datatype),
	__ATTR_RW(set_subbuffer_size),
};

static struct most_c_attr xact_c_attr = __ATTR_RW(set_packets_per_xact);

static struct most_c_attr dbr_c_attr = __ATTR_RW(set_dbr_size);

/**
 * create_most_c_obj - allocates a channel object
 * @name: name of the channel object
 * @parent: parent kobject
 *
 * This create a channel object and registers it with sysfs.
 * Returns a pointer to the object or NULL when something went wrong.
 */
static struct most_c_obj *create_most_c_obj(
	struct kobj_type *ktype, const char *name, struct kobject *parent)
{
	struct most_c_obj *c;
	int retval;

	c = kzalloc(sizeof(*c), GFP_KERNEL);
	if (!c)
		return NULL;
	retval = kobject_init_and_add(&c->kobj, ktype, parent, "%s", name);
	if (retval) {
		kobject_put(&c->kobj);
		return NULL;
	}
	kobject_uevent(&c->kobj, KOBJ_ADD);
	return c;
}

/*		     ___	       ___
 *		     ___I N S T A N C E___
 */

static struct list_head instance_list;

/**
 * struct most_inst_attribute - to access the attributes of instance object
 * @attr: attributes of an instance
 * @show: pointer to the show function
 * @store: pointer to the store function
 */
struct most_inst_attribute {
	struct attribute attr;
	ssize_t (*show)(struct most_inst_obj *d,
			struct most_inst_attribute *attr,
			char *buf);
	ssize_t (*store)(struct most_inst_obj *d,
			 struct most_inst_attribute *attr,
			 const char *buf,
			 size_t count);
};

#define to_instance_attr(a) \
	container_of(a, struct most_inst_attribute, attr)

/**
 * instance_attr_show - show function for an instance object
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 */
static ssize_t instance_attr_show(struct kobject *kobj,
				  struct attribute *attr,
				  char *buf)
{
	struct most_inst_attribute *instance_attr;
	struct most_inst_obj *instance_obj;

	instance_attr = to_instance_attr(attr);
	instance_obj = to_inst_obj(kobj);

	if (!instance_attr->show)
		return -EIO;

	return instance_attr->show(instance_obj, instance_attr, buf);
}

/**
 * instance_attr_store - store function for an instance object
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 * @len: length of buffer
 */
static ssize_t instance_attr_store(struct kobject *kobj,
				   struct attribute *attr,
				   const char *buf,
				   size_t len)
{
	struct most_inst_attribute *instance_attr;
	struct most_inst_obj *instance_obj;

	instance_attr = to_instance_attr(attr);
	instance_obj = to_inst_obj(kobj);

	if (!instance_attr->store)
		return -EIO;

	return instance_attr->store(instance_obj, instance_attr, buf, len);
}

static const struct sysfs_ops most_inst_sysfs_ops = {
	.show = instance_attr_show,
	.store = instance_attr_store,
};

/**
 * most_inst_release - release function for instance object
 * @kobj: pointer to instance's kobject
 *
 * This frees the allocated memory for the instance object
 */
static void most_inst_release(struct kobject *kobj)
{
	struct most_inst_obj *inst = to_inst_obj(kobj);

	kfree(inst);
}

static ssize_t description_show(struct most_inst_obj *instance_obj,
				struct most_inst_attribute *attr,
				char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n",
			instance_obj->iface->description);
}

static ssize_t interface_show(struct most_inst_obj *instance_obj,
			      struct most_inst_attribute *attr,
			      char *buf)
{
	switch (instance_obj->iface->interface) {
	case ITYPE_LOOPBACK:
		return snprintf(buf, PAGE_SIZE, "loopback\n");
	case ITYPE_I2C:
		return snprintf(buf, PAGE_SIZE, "i2c\n");
	case ITYPE_I2S:
		return snprintf(buf, PAGE_SIZE, "i2s\n");
	case ITYPE_TSI:
		return snprintf(buf, PAGE_SIZE, "tsi\n");
	case ITYPE_HBI:
		return snprintf(buf, PAGE_SIZE, "hbi\n");
	case ITYPE_MEDIALB_DIM:
		return snprintf(buf, PAGE_SIZE, "mlb_dim\n");
	case ITYPE_MEDIALB_DIM2:
		return snprintf(buf, PAGE_SIZE, "mlb_dim2\n");
	case ITYPE_USB:
		return snprintf(buf, PAGE_SIZE, "usb\n");
	case ITYPE_PCIE:
		return snprintf(buf, PAGE_SIZE, "pcie\n");
	}
	return snprintf(buf, PAGE_SIZE, "unknown\n");
}

static struct most_inst_attribute most_inst_attr_description =
	__ATTR_RO(description);

static struct most_inst_attribute most_inst_attr_interface =
	__ATTR_RO(interface);

static struct attribute *most_inst_def_attrs[] = {
	&most_inst_attr_description.attr,
	&most_inst_attr_interface.attr,
	NULL,
};

static struct kobj_type most_inst_ktype = {
	.sysfs_ops = &most_inst_sysfs_ops,
	.release = most_inst_release,
	.default_attrs = most_inst_def_attrs,
};

static struct kset *most_inst_kset;

/**
 * create_most_inst_obj - creates an instance object
 * @name: name of the object to be created
 *
 * This allocates memory for an instance structure, assigns the proper kset
 * and registers it with sysfs.
 *
 * Returns a pointer to the instance object or NULL when something went wrong.
 */
static struct most_inst_obj *create_most_inst_obj(const char *name)
{
	struct most_inst_obj *inst;
	int retval;

	inst = kzalloc(sizeof(*inst), GFP_KERNEL);
	if (!inst)
		return NULL;
	inst->kobj.kset = most_inst_kset;
	retval = kobject_init_and_add(&inst->kobj, &most_inst_ktype, NULL,
				      "%s", name);
	if (retval) {
		kobject_put(&inst->kobj);
		return NULL;
	}
	kobject_uevent(&inst->kobj, KOBJ_ADD);
	return inst;
}

/**
 * destroy_most_inst_obj - MOST instance release function
 * @inst: pointer to the instance object
 *
 * This decrements the reference counter of the instance object.
 * If the reference count turns zero, its release function is called
 */
static void destroy_most_inst_obj(struct most_inst_obj *inst)
{
	struct most_c_obj *c, *tmp;

	list_for_each_entry_safe(c, tmp, &inst->channel_list, list) {
		flush_trash_fifo(c);
		flush_channel_fifos(c);
		kobject_put(&c->kobj);
	}
	kobject_put(&inst->kobj);
}

/*		     ___     ___
 *		     ___A I M___
 */
struct most_aim_obj {
	struct kobject kobj;
	struct list_head list;
	struct most_aim *driver;
};

#define to_aim_obj(d) container_of(d, struct most_aim_obj, kobj)

static struct list_head aim_list;

/**
 * struct most_aim_attribute - to access the attributes of AIM object
 * @attr: attributes of an AIM
 * @show: pointer to the show function
 * @store: pointer to the store function
 */
struct most_aim_attribute {
	struct attribute attr;
	ssize_t (*show)(struct most_aim_obj *d,
			struct most_aim_attribute *attr,
			char *buf);
	ssize_t (*store)(struct most_aim_obj *d,
			 struct most_aim_attribute *attr,
			 const char *buf,
			 size_t count);
};

#define to_aim_attr(a) container_of(a, struct most_aim_attribute, attr)

/**
 * aim_attr_show - show function of an AIM object
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 */
static ssize_t aim_attr_show(struct kobject *kobj,
			     struct attribute *attr,
			     char *buf)
{
	struct most_aim_attribute *aim_attr;
	struct most_aim_obj *aim_obj;

	aim_attr = to_aim_attr(attr);
	aim_obj = to_aim_obj(kobj);

	if (!aim_attr->show)
		return -EIO;

	return aim_attr->show(aim_obj, aim_attr, buf);
}

/**
 * aim_attr_store - store function of an AIM object
 * @kobj: pointer to kobject
 * @attr: pointer to attribute struct
 * @buf: buffer
 * @len: length of buffer
 */
static ssize_t aim_attr_store(struct kobject *kobj,
			      struct attribute *attr,
			      const char *buf,
			      size_t len)
{
	struct most_aim_attribute *aim_attr;
	struct most_aim_obj *aim_obj;

	aim_attr = to_aim_attr(attr);
	aim_obj = to_aim_obj(kobj);

	if (!aim_attr->store)
		return -EIO;
	return aim_attr->store(aim_obj, aim_attr, buf, len);
}

static const struct sysfs_ops most_aim_sysfs_ops = {
	.show = aim_attr_show,
	.store = aim_attr_store,
};

/**
 * most_aim_release - AIM release function
 * @kobj: pointer to AIM's kobject
 */
static void most_aim_release(struct kobject *kobj)
{
	struct most_aim_obj *aim_obj = to_aim_obj(kobj);

	kfree(aim_obj);
}

static ssize_t links_show(struct most_aim_obj *aim_obj,
			  struct most_aim_attribute *attr,
			  char *buf)
{
	struct most_c_obj *c;
	struct most_inst_obj *i;
	int offs = 0;

	list_for_each_entry(i, &instance_list, list) {
		list_for_each_entry(c, &i->channel_list, list) {
			if (c->aim0.ptr == aim_obj->driver ||
			    c->aim1.ptr == aim_obj->driver) {
				offs += snprintf(buf + offs, PAGE_SIZE - offs,
						 "%s:%s\n",
						 kobject_name(&i->kobj),
						 kobject_name(&c->kobj));
			}
		}
	}

	return offs;
}

/**
 * split_string - parses and changes string in the buffer buf and
 * splits it into two mandatory and one optional substrings.
 *
 * @buf: complete string from attribute 'add_channel'
 * @a: address of pointer to 1st substring (=instance name)
 * @b: address of pointer to 2nd substring (=channel name)
 * @c: optional address of pointer to 3rd substring (=user defined name)
 *
 * Examples:
 *
 * Input: "mdev0:ch6:my_channel\n" or
 *        "mdev0:ch6:my_channel"
 *
 * Output: *a -> "mdev0", *b -> "ch6", *c -> "my_channel"
 *
 * Input: "mdev1:ep81\n"
 * Output: *a -> "mdev1", *b -> "ep81", *c -> ""
 *
 * Input: "mdev1:ep81"
 * Output: *a -> "mdev1", *b -> "ep81", *c == NULL
 */
static int split_string(char *buf, char **a, char **b, char **c)
{
	*a = strsep(&buf, ":");
	if (!*a)
		return -EIO;

	*b = strsep(&buf, ":\n");
	if (!*b)
		return -EIO;

	if (c)
		*c = strsep(&buf, ":\n");

	return 0;
}

/**
 * get_channel_by_name - get pointer to channel object
 * @mdev: name of the device instance
 * @mdev_ch: name of the respective channel
 *
 * This retrieves the pointer to a channel object.
 */
static struct
most_c_obj *get_channel_by_name(char *mdev, char *mdev_ch)
{
	struct most_c_obj *c, *tmp;
	struct most_inst_obj *i, *i_tmp;
	int found = 0;

	list_for_each_entry_safe(i, i_tmp, &instance_list, list) {
		if (!strcmp(kobject_name(&i->kobj), mdev)) {
			found++;
			break;
		}
	}
	if (unlikely(!found))
		return ERR_PTR(-EIO);

	list_for_each_entry_safe(c, tmp, &i->channel_list, list) {
		if (!strcmp(kobject_name(&c->kobj), mdev_ch)) {
			found++;
			break;
		}
	}
	if (unlikely(found < 2))
		return ERR_PTR(-EIO);
	return c;
}

/**
 * add_link_store - store() function for add_link attribute
 * @aim_obj: pointer to AIM object
 * @attr: its attributes
 * @buf: buffer
 * @len: buffer length
 *
 * This parses the string given by buf and splits it into
 * three substrings. Note: third substring is optional. In case a cdev
 * AIM is loaded the optional 3rd substring will make up the name of
 * device node in the /dev directory. If omitted, the device node will
 * inherit the channel's name within sysfs.
 *
 * Searches for a pair of device and channel and probes the AIM
 *
 * Example:
 * (1) echo "mdev0:ch6:my_rxchannel" >add_link
 * (2) echo "mdev1:ep81" >add_link
 *
 * (1) would create the device node /dev/my_rxchannel
 * (2) would create the device node /dev/mdev1-ep81
 */
static ssize_t add_link_store(struct most_aim_obj *aim_obj,
			      struct most_aim_attribute *attr,
			      const char *buf,
			      size_t len)
{
	struct most_c_obj *c;
	struct most_aim **aim_ptr;
	char buffer[STRING_SIZE];
	char *mdev;
	char *mdev_ch;
	char *mdev_devnod;
	char devnod_buf[STRING_SIZE];
	int ret;
	size_t max_len = min_t(size_t, len + 1, STRING_SIZE);

	strlcpy(buffer, buf, max_len);

	ret = split_string(buffer, &mdev, &mdev_ch, &mdev_devnod);
	if (ret)
		return ret;

	if (!mdev_devnod || *mdev_devnod == 0) {
		snprintf(devnod_buf, sizeof(devnod_buf), "%s-%s", mdev,
			 mdev_ch);
		mdev_devnod = devnod_buf;
	}

	c = get_channel_by_name(mdev, mdev_ch);
	if (IS_ERR(c))
		return -ENODEV;

	if (!c->aim0.ptr)
		aim_ptr = &c->aim0.ptr;
	else if (!c->aim1.ptr)
		aim_ptr = &c->aim1.ptr;
	else
		return -ENOSPC;

	*aim_ptr = aim_obj->driver;
	ret = aim_obj->driver->probe_channel(c->iface, c->channel_id,
					     &c->cfg, &c->kobj, mdev_devnod);
	if (ret) {
		*aim_ptr = NULL;
		return ret;
	}

	return len;
}

/**
 * remove_link_store - store function for remove_link attribute
 * @aim_obj: pointer to AIM object
 * @attr: its attributes
 * @buf: buffer
 * @len: buffer length
 *
 * Example:
 * echo "mdev0:ep81" >remove_link
 */
static ssize_t remove_link_store(struct most_aim_obj *aim_obj,
				 struct most_aim_attribute *attr,
				 const char *buf,
				 size_t len)
{
	struct most_c_obj *c;
	char buffer[STRING_SIZE];
	char *mdev;
	char *mdev_ch;
	int ret;
	size_t max_len = min_t(size_t, len + 1, STRING_SIZE);

	strlcpy(buffer, buf, max_len);
	ret = split_string(buffer, &mdev, &mdev_ch, NULL);
	if (ret)
		return ret;

	c = get_channel_by_name(mdev, mdev_ch);
	if (IS_ERR(c))
		return -ENODEV;

	if (aim_obj->driver->disconnect_channel(c->iface, c->channel_id))
		return -EIO;
	if (c->aim0.ptr == aim_obj->driver)
		c->aim0.ptr = NULL;
	if (c->aim1.ptr == aim_obj->driver)
		c->aim1.ptr = NULL;
	return len;
}

static struct most_aim_attribute most_aim_attrs[] = {
	__ATTR_RO(links),
	__ATTR_WO(add_link),
	__ATTR_WO(remove_link),
};

static struct attribute *most_aim_def_attrs[] = {
	&most_aim_attrs[0].attr,
	&most_aim_attrs[1].attr,
	&most_aim_attrs[2].attr,
	NULL,
};

static struct kobj_type most_aim_ktype = {
	.sysfs_ops = &most_aim_sysfs_ops,
	.release = most_aim_release,
	.default_attrs = most_aim_def_attrs,
};

static struct kset *most_aim_kset;

/**
 * create_most_aim_obj - creates an AIM object
 * @name: name of the AIM
 *
 * This creates an AIM object assigns the proper kset and registers
 * it with sysfs.
 * Returns a pointer to the object or NULL if something went wrong.
 */
static struct most_aim_obj *create_most_aim_obj(const char *name)
{
	struct most_aim_obj *most_aim;
	int retval;

	most_aim = kzalloc(sizeof(*most_aim), GFP_KERNEL);
	if (!most_aim)
		return NULL;
	most_aim->kobj.kset = most_aim_kset;
	retval = kobject_init_and_add(&most_aim->kobj, &most_aim_ktype,
				      NULL, "%s", name);
	if (retval) {
		kobject_put(&most_aim->kobj);
		return NULL;
	}
	kobject_uevent(&most_aim->kobj, KOBJ_ADD);
	return most_aim;
}

/**
 * destroy_most_aim_obj - AIM release function
 * @p: pointer to AIM object
 *
 * This decrements the reference counter of the AIM object. If the
 * reference count turns zero, its release function will be called.
 */
static void destroy_most_aim_obj(struct most_aim_obj *p)
{
	kobject_put(&p->kobj);
}

/*		     ___       ___
 *		     ___C O R E___
 */

/**
 * Instantiation of the MOST bus
 */
static struct bus_type most_bus = {
	.name = "most",
};

/**
 * Instantiation of the core driver
 */
static struct device_driver mostcore = {
	.name = "mostcore",
	.bus = &most_bus,
};

static inline void trash_mbo(struct mbo *mbo)
{
	unsigned long flags;
	struct most_c_obj *c = mbo->context;

	spin_lock_irqsave(&c->fifo_lock, flags);
	list_add(&mbo->list, &c->trash_fifo);
	spin_unlock_irqrestore(&c->fifo_lock, flags);
}

static bool hdm_mbo_ready(struct most_c_obj *c)
{
	bool empty;

	if (c->enqueue_halt)
		return false;

	spin_lock_irq(&c->fifo_lock);
	empty = list_empty(&c->halt_fifo);
	spin_unlock_irq(&c->fifo_lock);

	return !empty;
}

static void nq_hdm_mbo(struct mbo *mbo)
{
	unsigned long flags;
	struct most_c_obj *c = mbo->context;

	spin_lock_irqsave(&c->fifo_lock, flags);
	list_add_tail(&mbo->list, &c->halt_fifo);
	spin_unlock_irqrestore(&c->fifo_lock, flags);
	wake_up_interruptible(&c->hdm_fifo_wq);
}

static int hdm_enqueue_thread(void *data)
{
	struct most_c_obj *c = data;
	struct mbo *mbo;
	int ret;
	typeof(c->iface->enqueue) enqueue = c->iface->enqueue;

	while (likely(!kthread_should_stop())) {
		wait_event_interruptible(c->hdm_fifo_wq,
					 hdm_mbo_ready(c) ||
					 kthread_should_stop());

		mutex_lock(&c->nq_mutex);
		spin_lock_irq(&c->fifo_lock);
		if (unlikely(c->enqueue_halt || list_empty(&c->halt_fifo))) {
			spin_unlock_irq(&c->fifo_lock);
			mutex_unlock(&c->nq_mutex);
			continue;
		}

		mbo = list_pop_mbo(&c->halt_fifo);
		spin_unlock_irq(&c->fifo_lock);

		if (c->cfg.direction == MOST_CH_RX)
			mbo->buffer_length = c->cfg.buffer_size;

		ret = enqueue(mbo->ifp, mbo->hdm_channel_id, mbo);
		mutex_unlock(&c->nq_mutex);

		if (unlikely(ret)) {
			pr_err("hdm enqueue failed\n");
			nq_hdm_mbo(mbo);
			c->hdm_enqueue_task = NULL;
			return 0;
		}
	}

	return 0;
}

static int run_enqueue_thread(struct most_c_obj *c, int channel_id)
{
	struct task_struct *task =
		kthread_run(hdm_enqueue_thread, c, "hdm_fifo_%d",
			    channel_id);

	if (IS_ERR(task))
		return PTR_ERR(task);

	c->hdm_enqueue_task = task;
	return 0;
}

/**
 * arm_mbo - recycle MBO for further usage
 * @mbo: buffer object
 *
 * This puts an MBO back to the list to have it ready for up coming
 * tx transactions.
 *
 * In case the MBO belongs to a channel that recently has been
 * poisoned, the MBO is scheduled to be trashed.
 * Calls the completion handler of an attached AIM.
 */
static void arm_mbo(struct mbo *mbo)
{
	unsigned long flags;
	struct most_c_obj *c;

	BUG_ON((!mbo) || (!mbo->context));
	c = mbo->context;

	if (c->is_poisoned) {
		trash_mbo(mbo);
		return;
	}

	spin_lock_irqsave(&c->fifo_lock, flags);
	++*mbo->num_buffers_ptr;
	list_add_tail(&mbo->list, &c->fifo);
	spin_unlock_irqrestore(&c->fifo_lock, flags);

	if (c->aim0.refs && c->aim0.ptr->tx_completion)
		c->aim0.ptr->tx_completion(c->iface, c->channel_id);

	if (c->aim1.refs && c->aim1.ptr->tx_completion)
		c->aim1.ptr->tx_completion(c->iface, c->channel_id);
}

/**
 * arm_mbo_chain - helper function that arms an MBO chain for the HDM
 * @c: pointer to interface channel
 * @dir: direction of the channel
 * @compl: pointer to completion function
 *
 * This allocates buffer objects including the containing DMA coherent
 * buffer and puts them in the fifo.
 * Buffers of Rx channels are put in the kthread fifo, hence immediately
 * submitted to the HDM.
 *
 * Returns the number of allocated and enqueued MBOs.
 */
static int arm_mbo_chain(struct most_c_obj *c, int dir,
			 void (*compl)(struct mbo *))
{
	unsigned int i;
	struct mbo *mbo;
	unsigned long flags;
	u32 coherent_buf_size = c->cfg.buffer_size + c->cfg.extra_len;

	atomic_set(&c->mbo_nq_level, 0);

	for (i = 0; i < c->cfg.num_buffers; i++) {
		mbo = kzalloc(sizeof(*mbo), GFP_KERNEL);
		if (!mbo)
			goto flush_fifos;

		mbo->context = c;
		mbo->ifp = c->iface;
		mbo->hdm_channel_id = c->channel_id;
		if (c->iface->dma_alloc) {
			mbo->virt_address =
				c->iface->dma_alloc(mbo, coherent_buf_size);
		} else {
			mbo->virt_address =
				kzalloc(coherent_buf_size, GFP_KERNEL);
		}
		if (!mbo->virt_address)
			goto release_mbo;

		mbo->complete = compl;
		mbo->num_buffers_ptr = &dummy_num_buffers;
		if (dir == MOST_CH_RX) {
			nq_hdm_mbo(mbo);
			atomic_inc(&c->mbo_nq_level);
		} else {
			spin_lock_irqsave(&c->fifo_lock, flags);
			list_add_tail(&mbo->list, &c->fifo);
			spin_unlock_irqrestore(&c->fifo_lock, flags);
		}
	}
	return c->cfg.num_buffers;

release_mbo:
	kfree(mbo);

flush_fifos:
	flush_channel_fifos(c);
	return 0;
}

/**
 * most_submit_mbo - submits an MBO to fifo
 * @mbo: pointer to the MBO
 */
void most_submit_mbo(struct mbo *mbo)
{
	if (WARN_ONCE(!mbo || !mbo->context,
		      "bad mbo or missing channel reference\n"))
		return;

	nq_hdm_mbo(mbo);
}
EXPORT_SYMBOL_GPL(most_submit_mbo);

/**
 * most_write_completion - write completion handler
 * @mbo: pointer to MBO
 *
 * This recycles the MBO for further usage. In case the channel has been
 * poisoned, the MBO is scheduled to be trashed.
 */
static void most_write_completion(struct mbo *mbo)
{
	struct most_c_obj *c;

	BUG_ON((!mbo) || (!mbo->context));

	c = mbo->context;
	if (mbo->status == MBO_E_INVAL)
		pr_info("WARN: Tx MBO status: invalid\n");
	if (unlikely(c->is_poisoned || (mbo->status == MBO_E_CLOSE)))
		trash_mbo(mbo);
	else
		arm_mbo(mbo);
}

/**
 * get_channel_by_iface - get pointer to channel object
 * @iface: pointer to interface instance
 * @id: channel ID
 *
 * This retrieves a pointer to a channel of the given interface and channel ID.
 */
static struct
most_c_obj *get_channel_by_iface(struct most_interface *iface, int id)
{
	struct most_inst_obj *i;

	if (unlikely(!iface)) {
		pr_err("Bad interface\n");
		return NULL;
	}
	if (unlikely((id < 0) || (id >= iface->num_channels))) {
		pr_err("Channel index (%d) out of range\n", id);
		return NULL;
	}
	i = iface->priv;
	if (unlikely(!i)) {
		pr_err("interface is not registered\n");
		return NULL;
	}
	return i->channel[id];
}

int channel_has_mbo(struct most_interface *iface, int id, struct most_aim *aim)
{
	struct most_c_obj *c = get_channel_by_iface(iface, id);
	unsigned long flags;
	int empty;

	if (unlikely(!c))
		return -EINVAL;

	if (c->aim0.refs && c->aim1.refs &&
	    ((aim == c->aim0.ptr && c->aim0.num_buffers <= 0) ||
	     (aim == c->aim1.ptr && c->aim1.num_buffers <= 0)))
		return 0;

	spin_lock_irqsave(&c->fifo_lock, flags);
	empty = list_empty(&c->fifo);
	spin_unlock_irqrestore(&c->fifo_lock, flags);
	return !empty;
}
EXPORT_SYMBOL_GPL(channel_has_mbo);

/**
 * most_get_mbo - get pointer to an MBO of pool
 * @iface: pointer to interface instance
 * @id: channel ID
 *
 * This attempts to get a free buffer out of the channel fifo.
 * Returns a pointer to MBO on success or NULL otherwise.
 */
struct mbo *most_get_mbo(struct most_interface *iface, int id,
			 struct most_aim *aim)
{
	struct mbo *mbo;
	struct most_c_obj *c;
	unsigned long flags;
	int *num_buffers_ptr;

	c = get_channel_by_iface(iface, id);
	if (unlikely(!c))
		return NULL;

	if (c->aim0.refs && c->aim1.refs &&
	    ((aim == c->aim0.ptr && c->aim0.num_buffers <= 0) ||
	     (aim == c->aim1.ptr && c->aim1.num_buffers <= 0)))
		return NULL;

	if (aim == c->aim0.ptr)
		num_buffers_ptr = &c->aim0.num_buffers;
	else if (aim == c->aim1.ptr)
		num_buffers_ptr = &c->aim1.num_buffers;
	else
		num_buffers_ptr = &dummy_num_buffers;

	spin_lock_irqsave(&c->fifo_lock, flags);
	if (list_empty(&c->fifo)) {
		spin_unlock_irqrestore(&c->fifo_lock, flags);
		return NULL;
	}
	mbo = list_pop_mbo(&c->fifo);
	--*num_buffers_ptr;
	spin_unlock_irqrestore(&c->fifo_lock, flags);

	mbo->num_buffers_ptr = num_buffers_ptr;
	mbo->buffer_length = c->cfg.buffer_size;
	return mbo;
}
EXPORT_SYMBOL_GPL(most_get_mbo);

/**
 * most_put_mbo - return buffer to pool
 * @mbo: buffer object
 */
void most_put_mbo(struct mbo *mbo)
{
	struct most_c_obj *c = mbo->context;

	if (c->cfg.direction == MOST_CH_TX) {
		arm_mbo(mbo);
		return;
	}
	nq_hdm_mbo(mbo);
	atomic_inc(&c->mbo_nq_level);
}
EXPORT_SYMBOL_GPL(most_put_mbo);

/**
 * most_read_completion - read completion handler
 * @mbo: pointer to MBO
 *
 * This function is called by the HDM when data has been received from the
 * hardware and copied to the buffer of the MBO.
 *
 * In case the channel has been poisoned it puts the buffer in the trash queue.
 * Otherwise, it passes the buffer to an AIM for further processing.
 */
static void most_read_completion(struct mbo *mbo)
{
	struct most_c_obj *c = mbo->context;

	if (unlikely(c->is_poisoned || (mbo->status == MBO_E_CLOSE))) {
		trash_mbo(mbo);
		return;
	}

	if (mbo->status == MBO_E_INVAL) {
		nq_hdm_mbo(mbo);
		atomic_inc(&c->mbo_nq_level);
		return;
	}

	if (atomic_sub_and_test(1, &c->mbo_nq_level))
		c->is_starving = 1;

	if (c->aim0.refs && c->aim0.ptr->rx_completion &&
	    c->aim0.ptr->rx_completion(mbo) == 0)
		return;

	if (c->aim1.refs && c->aim1.ptr->rx_completion &&
	    c->aim1.ptr->rx_completion(mbo) == 0)
		return;

	most_put_mbo(mbo);
}

/**
 * most_start_channel - prepares a channel for communication
 * @iface: pointer to interface instance
 * @id: channel ID
 *
 * This prepares the channel for usage. Cross-checks whether the
 * channel's been properly configured.
 *
 * Returns 0 on success or error code otherwise.
 */
int most_start_channel(struct most_interface *iface, int id,
		       struct most_aim *aim)
{
	int num_buffer;
	int ret;
	struct most_c_obj *c = get_channel_by_iface(iface, id);

	if (unlikely(!c))
		return -EINVAL;

	mutex_lock(&c->start_mutex);
	if (c->aim0.refs + c->aim1.refs > 0)
		goto out; /* already started by other aim */

	if (!try_module_get(iface->mod)) {
		pr_info("failed to acquire HDM lock\n");
		mutex_unlock(&c->start_mutex);
		return -ENOLCK;
	}

	c->cfg.extra_len = 0;
	if (c->iface->configure(c->iface, c->channel_id, &c->cfg)) {
		pr_info("channel configuration failed. Go check settings...\n");
		ret = -EINVAL;
		goto error;
	}

	init_waitqueue_head(&c->hdm_fifo_wq);

	if (c->cfg.direction == MOST_CH_RX)
		num_buffer = arm_mbo_chain(c, c->cfg.direction,
					   most_read_completion);
	else
		num_buffer = arm_mbo_chain(c, c->cfg.direction,
					   most_write_completion);
	if (unlikely(!num_buffer)) {
		pr_info("failed to allocate memory\n");
		ret = -ENOMEM;
		goto error;
	}

	ret = run_enqueue_thread(c, id);
	if (ret)
		goto error;

	c->is_starving = 0;
	c->aim0.num_buffers = c->cfg.num_buffers / 2;
	c->aim1.num_buffers = c->cfg.num_buffers - c->aim0.num_buffers;
	atomic_set(&c->mbo_ref, num_buffer);

out:
	if (aim == c->aim0.ptr)
		c->aim0.refs++;
	if (aim == c->aim1.ptr)
		c->aim1.refs++;
	mutex_unlock(&c->start_mutex);
	return 0;

error:
	module_put(iface->mod);
	mutex_unlock(&c->start_mutex);
	return ret;
}
EXPORT_SYMBOL_GPL(most_start_channel);

/**
 * most_stop_channel - stops a running channel
 * @iface: pointer to interface instance
 * @id: channel ID
 */
int most_stop_channel(struct most_interface *iface, int id,
		      struct most_aim *aim)
{
	struct most_c_obj *c;

	if (unlikely((!iface) || (id >= iface->num_channels) || (id < 0))) {
		pr_err("Bad interface or index out of range\n");
		return -EINVAL;
	}
	c = get_channel_by_iface(iface, id);
	if (unlikely(!c))
		return -EINVAL;

	mutex_lock(&c->start_mutex);
	if (c->aim0.refs + c->aim1.refs >= 2)
		goto out;

	if (c->hdm_enqueue_task)
		kthread_stop(c->hdm_enqueue_task);
	c->hdm_enqueue_task = NULL;

	if (iface->mod)
		module_put(iface->mod);

	c->is_poisoned = true;
	if (c->iface->poison_channel(c->iface, c->channel_id)) {
		pr_err("Cannot stop channel %d of mdev %s\n", c->channel_id,
		       c->iface->description);
		mutex_unlock(&c->start_mutex);
		return -EAGAIN;
	}
	flush_trash_fifo(c);
	flush_channel_fifos(c);

#ifdef CMPL_INTERRUPTIBLE
	if (wait_for_completion_interruptible(&c->cleanup)) {
		pr_info("Interrupted while clean up ch %d\n", c->channel_id);
		mutex_unlock(&c->start_mutex);
		return -EINTR;
	}
#else
	wait_for_completion(&c->cleanup);
#endif
	c->is_poisoned = false;

out:
	if (aim == c->aim0.ptr)
		c->aim0.refs--;
	if (aim == c->aim1.ptr)
		c->aim1.refs--;
	mutex_unlock(&c->start_mutex);
	return 0;
}
EXPORT_SYMBOL_GPL(most_stop_channel);

/**
 * most_register_aim - registers an AIM (driver) with the core
 * @aim: instance of AIM to be registered
 */
int most_register_aim(struct most_aim *aim)
{
	struct most_aim_obj *aim_obj;

	if (!aim) {
		pr_err("Bad driver\n");
		return -EINVAL;
	}
	aim_obj = create_most_aim_obj(aim->name);
	if (!aim_obj) {
		pr_info("failed to alloc driver object\n");
		return -ENOMEM;
	}
	aim_obj->driver = aim;
	aim->context = aim_obj;
	pr_info("registered new application interfacing module %s\n",
		aim->name);
	list_add_tail(&aim_obj->list, &aim_list);
	return 0;
}
EXPORT_SYMBOL_GPL(most_register_aim);

/**
 * most_deregister_aim - deregisters an AIM (driver) with the core
 * @aim: AIM to be removed
 */
int most_deregister_aim(struct most_aim *aim)
{
	struct most_aim_obj *aim_obj;
	struct most_c_obj *c, *tmp;
	struct most_inst_obj *i, *i_tmp;

	if (!aim) {
		pr_err("Bad driver\n");
		return -EINVAL;
	}

	aim_obj = aim->context;
	if (!aim_obj) {
		pr_info("driver not registered.\n");
		return -EINVAL;
	}
	list_for_each_entry_safe(i, i_tmp, &instance_list, list) {
		list_for_each_entry_safe(c, tmp, &i->channel_list, list) {
			if (c->aim0.ptr == aim || c->aim1.ptr == aim)
				aim->disconnect_channel(
					c->iface, c->channel_id);
			if (c->aim0.ptr == aim)
				c->aim0.ptr = NULL;
			if (c->aim1.ptr == aim)
				c->aim1.ptr = NULL;
		}
	}
	list_del(&aim_obj->list);
	destroy_most_aim_obj(aim_obj);
	pr_info("deregistering application interfacing module %s\n", aim->name);
	return 0;
}
EXPORT_SYMBOL_GPL(most_deregister_aim);

/**
 * most_register_interface - registers an interface with core
 * @iface: pointer to the instance of the interface description.
 *
 * Allocates and initializes a new interface instance and all of its channels.
 * Returns a pointer to kobject or an error pointer.
 */
struct kobject *most_register_interface(struct most_interface *iface)
{
	unsigned int i;
	int id;
	char name[STRING_SIZE];
	char channel_name[STRING_SIZE];
	struct most_c_obj *c;
	struct most_inst_obj *inst;

	if (!iface || !iface->enqueue || !iface->configure ||
	    !iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) {
		pr_err("Bad interface or channel overflow\n");
		return ERR_PTR(-EINVAL);
	}

	id = ida_simple_get(&mdev_id, 0, 0, GFP_KERNEL);
	if (id < 0) {
		pr_info("Failed to alloc mdev ID\n");
		return ERR_PTR(id);
	}
	snprintf(name, STRING_SIZE, "mdev%d", id);

	inst = create_most_inst_obj(name);
	if (!inst) {
		pr_info("Failed to allocate interface instance\n");
		ida_simple_remove(&mdev_id, id);
		return ERR_PTR(-ENOMEM);
	}

	iface->priv = inst;
	iface->ktype.sysfs_ops = &most_channel_sysfs_ops,
	iface->ktype.release = most_channel_release,
	iface->ktype.default_attrs = iface->attrs;

	BUILD_BUG_ON(ARRAY_SIZE(iface->attrs) < ARRAY_SIZE(common_c_attrs) + 2);
	for (i = 0; i < ARRAY_SIZE(common_c_attrs); i++)
		iface->attrs[i] = &common_c_attrs[i].attr;
	switch (iface->extra_attrs) {
	case XACT_ATTRS:
		iface->attrs[i] = &xact_c_attr.attr;
		i++;
		break;
	case DBR_ATTRS:
		iface->attrs[i] = &dbr_c_attr.attr;
		i++;
		break;
	default:
		break;
	}
	iface->attrs[i] = NULL;

	INIT_LIST_HEAD(&inst->channel_list);
	inst->iface = iface;
	inst->dev_id = id;
	list_add_tail(&inst->list, &instance_list);

	for (i = 0; i < iface->num_channels; i++) {
		const char *name_suffix = iface->channel_vector[i].name_suffix;

		if (!name_suffix)
			snprintf(channel_name, STRING_SIZE, "ch%d", i);
		else
			snprintf(channel_name, STRING_SIZE, "%s", name_suffix);

		/* this increments the reference count of this instance */
		c = create_most_c_obj(&iface->ktype, channel_name, &inst->kobj);
		if (!c)
			goto free_instance;
		inst->channel[i] = c;
		c->is_starving = 0;
		c->iface = iface;
		c->inst = inst;
		c->channel_id = i;
		c->keep_mbo = false;
		c->enqueue_halt = false;
		c->is_poisoned = false;
		c->cfg.direction = 0;
		c->cfg.data_type = 0;
		c->cfg.num_buffers = 0;
		c->cfg.buffer_size = 0;
		c->cfg.subbuffer_size = 0;
		c->cfg.packets_per_xact = 0;
		spin_lock_init(&c->fifo_lock);
		INIT_LIST_HEAD(&c->fifo);
		INIT_LIST_HEAD(&c->trash_fifo);
		INIT_LIST_HEAD(&c->halt_fifo);
		init_completion(&c->cleanup);
		atomic_set(&c->mbo_ref, 0);
		mutex_init(&c->start_mutex);
		mutex_init(&c->nq_mutex);
		list_add_tail(&c->list, &inst->channel_list);
	}
	pr_info("registered new MOST device mdev%d (%s)\n",
		inst->dev_id, iface->description);
	return &inst->kobj;

free_instance:
	pr_info("Failed allocate channel(s)\n");
	list_del(&inst->list);
	ida_simple_remove(&mdev_id, id);
	destroy_most_inst_obj(inst);
	return ERR_PTR(-ENOMEM);
}
EXPORT_SYMBOL_GPL(most_register_interface);

/**
 * most_deregister_interface - deregisters an interface with core
 * @iface: pointer to the interface instance description.
 *
 * Before removing an interface instance from the list, all running
 * channels are stopped and poisoned.
 */
void most_deregister_interface(struct most_interface *iface)
{
	struct most_inst_obj *i = iface->priv;
	struct most_c_obj *c;

	if (unlikely(!i)) {
		pr_info("Bad Interface\n");
		return;
	}
	pr_info("deregistering MOST device %s (%s)\n", i->kobj.name,
		iface->description);

	list_for_each_entry(c, &i->channel_list, list) {
		if (c->aim0.ptr)
			c->aim0.ptr->disconnect_channel(c->iface,
							c->channel_id);
		if (c->aim1.ptr)
			c->aim1.ptr->disconnect_channel(c->iface,
							c->channel_id);
		c->aim0.ptr = NULL;
		c->aim1.ptr = NULL;
	}

	ida_simple_remove(&mdev_id, i->dev_id);
	list_del(&i->list);
	destroy_most_inst_obj(i);
}
EXPORT_SYMBOL_GPL(most_deregister_interface);

/**
 * most_stop_enqueue - prevents core from enqueueing MBOs
 * @iface: pointer to interface
 * @id: channel id
 *
 * This is called by an HDM that _cannot_ attend to its duties and
 * is imminent to get run over by the core. The core is not going to
 * enqueue any further packets unless the flagging HDM calls
 * most_resume enqueue().
 */
void most_stop_enqueue(struct most_interface *iface, int id)
{
	struct most_c_obj *c = get_channel_by_iface(iface, id);

	if (!c)
		return;

	mutex_lock(&c->nq_mutex);
	c->enqueue_halt = true;
	mutex_unlock(&c->nq_mutex);
}
EXPORT_SYMBOL_GPL(most_stop_enqueue);

/**
 * most_resume_enqueue - allow core to enqueue MBOs again
 * @iface: pointer to interface
 * @id: channel id
 *
 * This clears the enqueue halt flag and enqueues all MBOs currently
 * sitting in the wait fifo.
 */
void most_resume_enqueue(struct most_interface *iface, int id)
{
	struct most_c_obj *c = get_channel_by_iface(iface, id);

	if (!c)
		return;

	mutex_lock(&c->nq_mutex);
	c->enqueue_halt = false;
	mutex_unlock(&c->nq_mutex);

	wake_up_interruptible(&c->hdm_fifo_wq);
}
EXPORT_SYMBOL_GPL(most_resume_enqueue);

static int __init most_init(void)
{
	int err;

	pr_info("init()\n");
	INIT_LIST_HEAD(&instance_list);
	INIT_LIST_HEAD(&aim_list);
	ida_init(&mdev_id);

	err = bus_register(&most_bus);
	if (err) {
		pr_info("Cannot register most bus\n");
		return err;
	}

	most_class = class_create(THIS_MODULE, "most");
	if (IS_ERR(most_class)) {
		pr_info("No udev support.\n");
		err = PTR_ERR(most_class);
		goto exit_bus;
	}

	err = driver_register(&mostcore);
	if (err) {
		pr_info("Cannot register core driver\n");
		goto exit_class;
	}

	core_dev = device_create(most_class, NULL, 0, NULL, "mostcore");
	if (IS_ERR(core_dev)) {
		err = PTR_ERR(core_dev);
		goto exit_driver;
	}

	most_aim_kset = kset_create_and_add("aims", NULL, &core_dev->kobj);
	if (!most_aim_kset) {
		err = -ENOMEM;
		goto exit_class_container;
	}

	most_inst_kset = kset_create_and_add("devices", NULL, &core_dev->kobj);
	if (!most_inst_kset) {
		err = -ENOMEM;
		goto exit_driver_kset;
	}

	return 0;

exit_driver_kset:
	kset_unregister(most_aim_kset);
exit_class_container:
	device_destroy(most_class, 0);
exit_driver:
	driver_unregister(&mostcore);
exit_class:
	class_destroy(most_class);
exit_bus:
	bus_unregister(&most_bus);
	return err;
}

static void __exit most_exit(void)
{
	struct most_inst_obj *i, *i_tmp;
	struct most_aim_obj *d, *d_tmp;

	pr_info("exit core module\n");
	list_for_each_entry_safe(d, d_tmp, &aim_list, list) {
		destroy_most_aim_obj(d);
	}

	list_for_each_entry_safe(i, i_tmp, &instance_list, list) {
		list_del(&i->list);
		destroy_most_inst_obj(i);
	}
	kset_unregister(most_inst_kset);
	kset_unregister(most_aim_kset);
	device_destroy(most_class, 0);
	driver_unregister(&mostcore);
	class_destroy(most_class);
	bus_unregister(&most_bus);
	ida_destroy(&mdev_id);
}

module_init(most_init);
module_exit(most_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Gromm <christian.gromm@microchip.com>");
MODULE_DESCRIPTION("Core module of stacked MOST Linux driver");