msm: ipc: Do not broadcast remove client messages, if possible

Remove client message is broadcast to all subsystems within the system.
If a client has never communicated over any link, then do not send the
remove client message over any link. If a client has been communicating
only over a specific link, then send the remove client message only over
that link. If a client has been communicating over multiple links, then
broadcast the remove client message.

CRs-Fixed: 454809
Change-Id: I007c7f1ace66191ce9cbf0bad033381c4f3b750f
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
diff --git a/arch/arm/mach-msm/include/mach/msm_ipc_router.h b/arch/arm/mach-msm/include/mach/msm_ipc_router.h
index c68c783..894379e 100644
--- a/arch/arm/mach-msm/include/mach/msm_ipc_router.h
+++ b/arch/arm/mach-msm/include/mach/msm_ipc_router.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2013, 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
@@ -31,6 +31,11 @@
 	MSM_IPC_ROUTER_WRITE_DONE,
 };
 
+struct comm_mode_info {
+	int mode;
+	void *xprt_info;
+};
+
 struct msm_ipc_port {
 	struct list_head list;
 
@@ -39,6 +44,7 @@
 	uint32_t type;
 	unsigned flags;
 	spinlock_t port_lock;
+	struct comm_mode_info mode_info;
 
 	struct list_head incomplete;
 	struct mutex incomplete_lock;
diff --git a/arch/arm/mach-msm/ipc_router.c b/arch/arm/mach-msm/ipc_router.c
index 38f8d19..914cfe1 100644
--- a/arch/arm/mach-msm/ipc_router.c
+++ b/arch/arm/mach-msm/ipc_router.c
@@ -1243,6 +1243,67 @@
 	return 0;
 }
 
+static int msm_ipc_router_send_remove_client(struct comm_mode_info *mode_info,
+					uint32_t node_id, uint32_t port_id)
+{
+	union rr_control_msg msg;
+	struct msm_ipc_router_xprt_info *tmp_xprt_info;
+	int mode;
+	void *xprt_info;
+	int rc = 0;
+
+	if (!mode_info) {
+		pr_err("%s: NULL mode_info\n", __func__);
+		return -EINVAL;
+	}
+	mode = mode_info->mode;
+	xprt_info = mode_info->xprt_info;
+
+	msg.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT;
+	msg.cli.node_id = node_id;
+	msg.cli.port_id = port_id;
+
+	if ((mode == SINGLE_LINK_MODE) && xprt_info) {
+		mutex_lock(&xprt_info_list_lock);
+		list_for_each_entry(tmp_xprt_info, &xprt_info_list, list) {
+			if (tmp_xprt_info != xprt_info)
+				continue;
+			msm_ipc_router_send_control_msg(tmp_xprt_info, &msg);
+			break;
+		}
+		mutex_unlock(&xprt_info_list_lock);
+	} else if ((mode == SINGLE_LINK_MODE) && !xprt_info) {
+		broadcast_ctl_msg_locally(&msg);
+	} else if (mode == MULTI_LINK_MODE) {
+		broadcast_ctl_msg(&msg);
+		broadcast_ctl_msg_locally(&msg);
+	} else if (mode != NULL_MODE) {
+		pr_err("%s: Invalid mode(%d) + xprt_inf(%p) for %08x:%08x\n",
+			__func__, mode, xprt_info, node_id, port_id);
+		rc = -EINVAL;
+	}
+	return rc;
+}
+
+static void update_comm_mode_info(struct comm_mode_info *mode_info,
+				  struct msm_ipc_router_xprt_info *xprt_info)
+{
+	if (!mode_info) {
+		pr_err("%s: NULL mode_info\n", __func__);
+		return;
+	}
+
+	if (mode_info->mode == NULL_MODE) {
+		mode_info->xprt_info = xprt_info;
+		mode_info->mode = SINGLE_LINK_MODE;
+	} else if (mode_info->mode == SINGLE_LINK_MODE &&
+		   mode_info->xprt_info != xprt_info) {
+		mode_info->mode = MULTI_LINK_MODE;
+	}
+
+	return;
+}
+
 static void reset_remote_port_info(uint32_t node_id, uint32_t port_id)
 {
 	struct msm_ipc_router_remote_port *rport_ptr;
@@ -1913,6 +1974,7 @@
 	broadcast_ctl_msg(&ctl);
 	spin_lock_irqsave(&port_ptr->port_lock, flags);
 	port_ptr->type = SERVER_PORT;
+	port_ptr->mode_info.mode = MULTI_LINK_MODE;
 	port_ptr->port_name.service = server->name.service;
 	port_ptr->port_name.instance = server->name.instance;
 	spin_unlock_irqrestore(&port_ptr->port_lock, flags);
@@ -2024,6 +2086,7 @@
 	ret_len = pkt->length;
 	wake_up(&port_ptr->port_rx_wait_q);
 	mutex_unlock(&port_ptr->port_rx_q_lock);
+	update_comm_mode_info(&src->mode_info, NULL);
 	mutex_unlock(&local_ports_lock);
 
 	return ret_len;
@@ -2116,6 +2179,7 @@
 		pr_err("%s: Write on XPRT failed\n", __func__);
 		return ret;
 	}
+	update_comm_mode_info(&src->mode_info, xprt_info);
 
 	RAW_HDR("[w rr_h] "
 		"ver=%i,type=%s,src_nid=%08x,src_port_id=%08x,"
@@ -2405,13 +2469,11 @@
 		 * Server port could have been a client port earlier.
 		 * Send REMOVE_CLIENT message in either case.
 		 */
-		msg.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT;
-		msg.cli.node_id = port_ptr->this_port.node_id;
-		msg.cli.port_id = port_ptr->this_port.port_id;
 		RR("x REMOVE_CLIENT id=%d:%08x\n",
-		   msg.cli.node_id, msg.cli.port_id);
-		broadcast_ctl_msg(&msg);
-		broadcast_ctl_msg_locally(&msg);
+		   port_ptr->this_port.node_id, port_ptr->this_port.port_id);
+		msm_ipc_router_send_remove_client(&port_ptr->mode_info,
+			port_ptr->this_port.node_id,
+			port_ptr->this_port.port_id);
 	} else if (port_ptr->type == CONTROL_PORT) {
 		mutex_lock(&control_ports_lock);
 		list_del(&port_ptr->list);
diff --git a/arch/arm/mach-msm/ipc_router.h b/arch/arm/mach-msm/ipc_router.h
index 064689c..55aeade 100644
--- a/arch/arm/mach-msm/ipc_router.h
+++ b/arch/arm/mach-msm/ipc_router.h
@@ -62,6 +62,12 @@
 	IRSC_PORT,
 };
 
+enum {
+	NULL_MODE,
+	SINGLE_LINK_MODE,
+	MULTI_LINK_MODE,
+};
+
 union rr_control_msg {
 	uint32_t cmd;
 	struct {