aboutsummaryrefslogtreecommitdiff
path: root/net/embms_kernel/embms_kernel.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/embms_kernel/embms_kernel.c')
-rw-r--r--net/embms_kernel/embms_kernel.c1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/net/embms_kernel/embms_kernel.c b/net/embms_kernel/embms_kernel.c
new file mode 100644
index 00000000000..6736fa329ee
--- /dev/null
+++ b/net/embms_kernel/embms_kernel.c
@@ -0,0 +1,1012 @@
+/*************************************************************************
+ * EMBMS.C
+ *************************************************************************
+ */
+
+/*************************************************************************
+ * -----------------------------------------------------------------------
+ * Copyright (c) 2013-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.
+ * -----------------------------------------------------------------------
+
+ * 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(memcmp(skb->dev->name, embms_conf.embms_iface,
+ strlen(embms_conf.embms_iface)) != 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);
+
+ 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");