/*************************************************************************
				 * EMBMS.C
 *************************************************************************
 */

/*************************************************************************
 * -----------------------------------------------------------------------
 * Copyright (c) 2013-2016, 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.
 * -----------------------------------------------------------------------

 * DESCRIPTION
 * Main file for eMBMs Tunneling Module in kernel.
*************************************************************************
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <net/ip.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/etherdevice.h>

#include <linux/inetdevice.h>
#include <linux/netfilter.h>
#include <net/arp.h>
#include <net/neighbour.h>

#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/in.h>
#include <net/netfilter/nf_conntrack.h>
#include <linux/miscdevice.h>
#include "embms_kernel.h"

/* Embms device used for communication*/
struct miscdevice embms_device;

struct embms_info_internal embms_conf;

/* Global structures used for tunneling. These include
 * iphdr and udphdr which are appended to skbs for
 * tunneling, net_device and tunnleing related
 * structs and params
 */

unsigned char hdr_buff[sizeof(struct iphdr) + sizeof(struct udphdr)];
struct iphdr *iph_global;
struct udphdr *udph_global;
struct net_device *dev_global;

static struct tmgi_to_clnt_info tmgi_to_clnt_map_tbl;

/* handle_multicast_stream - packet forwarding
 * function for multicast stream
 * Main use case is for EMBMS Over Softap feature
 */

static int handle_multicast_stream(struct sk_buff *skb)
{
	struct iphdr *iph;
	struct udphdr *udph;
	struct in_device *in_dev;
	unsigned char *tmp_ptr = NULL;
	struct sk_buff *skb_new = NULL;
	struct sk_buff *skb_cpy = NULL;
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *temp_tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;
	int hdr_size = sizeof(*udph) + sizeof(*iph) + ETH_HLEN;

	/* only IP packets */
	if (htons(ETH_P_IP) != skb->protocol) {
		embms_error("Not an IP packet\n");
		return 0;
	}

	if (embms_conf.embms_tunneling_status == TUNNELING_OFF) {
		embms_debug("Tunneling Disabled. Can't process packets\n");
		return 0;
	}
	if (unlikely(!skb->dev)) {
		embms_error("Can't get skb dev name\n");
		return 0;
	}

	if (unlikely(memcmp(skb->dev->name, embms_conf.embms_iface,
		     MIN(strlen(embms_conf.embms_iface),
			 sizeof(skb->dev->name))) != 0)) {
		embms_error("Packet received on %s iface. NOT an EMBMS Iface\n",
			    skb->dev->name);
		return 0;
	}

	/* Check if dst ip of packet is same as multicast ip of any tmgi*/

	iph = (struct iphdr *)skb->data;
	udph = (struct udphdr *)(skb->data + sizeof(struct iphdr));

	spin_lock_bh(&embms_conf.lock);

	list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
		temp_tmgi = list_entry(tmgi_entry_ptr,
				       struct tmgi_to_clnt_info,
				       tmgi_list_ptr);

		if ((temp_tmgi->tmgi_multicast_addr == iph->daddr) &&
		    (temp_tmgi->tmgi_port == udph->dest))
			break;
	}

	if (tmgi_entry_ptr == &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
		embms_error("handle_multicast_stream:");
		embms_error("could not find matchin tmgi entry\n");
		spin_unlock_bh(&embms_conf.lock);
		return 0;
	}

	/* Found a matching tmgi entry. Realloc headroom to
	 * accommodate new Ethernet, IP and UDP header
	 */

	skb_new = skb_realloc_headroom(skb, hdr_size);
	if (unlikely(!skb_new)) {
		embms_error("Can't allocate headroom\n");
		spin_unlock_bh(&embms_conf.lock);
		return 0;
	}

	/* push skb->data and copy IP and UDP headers*/

	tmp_ptr = skb_push(skb_new, sizeof(struct udphdr)+sizeof(struct iphdr));

	iph = (struct iphdr *)tmp_ptr;
	udph = (struct udphdr *)(tmp_ptr + sizeof(struct iphdr));

	memcpy(tmp_ptr, hdr_buff, hdr_size - ETH_HLEN);
	udph->len = htons(skb_new->len - sizeof(struct iphdr));
	iph->tot_len = htons(skb_new->len);

	list_for_each_safe(clnt_ptr, prev_clnt_ptr,
			   &temp_tmgi->client_list_head) {
		temp_client = list_entry(clnt_ptr,
					 struct clnt_info,
					 client_list_ptr);

		/* Make a copy of skb_new with new IP and UDP header.
		 * We can't use skb_new or its clone here since we need to
		 * constantly change dst ip and dst port which is not possible
		 * for shared memory as is the case with skb_new.
		 */

		skb_cpy = skb_copy(skb_new, GFP_ATOMIC);
		if (unlikely(!skb_cpy)) {
			embms_error("Can't copy skb\n");
			kfree_skb(skb_new);
			return 0;
		}

		iph = (struct iphdr *)skb_cpy->data;
		udph = (struct udphdr *)(skb_cpy->data + sizeof(struct iphdr));

		iph->id = htons(atomic_inc_return(&embms_conf.ip_ident));

		/* Calculate checksum for new IP and UDP header*/

		udph->dest = temp_client->port;
		skb_cpy->csum = csum_partial((char *)udph,
					     ntohs(udph->len),
					     skb_cpy->csum);

		iph->daddr = temp_client->addr;
		ip_send_check(iph);

		udph->check = 0;
		udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
						ntohs(udph->len),
						IPPROTO_UDP,
						skb_cpy->csum);

		if (udph->check == 0)
			udph->check = CSUM_MANGLED_0;

		if (unlikely(dev_global == NULL)) {
			embms_error("Global device NULL\n");
			kfree_skb(skb_cpy);
			kfree_skb(skb_new);
			return 0;
		}

		/* update device info and add MAC header*/

		skb_cpy->dev = dev_global;

		skb_cpy->dev->header_ops->create(skb_cpy, skb_cpy->dev,
						ETH_P_IP, temp_client->dmac,
						NULL, skb_cpy->len);
		dev_queue_xmit(skb_cpy);
	}

	spin_unlock_bh(&embms_conf.lock);
	kfree_skb(skb_new);
	return 1;
}

static int check_embms_device(atomic_t *use_count)
{
	int ret;

	if (atomic_inc_return(use_count) == 1) {
		ret = 0;
	} else {
		atomic_dec(use_count);
		ret = -EBUSY;
	}
	return ret;
}

static int embms_device_open(struct inode *inode, struct file *file)
{
	/*Check if the device is busy*/
	if (check_embms_device(&embms_conf.device_under_use)) {
		embms_error("embms_tm_open : EMBMS device busy\n");
		return -EBUSY;
	}

	try_module_get(THIS_MODULE);
	return SUCCESS;
}

static int embms_device_release(struct inode *inode, struct file *file)
{
	/* Reduce device use count before leaving*/
	embms_debug("Releasing EMBMS device..\n");
	atomic_dec(&embms_conf.device_under_use);
	embms_conf.embms_tunneling_status = TUNNELING_OFF;
	module_put(THIS_MODULE);
	return SUCCESS;
}

static struct tmgi_to_clnt_info *check_for_tmgi_entry(u_int32_t addr,
						      u_int16_t port)
{
	struct list_head *tmgi_ptr, *prev_tmgi_ptr;
	struct tmgi_to_clnt_info *temp_tmgi = NULL;

	embms_debug("check_for_tmgi_entry: mcast addr :%pI4, port %u\n",
		    &addr, ntohs(port));

	list_for_each_safe(tmgi_ptr,
			   prev_tmgi_ptr,
			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
		temp_tmgi = list_entry(tmgi_ptr,
				       struct tmgi_to_clnt_info,
				       tmgi_list_ptr);

		if ((temp_tmgi->tmgi_multicast_addr == addr) &&
		    (temp_tmgi->tmgi_port == port)) {
			embms_debug("check_for_tmgi_entry:TMGI entry found\n");
			return temp_tmgi;
		}
	}
	return NULL;
}

static struct clnt_info *chk_clnt_entry(struct tmgi_to_clnt_info *tmgi,
					struct tmgi_to_clnt_info_update *clnt)
{
	struct list_head *clnt_ptr, *prev_clnt_ptr;
	struct clnt_info *temp_client = NULL;

	embms_debug("check_for_client_entry: clnt addr :%pI4, port %u\n",
		    &clnt->client_addr, ntohs(clnt->client_port));

	list_for_each_safe(clnt_ptr,
			   prev_clnt_ptr,
			   &tmgi->client_list_head) {
		temp_client = list_entry(clnt_ptr,
					 struct clnt_info,
					 client_list_ptr);
		if ((temp_client->addr == clnt->client_addr) &&
		    (temp_client->port == clnt->client_port)) {
			embms_debug("Clnt entry present\n");
			return temp_client;
		}
	}
	return NULL;
}

static int add_new_tmgi_entry(struct tmgi_to_clnt_info_update *info_update,
			      struct clnt_info *clnt)
{
	struct tmgi_to_clnt_info *new_tmgi = NULL;

	embms_debug("add_new_tmgi_entry:Enter\n");

	new_tmgi = kzalloc(sizeof(*new_tmgi),
			   GFP_ATOMIC);
	if (new_tmgi == NULL) {
		embms_error("add_new_tmgi_entry: mem alloc failed\n");
		return -ENOMEM;
	}

	memset(new_tmgi, 0, sizeof(struct tmgi_to_clnt_info));

	new_tmgi->tmgi_multicast_addr = info_update->multicast_addr;
	new_tmgi->tmgi_port = info_update->multicast_port;

	embms_debug("add_new_tmgi_entry:");
	embms_debug("New tmgi multicast addr :%pI4 , port %u\n",
		    &info_update->multicast_addr,
		    ntohs(info_update->multicast_port));

	embms_debug("add_new_tmgi_entry:Adding client entry\n");

	spin_lock_bh(&embms_conf.lock);

	INIT_LIST_HEAD(&new_tmgi->client_list_head);
	list_add(&clnt->client_list_ptr,
		 &new_tmgi->client_list_head);
	new_tmgi->no_of_clients++;

	/* Once above steps are done successfully,
	 * we add tmgi entry to our local table
	 */

	list_add(&new_tmgi->tmgi_list_ptr,
		 &tmgi_to_clnt_map_tbl.tmgi_list_ptr);
	embms_conf.no_of_tmgi_sessions++;

	spin_unlock_bh(&embms_conf.lock);

	return SUCCESS;
}

static void print_tmgi_to_client_table(void)
{
	int i, j;
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *temp_tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;

	embms_debug("====================================================\n");
	embms_debug("Printing TMGI to Client Table :\n");
	embms_debug("No of Active TMGIs : %d\n",
		    embms_conf.no_of_tmgi_sessions);
	embms_debug("====================================================\n\n");

	if (embms_conf.no_of_tmgi_sessions > 0) {
		i = 1;
		list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
				   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
			temp_tmgi = list_entry(tmgi_entry_ptr,
					       struct tmgi_to_clnt_info,
					       tmgi_list_ptr);

			embms_debug("TMGI entry %d :\n", i);
			embms_debug("TMGI multicast addr : %pI4 , port %u\n\n",
				    &temp_tmgi->tmgi_multicast_addr,
				    ntohs(temp_tmgi->tmgi_port));
			embms_debug("No of clients : %d\n",
				    temp_tmgi->no_of_clients);
			j = 1;

			list_for_each_safe(clnt_ptr, prev_clnt_ptr,
					   &temp_tmgi->client_list_head) {
				temp_client = list_entry(clnt_ptr,
							 struct clnt_info,
							 client_list_ptr);
				embms_debug("Client entry %d :\n", j);
				embms_debug("client addr : %pI4 , port %u\n\n",
					    &temp_client->addr,
					    ntohs(temp_client->port));
				j++;
			}
			i++;
			embms_debug("===========================================\n\n");
		}
	} else {
		embms_debug("No TMGI entries to Display\n");
	}
	embms_debug("==================================================================\n\n");
}

/**
 * delete_tmgi_entry_from_table() - deletes tmgi from global tmgi-client table
 * @buffer:	Buffer containing TMGI info for deletion.
 *
 * This function completely removes the TMGI from
 * global TMGI-client table, along with the client list
 * so that no packets for this TMGI are processed
 *
 * Return: Success on deleting TMGI entry, error otherwise.
 */

int delete_tmgi_entry_from_table(char *buffer)
{
	int i;
	struct tmgi_to_clnt_info_update *info_update;
	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *temp_tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;

	embms_debug("delete_tmgi_entry_from_table: Enter\n");

	info_update = (struct tmgi_to_clnt_info_update *)buffer;

	if (info_update == NULL) {
		embms_error("delete_tmgi_entry_from_table:");
		embms_error("NULL arguments passed\n");
		return -EBADPARAM;
	}

	/* This function is used to delete a specific TMGI entry
	 * when that particular TMGI goes down
	 * Search for the TMGI entry in our local table
	 */
	if (embms_conf.no_of_tmgi_sessions == 0) {
		embms_error("TMGI count 0. Nothing to delete\n");
		return SUCCESS;
	}

	temp_tmgi = check_for_tmgi_entry(info_update->multicast_addr,
					 info_update->multicast_port);

	if (temp_tmgi == NULL) {
		/* TMGI entry was not found in our local table*/
		embms_error("delete_client_entry_from_table :");
		embms_error("Desired TMGI entry not found\n");
		return -EBADPARAM;
	}

	spin_lock_bh(&embms_conf.lock);

	/* We need to free memory allocated to client entries
	 * for a particular TMGI entry
	 */

	list_for_each_safe(clnt_ptr, prev_clnt_ptr,
			   &temp_tmgi->client_list_head) {
		temp_client = list_entry(clnt_ptr,
					 struct clnt_info,
					 client_list_ptr);
		embms_debug("delete_tmgi_entry_from_table :");
		embms_debug("Client addr to delete :%pI4 , port %u\n",
			    &temp_client->addr, ntohs(temp_client->port));
		list_del(&temp_client->client_list_ptr);
		temp_tmgi->no_of_clients--;
		kfree(temp_client);
	}

	/* Free memory allocated to tmgi entry*/

	list_del(&temp_tmgi->tmgi_list_ptr);
	kfree(temp_tmgi);
	embms_conf.no_of_tmgi_sessions--;

	spin_unlock_bh(&embms_conf.lock);

	embms_debug("delete_tmgi_entry_from_table : TMGI Entry deleted.\n");

	return SUCCESS;
}

/**
 * delete_client_entry_from_all_tmgi() - deletes client from all tmgi lists
 * @buffer:	Buffer containing client info for deletion.
 *
 * This function completely removes a client from
 * all TMGIs in global TMGI-client table. Also delets TMGI
 * entries if no more clients are there
 *
 * Return: Success on deleting client entry, error otherwise.
 */
int delete_client_entry_from_all_tmgi(char *buffer)
{
	int i;
	struct tmgi_to_clnt_info_update *info_update;
	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;

	/* We use this function when we want to delete any
	 * client entry from all TMGI entries. This scenario
	 * happens when any client disconnects and hence
	 * we need to clean all realted client entries
	 * in our mapping table
	 */

	embms_debug("del_clnt_from_all_tmgi: Enter\n");

	info_update = (struct tmgi_to_clnt_info_update *)buffer;

	if (info_update == NULL) {
		embms_error("del_clnt_from_all_tmgi:");
		embms_error("NULL arguments passed\n");
		return -EBADPARAM;
	}

	/* We start checking from first TMGI entry and if client
	 * entry is found in client entries of any TMGI, we clean
	 * up that client entry from that TMGI entry
	 */
	if (embms_conf.no_of_tmgi_sessions == 0)
		return SUCCESS;

	list_for_each_safe(tmgi_entry_ptr, prev_tmgi_entry_ptr,
			   &tmgi_to_clnt_map_tbl.tmgi_list_ptr) {
		tmgi = list_entry(tmgi_entry_ptr,
				  struct tmgi_to_clnt_info,
				  tmgi_list_ptr);

		temp_client = chk_clnt_entry(tmgi, info_update);
		if (temp_client == NULL)
			continue;

		spin_lock_bh(&embms_conf.lock);

		list_del(&temp_client->client_list_ptr);
		tmgi->no_of_clients--;
		kfree(temp_client);

		spin_unlock_bh(&embms_conf.lock);

		temp_client = NULL;

		if (tmgi->no_of_clients == 0) {
			/* Deleted clnt was the only clnt for
			 * that TMGI we need to delete TMGI
			 * entry from table
			 */
			embms_debug("del_clnt_from_all_tmgi:");
			embms_debug("Deleted client was ");
			embms_debug("last client for tmgi\n");
			embms_debug("del_clnt_from_all_tmgi:");
			embms_debug("Delting tmgi as it has ");
			embms_debug("zero clients.TMGI IP ");
			embms_debug(":%pI4 , port %u\n",
				    &tmgi->tmgi_multicast_addr,
				    ntohs(tmgi->tmgi_port));

			spin_lock_bh(&embms_conf.lock);

			list_del(&tmgi->tmgi_list_ptr);
			embms_conf.no_of_tmgi_sessions--;
			kfree(tmgi);

			spin_unlock_bh(&embms_conf.lock);

			embms_debug("del_clnt_from_all_tmgi:");
			embms_debug("TMGI entry deleted\n");
		}
	}

	embms_debug("del_clnt_from_all_tmgi Successful\n");
	return SUCCESS;
}

/**
 * add_client_entry_to_table() - add client entry to specified TMGI
 * @buffer:	Buffer containing client info for addition.
 *
 * This function adds a client to the specified TMGI in
 * the global TMGI-client table. If TMGI entry is not
 * present, it adds a new TMGI entry and adds client
 * entry to it.
 *
 * Return: Success on adding client entry, error otherwise.
 */
int add_client_entry_to_table(char *buffer)
{
	int i, ret;
	struct tmgi_to_clnt_info_update *info_update;
	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
	struct clnt_info *new_client = NULL;
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *new_tmgi = NULL;
	struct tmgi_to_clnt_info *tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;
	struct neighbour *neigh_entry;
	struct in_device *iface_dev;
	struct in_ifaddr *iface_info;

	embms_debug("add_client_entry_to_table: Enter\n");

	info_update = (struct tmgi_to_clnt_info_update *)buffer;

	if (info_update == NULL) {
		embms_error("add_client_entry_to_table:");
		embms_error("NULL arguments passed\n");
		return -EBADPARAM;
	}

	new_client = kzalloc(sizeof(*new_client), GFP_ATOMIC);
	if (new_client == NULL) {
		embms_error("add_client_entry_to_table:");
		embms_error("Cannot allocate memory\n");
		return -ENOMEM;
	}

	new_client->addr = info_update->client_addr;
	new_client->port = info_update->client_port;

	neigh_entry = __ipv4_neigh_lookup(dev_global,
					  (__force u32)(new_client->addr));
	if (NULL == neigh_entry) {
		embms_error("add_client_entry_to_table :");
		embms_error("Can't find neighbour entry\n");
		kfree(new_client);
		return -EBADPARAM;
	}

	ether_addr_copy(new_client->dmac, neigh_entry->ha);

	embms_debug("DMAC of client : %pM\n", new_client->dmac);

	embms_debug("add_client_entry_to_table:");
	embms_debug("New client addr :%pI4 , port %u\n",
		    &info_update->client_addr,
		    ntohs(info_update->client_port));

	if (embms_conf.no_of_tmgi_sessions == 0) {
		/* TMGI Client mapping table is empty.
		 * First client entry is being added
		 */

		embms_debug("tmgi_to_clnt_map_tbl is empty\n");

		ret = add_new_tmgi_entry(info_update, new_client);
		if (ret != SUCCESS) {
			kfree(new_client);
			new_client = NULL;
		}

		goto exit_add;
	}

	/* In this case, table already has some entries
	 * and we need to search for the specific tmgi entry
	 * for which client entry is to be added
	 */

	tmgi = check_for_tmgi_entry(info_update->multicast_addr,
				    info_update->multicast_port);
	if (tmgi != NULL) {
		if (chk_clnt_entry(tmgi, info_update) != NULL) {
			kfree(new_client);
			return -ENOEFFECT;
		}

		/* Adding client to the client list
		 * for the specified TMGI
		 */

		spin_lock_bh(&embms_conf.lock);

		list_add(&new_client->client_list_ptr,
			 &tmgi->client_list_head);
			tmgi->no_of_clients++;

		spin_unlock_bh(&embms_conf.lock);

		ret = SUCCESS;
	} else {
		/* TMGI specified in the message was not found in
		 * mapping table.Hence, we need to add a new entry
		 * for this TMGI and add the specified client to the client
		 * list
		 */

		embms_debug("TMGI entry not present. Adding tmgi entry\n");

		ret = add_new_tmgi_entry(info_update, new_client);
		if (ret != SUCCESS) {
			kfree(new_client);
			new_client = NULL;
		}
	}

exit_add:
	return ret;
}

/**
 * delete_client_entry_from_table() - delete client entry from specified TMGI
 * @buffer:	Buffer containing client info for deletion.
 *
 * This function deletes a client from the specified TMGI in
 * the global TMGI-client table. If this was the last client
 * entry, it also deletes the TMGI entry.
 *
 * Return: Success on deleting client entry, error otherwise.
 */
int delete_client_entry_from_table(char *buffer)
{
	int i;
	struct tmgi_to_clnt_info_update *info_update;
	char message_buffer[sizeof(struct tmgi_to_clnt_info_update)];
	struct clnt_info *temp_client = NULL;
	struct tmgi_to_clnt_info *temp_tmgi = NULL;
	struct list_head *tmgi_entry_ptr, *prev_tmgi_entry_ptr;
	struct list_head *clnt_ptr, *prev_clnt_ptr;

	embms_debug("delete_client_entry_from_table: Enter\n");

	info_update = (struct tmgi_to_clnt_info_update *)buffer;

	if (info_update == NULL) {
		embms_error("delete_client_entry_from_table:");
		embms_error("NULL arguments passed\n");
		return -EBADPARAM;
	}

	/* Search for the TMGI entry*/
	if (embms_conf.no_of_tmgi_sessions == 0)
		return SUCCESS;

	temp_tmgi = check_for_tmgi_entry(info_update->multicast_addr,
					 info_update->multicast_port);

	if (temp_tmgi == NULL) {
		embms_error("delete_client_entry_from_table:TMGI not found\n");
		return -EBADPARAM;
	}
	/* Delete client entry for a specific tmgi*/

	embms_debug("delete_client_entry_from_table:clnt addr :%pI4,port %u\n",
		    &info_update->client_addr,
		    ntohs(info_update->client_port));

	temp_client = chk_clnt_entry(temp_tmgi, info_update);

	if (temp_client == NULL) {
		/* Specified client entry was not found in client list
		 * of specified TMGI
		 */
		embms_error("delete_client_entry_from_table:Clnt not found\n");
		return -EBADPARAM;
	}

	spin_lock_bh(&embms_conf.lock);

	list_del(&temp_client->client_list_ptr);
	temp_tmgi->no_of_clients--;

	spin_unlock_bh(&embms_conf.lock);

	kfree(temp_client);
	temp_client = NULL;

	embms_debug("delete_client_entry_from_table:Client entry deleted\n");

	if (temp_tmgi->no_of_clients == 0) {
		/* If deleted client was the only client for that TMGI
		 * we need to delete TMGI entry from table
		 */
		embms_debug("delete_client_entry_from_table:");
		embms_debug("Deleted client was the last client for tmgi\n");
		embms_debug("delete_client_entry_from_table:");
		embms_debug("Deleting tmgi since it has zero clients\n");

		spin_lock_bh(&embms_conf.lock);

		list_del(&temp_tmgi->tmgi_list_ptr);
		embms_conf.no_of_tmgi_sessions--;
		kfree(temp_tmgi);

		spin_unlock_bh(&embms_conf.lock);

		embms_debug("delete_client_entry_from_table: TMGI deleted\n");
	}

	if (embms_conf.no_of_tmgi_sessions == 0)
		embms_conf.embms_tunneling_status = TUNNELING_OFF;

	return SUCCESS;
}

/**
 * embms_device_ioctl() - handle IOCTL calls to device
 * @file:	File descriptor of file opened from userspace process
 * @ioctl_num:	IOCTL to use
 * @ioctl_param:	IOCTL parameters/arguments
 *
 * This function is called whenever a process tries to do
 * an ioctl on our device file. As per the IOCTL number,
 * it calls various functions to manipulate global
 * TMGI-client table
 *
 * Return: Success if functoin call returns SUCCESS, error otherwise.
 */

int embms_device_ioctl(struct file *file, unsigned int ioctl_num,
		       unsigned long ioctl_param)
{
	int i, ret, error;
	char *temp;
	char buffer[BUF_LEN];
	struct in_device *iface_dev;
	struct in_ifaddr *iface_info;
	struct tmgi_to_clnt_info_update *info_update;
	char __user *argp = (char __user *)ioctl_param;

	memset(buffer, 0, BUF_LEN);

	/* Switch according to the ioctl called*/
	switch (ioctl_num) {
	case ADD_EMBMS_TUNNEL:
		if (copy_from_user(buffer, argp,
				   sizeof(struct tmgi_to_clnt_info_update)))
			return -EFAULT;

		ret = add_client_entry_to_table(buffer);
		print_tmgi_to_client_table();
		break;

	case DEL_EMBMS_TUNNEL:
		if (copy_from_user(buffer, argp,
				   sizeof(struct tmgi_to_clnt_info_update)))
			return -EFAULT;

		ret = delete_client_entry_from_table(buffer);
		print_tmgi_to_client_table();
		break;

	case TMGI_DEACTIVATE:
		if (copy_from_user(buffer, argp,
				   sizeof(struct tmgi_to_clnt_info_update)))
			return -EFAULT;

		ret = delete_tmgi_entry_from_table(buffer);
		print_tmgi_to_client_table();
		break;

	case CLIENT_DEACTIVATE:
		if (copy_from_user(buffer, argp,
				   sizeof(struct tmgi_to_clnt_info_update)))
			return -EFAULT;

		ret = delete_client_entry_from_all_tmgi(buffer);
		print_tmgi_to_client_table();
		break;

	case GET_EMBMS_TUNNELING_STATUS:
		/* This ioctl is both input (ioctl_param) and
		 * output (the return value of this function)
		 */
		embms_debug("Sending tunneling status : %d\n",
			    embms_conf.embms_tunneling_status);
		ret = embms_conf.embms_tunneling_status;
		break;

	case START_EMBMS_TUNNEL:

		if (copy_from_user(buffer, argp,
				   sizeof(struct tmgi_to_clnt_info_update)))
			return -EFAULT;

		info_update = (struct tmgi_to_clnt_info_update *)buffer;
		embms_conf.embms_data_port = info_update->data_port;
		udph_global->source = embms_conf.embms_data_port;

		memset(embms_conf.embms_iface, 0, EMBMS_MAX_IFACE_NAME);
		memcpy(embms_conf.embms_iface, info_update->iface_name,
		       EMBMS_MAX_IFACE_NAME);

		embms_conf.embms_tunneling_status = TUNNELING_ON;
		embms_debug("Starting Tunneling. Embms_data_port  = %d\n",
			    ntohs(embms_conf.embms_data_port));
		embms_debug("Embms Data Iface = %s\n", embms_conf.embms_iface);
		ret = SUCCESS;

		/*Initialise dev_global to bridge device*/
		dev_global = __dev_get_by_name(&init_net, BRIDGE_IFACE);
		if (!dev_global) {
			embms_error("Error in getting device info\n");
			ret = FAILURE;
		} else {
			iface_dev = (struct in_device *)dev_global->ip_ptr;
			iface_info = iface_dev->ifa_list;
			while (NULL != iface_info) {
				if (memcmp(iface_info->ifa_label,
					   BRIDGE_IFACE,
					   strlen(BRIDGE_IFACE)) == 0)
					break;

				iface_info = iface_info->ifa_next;
			}
			if (iface_info != NULL) {
				embms_debug("IP address of %s iface is %pI4\n",
					    BRIDGE_IFACE,
					    &iface_info->ifa_address);
				/*Populate source addr for header*/
				iph_global->saddr = iface_info->ifa_address;
				ret = SUCCESS;
			} else {
				embms_debug("Could not find iface address\n");
				ret = FAILURE;
			}
		}

		break;

	case STOP_EMBMS_TUNNEL:

		embms_conf.embms_tunneling_status = TUNNELING_OFF;
		embms_debug("Stopped Tunneling..\n");
		ret = SUCCESS;
		break;
	}

	return ret;
}

/* Module Declarations
 * This structure will hold the functions to be called
 * when a process does something to the device we
 * created. Since a pointer to this structure is kept in
 * the devices table, it can't be local to
 * init_module. NULL is for unimplemented functions.
 */
static const struct file_operations embms_device_fops = {
	.owner = THIS_MODULE,
	.open = embms_device_open,
	.release = embms_device_release,
	.read = NULL,
	.write = NULL,
	.unlocked_ioctl = embms_device_ioctl,
};

/*Initialize the module - Register the misc device*/

static int __init start_embms(void)
{
	int ret = 0;

	iph_global = (struct iphdr *)hdr_buff;
	udph_global = (struct udphdr *)(hdr_buff + sizeof(struct iphdr));

	embms_device.name = kzalloc(sizeof(EMBMS_DEVICE_NAME), GFP_KERNEL);
	if (embms_device.name == NULL)
		return 1;
	strlcpy(embms_device.name, EMBMS_DEVICE_NAME,
		sizeof(EMBMS_DEVICE_NAME));
	embms_device.fops = &embms_device_fops;
	embms_device.minor = MISC_DYNAMIC_MINOR;
	embms_conf.embms_tunneling_status = TUNNELING_OFF;
	embms_conf.no_of_tmgi_sessions = 0;
	embms_conf.embms_data_port = 0;
	atomic_set(&embms_conf.device_under_use, 0);
	atomic_set(&embms_conf.ip_ident, 0);
	spin_lock_init(&embms_conf.lock);

	embms_debug("Registering embms device\n");

	ret = misc_register(&embms_device);
	if (ret) {
		embms_error("embms device failed to register");
		goto fail_init;
	}

	INIT_LIST_HEAD(&tmgi_to_clnt_map_tbl.tmgi_list_ptr);

	memset(hdr_buff, 0, sizeof(struct udphdr)+sizeof(struct iphdr));
	udph_global->check = UDP_CHECKSUM;
	iph_global->version = IP_VERSION;
	iph_global->ihl = IP_IHL;
	iph_global->tos = IP_TOS;
	iph_global->frag_off = IP_FRAG_OFFSET;
	iph_global->ttl = IP_TTL;
	iph_global->protocol = IPPROTO_UDP;

	dev_global = NULL;

	if (embms_tm_multicast_recv == NULL)
		RCU_INIT_POINTER(embms_tm_multicast_recv,
				 handle_multicast_stream);

	return ret;

fail_init:
	misc_deregister(&embms_device);
	return ret;
}

/*Cleanup - unregister the appropriate file from proc*/

static void __exit stop_embms(void)
{
	misc_deregister(&embms_device);

	if (rcu_dereference(embms_tm_multicast_recv))
		RCU_INIT_POINTER(embms_tm_multicast_recv, NULL);

	embms_debug("unregister_chrdev done\n");
}

module_init(start_embms);
module_exit(stop_embms);
MODULE_LICENSE("GPL v2");
