/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */
#include <linux/err.h>
#include <linux/ipc_logging.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/uio.h>
#include <soc/qcom/glink.h>
#include <soc/qcom/tracer_pkt.h>
#include "glink_loopback_commands.h"


/* Number of internal IPC Logging log pages */
#define GLINK_LBSRV_NUM_LOG_PAGES	3

static void *glink_lbsrv_log_ctx;

#define GLINK_LBSRV_IPC_LOG_STR(x...) do { \
	if (glink_lbsrv_log_ctx) \
		ipc_log_string(glink_lbsrv_log_ctx, x); \
} while (0)

#define LBSRV_INFO(x...) GLINK_LBSRV_IPC_LOG_STR("<LBSRV> " x)

#define LBSRV_ERR(x...) do {                              \
	pr_err("<LBSRV> " x); \
	GLINK_LBSRV_IPC_LOG_STR("<LBSRV> " x);  \
} while (0)

enum ch_type {
	CTL,
	DATA,
};

enum buf_type {
	LINEAR,
	VECTOR,
};

struct tx_config_info {
	uint32_t random_delay;
	uint32_t delay_ms;
	uint32_t echo_count;
	uint32_t transform_type;
};

struct rx_done_config_info {
	uint32_t random_delay;
	uint32_t delay_ms;
};

struct rmt_rx_intent_req_work_info {
	size_t req_intent_size;
	struct delayed_work work;
	struct ch_info *work_ch_info;
};

struct queue_rx_intent_work_info {
	uint32_t req_id;
	bool deferred;
	struct ch_info *req_ch_info;
	uint32_t num_intents;
	uint32_t intent_size;
	uint32_t random_delay;
	uint32_t delay_ms;
	struct delayed_work work;
	struct ch_info *work_ch_info;
};

struct lbsrv_vec {
	uint32_t num_bufs;
	struct kvec vec[0];
};

struct tx_work_info {
	struct tx_config_info tx_config;
	struct delayed_work work;
	struct ch_info *tx_ch_info;
	void *data;
	bool tracer_pkt;
	uint32_t buf_type;
	size_t size;
	void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size);
	void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size);
};

struct rx_done_work_info {
	struct delayed_work work;
	struct ch_info *rx_done_ch_info;
	void *ptr;
};

struct rx_work_info {
	struct ch_info *rx_ch_info;
	void *pkt_priv;
	void *ptr;
	bool tracer_pkt;
	uint32_t buf_type;
	size_t size;
	void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size);
	void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size);
	struct delayed_work work;
};

struct ch_info {
	struct list_head list;
	struct mutex ch_info_lock;
	char name[MAX_NAME_LEN];
	char edge[GLINK_NAME_SIZE];
	char transport[GLINK_NAME_SIZE];
	void *handle;
	bool fully_opened;
	uint32_t type;
	struct delayed_work open_work;
	struct delayed_work close_work;
	struct tx_config_info tx_config;
	struct rx_done_config_info rx_done_config;
	struct queue_rx_intent_work_info *queue_rx_intent_work_info;
};

struct ctl_ch_info {
	char name[MAX_NAME_LEN];
	char edge[GLINK_NAME_SIZE];
	char transport[GLINK_NAME_SIZE];
};

static struct ctl_ch_info ctl_ch_tbl[] = {
	{"LOCAL_LOOPBACK_SRV", "local", "lloop"},
	{"LOOPBACK_CTL_APSS", "mpss", "smem"},
	{"LOOPBACK_CTL_APSS", "lpass", "smem"},
	{"LOOPBACK_CTL_APSS", "dsps", "smem"},
	{"LOOPBACK_CTL_APSS", "spss", "mailbox"},
};

static DEFINE_MUTEX(ctl_ch_list_lock);
static LIST_HEAD(ctl_ch_list);
static DEFINE_MUTEX(data_ch_list_lock);
static LIST_HEAD(data_ch_list);

struct workqueue_struct *glink_lbsrv_wq;

/**
 * link_state_work_info - Information about work handling link state updates
 * edge:	Remote subsystem name in the link.
 * transport:	Name of the transport/link.
 * link_state:	State of the transport/link.
 * work:	Reference to the work item.
 */
struct link_state_work_info {
	char edge[GLINK_NAME_SIZE];
	char transport[GLINK_NAME_SIZE];
	enum glink_link_state link_state;
	struct delayed_work work;
};

static void glink_lbsrv_link_state_cb(struct glink_link_state_cb_info *cb_info,
				      void *priv);
static struct glink_link_info glink_lbsrv_link_info = {
			NULL, NULL, glink_lbsrv_link_state_cb};
static void *glink_lbsrv_link_state_notif_handle;

static void glink_lbsrv_open_worker(struct work_struct *work);
static void glink_lbsrv_close_worker(struct work_struct *work);
static void glink_lbsrv_rmt_rx_intent_req_worker(struct work_struct *work);
static void glink_lbsrv_queue_rx_intent_worker(struct work_struct *work);
static void glink_lbsrv_rx_worker(struct work_struct *work);
static void glink_lbsrv_rx_done_worker(struct work_struct *work);
static void glink_lbsrv_tx_worker(struct work_struct *work);

int glink_lbsrv_send_response(void *handle, uint32_t req_id, uint32_t req_type,
		uint32_t response)
{
	struct resp *resp_pkt = kzalloc(sizeof(struct resp), GFP_KERNEL);

	if (!resp_pkt) {
		LBSRV_ERR("%s: Error allocating response packet\n", __func__);
		return -ENOMEM;
	}

	resp_pkt->req_id = req_id;
	resp_pkt->req_type = req_type;
	resp_pkt->response = response;

	return glink_tx(handle, (void *)LINEAR, (void *)resp_pkt,
			sizeof(struct resp), 0);
}

static uint32_t calc_delay_ms(uint32_t random_delay, uint32_t delay_ms)
{
	uint32_t tmp_delay_ms;

	if (random_delay && delay_ms)
		tmp_delay_ms = prandom_u32() % delay_ms;
	else if (random_delay)
		tmp_delay_ms = prandom_u32();
	else
		tmp_delay_ms = delay_ms;

	return tmp_delay_ms;
}

static int create_ch_info(char *name, char *edge, char *transport,
			  uint32_t type, struct ch_info **ret_ch_info)
{
	struct ch_info *tmp_ch_info;

	tmp_ch_info = kzalloc(sizeof(struct ch_info), GFP_KERNEL);
	if (!tmp_ch_info) {
		LBSRV_ERR("%s: Error allocation ch_info\n", __func__);
		return -ENOMEM;
	}

	INIT_LIST_HEAD(&tmp_ch_info->list);
	mutex_init(&tmp_ch_info->ch_info_lock);
	strlcpy(tmp_ch_info->name, name, MAX_NAME_LEN);
	strlcpy(tmp_ch_info->edge, edge, GLINK_NAME_SIZE);
	strlcpy(tmp_ch_info->transport, transport, GLINK_NAME_SIZE);
	tmp_ch_info->type = type;
	INIT_DELAYED_WORK(&tmp_ch_info->open_work,
			  glink_lbsrv_open_worker);
	INIT_DELAYED_WORK(&tmp_ch_info->close_work,
			  glink_lbsrv_close_worker);
	tmp_ch_info->tx_config.echo_count = 1;

	if (type == CTL) {
		mutex_lock(&ctl_ch_list_lock);
		list_add_tail(&tmp_ch_info->list, &ctl_ch_list);
		mutex_unlock(&ctl_ch_list_lock);
	} else if (type == DATA) {
		mutex_lock(&data_ch_list_lock);
		list_add_tail(&tmp_ch_info->list, &data_ch_list);
		mutex_unlock(&data_ch_list_lock);
	} else {
		LBSRV_ERR("%s:%s:%s %s: Invalid ch type %d\n", transport,
				edge, name, __func__, type);
		kfree(tmp_ch_info);
		return -EINVAL;
	}
	*ret_ch_info = tmp_ch_info;
	return 0;
}

struct ch_info *lookup_ch_list(char *name, char *edge, char *transport,
			       uint32_t type)
{
	struct list_head *ch_list;
	struct mutex *lock;
	struct ch_info *tmp_ch_info;

	if (type == DATA) {
		ch_list = &data_ch_list;
		lock = &data_ch_list_lock;
	} else if (type == CTL) {
		ch_list = &ctl_ch_list;
		lock = &ctl_ch_list_lock;
	} else {
		LBSRV_ERR("%s:%s:%s %s: Invalid ch type %d\n", transport,
			    edge, name, __func__, type);
		return NULL;
	}

	mutex_lock(lock);
	list_for_each_entry(tmp_ch_info, ch_list, list) {
		if (!strcmp(name, tmp_ch_info->name) &&
		    !strcmp(edge, tmp_ch_info->edge) &&
		    !strcmp(transport, tmp_ch_info->transport)) {
			mutex_unlock(lock);
			return tmp_ch_info;
		}
	}
	mutex_unlock(lock);
	return NULL;
}

int glink_lbsrv_handle_open_req(struct ch_info *rx_ch_info,
				struct open_req req)
{
	struct ch_info *tmp_ch_info;
	int ret;
	char name[MAX_NAME_LEN];
	char *temp;

	strlcpy(name, req.ch_name, MAX_NAME_LEN);
	if (!strcmp(rx_ch_info->transport, "lloop")) {
		temp = strnstr(name, "_CLNT", MAX_NAME_LEN);
		if (temp)
			*temp = '\0';
		strlcat(name, "_SRV", MAX_NAME_LEN);
	}
	LBSRV_INFO("%s:%s:%s %s: delay_ms[%d]\n",
		   rx_ch_info->transport, rx_ch_info->edge,
		   name, __func__, req.delay_ms);
	tmp_ch_info = lookup_ch_list(name, rx_ch_info->edge,
				     rx_ch_info->transport, DATA);
	if (tmp_ch_info)
		goto queue_open_work;

	ret = create_ch_info(name, rx_ch_info->edge, rx_ch_info->transport,
			     DATA, &tmp_ch_info);
	if (ret)
		return ret;
queue_open_work:
	queue_delayed_work(glink_lbsrv_wq, &tmp_ch_info->open_work,
			   msecs_to_jiffies(req.delay_ms));
	return 0;
}

int glink_lbsrv_handle_close_req(struct ch_info *rx_ch_info,
				 struct close_req req)
{
	struct ch_info *tmp_ch_info;
	char name[MAX_NAME_LEN];
	char *temp;

	strlcpy(name, req.ch_name, MAX_NAME_LEN);
	if (!strcmp(rx_ch_info->transport, "lloop")) {
		temp = strnstr(name, "_CLNT", MAX_NAME_LEN);
		if (temp)
			*temp = '\0';
		strlcat(name, "_SRV", MAX_NAME_LEN);
	}
	LBSRV_INFO("%s:%s:%s %s: delay_ms[%d]\n",
		    rx_ch_info->transport, rx_ch_info->edge,
		    name, __func__, req.delay_ms);
	tmp_ch_info = lookup_ch_list(name, rx_ch_info->edge,
				     rx_ch_info->transport, DATA);
	if (tmp_ch_info)
		queue_delayed_work(glink_lbsrv_wq, &tmp_ch_info->close_work,
				   msecs_to_jiffies(req.delay_ms));
	return 0;
}

int glink_lbsrv_handle_queue_rx_intent_config_req(struct ch_info *rx_ch_info,
			struct queue_rx_intent_config_req req, uint32_t req_id)
{
	struct ch_info *tmp_ch_info;
	struct queue_rx_intent_work_info *tmp_work_info;
	char name[MAX_NAME_LEN];
	char *temp;
	uint32_t delay_ms;

	strlcpy(name, req.ch_name, MAX_NAME_LEN);
	if (!strcmp(rx_ch_info->transport, "lloop")) {
		temp = strnstr(name, "_CLNT", MAX_NAME_LEN);
		if (temp)
			*temp = '\0';
		strlcat(name, "_SRV", MAX_NAME_LEN);
	}
	LBSRV_INFO("%s:%s:%s %s: num_intents[%d] size[%d]\n",
		   rx_ch_info->transport, rx_ch_info->edge, name, __func__,
		   req.num_intents, req.intent_size);
	tmp_ch_info = lookup_ch_list(name, rx_ch_info->edge,
				     rx_ch_info->transport, DATA);
	if (!tmp_ch_info) {
		LBSRV_ERR("%s:%s:%s %s: Channel info not found\n",
				rx_ch_info->transport, rx_ch_info->edge,
				name, __func__);
		return -EINVAL;
	}

	tmp_work_info = kzalloc(sizeof(struct queue_rx_intent_work_info),
				GFP_KERNEL);
	if (!tmp_work_info) {
		LBSRV_ERR("%s: Error allocating work_info\n", __func__);
		return -ENOMEM;
	}

	tmp_work_info->req_id = req_id;
	tmp_work_info->req_ch_info = rx_ch_info;
	tmp_work_info->num_intents = req.num_intents;
	tmp_work_info->intent_size = req.intent_size;
	tmp_work_info->random_delay =  req.random_delay;
	tmp_work_info->delay_ms = req.delay_ms;
	INIT_DELAYED_WORK(&tmp_work_info->work,
			  glink_lbsrv_queue_rx_intent_worker);
	tmp_work_info->work_ch_info = tmp_ch_info;

	mutex_lock(&tmp_ch_info->ch_info_lock);
	if (tmp_ch_info->fully_opened) {
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		delay_ms = calc_delay_ms(tmp_work_info->random_delay,
					 tmp_work_info->delay_ms);
		queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work,
				   msecs_to_jiffies(delay_ms));

		if (tmp_work_info->random_delay || tmp_work_info->delay_ms)
			glink_lbsrv_send_response(rx_ch_info->handle, req_id,
					QUEUE_RX_INTENT_CONFIG, 0);
	} else {
		tmp_work_info->deferred = true;
		tmp_ch_info->queue_rx_intent_work_info = tmp_work_info;
		mutex_unlock(&tmp_ch_info->ch_info_lock);

		glink_lbsrv_send_response(rx_ch_info->handle, req_id,
				QUEUE_RX_INTENT_CONFIG, 0);
	}

	return 0;
}

int glink_lbsrv_handle_tx_config_req(struct ch_info *rx_ch_info,
				     struct tx_config_req req)
{
	struct ch_info *tmp_ch_info;
	char name[MAX_NAME_LEN];
	char *temp;

	strlcpy(name, req.ch_name, MAX_NAME_LEN);
	if (!strcmp(rx_ch_info->transport, "lloop")) {
		temp = strnstr(name, "_CLNT", MAX_NAME_LEN);
		if (temp)
			*temp = '\0';
		strlcat(name, "_SRV", MAX_NAME_LEN);
	}
	LBSRV_INFO("%s:%s:%s %s: echo_count[%d] transform[%d]\n",
		   rx_ch_info->transport, rx_ch_info->edge, name, __func__,
		   req.echo_count, req.transform_type);
	tmp_ch_info = lookup_ch_list(name, rx_ch_info->edge,
				     rx_ch_info->transport, DATA);
	if (!tmp_ch_info) {
		LBSRV_ERR("%s:%s:%s %s: Channel info not found\n",
				rx_ch_info->transport, rx_ch_info->edge,
				name, __func__);
		return -EINVAL;
	}

	mutex_lock(&tmp_ch_info->ch_info_lock);
	tmp_ch_info->tx_config.random_delay = req.random_delay;
	tmp_ch_info->tx_config.delay_ms = req.delay_ms;
	tmp_ch_info->tx_config.echo_count = req.echo_count;
	tmp_ch_info->tx_config.transform_type = req.transform_type;
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	return 0;
}

int glink_lbsrv_handle_rx_done_config_req(struct ch_info *rx_ch_info,
					  struct rx_done_config_req req)
{
	struct ch_info *tmp_ch_info;
	char name[MAX_NAME_LEN];
	char *temp;

	strlcpy(name, req.ch_name, MAX_NAME_LEN);
	if (!strcmp(rx_ch_info->transport, "lloop")) {
		temp = strnstr(name, "_CLNT", MAX_NAME_LEN);
		if (temp)
			*temp = '\0';
		strlcat(name, "_SRV", MAX_NAME_LEN);
	}
	LBSRV_INFO("%s:%s:%s %s: delay_ms[%d] random_delay[%d]\n",
		   rx_ch_info->transport, rx_ch_info->edge, name,
		   __func__, req.delay_ms, req.random_delay);
	tmp_ch_info = lookup_ch_list(name, rx_ch_info->edge,
				     rx_ch_info->transport, DATA);
	if (!tmp_ch_info) {
		LBSRV_ERR("%s:%s:%s %s: Channel info not found\n",
				rx_ch_info->transport, rx_ch_info->edge,
				name, __func__);
		return -EINVAL;
	}

	mutex_lock(&tmp_ch_info->ch_info_lock);
	tmp_ch_info->rx_done_config.random_delay = req.random_delay;
	tmp_ch_info->rx_done_config.delay_ms = req.delay_ms;
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	return 0;
}

/**
 * glink_lbsrv_handle_req() - Handle the request commands received by clients
 *
 * rx_ch_info:	Channel info on which the request is received
 * pkt:	Request structure received from client
 *
 * This function handles the all supported request types received from client
 * and send the response back to client
 */
void glink_lbsrv_handle_req(struct ch_info *rx_ch_info, struct req pkt)
{
	int ret;

	LBSRV_INFO("%s:%s:%s %s: Request packet type[%d]:id[%d]\n",
			rx_ch_info->transport, rx_ch_info->edge,
			rx_ch_info->name, __func__, pkt.hdr.req_type,
			pkt.hdr.req_id);
	switch (pkt.hdr.req_type) {
	case OPEN:
		ret = glink_lbsrv_handle_open_req(rx_ch_info,
						  pkt.payload.open);
		break;
	case CLOSE:
		ret = glink_lbsrv_handle_close_req(rx_ch_info,
						   pkt.payload.close);
		break;
	case QUEUE_RX_INTENT_CONFIG:
		ret = glink_lbsrv_handle_queue_rx_intent_config_req(
			rx_ch_info, pkt.payload.q_rx_int_conf, pkt.hdr.req_id);
		break;
	case TX_CONFIG:
		ret = glink_lbsrv_handle_tx_config_req(rx_ch_info,
						       pkt.payload.tx_conf);
		break;
	case RX_DONE_CONFIG:
		ret = glink_lbsrv_handle_rx_done_config_req(rx_ch_info,
						pkt.payload.rx_done_conf);
		break;
	default:
		LBSRV_ERR("%s:%s:%s %s: Invalid Request type [%d]\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__, pkt.hdr.req_type);
		ret = -1;
		break;
	}

	if (pkt.hdr.req_type != QUEUE_RX_INTENT_CONFIG)
		glink_lbsrv_send_response(rx_ch_info->handle, pkt.hdr.req_id,
				pkt.hdr.req_type, ret);
}

static void *glink_lbsrv_vbuf_provider(void *iovec, size_t offset,
				       size_t *buf_size)
{
	struct lbsrv_vec *tmp_vec_info = (struct lbsrv_vec *)iovec;
	uint32_t i;
	size_t temp_size = 0;

	for (i = 0; i < tmp_vec_info->num_bufs; i++) {
		temp_size += tmp_vec_info->vec[i].iov_len;
		if (offset >= temp_size)
			continue;
		*buf_size = temp_size - offset;
		return (void *)tmp_vec_info->vec[i].iov_base +
			tmp_vec_info->vec[i].iov_len - *buf_size;
	}
	*buf_size = 0;
	return NULL;
}

static void glink_lbsrv_free_data(void *data, uint32_t buf_type)
{
	struct lbsrv_vec *tmp_vec_info;
	uint32_t i;

	if (buf_type == LINEAR) {
		kfree(data);
	} else {
		tmp_vec_info = (struct lbsrv_vec *)data;
		for (i = 0; i < tmp_vec_info->num_bufs; i++) {
			kfree(tmp_vec_info->vec[i].iov_base);
			tmp_vec_info->vec[i].iov_base = NULL;
		}
		kfree(tmp_vec_info);
	}
}

static void *copy_linear_data(struct rx_work_info *tmp_rx_work_info)
{
	char *data;
	struct ch_info *rx_ch_info = tmp_rx_work_info->rx_ch_info;

	data = kmalloc(tmp_rx_work_info->size, GFP_KERNEL);
	if (data)
		memcpy(data, tmp_rx_work_info->ptr, tmp_rx_work_info->size);
	else
		LBSRV_ERR("%s:%s:%s %s: Error allocating the data\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__);
	return data;
}

static void *copy_vector_data(struct rx_work_info *tmp_rx_work_info)
{
	uint32_t num_bufs = 0;
	struct ch_info *rx_ch_info = tmp_rx_work_info->rx_ch_info;
	struct lbsrv_vec *tmp_vec_info;
	void *buf, *pbuf, *dest_buf;
	size_t offset = 0;
	size_t buf_size;
	uint32_t i;

	do {
		if (tmp_rx_work_info->vbuf_provider)
			buf = tmp_rx_work_info->vbuf_provider(
				tmp_rx_work_info->ptr, offset, &buf_size);
		else
			buf = tmp_rx_work_info->pbuf_provider(
				tmp_rx_work_info->ptr, offset, &buf_size);
		if (!buf)
			break;
		offset += buf_size;
		num_bufs++;
	} while (buf);

	tmp_vec_info = kzalloc(sizeof(*tmp_vec_info) +
			       num_bufs * sizeof(struct kvec), GFP_KERNEL);
	if (!tmp_vec_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating vector info\n",
			  rx_ch_info->transport, rx_ch_info->edge,
			  rx_ch_info->name, __func__);
		return NULL;
	}
	tmp_vec_info->num_bufs = num_bufs;

	offset = 0;
	for (i = 0; i < num_bufs; i++) {
		if (tmp_rx_work_info->vbuf_provider) {
			buf = tmp_rx_work_info->vbuf_provider(
				tmp_rx_work_info->ptr, offset, &buf_size);
		} else {
			pbuf = tmp_rx_work_info->pbuf_provider(
				tmp_rx_work_info->ptr, offset, &buf_size);
			buf = phys_to_virt((unsigned long)pbuf);
		}
		dest_buf = kmalloc(buf_size, GFP_KERNEL);
		if (!dest_buf) {
			LBSRV_ERR("%s:%s:%s %s: Error allocating data\n",
				  rx_ch_info->transport, rx_ch_info->edge,
				  rx_ch_info->name, __func__);
			goto out_copy_vector_data;
		}
		memcpy(dest_buf, buf, buf_size);
		tmp_vec_info->vec[i].iov_base = dest_buf;
		tmp_vec_info->vec[i].iov_len = buf_size;
		offset += buf_size;
	}
	return tmp_vec_info;
out_copy_vector_data:
	glink_lbsrv_free_data((void *)tmp_vec_info, VECTOR);
	return NULL;
}

static void *glink_lbsrv_copy_data(struct rx_work_info *tmp_rx_work_info)
{
	if (tmp_rx_work_info->buf_type == LINEAR)
		return copy_linear_data(tmp_rx_work_info);
	else
		return copy_vector_data(tmp_rx_work_info);
}

static int glink_lbsrv_handle_data(struct rx_work_info *tmp_rx_work_info)
{
	void *data;
	int ret;
	struct ch_info *rx_ch_info = tmp_rx_work_info->rx_ch_info;
	struct tx_work_info *tmp_tx_work_info;
	struct rx_done_work_info *tmp_rx_done_work_info;
	uint32_t delay_ms;

	data = glink_lbsrv_copy_data(tmp_rx_work_info);
	if (!data) {
		ret = -ENOMEM;
		goto out_handle_data;
	}

	tmp_rx_done_work_info = kmalloc(sizeof(struct rx_done_work_info),
					GFP_KERNEL);
	if (!tmp_rx_done_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating rx_done_work_info\n",
			  rx_ch_info->transport, rx_ch_info->edge,
			  rx_ch_info->name, __func__);
		glink_lbsrv_free_data(data, tmp_rx_work_info->buf_type);
		ret = -ENOMEM;
		goto out_handle_data;
	}
	INIT_DELAYED_WORK(&tmp_rx_done_work_info->work,
			  glink_lbsrv_rx_done_worker);
	tmp_rx_done_work_info->rx_done_ch_info = rx_ch_info;
	tmp_rx_done_work_info->ptr = tmp_rx_work_info->ptr;
	delay_ms = calc_delay_ms(rx_ch_info->rx_done_config.random_delay,
				 rx_ch_info->rx_done_config.delay_ms);
	queue_delayed_work(glink_lbsrv_wq, &tmp_rx_done_work_info->work,
			   msecs_to_jiffies(delay_ms));

	tmp_tx_work_info = kmalloc(sizeof(struct tx_work_info), GFP_KERNEL);
	if (!tmp_tx_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating tx_work_info\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__);
		glink_lbsrv_free_data(data, tmp_rx_work_info->buf_type);
		return -ENOMEM;
	}
	mutex_lock(&rx_ch_info->ch_info_lock);
	tmp_tx_work_info->tx_config.random_delay =
					rx_ch_info->tx_config.random_delay;
	tmp_tx_work_info->tx_config.delay_ms = rx_ch_info->tx_config.delay_ms;
	tmp_tx_work_info->tx_config.echo_count =
					rx_ch_info->tx_config.echo_count;
	tmp_tx_work_info->tx_config.transform_type =
					rx_ch_info->tx_config.transform_type;
	mutex_unlock(&rx_ch_info->ch_info_lock);
	INIT_DELAYED_WORK(&tmp_tx_work_info->work, glink_lbsrv_tx_worker);
	tmp_tx_work_info->tx_ch_info = rx_ch_info;
	tmp_tx_work_info->data = data;
	tmp_tx_work_info->tracer_pkt = tmp_rx_work_info->tracer_pkt;
	tmp_tx_work_info->buf_type = tmp_rx_work_info->buf_type;
	tmp_tx_work_info->size = tmp_rx_work_info->size;
	if (tmp_tx_work_info->buf_type == VECTOR)
		tmp_tx_work_info->vbuf_provider = glink_lbsrv_vbuf_provider;
	else
		tmp_tx_work_info->vbuf_provider = NULL;
	tmp_tx_work_info->pbuf_provider = NULL;
	delay_ms = calc_delay_ms(tmp_tx_work_info->tx_config.random_delay,
				 tmp_tx_work_info->tx_config.delay_ms);
	queue_delayed_work(glink_lbsrv_wq, &tmp_tx_work_info->work,
			   msecs_to_jiffies(delay_ms));
	return 0;
out_handle_data:
	glink_rx_done(rx_ch_info->handle, tmp_rx_work_info->ptr, false);
	return ret;
}

void glink_lpbsrv_notify_rx(void *handle, const void *priv,
			    const void *pkt_priv, const void *ptr, size_t size)
{
	struct rx_work_info *tmp_work_info;
	struct ch_info *rx_ch_info = (struct ch_info *)priv;

	LBSRV_INFO(
		"%s:%s:%s %s: end (Success) RX priv[%p] data[%p] size[%zu]\n",
		rx_ch_info->transport, rx_ch_info->edge, rx_ch_info->name,
		__func__, pkt_priv, (char *)ptr, size);
	tmp_work_info = kzalloc(sizeof(struct rx_work_info), GFP_ATOMIC);
	if (!tmp_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating rx_work\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__);
		return;
	}

	tmp_work_info->rx_ch_info = rx_ch_info;
	tmp_work_info->pkt_priv = (void *)pkt_priv;
	tmp_work_info->ptr = (void *)ptr;
	tmp_work_info->buf_type = LINEAR;
	tmp_work_info->size = size;
	INIT_DELAYED_WORK(&tmp_work_info->work, glink_lbsrv_rx_worker);
	queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work, 0);
}

void glink_lpbsrv_notify_rxv(void *handle, const void *priv,
	const void *pkt_priv, void *ptr, size_t size,
	void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size),
	void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size))
{
	struct rx_work_info *tmp_work_info;
	struct ch_info *rx_ch_info = (struct ch_info *)priv;

	LBSRV_INFO("%s:%s:%s %s: priv[%p] data[%p] size[%zu]\n",
		   rx_ch_info->transport, rx_ch_info->edge, rx_ch_info->name,
		   __func__, pkt_priv, (char *)ptr, size);
	tmp_work_info = kzalloc(sizeof(struct rx_work_info), GFP_ATOMIC);
	if (!tmp_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating rx_work\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__);
		return;
	}

	tmp_work_info->rx_ch_info = rx_ch_info;
	tmp_work_info->pkt_priv = (void *)pkt_priv;
	tmp_work_info->ptr = (void *)ptr;
	tmp_work_info->buf_type = VECTOR;
	tmp_work_info->size = size;
	tmp_work_info->vbuf_provider = vbuf_provider;
	tmp_work_info->pbuf_provider = pbuf_provider;
	INIT_DELAYED_WORK(&tmp_work_info->work, glink_lbsrv_rx_worker);
	queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work, 0);
}

void glink_lpbsrv_notify_rx_tp(void *handle, const void *priv,
			    const void *pkt_priv, const void *ptr, size_t size)
{
	struct rx_work_info *tmp_work_info;
	struct ch_info *rx_ch_info = (struct ch_info *)priv;

	LBSRV_INFO(
		"%s:%s:%s %s: end (Success) RX priv[%p] data[%p] size[%zu]\n",
		rx_ch_info->transport, rx_ch_info->edge, rx_ch_info->name,
		__func__, pkt_priv, (char *)ptr, size);
	tracer_pkt_log_event((void *)ptr, LOOPBACK_SRV_RX);
	tmp_work_info = kmalloc(sizeof(struct rx_work_info), GFP_ATOMIC);
	if (!tmp_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating rx_work\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__);
		return;
	}

	tmp_work_info->rx_ch_info = rx_ch_info;
	tmp_work_info->pkt_priv = (void *)pkt_priv;
	tmp_work_info->ptr = (void *)ptr;
	tmp_work_info->tracer_pkt = true;
	tmp_work_info->buf_type = LINEAR;
	tmp_work_info->size = size;
	INIT_DELAYED_WORK(&tmp_work_info->work, glink_lbsrv_rx_worker);
	queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work, 0);
}

void glink_lpbsrv_notify_tx_done(void *handle, const void *priv,
				 const void *pkt_priv, const void *ptr)
{
	struct ch_info *tx_done_ch_info = (struct ch_info *)priv;
	LBSRV_INFO("%s:%s:%s %s: end (Success) TX_DONE ptr[%p]\n",
			tx_done_ch_info->transport, tx_done_ch_info->edge,
			tx_done_ch_info->name, __func__, ptr);

	if (pkt_priv != (const void *)0xFFFFFFFF)
		glink_lbsrv_free_data((void *)ptr,
				(uint32_t)(uintptr_t)pkt_priv);
}

void glink_lpbsrv_notify_state(void *handle, const void *priv, unsigned event)
{
	int ret;
	uint32_t delay_ms;
	struct ch_info *tmp_ch_info = (struct ch_info *)priv;
	struct queue_rx_intent_work_info *tmp_work_info = NULL;

	LBSRV_INFO("%s:%s:%s %s: event[%d]\n",
			tmp_ch_info->transport, tmp_ch_info->edge,
			tmp_ch_info->name, __func__, event);
	if (tmp_ch_info->type == CTL) {
		if (event == GLINK_CONNECTED) {
			ret = glink_queue_rx_intent(handle,
					priv, sizeof(struct req));
			LBSRV_INFO(
				"%s:%s:%s %s: QUEUE RX INTENT size[%zu] ret[%d]\n",
				tmp_ch_info->transport,
				tmp_ch_info->edge,
				tmp_ch_info->name,
				__func__, sizeof(struct req), ret);
		} else if (event == GLINK_LOCAL_DISCONNECTED) {
			queue_delayed_work(glink_lbsrv_wq,
					&tmp_ch_info->open_work,
					msecs_to_jiffies(0));
		} else if (event == GLINK_REMOTE_DISCONNECTED)
			if (!IS_ERR_OR_NULL(tmp_ch_info->handle))
				queue_delayed_work(glink_lbsrv_wq,
					&tmp_ch_info->close_work, 0);
	} else if (tmp_ch_info->type == DATA) {

		if (event == GLINK_CONNECTED) {
			mutex_lock(&tmp_ch_info->ch_info_lock);
			tmp_ch_info->fully_opened = true;
			tmp_work_info = tmp_ch_info->queue_rx_intent_work_info;
			tmp_ch_info->queue_rx_intent_work_info = NULL;
			mutex_unlock(&tmp_ch_info->ch_info_lock);

			if (tmp_work_info) {
				delay_ms = calc_delay_ms(
						tmp_work_info->random_delay,
						tmp_work_info->delay_ms);
				queue_delayed_work(glink_lbsrv_wq,
						&tmp_work_info->work,
						msecs_to_jiffies(delay_ms));
			}
		} else if (event == GLINK_LOCAL_DISCONNECTED ||
			event == GLINK_REMOTE_DISCONNECTED) {

			mutex_lock(&tmp_ch_info->ch_info_lock);
			tmp_ch_info->fully_opened = false;
			/*
			* If the state has changed to LOCAL_DISCONNECTED,
			* the channel has been fully closed and can now be
			* re-opened. If the handle value is -EBUSY, an earlier
			* open request failed because the channel was in the
			* process of closing. Requeue the work from the open
			* request.
			*/
			if (event == GLINK_LOCAL_DISCONNECTED &&
				tmp_ch_info->handle == ERR_PTR(-EBUSY)) {
				queue_delayed_work(glink_lbsrv_wq,
				&tmp_ch_info->open_work,
				msecs_to_jiffies(0));
			}
			if (event == GLINK_REMOTE_DISCONNECTED)
				if (!IS_ERR_OR_NULL(tmp_ch_info->handle))
					queue_delayed_work(
					glink_lbsrv_wq,
					&tmp_ch_info->close_work, 0);
			mutex_unlock(&tmp_ch_info->ch_info_lock);
		}
	}
}

bool glink_lpbsrv_rmt_rx_intent_req_cb(void *handle, const void *priv,
				       size_t sz)
{
	struct rmt_rx_intent_req_work_info *tmp_work_info;
	struct ch_info *tmp_ch_info = (struct ch_info *)priv;
	LBSRV_INFO("%s:%s:%s %s: QUEUE RX INTENT to receive size[%zu]\n",
		   tmp_ch_info->transport, tmp_ch_info->edge, tmp_ch_info->name,
		   __func__, sz);

	tmp_work_info = kmalloc(sizeof(struct rmt_rx_intent_req_work_info),
				GFP_ATOMIC);
	if (!tmp_work_info) {
		LBSRV_ERR("%s:%s:%s %s: Error allocating rx_work\n",
				tmp_ch_info->transport, tmp_ch_info->edge,
				tmp_ch_info->name, __func__);
		return false;
	}
	tmp_work_info->req_intent_size = sz;
	tmp_work_info->work_ch_info = tmp_ch_info;

	INIT_DELAYED_WORK(&tmp_work_info->work,
			  glink_lbsrv_rmt_rx_intent_req_worker);
	queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work, 0);
	return true;
}

void glink_lpbsrv_notify_rx_sigs(void *handle, const void *priv,
			uint32_t old_sigs, uint32_t new_sigs)
{
	LBSRV_INFO(" %s old_sigs[0x%x] New_sigs[0x%x]\n",
				__func__, old_sigs, new_sigs);
	glink_sigs_set(handle, new_sigs);
}

static void glink_lbsrv_rx_worker(struct work_struct *work)
{
	struct delayed_work *rx_work = to_delayed_work(work);
	struct rx_work_info *tmp_rx_work_info =
		container_of(rx_work, struct rx_work_info, work);
	struct ch_info *rx_ch_info = tmp_rx_work_info->rx_ch_info;
	struct req request_pkt;
	int ret;

	if (rx_ch_info->type == CTL) {
		request_pkt = *((struct req *)tmp_rx_work_info->ptr);
		glink_rx_done(rx_ch_info->handle, tmp_rx_work_info->ptr, false);
		ret = glink_queue_rx_intent(rx_ch_info->handle, rx_ch_info,
					    sizeof(struct req));
		LBSRV_INFO("%s:%s:%s %s: QUEUE RX INTENT size[%zu] ret[%d]\n",
				rx_ch_info->transport, rx_ch_info->edge,
				rx_ch_info->name, __func__,
				sizeof(struct req), ret);
		glink_lbsrv_handle_req(rx_ch_info, request_pkt);
	} else {
		ret = glink_lbsrv_handle_data(tmp_rx_work_info);
	}
	kfree(tmp_rx_work_info);
}

static void glink_lbsrv_open_worker(struct work_struct *work)
{
	struct delayed_work *open_work = to_delayed_work(work);
	struct ch_info *tmp_ch_info =
		container_of(open_work, struct ch_info, open_work);
	struct glink_open_config open_cfg;

	LBSRV_INFO("%s: glink_loopback_server_init\n", __func__);
	mutex_lock(&tmp_ch_info->ch_info_lock);
	if (!IS_ERR_OR_NULL(tmp_ch_info->handle)) {
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		return;
	}

	memset(&open_cfg, 0, sizeof(struct glink_open_config));
	open_cfg.transport = tmp_ch_info->transport;
	open_cfg.edge = tmp_ch_info->edge;
	open_cfg.name = tmp_ch_info->name;

	open_cfg.notify_rx = glink_lpbsrv_notify_rx;
	if (tmp_ch_info->type == DATA)
		open_cfg.notify_rxv = glink_lpbsrv_notify_rxv;
	open_cfg.notify_tx_done = glink_lpbsrv_notify_tx_done;
	open_cfg.notify_state = glink_lpbsrv_notify_state;
	open_cfg.notify_rx_intent_req = glink_lpbsrv_rmt_rx_intent_req_cb;
	open_cfg.notify_rx_sigs = glink_lpbsrv_notify_rx_sigs;
	open_cfg.notify_rx_abort = NULL;
	open_cfg.notify_tx_abort = NULL;
	open_cfg.notify_rx_tracer_pkt = glink_lpbsrv_notify_rx_tp;
	open_cfg.priv = tmp_ch_info;

	tmp_ch_info->handle = glink_open(&open_cfg);
	if (IS_ERR_OR_NULL(tmp_ch_info->handle)) {
		LBSRV_ERR("%s:%s:%s %s: unable to open channel\n",
			  open_cfg.transport, open_cfg.edge, open_cfg.name,
			  __func__);
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		return;
	}
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	LBSRV_INFO("%s:%s:%s %s: Open complete\n", open_cfg.transport,
			open_cfg.edge, open_cfg.name, __func__);
}

static void glink_lbsrv_close_worker(struct work_struct *work)
{
	struct delayed_work *close_work = to_delayed_work(work);
	struct ch_info *tmp_ch_info =
		container_of(close_work, struct ch_info, close_work);

	mutex_lock(&tmp_ch_info->ch_info_lock);
	if (!IS_ERR_OR_NULL(tmp_ch_info->handle)) {
		glink_close(tmp_ch_info->handle);
		tmp_ch_info->handle = NULL;
	}
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	LBSRV_INFO("%s:%s:%s %s: Close complete\n", tmp_ch_info->transport,
			tmp_ch_info->edge, tmp_ch_info->name, __func__);
}

static void glink_lbsrv_rmt_rx_intent_req_worker(struct work_struct *work)
{

	struct delayed_work *rmt_rx_intent_req_work = to_delayed_work(work);
	struct rmt_rx_intent_req_work_info *tmp_work_info =
		container_of(rmt_rx_intent_req_work,
			struct rmt_rx_intent_req_work_info, work);
	struct ch_info *tmp_ch_info = tmp_work_info->work_ch_info;
	int ret;

	mutex_lock(&tmp_ch_info->ch_info_lock);
	if (IS_ERR_OR_NULL(tmp_ch_info->handle)) {
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		LBSRV_ERR("%s:%s:%s %s: Invalid CH handle\n",
				  tmp_ch_info->transport,
				  tmp_ch_info->edge,
				  tmp_ch_info->name, __func__);
		kfree(tmp_work_info);
		return;
	}
	ret = glink_queue_rx_intent(tmp_ch_info->handle,
			(void *)tmp_ch_info, tmp_work_info->req_intent_size);
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	LBSRV_INFO("%s:%s:%s %s: QUEUE RX INTENT size[%zu] ret[%d]\n",
		   tmp_ch_info->transport, tmp_ch_info->edge,
		   tmp_ch_info->name, __func__, tmp_work_info->req_intent_size,
		   ret);
	if (ret < 0) {
		LBSRV_ERR("%s:%s:%s %s: Err %d q'ing intent size %zu\n",
			  tmp_ch_info->transport, tmp_ch_info->edge,
			  tmp_ch_info->name, __func__, ret,
			  tmp_work_info->req_intent_size);
	}
	kfree(tmp_work_info);
	return;
}

static void glink_lbsrv_queue_rx_intent_worker(struct work_struct *work)
{
	struct delayed_work *queue_rx_intent_work = to_delayed_work(work);
	struct queue_rx_intent_work_info *tmp_work_info =
		container_of(queue_rx_intent_work,
			struct queue_rx_intent_work_info, work);
	struct ch_info *tmp_ch_info = tmp_work_info->work_ch_info;
	int ret;
	uint32_t delay_ms;

	while (1) {
		mutex_lock(&tmp_ch_info->ch_info_lock);
		if (IS_ERR_OR_NULL(tmp_ch_info->handle)) {
			mutex_unlock(&tmp_ch_info->ch_info_lock);
			return;
		}

		ret = glink_queue_rx_intent(tmp_ch_info->handle,
			(void *)tmp_ch_info, tmp_work_info->intent_size);
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		if (ret < 0) {
			LBSRV_ERR("%s:%s:%s %s: Err %d q'ing intent size %d\n",
				  tmp_ch_info->transport, tmp_ch_info->edge,
				  tmp_ch_info->name, __func__, ret,
				  tmp_work_info->intent_size);
			kfree(tmp_work_info);
			return;
		}
		LBSRV_INFO("%s:%s:%s %s: Queued rx intent of size %d\n",
			   tmp_ch_info->transport, tmp_ch_info->edge,
			   tmp_ch_info->name, __func__,
			   tmp_work_info->intent_size);
		tmp_work_info->num_intents--;
		if (!tmp_work_info->num_intents)
			break;

		delay_ms = calc_delay_ms(tmp_work_info->random_delay,
					 tmp_work_info->delay_ms);
		if (delay_ms) {
			queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work,
					   msecs_to_jiffies(delay_ms));
			return;
		}
	}
	LBSRV_INFO("%s:%s:%s %s: Queued all intents. size:%d\n",
		   tmp_ch_info->transport, tmp_ch_info->edge, tmp_ch_info->name,
		   __func__, tmp_work_info->intent_size);

	if (!tmp_work_info->deferred && !tmp_work_info->random_delay &&
			!tmp_work_info->delay_ms)
		glink_lbsrv_send_response(tmp_work_info->req_ch_info->handle,
				tmp_work_info->req_id, QUEUE_RX_INTENT_CONFIG,
				0);
	kfree(tmp_work_info);
}

static void glink_lbsrv_rx_done_worker(struct work_struct *work)
{
	struct delayed_work *rx_done_work = to_delayed_work(work);
	struct rx_done_work_info *tmp_work_info =
		container_of(rx_done_work, struct rx_done_work_info, work);
	struct ch_info *tmp_ch_info = tmp_work_info->rx_done_ch_info;

	mutex_lock(&tmp_ch_info->ch_info_lock);
	if (!IS_ERR_OR_NULL(tmp_ch_info->handle))
		glink_rx_done(tmp_ch_info->handle, tmp_work_info->ptr, false);
	mutex_unlock(&tmp_ch_info->ch_info_lock);
	kfree(tmp_work_info);
}

static void glink_lbsrv_tx_worker(struct work_struct *work)
{
	struct delayed_work *tx_work = to_delayed_work(work);
	struct tx_work_info *tmp_work_info =
		container_of(tx_work, struct tx_work_info, work);
	struct ch_info *tmp_ch_info = tmp_work_info->tx_ch_info;
	int ret;
	uint32_t delay_ms;
	uint32_t flags;

	LBSRV_INFO("%s:%s:%s %s: start TX data[%p] size[%zu]\n",
		   tmp_ch_info->transport, tmp_ch_info->edge, tmp_ch_info->name,
		   __func__, tmp_work_info->data, tmp_work_info->size);
	while (1) {
		mutex_lock(&tmp_ch_info->ch_info_lock);
		if (IS_ERR_OR_NULL(tmp_ch_info->handle)) {
			mutex_unlock(&tmp_ch_info->ch_info_lock);
			return;
		}

		flags = 0;
		if (tmp_work_info->tracer_pkt) {
			flags |= GLINK_TX_TRACER_PKT;
			tracer_pkt_log_event(tmp_work_info->data,
					     LOOPBACK_SRV_TX);
		}
		if (tmp_work_info->buf_type == LINEAR)
			ret = glink_tx(tmp_ch_info->handle,
			       (tmp_work_info->tx_config.echo_count > 1 ?
					(void *)0xFFFFFFFF :
					(void *)(uintptr_t)
						tmp_work_info->buf_type),
			       (void *)tmp_work_info->data,
			       tmp_work_info->size, flags);
		else
			ret = glink_txv(tmp_ch_info->handle,
				(tmp_work_info->tx_config.echo_count > 1 ?
					(void *)0xFFFFFFFF :
					(void *)(uintptr_t)
						tmp_work_info->buf_type),
				(void *)tmp_work_info->data,
				tmp_work_info->size,
				tmp_work_info->vbuf_provider,
				tmp_work_info->pbuf_provider,
				flags);
		mutex_unlock(&tmp_ch_info->ch_info_lock);
		if (ret < 0 && ret != -EAGAIN) {
			LBSRV_ERR("%s:%s:%s %s: TX Error %d\n",
					tmp_ch_info->transport,
					tmp_ch_info->edge,
					tmp_ch_info->name, __func__, ret);
			glink_lbsrv_free_data(tmp_work_info->data,
					      tmp_work_info->buf_type);
			kfree(tmp_work_info);
			return;
		}
		if (ret != -EAGAIN)
			tmp_work_info->tx_config.echo_count--;
		if (!tmp_work_info->tx_config.echo_count)
			break;

		delay_ms = calc_delay_ms(tmp_work_info->tx_config.random_delay,
					 tmp_work_info->tx_config.delay_ms);
		if (delay_ms) {
			queue_delayed_work(glink_lbsrv_wq, &tmp_work_info->work,
					   msecs_to_jiffies(delay_ms));
			return;
		}
	}
	kfree(tmp_work_info);
}

/**
 * glink_lbsrv_link_state_worker() - Function to handle link state updates
 * work:	Pointer to the work item in the link_state_work_info.
 *
 * This worker function is scheduled when there is a link state update. Since
 * the loopback server registers for all transports, it receives all link state
 * updates about all transports that get registered in the system.
 */
static void glink_lbsrv_link_state_worker(struct work_struct *work)
{
	struct delayed_work *ls_work = to_delayed_work(work);
	struct link_state_work_info *ls_info =
		container_of(ls_work, struct link_state_work_info, work);
	struct ch_info *tmp_ch_info;

	if (ls_info->link_state == GLINK_LINK_STATE_UP) {
		LBSRV_INFO("%s: LINK_STATE_UP %s:%s\n",
			  __func__, ls_info->edge, ls_info->transport);
		mutex_lock(&ctl_ch_list_lock);
		list_for_each_entry(tmp_ch_info, &ctl_ch_list, list) {
			if (strcmp(tmp_ch_info->edge, ls_info->edge) ||
			    strcmp(tmp_ch_info->transport, ls_info->transport))
				continue;
			queue_delayed_work(glink_lbsrv_wq,
					   &tmp_ch_info->open_work, 0);
		}
		mutex_unlock(&ctl_ch_list_lock);
	} else if (ls_info->link_state == GLINK_LINK_STATE_DOWN) {
		LBSRV_INFO("%s: LINK_STATE_DOWN %s:%s\n",
			  __func__, ls_info->edge, ls_info->transport);

	}
	kfree(ls_info);
	return;
}

/**
 * glink_lbsrv_link_state_cb() - Callback to receive link state updates
 * cb_info:	Information containing link & its state.
 * priv:	Private data passed during the link state registration.
 *
 * This function is called by the GLINK core to notify the loopback server
 * regarding the link state updates. This function is registered with the
 * GLINK core by the loopback server during glink_register_link_state_cb().
 */
static void glink_lbsrv_link_state_cb(struct glink_link_state_cb_info *cb_info,
				      void *priv)
{
	struct link_state_work_info *ls_info;

	if (!cb_info)
		return;

	LBSRV_INFO("%s: %s:%s\n", __func__, cb_info->edge, cb_info->transport);
	ls_info = kmalloc(sizeof(*ls_info), GFP_KERNEL);
	if (!ls_info) {
		LBSRV_ERR("%s: Error allocating link state info\n", __func__);
		return;
	}

	strlcpy(ls_info->edge, cb_info->edge, GLINK_NAME_SIZE);
	strlcpy(ls_info->transport, cb_info->transport, GLINK_NAME_SIZE);
	ls_info->link_state = cb_info->link_state;
	INIT_DELAYED_WORK(&ls_info->work, glink_lbsrv_link_state_worker);
	queue_delayed_work(glink_lbsrv_wq, &ls_info->work, 0);
}

static int glink_loopback_server_init(void)
{
	int i;
	int ret;
	struct ch_info *tmp_ch_info;

	glink_lbsrv_log_ctx = ipc_log_context_create(GLINK_LBSRV_NUM_LOG_PAGES,
							"glink_lbsrv", 0);
	if (!glink_lbsrv_log_ctx)
		pr_err("%s: unable to create log context\n", __func__);

	glink_lbsrv_wq = create_singlethread_workqueue("glink_lbsrv");
	if (!glink_lbsrv_wq) {
		LBSRV_ERR("%s: Error creating glink_lbsrv_wq\n", __func__);
		return -EFAULT;
	}

	for (i = 0; i < ARRAY_SIZE(ctl_ch_tbl); i++) {
		ret = create_ch_info(ctl_ch_tbl[i].name, ctl_ch_tbl[i].edge,
				     ctl_ch_tbl[i].transport, CTL,
				     &tmp_ch_info);
		if (ret < 0) {
			LBSRV_ERR("%s: Error creating ctl ch index %d\n",
				__func__, i);
			continue;
		}
	}
	glink_lbsrv_link_state_notif_handle = glink_register_link_state_cb(
						&glink_lbsrv_link_info, NULL);
	return 0;
}

module_init(glink_loopback_server_init);

MODULE_DESCRIPTION("MSM Generic Link (G-Link) Loopback Server");
MODULE_LICENSE("GPL v2");
