diff options
Diffstat (limited to 'net/ipc_router')
-rw-r--r-- | net/ipc_router/Kconfig | 25 | ||||
-rw-r--r-- | net/ipc_router/Makefile | 7 | ||||
-rw-r--r-- | net/ipc_router/ipc_router_core.c | 4434 | ||||
-rw-r--r-- | net/ipc_router/ipc_router_private.h | 150 | ||||
-rw-r--r-- | net/ipc_router/ipc_router_security.c | 328 | ||||
-rw-r--r-- | net/ipc_router/ipc_router_security.h | 120 | ||||
-rw-r--r-- | net/ipc_router/ipc_router_socket.c | 693 |
7 files changed, 5757 insertions, 0 deletions
diff --git a/net/ipc_router/Kconfig b/net/ipc_router/Kconfig new file mode 100644 index 000000000000..30cd45a70208 --- /dev/null +++ b/net/ipc_router/Kconfig @@ -0,0 +1,25 @@ +# +# IPC_ROUTER Configuration +# + +menuconfig IPC_ROUTER + bool "IPC Router support" + help + IPC Router provides a connectionless message routing service + between multiple modules within a System-on-Chip(SoC). The + communicating entities can run either in the same processor or + in a different processor within the SoC. The IPC Router has been + designed to route messages of any types and support a broader + network of processors. + + If in doubt, say N. + +config IPC_ROUTER_SECURITY + depends on IPC_ROUTER + bool "IPC Router Security support" + help + This feature of IPC Router will enforce security rules + configured by a security script from the user-space. IPC Router + once configured with the security rules will ensure that the + sender of the message to a service belongs to the relevant + Linux group as configured by the security script. diff --git a/net/ipc_router/Makefile b/net/ipc_router/Makefile new file mode 100644 index 000000000000..501688e42e3d --- /dev/null +++ b/net/ipc_router/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Linux IPC_ROUTER +# + +obj-$(CONFIG_IPC_ROUTER) := ipc_router_core.o +obj-$(CONFIG_IPC_ROUTER) += ipc_router_socket.o +obj-$(CONFIG_IPC_ROUTER_SECURITY) += ipc_router_security.o diff --git a/net/ipc_router/ipc_router_core.c b/net/ipc_router/ipc_router_core.c new file mode 100644 index 000000000000..bfb76a84be73 --- /dev/null +++ b/net/ipc_router/ipc_router_core.c @@ -0,0 +1,4434 @@ +/* Copyright (c) 2011-2018, 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/slab.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/poll.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/rwsem.h> +#include <linux/ipc_logging.h> +#include <linux/uaccess.h> +#include <linux/ipc_router.h> +#include <linux/ipc_router_xprt.h> +#include <linux/kref.h> +#include <soc/qcom/subsystem_notif.h> +#include <soc/qcom/subsystem_restart.h> + +#include <asm/byteorder.h> + +#include <soc/qcom/smem_log.h> + +#include "ipc_router_private.h" +#include "ipc_router_security.h" + +enum { + SMEM_LOG = 1U << 0, + RTR_DBG = 1U << 1, +}; + +static int msm_ipc_router_debug_mask; +module_param_named(debug_mask, msm_ipc_router_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); +#define MODULE_NAME "ipc_router" + +#define IPC_RTR_INFO_PAGES 6 + +#define IPC_RTR_INFO(log_ctx, x...) do { \ +if (log_ctx) \ + ipc_log_string(log_ctx, x); \ +if (msm_ipc_router_debug_mask & RTR_DBG) \ + pr_info("[IPCRTR] "x); \ +} while (0) + +#define IPC_ROUTER_LOG_EVENT_TX 0x01 +#define IPC_ROUTER_LOG_EVENT_RX 0x02 +#define IPC_ROUTER_LOG_EVENT_TX_ERR 0x03 +#define IPC_ROUTER_LOG_EVENT_RX_ERR 0x04 +#define IPC_ROUTER_DUMMY_DEST_NODE 0xFFFFFFFF + +#define ipc_port_sk(port) ((struct sock *)(port)) + +static LIST_HEAD(control_ports); +static DECLARE_RWSEM(control_ports_lock_lha5); + +#define LP_HASH_SIZE 32 +static struct list_head local_ports[LP_HASH_SIZE]; +static DECLARE_RWSEM(local_ports_lock_lhc2); + +/* Server info is organized as a hash table. The server's service ID is + * used to index into the hash table. The instance ID of most of the servers + * are 1 or 2. The service IDs are well distributed compared to the instance + * IDs and hence choosing service ID to index into this hash table optimizes + * the hash table operations like add, lookup, destroy. + */ +#define SRV_HASH_SIZE 32 +static struct list_head server_list[SRV_HASH_SIZE]; +static DECLARE_RWSEM(server_list_lock_lha2); + +struct msm_ipc_server { + struct list_head list; + struct kref ref; + struct msm_ipc_port_name name; + char pdev_name[32]; + int next_pdev_id; + int synced_sec_rule; + struct list_head server_port_list; +}; + +struct msm_ipc_server_port { + struct list_head list; + struct platform_device *pdev; + struct msm_ipc_port_addr server_addr; + struct msm_ipc_router_xprt_info *xprt_info; +}; + +struct msm_ipc_resume_tx_port { + struct list_head list; + uint32_t port_id; + uint32_t node_id; +}; + +struct ipc_router_conn_info { + struct list_head list; + uint32_t port_id; +}; + +enum { + RESET = 0, + VALID = 1, +}; + +#define RP_HASH_SIZE 32 +struct msm_ipc_router_remote_port { + struct list_head list; + struct kref ref; + struct mutex rport_lock_lhb2; + uint32_t node_id; + uint32_t port_id; + int status; + uint32_t tx_quota_cnt; + struct list_head resume_tx_port_list; + struct list_head conn_info_list; + void *sec_rule; + struct msm_ipc_server *server; +}; + +struct msm_ipc_router_xprt_info { + struct list_head list; + struct msm_ipc_router_xprt *xprt; + uint32_t remote_node_id; + uint32_t initialized; + struct list_head pkt_list; + struct wakeup_source ws; + struct mutex rx_lock_lhb2; + struct mutex tx_lock_lhb2; + uint32_t need_len; + uint32_t abort_data_read; + struct work_struct read_data; + struct workqueue_struct *workqueue; + void *log_ctx; + struct kref ref; + struct completion ref_complete; + bool dynamic_ws; +}; + +#define RT_HASH_SIZE 4 +struct msm_ipc_routing_table_entry { + struct list_head list; + struct kref ref; + uint32_t node_id; + uint32_t neighbor_node_id; + struct list_head remote_port_list[RP_HASH_SIZE]; + struct msm_ipc_router_xprt_info *xprt_info; + struct rw_semaphore lock_lha4; + unsigned long num_tx_bytes; + unsigned long num_rx_bytes; +}; + +#define LOG_CTX_NAME_LEN 32 +struct ipc_rtr_log_ctx { + struct list_head list; + char log_ctx_name[LOG_CTX_NAME_LEN]; + void *log_ctx; +}; + +static struct list_head routing_table[RT_HASH_SIZE]; +static DECLARE_RWSEM(routing_table_lock_lha3); +static int routing_table_inited; + +static void do_read_data(struct work_struct *work); + +static LIST_HEAD(xprt_info_list); +static DECLARE_RWSEM(xprt_info_list_lock_lha5); + +static DEFINE_MUTEX(log_ctx_list_lock_lha0); +static LIST_HEAD(log_ctx_list); +static DEFINE_MUTEX(ipc_router_init_lock); +static bool is_ipc_router_inited; +static int ipc_router_core_init(void); +#define IPC_ROUTER_INIT_TIMEOUT (10 * HZ) + +static uint32_t next_port_id; +static DEFINE_MUTEX(next_port_id_lock_lhc1); +static struct workqueue_struct *msm_ipc_router_workqueue; + +static void *local_log_ctx; +static void *ipc_router_get_log_ctx(char *sub_name); +static int process_resume_tx_msg(union rr_control_msg *msg, + struct rr_packet *pkt); +static void ipc_router_reset_conn(struct msm_ipc_router_remote_port *rport_ptr); +static int ipc_router_get_xprt_info_ref( + struct msm_ipc_router_xprt_info *xprt_info); +static void ipc_router_put_xprt_info_ref( + struct msm_ipc_router_xprt_info *xprt_info); +static void ipc_router_release_xprt_info_ref(struct kref *ref); + +struct pil_vote_info { + void *pil_handle; + struct work_struct load_work; + struct work_struct unload_work; +}; + +#define PIL_SUBSYSTEM_NAME_LEN 32 +static char default_peripheral[PIL_SUBSYSTEM_NAME_LEN]; + +enum { + DOWN, + UP, +}; + +static bool is_wakeup_source_allowed; + +void msm_ipc_router_set_ws_allowed(bool flag) +{ + is_wakeup_source_allowed = flag; +} + +/** + * is_sensor_port() - Check if the remote port is sensor service or not + * @rport: Pointer to the remote port. + * + * Return: true if the remote port is sensor service else false. + */ +static int is_sensor_port(struct msm_ipc_router_remote_port *rport) +{ + u32 svcid = 0; + + if (rport && rport->server) { + svcid = rport->server->name.service; + if (svcid == 400 || (svcid >= 256 && svcid <= 320)) + return true; + } + + return false; +} + +static void init_routing_table(void) +{ + int i; + for (i = 0; i < RT_HASH_SIZE; i++) + INIT_LIST_HEAD(&routing_table[i]); +} + +/** + * ipc_router_calc_checksum() - compute the checksum for extended HELLO message + * @msg: Reference to the IPC Router HELLO message. + * + * Return: Computed checksum value, 0 if msg is NULL. + */ +static uint32_t ipc_router_calc_checksum(union rr_control_msg *msg) +{ + uint32_t checksum = 0; + int i, len; + uint16_t upper_nb; + uint16_t lower_nb; + void *hello; + + if (!msg) + return checksum; + hello = msg; + len = sizeof(*msg); + + for (i = 0; i < len/IPCR_WORD_SIZE; i++) { + lower_nb = (*((uint32_t *)hello)) & IPC_ROUTER_CHECKSUM_MASK; + upper_nb = ((*((uint32_t *)hello)) >> 16) & + IPC_ROUTER_CHECKSUM_MASK; + checksum = checksum + upper_nb + lower_nb; + hello = ((uint32_t *)hello) + 1; + } + while (checksum > 0xFFFF) + checksum = (checksum & IPC_ROUTER_CHECKSUM_MASK) + + ((checksum >> 16) & IPC_ROUTER_CHECKSUM_MASK); + + checksum = ~checksum & IPC_ROUTER_CHECKSUM_MASK; + return checksum; +} + +/** + * skb_copy_to_log_buf() - copies the required number bytes from the skb_queue + * @skb_head: skb_queue head that contains the data. + * @pl_len: length of payload need to be copied. + * @hdr_offset: length of the header present in first skb + * @log_buf: The output buffer which will contain the formatted log string + * + * This function copies the first specified number of bytes from the skb_queue + * to a new buffer and formats them to a string for logging. + */ +static void skb_copy_to_log_buf(struct sk_buff_head *skb_head, + unsigned int pl_len, unsigned int hdr_offset, + unsigned char *log_buf) +{ + struct sk_buff *temp_skb; + unsigned int copied_len = 0, copy_len = 0; + int remaining; + + if (!skb_head) { + IPC_RTR_ERR("%s: NULL skb_head\n", __func__); + return; + } + temp_skb = skb_peek(skb_head); + if (unlikely(!temp_skb || !temp_skb->data)) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return; + } + + remaining = temp_skb->len - hdr_offset; + skb_queue_walk(skb_head, temp_skb) { + copy_len = remaining < pl_len ? remaining : pl_len; + memcpy(log_buf + copied_len, + temp_skb->data + hdr_offset, copy_len); + copied_len += copy_len; + hdr_offset = 0; + if (copied_len == pl_len) + break; + remaining = pl_len - remaining; + } + return; +} + +/** + * ipc_router_log_msg() - log all data messages exchanged + * @log_ctx: IPC Logging context specfic to each transport + * @xchng_type: Identifies the data to be a receive or send. + * @data: IPC Router data packet or control msg recieved or to be send. + * @hdr: Reference to the router header + * @port_ptr: Local IPC Router port. + * @rport_ptr: Remote IPC Router port + * + * This function builds the log message that would be passed on to the IPC + * logging framework. The data messages that would be passed corresponds to + * the information that is exchanged between the IPC Router and it's clients. + */ +static void ipc_router_log_msg(void *log_ctx, uint32_t xchng_type, + void *data, struct rr_header_v1 *hdr, + struct msm_ipc_port *port_ptr, + struct msm_ipc_router_remote_port *rport_ptr) +{ + struct sk_buff_head *skb_head = NULL; + union rr_control_msg *msg = NULL; + struct rr_packet *pkt = NULL; + uint64_t pl_buf = 0; + struct sk_buff *skb; + uint32_t buf_len = 8; + uint32_t svcId = 0; + uint32_t svcIns = 0; + unsigned int hdr_offset = 0; + uint32_t port_type = 0; + + if (!log_ctx || !hdr || !data) + return; + + if (hdr->type == IPC_ROUTER_CTRL_CMD_DATA) { + pkt = (struct rr_packet *)data; + skb_head = pkt->pkt_fragment_q; + skb = skb_peek(skb_head); + if (!skb || !skb->data) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return; + } + + if (skb_queue_len(skb_head) == 1 && skb->len < 8) + buf_len = skb->len; + if (xchng_type == IPC_ROUTER_LOG_EVENT_TX && hdr->dst_node_id + != IPC_ROUTER_NID_LOCAL) { + if (hdr->version == IPC_ROUTER_V1) + hdr_offset = sizeof(struct rr_header_v1); + else if (hdr->version == IPC_ROUTER_V2) + hdr_offset = sizeof(struct rr_header_v2); + } + skb_copy_to_log_buf(skb_head, buf_len, hdr_offset, + (unsigned char *)&pl_buf); + + if (port_ptr && rport_ptr && (port_ptr->type == CLIENT_PORT) + && (rport_ptr->server != NULL)) { + svcId = rport_ptr->server->name.service; + svcIns = rport_ptr->server->name.instance; + port_type = CLIENT_PORT; + port_ptr->last_served_svc_id = + rport_ptr->server->name.service; + } else if (port_ptr && (port_ptr->type == SERVER_PORT)) { + svcId = port_ptr->port_name.service; + svcIns = port_ptr->port_name.instance; + port_type = SERVER_PORT; + } + IPC_RTR_INFO(log_ctx, + "%s %s %s Len:0x%x T:0x%x CF:0x%x SVC:<0x%x:0x%x> SRC:<0x%x:0x%x> DST:<0x%x:0x%x> DATA: %08x %08x", + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? + current->comm : "")), + (port_type == CLIENT_PORT ? "CLI" : "SRV"), + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "RX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? "TX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX_ERR ? "TX_ERR" : + (xchng_type == IPC_ROUTER_LOG_EVENT_RX_ERR ? "RX_ERR" : + "UNKNOWN")))), + hdr->size, hdr->type, hdr->control_flag, + svcId, svcIns, hdr->src_node_id, hdr->src_port_id, + hdr->dst_node_id, hdr->dst_port_id, + (unsigned int)pl_buf, (unsigned int)(pl_buf>>32)); + + } else { + msg = (union rr_control_msg *)data; + if (msg->cmd == IPC_ROUTER_CTRL_CMD_NEW_SERVER || + msg->cmd == IPC_ROUTER_CTRL_CMD_REMOVE_SERVER) + IPC_RTR_INFO(log_ctx, + "CTL MSG: %s cmd:0x%x SVC:<0x%x:0x%x> ADDR:<0x%x:0x%x>", + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "RX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? "TX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX_ERR ? "TX_ERR" : + (xchng_type == IPC_ROUTER_LOG_EVENT_RX_ERR ? "RX_ERR" : + "UNKNOWN")))), + msg->cmd, msg->srv.service, msg->srv.instance, + msg->srv.node_id, msg->srv.port_id); + else if (msg->cmd == IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT || + msg->cmd == IPC_ROUTER_CTRL_CMD_RESUME_TX) + IPC_RTR_INFO(log_ctx, + "CTL MSG: %s cmd:0x%x ADDR: <0x%x:0x%x>", + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "RX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? "TX" : "ERR")), + msg->cmd, msg->cli.node_id, msg->cli.port_id); + else if (msg->cmd == IPC_ROUTER_CTRL_CMD_HELLO && hdr) + IPC_RTR_INFO(log_ctx, "CTL MSG %s cmd:0x%x ADDR:0x%x", + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "RX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? "TX" : "ERR")), + msg->cmd, hdr->src_node_id); + else + IPC_RTR_INFO(log_ctx, "%s UNKNOWN cmd:0x%x", + (xchng_type == IPC_ROUTER_LOG_EVENT_RX ? "RX" : + (xchng_type == IPC_ROUTER_LOG_EVENT_TX ? "TX" : "ERR")), + msg->cmd); + } +} + +/* Must be called with routing_table_lock_lha3 locked. */ +static struct msm_ipc_routing_table_entry *lookup_routing_table( + uint32_t node_id) +{ + uint32_t key = (node_id % RT_HASH_SIZE); + struct msm_ipc_routing_table_entry *rt_entry; + + list_for_each_entry(rt_entry, &routing_table[key], list) { + if (rt_entry->node_id == node_id) + return rt_entry; + } + return NULL; +} + +/** + * create_routing_table_entry() - Lookup and create a routing table entry + * @node_id: Node ID of the routing table entry to be created. + * @xprt_info: XPRT through which the node ID is reachable. + * + * @return: a reference to the routing table entry on success, NULL on failure. + */ +static struct msm_ipc_routing_table_entry *create_routing_table_entry( + uint32_t node_id, struct msm_ipc_router_xprt_info *xprt_info) +{ + int i; + struct msm_ipc_routing_table_entry *rt_entry; + uint32_t key; + + down_write(&routing_table_lock_lha3); + rt_entry = lookup_routing_table(node_id); + if (rt_entry) + goto out_create_rtentry1; + + rt_entry = kmalloc(sizeof(struct msm_ipc_routing_table_entry), + GFP_KERNEL); + if (!rt_entry) { + IPC_RTR_ERR("%s: rt_entry allocation failed for %d\n", + __func__, node_id); + goto out_create_rtentry2; + } + + for (i = 0; i < RP_HASH_SIZE; i++) + INIT_LIST_HEAD(&rt_entry->remote_port_list[i]); + init_rwsem(&rt_entry->lock_lha4); + kref_init(&rt_entry->ref); + rt_entry->node_id = node_id; + rt_entry->xprt_info = xprt_info; + if (xprt_info) + rt_entry->neighbor_node_id = xprt_info->remote_node_id; + + key = (node_id % RT_HASH_SIZE); + list_add_tail(&rt_entry->list, &routing_table[key]); +out_create_rtentry1: + kref_get(&rt_entry->ref); +out_create_rtentry2: + up_write(&routing_table_lock_lha3); + return rt_entry; +} + +/** + * ipc_router_get_rtentry_ref() - Get a reference to the routing table entry + * @node_id: Node ID of the routing table entry. + * + * @return: a reference to the routing table entry on success, NULL on failure. + * + * This function is used to obtain a reference to the rounting table entry + * corresponding to a node id. + */ +static struct msm_ipc_routing_table_entry *ipc_router_get_rtentry_ref( + uint32_t node_id) +{ + struct msm_ipc_routing_table_entry *rt_entry; + + down_read(&routing_table_lock_lha3); + rt_entry = lookup_routing_table(node_id); + if (rt_entry) + kref_get(&rt_entry->ref); + up_read(&routing_table_lock_lha3); + return rt_entry; +} + +/** + * ipc_router_release_rtentry() - Cleanup and release the routing table entry + * @ref: Reference to the entry. + * + * This function is called when all references to the routing table entry are + * released. + */ +void ipc_router_release_rtentry(struct kref *ref) +{ + struct msm_ipc_routing_table_entry *rt_entry = + container_of(ref, struct msm_ipc_routing_table_entry, ref); + + /* + * All references to a routing entry will be put only under SSR. + * As part of SSR, all the internals of the routing table entry + * are cleaned. So just free the routing table entry. + */ + kfree(rt_entry); +} + +struct rr_packet *rr_read(struct msm_ipc_router_xprt_info *xprt_info) +{ + struct rr_packet *temp_pkt; + + if (!xprt_info) + return NULL; + + mutex_lock(&xprt_info->rx_lock_lhb2); + if (xprt_info->abort_data_read) { + mutex_unlock(&xprt_info->rx_lock_lhb2); + IPC_RTR_ERR("%s detected SSR & exiting now\n", + xprt_info->xprt->name); + return NULL; + } + + if (list_empty(&xprt_info->pkt_list)) { + mutex_unlock(&xprt_info->rx_lock_lhb2); + return NULL; + } + + temp_pkt = list_first_entry(&xprt_info->pkt_list, + struct rr_packet, list); + list_del(&temp_pkt->list); + if (list_empty(&xprt_info->pkt_list)) + __pm_relax(&xprt_info->ws); + mutex_unlock(&xprt_info->rx_lock_lhb2); + return temp_pkt; +} + +struct rr_packet *clone_pkt(struct rr_packet *pkt) +{ + struct rr_packet *cloned_pkt; + struct sk_buff *temp_skb, *cloned_skb; + struct sk_buff_head *pkt_fragment_q; + + cloned_pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!cloned_pkt) { + IPC_RTR_ERR("%s: failure\n", __func__); + return NULL; + } + memcpy(&(cloned_pkt->hdr), &(pkt->hdr), sizeof(struct rr_header_v1)); + if (pkt->opt_hdr.len > 0) { + cloned_pkt->opt_hdr.data = kmalloc(pkt->opt_hdr.len, + GFP_KERNEL); + if (!cloned_pkt->opt_hdr.data) { + IPC_RTR_ERR("%s: Memory allocation Failed\n", __func__); + } else { + cloned_pkt->opt_hdr.len = pkt->opt_hdr.len; + memcpy(cloned_pkt->opt_hdr.data, pkt->opt_hdr.data, + pkt->opt_hdr.len); + } + } + + pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!pkt_fragment_q) { + IPC_RTR_ERR("%s: pkt_frag_q alloc failure\n", __func__); + kfree(cloned_pkt); + return NULL; + } + skb_queue_head_init(pkt_fragment_q); + kref_init(&cloned_pkt->ref); + + skb_queue_walk(pkt->pkt_fragment_q, temp_skb) { + cloned_skb = skb_clone(temp_skb, GFP_KERNEL); + if (!cloned_skb) + goto fail_clone; + skb_queue_tail(pkt_fragment_q, cloned_skb); + } + cloned_pkt->pkt_fragment_q = pkt_fragment_q; + cloned_pkt->length = pkt->length; + cloned_pkt->ws_need = pkt->ws_need; + return cloned_pkt; + +fail_clone: + while (!skb_queue_empty(pkt_fragment_q)) { + temp_skb = skb_dequeue(pkt_fragment_q); + kfree_skb(temp_skb); + } + kfree(pkt_fragment_q); + if (cloned_pkt->opt_hdr.len > 0) + kfree(cloned_pkt->opt_hdr.data); + kfree(cloned_pkt); + return NULL; +} + +/** + * create_pkt() - Create a Router packet + * @data: SKB queue to be contained inside the packet. + * + * @return: pointer to packet on success, NULL on failure. + */ +struct rr_packet *create_pkt(struct sk_buff_head *data) +{ + struct rr_packet *pkt; + struct sk_buff *temp_skb; + + pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!pkt) { + IPC_RTR_ERR("%s: failure\n", __func__); + return NULL; + } + + if (data) { + pkt->pkt_fragment_q = data; + skb_queue_walk(pkt->pkt_fragment_q, temp_skb) + pkt->length += temp_skb->len; + } else { + pkt->pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), + GFP_KERNEL); + if (!pkt->pkt_fragment_q) { + IPC_RTR_ERR("%s: Couldn't alloc pkt_fragment_q\n", + __func__); + kfree(pkt); + return NULL; + } + skb_queue_head_init(pkt->pkt_fragment_q); + } + kref_init(&pkt->ref); + return pkt; +} + +void release_pkt(struct rr_packet *pkt) +{ + struct sk_buff *temp_skb; + + if (!pkt) + return; + + if (!pkt->pkt_fragment_q) { + kfree(pkt); + return; + } + + while (!skb_queue_empty(pkt->pkt_fragment_q)) { + temp_skb = skb_dequeue(pkt->pkt_fragment_q); + kfree_skb(temp_skb); + } + kfree(pkt->pkt_fragment_q); + if (pkt->opt_hdr.len > 0) + kfree(pkt->opt_hdr.data); + kfree(pkt); + return; +} + +static struct sk_buff_head *msm_ipc_router_buf_to_skb(void *buf, + unsigned int buf_len) +{ + struct sk_buff_head *skb_head; + struct sk_buff *skb; + int first = 1, offset = 0; + int skb_size, data_size; + void *data; + int last = 1; + int align_size; + + skb_head = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!skb_head) { + IPC_RTR_ERR("%s: Couldnot allocate skb_head\n", __func__); + return NULL; + } + skb_queue_head_init(skb_head); + + data_size = buf_len; + align_size = ALIGN_SIZE(data_size); + while (offset != buf_len) { + skb_size = data_size; + if (first) + skb_size += IPC_ROUTER_HDR_SIZE; + if (last) + skb_size += align_size; + + skb = alloc_skb(skb_size, GFP_KERNEL); + if (!skb) { + if (skb_size <= (PAGE_SIZE/2)) { + IPC_RTR_ERR("%s: cannot allocate skb\n", + __func__); + goto buf_to_skb_error; + } + data_size = data_size / 2; + last = 0; + continue; + } + + if (first) { + skb_reserve(skb, IPC_ROUTER_HDR_SIZE); + first = 0; + } + + data = skb_put(skb, data_size); + memcpy(skb->data, buf + offset, data_size); + skb_queue_tail(skb_head, skb); + offset += data_size; + data_size = buf_len - offset; + last = 1; + } + return skb_head; + +buf_to_skb_error: + while (!skb_queue_empty(skb_head)) { + skb = skb_dequeue(skb_head); + kfree_skb(skb); + } + kfree(skb_head); + return NULL; +} + +static void *msm_ipc_router_skb_to_buf(struct sk_buff_head *skb_head, + unsigned int len) +{ + struct sk_buff *temp; + unsigned int offset = 0, buf_len = 0, copy_len; + void *buf; + + if (!skb_head) { + IPC_RTR_ERR("%s: NULL skb_head\n", __func__); + return NULL; + } + + temp = skb_peek(skb_head); + buf_len = len; + buf = kmalloc(buf_len, GFP_KERNEL); + if (!buf) { + IPC_RTR_ERR("%s: cannot allocate buf\n", __func__); + return NULL; + } + skb_queue_walk(skb_head, temp) { + copy_len = buf_len < temp->len ? buf_len : temp->len; + memcpy(buf + offset, temp->data, copy_len); + offset += copy_len; + buf_len -= copy_len; + } + return buf; +} + +void msm_ipc_router_free_skb(struct sk_buff_head *skb_head) +{ + struct sk_buff *temp_skb; + + if (!skb_head) + return; + + while (!skb_queue_empty(skb_head)) { + temp_skb = skb_dequeue(skb_head); + kfree_skb(temp_skb); + } + kfree(skb_head); +} + +/** + * extract_optional_header() - Extract the optional header from skb + * @pkt: Packet structure into which the header has to be extracted. + * @opt_len: The optional header length in word size. + * + * @return: Length of optional header in bytes if success, zero otherwise. + */ +static int extract_optional_header(struct rr_packet *pkt, uint8_t opt_len) +{ + size_t offset = 0, buf_len = 0, copy_len, opt_hdr_len; + struct sk_buff *temp; + struct sk_buff_head *skb_head; + + opt_hdr_len = opt_len * IPCR_WORD_SIZE; + pkt->opt_hdr.data = kmalloc(opt_hdr_len, GFP_KERNEL); + if (!pkt->opt_hdr.data) { + IPC_RTR_ERR("%s: Memory allocation Failed\n", __func__); + return 0; + } + skb_head = pkt->pkt_fragment_q; + buf_len = opt_hdr_len; + skb_queue_walk(skb_head, temp) { + copy_len = buf_len < temp->len ? buf_len : temp->len; + memcpy(pkt->opt_hdr.data + offset, temp->data, copy_len); + offset += copy_len; + buf_len -= copy_len; + skb_pull(temp, copy_len); + if (temp->len == 0) { + skb_dequeue(skb_head); + kfree_skb(temp); + } + } + pkt->opt_hdr.len = opt_hdr_len; + return opt_hdr_len; +} + +/** + * extract_header_v1() - Extract IPC Router header of version 1 + * @pkt: Packet structure into which the header has to be extraced. + * @skb: SKB from which the header has to be extracted. + * + * @return: 0 on success, standard Linux error codes on failure. + */ +static int extract_header_v1(struct rr_packet *pkt, struct sk_buff *skb) +{ + if (!pkt || !skb) { + IPC_RTR_ERR("%s: Invalid pkt or skb\n", __func__); + return -EINVAL; + } + + memcpy(&pkt->hdr, skb->data, sizeof(struct rr_header_v1)); + skb_pull(skb, sizeof(struct rr_header_v1)); + pkt->length -= sizeof(struct rr_header_v1); + return 0; +} + +/** + * extract_header_v2() - Extract IPC Router header of version 2 + * @pkt: Packet structure into which the header has to be extraced. + * @skb: SKB from which the header has to be extracted. + * + * @return: 0 on success, standard Linux error codes on failure. + */ +static int extract_header_v2(struct rr_packet *pkt, struct sk_buff *skb) +{ + struct rr_header_v2 *hdr; + uint8_t opt_len; + size_t opt_hdr_len; + size_t total_hdr_size = sizeof(*hdr); + + if (!pkt || !skb) { + IPC_RTR_ERR("%s: Invalid pkt or skb\n", __func__); + return -EINVAL; + } + + hdr = (struct rr_header_v2 *)skb->data; + pkt->hdr.version = (uint32_t)hdr->version; + pkt->hdr.type = (uint32_t)hdr->type; + pkt->hdr.src_node_id = (uint32_t)hdr->src_node_id; + pkt->hdr.src_port_id = (uint32_t)hdr->src_port_id; + pkt->hdr.size = (uint32_t)hdr->size; + pkt->hdr.control_flag = (uint32_t)hdr->control_flag; + pkt->hdr.dst_node_id = (uint32_t)hdr->dst_node_id; + pkt->hdr.dst_port_id = (uint32_t)hdr->dst_port_id; + opt_len = hdr->opt_len; + skb_pull(skb, total_hdr_size); + if (opt_len > 0) { + opt_hdr_len = extract_optional_header(pkt, opt_len); + total_hdr_size += opt_hdr_len; + } + pkt->length -= total_hdr_size; + return 0; +} + +/** + * extract_header() - Extract IPC Router header + * @pkt: Packet from which the header has to be extraced. + * + * @return: 0 on success, standard Linux error codes on failure. + * + * This function will check if the header version is v1 or v2 and invoke + * the corresponding helper function to extract the IPC Router header. + */ +static int extract_header(struct rr_packet *pkt) +{ + struct sk_buff *temp_skb; + int ret; + + if (!pkt) { + IPC_RTR_ERR("%s: NULL PKT\n", __func__); + return -EINVAL; + } + + temp_skb = skb_peek(pkt->pkt_fragment_q); + if (!temp_skb || !temp_skb->data) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return -EINVAL; + } + + if (temp_skb->data[0] == IPC_ROUTER_V1) { + ret = extract_header_v1(pkt, temp_skb); + } else if (temp_skb->data[0] == IPC_ROUTER_V2) { + ret = extract_header_v2(pkt, temp_skb); + } else { + IPC_RTR_ERR("%s: Invalid Header version %02x\n", + __func__, temp_skb->data[0]); + print_hex_dump(KERN_ERR, "Header: ", DUMP_PREFIX_ADDRESS, + 16, 1, temp_skb->data, pkt->length, true); + return -EINVAL; + } + return ret; +} + +/** + * calc_tx_header_size() - Calculate header size to be reserved in SKB + * @pkt: Packet in which the space for header has to be reserved. + * @dst_xprt_info: XPRT through which the destination is reachable. + * + * @return: required header size on success, + * starndard Linux error codes on failure. + * + * This function is used to calculate the header size that has to be reserved + * in a transmit SKB. The header size is calculated based on the XPRT through + * which the destination node is reachable. + */ +static int calc_tx_header_size(struct rr_packet *pkt, + struct msm_ipc_router_xprt_info *dst_xprt_info) +{ + int hdr_size = 0; + int xprt_version = 0; + struct msm_ipc_router_xprt_info *xprt_info = dst_xprt_info; + + if (!pkt) { + IPC_RTR_ERR("%s: NULL PKT\n", __func__); + return -EINVAL; + } + + if (xprt_info) + xprt_version = xprt_info->xprt->get_version(xprt_info->xprt); + + if (xprt_version == IPC_ROUTER_V1) { + pkt->hdr.version = IPC_ROUTER_V1; + hdr_size = sizeof(struct rr_header_v1); + } else if (xprt_version == IPC_ROUTER_V2) { + pkt->hdr.version = IPC_ROUTER_V2; + hdr_size = sizeof(struct rr_header_v2) + pkt->opt_hdr.len; + } else { + IPC_RTR_ERR("%s: Invalid xprt_version %d\n", + __func__, xprt_version); + hdr_size = -EINVAL; + } + + return hdr_size; +} + +/** + * calc_rx_header_size() - Calculate the RX header size + * @xprt_info: XPRT info of the received message. + * + * @return: valid header size on success, INT_MAX on failure. + */ +static int calc_rx_header_size(struct msm_ipc_router_xprt_info *xprt_info) +{ + int xprt_version = 0; + int hdr_size = INT_MAX; + + if (xprt_info) + xprt_version = xprt_info->xprt->get_version(xprt_info->xprt); + + if (xprt_version == IPC_ROUTER_V1) + hdr_size = sizeof(struct rr_header_v1); + else if (xprt_version == IPC_ROUTER_V2) + hdr_size = sizeof(struct rr_header_v2); + return hdr_size; +} + +/** + * prepend_header_v1() - Prepend IPC Router header of version 1 + * @pkt: Packet structure which contains the header info to be prepended. + * @hdr_size: Size of the header + * + * @return: 0 on success, standard Linux error codes on failure. + */ +static int prepend_header_v1(struct rr_packet *pkt, int hdr_size) +{ + struct sk_buff *temp_skb; + struct rr_header_v1 *hdr; + + if (!pkt || hdr_size <= 0) { + IPC_RTR_ERR("%s: Invalid input parameters\n", __func__); + return -EINVAL; + } + + temp_skb = skb_peek(pkt->pkt_fragment_q); + if (!temp_skb || !temp_skb->data) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return -EINVAL; + } + + if (skb_headroom(temp_skb) < hdr_size) { + temp_skb = alloc_skb(hdr_size, GFP_KERNEL); + if (!temp_skb) { + IPC_RTR_ERR("%s: Could not allocate SKB of size %d\n", + __func__, hdr_size); + return -ENOMEM; + } + skb_reserve(temp_skb, hdr_size); + } + + hdr = (struct rr_header_v1 *)skb_push(temp_skb, hdr_size); + memcpy(hdr, &pkt->hdr, hdr_size); + if (temp_skb != skb_peek(pkt->pkt_fragment_q)) + skb_queue_head(pkt->pkt_fragment_q, temp_skb); + pkt->length += hdr_size; + return 0; +} + +/** + * prepend_header_v2() - Prepend IPC Router header of version 2 + * @pkt: Packet structure which contains the header info to be prepended. + * @hdr_size: Size of the header + * + * @return: 0 on success, standard Linux error codes on failure. + */ +static int prepend_header_v2(struct rr_packet *pkt, int hdr_size) +{ + struct sk_buff *temp_skb; + struct rr_header_v2 *hdr; + + if (!pkt || hdr_size <= 0) { + IPC_RTR_ERR("%s: Invalid input parameters\n", __func__); + return -EINVAL; + } + + temp_skb = skb_peek(pkt->pkt_fragment_q); + if (!temp_skb || !temp_skb->data) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return -EINVAL; + } + + if (skb_headroom(temp_skb) < hdr_size) { + temp_skb = alloc_skb(hdr_size, GFP_KERNEL); + if (!temp_skb) { + IPC_RTR_ERR("%s: Could not allocate SKB of size %d\n", + __func__, hdr_size); + return -ENOMEM; + } + skb_reserve(temp_skb, hdr_size); + } + + hdr = (struct rr_header_v2 *)skb_push(temp_skb, hdr_size); + hdr->version = (uint8_t)pkt->hdr.version; + hdr->type = (uint8_t)pkt->hdr.type; + hdr->control_flag = (uint8_t)pkt->hdr.control_flag; + hdr->size = (uint32_t)pkt->hdr.size; + hdr->src_node_id = (uint16_t)pkt->hdr.src_node_id; + hdr->src_port_id = (uint16_t)pkt->hdr.src_port_id; + hdr->dst_node_id = (uint16_t)pkt->hdr.dst_node_id; + hdr->dst_port_id = (uint16_t)pkt->hdr.dst_port_id; + if (pkt->opt_hdr.len > 0) { + hdr->opt_len = pkt->opt_hdr.len/IPCR_WORD_SIZE; + memcpy(hdr + sizeof(*hdr), pkt->opt_hdr.data, pkt->opt_hdr.len); + } else { + hdr->opt_len = 0; + } + if (temp_skb != skb_peek(pkt->pkt_fragment_q)) + skb_queue_head(pkt->pkt_fragment_q, temp_skb); + pkt->length += hdr_size; + return 0; +} + +/** + * prepend_header() - Prepend IPC Router header + * @pkt: Packet structure which contains the header info to be prepended. + * @xprt_info: XPRT through which the packet is transmitted. + * + * @return: 0 on success, standard Linux error codes on failure. + * + * This function prepends the header to the packet to be transmitted. The + * IPC Router header version to be prepended depends on the XPRT through + * which the destination is reachable. + */ +static int prepend_header(struct rr_packet *pkt, + struct msm_ipc_router_xprt_info *xprt_info) +{ + int hdr_size; + struct sk_buff *temp_skb; + + if (!pkt) { + IPC_RTR_ERR("%s: NULL PKT\n", __func__); + return -EINVAL; + } + + temp_skb = skb_peek(pkt->pkt_fragment_q); + if (!temp_skb || !temp_skb->data) { + IPC_RTR_ERR("%s: No SKBs in skb_queue\n", __func__); + return -EINVAL; + } + + hdr_size = calc_tx_header_size(pkt, xprt_info); + if (hdr_size <= 0) + return hdr_size; + + if (pkt->hdr.version == IPC_ROUTER_V1) + return prepend_header_v1(pkt, hdr_size); + else if (pkt->hdr.version == IPC_ROUTER_V2) + return prepend_header_v2(pkt, hdr_size); + else + return -EINVAL; +} + +/** + * defragment_pkt() - Defragment and linearize the packet + * @pkt: Packet to be linearized. + * + * @return: 0 on success, standard Linux error codes on failure. + * + * Some packets contain fragments of data over multiple SKBs. If an XPRT + * does not supported fragmented writes, linearize multiple SKBs into one + * single SKB. + */ +static int defragment_pkt(struct rr_packet *pkt) +{ + struct sk_buff *dst_skb, *src_skb, *temp_skb; + int offset = 0, buf_len = 0, copy_len; + void *buf; + int align_size; + + if (!pkt || pkt->length <= 0) { + IPC_RTR_ERR("%s: Invalid PKT\n", __func__); + return -EINVAL; + } + + if (skb_queue_len(pkt->pkt_fragment_q) == 1) + return 0; + + align_size = ALIGN_SIZE(pkt->length); + dst_skb = alloc_skb(pkt->length + align_size, GFP_KERNEL); + if (!dst_skb) { + IPC_RTR_ERR("%s: could not allocate one skb of size %d\n", + __func__, pkt->length); + return -ENOMEM; + } + buf = skb_put(dst_skb, pkt->length); + buf_len = pkt->length; + + skb_queue_walk(pkt->pkt_fragment_q, src_skb) { + copy_len = buf_len < src_skb->len ? buf_len : src_skb->len; + memcpy(buf + offset, src_skb->data, copy_len); + offset += copy_len; + buf_len -= copy_len; + } + + while (!skb_queue_empty(pkt->pkt_fragment_q)) { + temp_skb = skb_dequeue(pkt->pkt_fragment_q); + kfree_skb(temp_skb); + } + skb_queue_tail(pkt->pkt_fragment_q, dst_skb); + return 0; +} + +static int post_pkt_to_port(struct msm_ipc_port *port_ptr, + struct rr_packet *pkt, int clone) +{ + struct rr_packet *temp_pkt = pkt; + void (*notify)(unsigned event, void *oob_data, + size_t oob_data_len, void *priv); + void (*data_ready)(struct sock *sk) = NULL; + struct sock *sk; + uint32_t pkt_type; + + if (unlikely(!port_ptr || !pkt)) + return -EINVAL; + + if (clone) { + temp_pkt = clone_pkt(pkt); + if (!temp_pkt) { + IPC_RTR_ERR( + "%s: Error cloning packet for port %08x:%08x\n", + __func__, port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + return -ENOMEM; + } + } + + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + if (pkt->ws_need) + __pm_stay_awake(port_ptr->port_rx_ws); + list_add_tail(&temp_pkt->list, &port_ptr->port_rx_q); + wake_up(&port_ptr->port_rx_wait_q); + notify = port_ptr->notify; + pkt_type = temp_pkt->hdr.type; + sk = (struct sock *)port_ptr->endpoint; + if (sk) { + read_lock(&sk->sk_callback_lock); + data_ready = sk->sk_data_ready; + read_unlock(&sk->sk_callback_lock); + } + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + if (notify) + notify(pkt_type, NULL, 0, port_ptr->priv); + else if (sk && data_ready) + data_ready(sk); + + return 0; +} + +/** + * ipc_router_peek_pkt_size() - Peek into the packet header to get potential packet size + * @data: Starting address of the packet which points to router header. + * + * @returns: potential packet size on success, < 0 on error. + * + * This function is used by the underlying transport abstraction layer to + * peek into the potential packet size of an incoming packet. This information + * is used to perform link layer fragmentation and re-assembly + */ +int ipc_router_peek_pkt_size(char *data) +{ + int size; + + if (!data) { + pr_err("%s: NULL PKT\n", __func__); + return -EINVAL; + } + + if (data[0] == IPC_ROUTER_V1) + size = ((struct rr_header_v1 *)data)->size + + sizeof(struct rr_header_v1); + else if (data[0] == IPC_ROUTER_V2) + size = ((struct rr_header_v2 *)data)->size + + ((struct rr_header_v2 *)data)->opt_len * IPCR_WORD_SIZE + + sizeof(struct rr_header_v2); + else + return -EINVAL; + + size += ALIGN_SIZE(size); + return size; +} + +static int post_control_ports(struct rr_packet *pkt) +{ + struct msm_ipc_port *port_ptr; + + if (!pkt) + return -EINVAL; + + down_read(&control_ports_lock_lha5); + list_for_each_entry(port_ptr, &control_ports, list) + post_pkt_to_port(port_ptr, pkt, 1); + up_read(&control_ports_lock_lha5); + return 0; +} + +static uint32_t allocate_port_id(void) +{ + uint32_t port_id = 0, prev_port_id, key; + struct msm_ipc_port *port_ptr; + + mutex_lock(&next_port_id_lock_lhc1); + prev_port_id = next_port_id; + down_read(&local_ports_lock_lhc2); + do { + next_port_id++; + if ((next_port_id & IPC_ROUTER_ADDRESS) == IPC_ROUTER_ADDRESS) + next_port_id = 1; + + key = (next_port_id & (LP_HASH_SIZE - 1)); + if (list_empty(&local_ports[key])) { + port_id = next_port_id; + break; + } + list_for_each_entry(port_ptr, &local_ports[key], list) { + if (port_ptr->this_port.port_id == next_port_id) { + port_id = next_port_id; + break; + } + } + if (!port_id) { + port_id = next_port_id; + break; + } + port_id = 0; + } while (next_port_id != prev_port_id); + up_read(&local_ports_lock_lhc2); + mutex_unlock(&next_port_id_lock_lhc1); + + return port_id; +} + +void msm_ipc_router_add_local_port(struct msm_ipc_port *port_ptr) +{ + uint32_t key; + + if (!port_ptr) + return; + + key = (port_ptr->this_port.port_id & (LP_HASH_SIZE - 1)); + down_write(&local_ports_lock_lhc2); + list_add_tail(&port_ptr->list, &local_ports[key]); + up_write(&local_ports_lock_lhc2); +} + +/** + * msm_ipc_router_create_raw_port() - Create an IPC Router port + * @endpoint: User-space space socket information to be cached. + * @notify: Function to notify incoming events on the port. + * @event: Event ID to be handled. + * @oob_data: Any out-of-band data associated with the event. + * @oob_data_len: Size of the out-of-band data, if valid. + * @priv: Private data registered during the port creation. + * @priv: Private Data to be passed during the event notification. + * + * @return: Valid pointer to port on success, NULL on failure. + * + * This function is used to create an IPC Router port. The port is used for + * communication locally or outside the subsystem. + */ +struct msm_ipc_port *msm_ipc_router_create_raw_port(void *endpoint, + void (*notify)(unsigned event, void *oob_data, + size_t oob_data_len, void *priv), + void *priv) +{ + struct msm_ipc_port *port_ptr; + + port_ptr = kzalloc(sizeof(struct msm_ipc_port), GFP_KERNEL); + if (!port_ptr) + return NULL; + + port_ptr->this_port.node_id = IPC_ROUTER_NID_LOCAL; + port_ptr->this_port.port_id = allocate_port_id(); + if (!port_ptr->this_port.port_id) { + IPC_RTR_ERR("%s: All port ids are in use\n", __func__); + kfree(port_ptr); + return NULL; + } + + mutex_init(&port_ptr->port_lock_lhc3); + INIT_LIST_HEAD(&port_ptr->port_rx_q); + mutex_init(&port_ptr->port_rx_q_lock_lhc3); + init_waitqueue_head(&port_ptr->port_rx_wait_q); + snprintf(port_ptr->rx_ws_name, MAX_WS_NAME_SZ, + "ipc%08x_%d_%s", + port_ptr->this_port.port_id, + task_pid_nr(current), + current->comm); + port_ptr->port_rx_ws = wakeup_source_register(port_ptr->rx_ws_name); + if (!port_ptr->port_rx_ws) { + kfree(port_ptr); + return NULL; + } + init_waitqueue_head(&port_ptr->port_tx_wait_q); + kref_init(&port_ptr->ref); + + port_ptr->endpoint = endpoint; + port_ptr->notify = notify; + port_ptr->priv = priv; + + msm_ipc_router_add_local_port(port_ptr); + if (endpoint) + sock_hold(ipc_port_sk(endpoint)); + return port_ptr; +} + +/** + * ipc_router_get_port_ref() - Get a reference to the local port + * @port_id: Port ID of the local port for which reference is get. + * + * @return: If port is found, a reference to the port is returned. + * Else NULL is returned. + */ +static struct msm_ipc_port *ipc_router_get_port_ref(uint32_t port_id) +{ + int key = (port_id & (LP_HASH_SIZE - 1)); + struct msm_ipc_port *port_ptr; + + down_read(&local_ports_lock_lhc2); + list_for_each_entry(port_ptr, &local_ports[key], list) { + if (port_ptr->this_port.port_id == port_id) { + kref_get(&port_ptr->ref); + up_read(&local_ports_lock_lhc2); + return port_ptr; + } + } + up_read(&local_ports_lock_lhc2); + return NULL; +} + +/** + * ipc_router_release_port() - Cleanup and release the port + * @ref: Reference to the port. + * + * This function is called when all references to the port are released. + */ +void ipc_router_release_port(struct kref *ref) +{ + struct rr_packet *pkt, *temp_pkt; + struct msm_ipc_port *port_ptr = + container_of(ref, struct msm_ipc_port, ref); + + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + list_for_each_entry_safe(pkt, temp_pkt, &port_ptr->port_rx_q, list) { + list_del(&pkt->list); + release_pkt(pkt); + } + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + wakeup_source_unregister(port_ptr->port_rx_ws); + if (port_ptr->endpoint) + sock_put(ipc_port_sk(port_ptr->endpoint)); + kfree(port_ptr); +} + +/** + * ipc_router_get_rport_ref()- Get reference to the remote port + * @node_id: Node ID corresponding to the remote port. + * @port_id: Port ID corresponding to the remote port. + * + * @return: a reference to the remote port on success, NULL on failure. + */ +static struct msm_ipc_router_remote_port *ipc_router_get_rport_ref( + uint32_t node_id, uint32_t port_id) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + int key = (port_id & (RP_HASH_SIZE - 1)); + + rt_entry = ipc_router_get_rtentry_ref(node_id); + if (!rt_entry) { + IPC_RTR_ERR("%s: Node is not up\n", __func__); + return NULL; + } + + down_read(&rt_entry->lock_lha4); + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[key], list) { + if (rport_ptr->port_id == port_id) { + kref_get(&rport_ptr->ref); + goto out_lookup_rmt_port1; + } + } + rport_ptr = NULL; +out_lookup_rmt_port1: + up_read(&rt_entry->lock_lha4); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return rport_ptr; +} + +/** + * ipc_router_create_rport() - Create a remote port + * @node_id: Node ID corresponding to the remote port. + * @port_id: Port ID corresponding to the remote port. + * @xprt_info: XPRT through which the concerned node is reachable. + * + * @return: a reference to the remote port on success, NULL on failure. + */ +static struct msm_ipc_router_remote_port *ipc_router_create_rport( + uint32_t node_id, uint32_t port_id, + struct msm_ipc_router_xprt_info *xprt_info) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + int key = (port_id & (RP_HASH_SIZE - 1)); + + rt_entry = create_routing_table_entry(node_id, xprt_info); + if (!rt_entry) { + IPC_RTR_ERR("%s: Node cannot be created\n", __func__); + return NULL; + } + + down_write(&rt_entry->lock_lha4); + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[key], list) { + if (rport_ptr->port_id == port_id) + goto out_create_rmt_port1; + } + + rport_ptr = kmalloc(sizeof(struct msm_ipc_router_remote_port), + GFP_KERNEL); + if (!rport_ptr) { + IPC_RTR_ERR("%s: Remote port alloc failed\n", __func__); + goto out_create_rmt_port2; + } + rport_ptr->port_id = port_id; + rport_ptr->node_id = node_id; + rport_ptr->status = VALID; + rport_ptr->sec_rule = NULL; + rport_ptr->server = NULL; + rport_ptr->tx_quota_cnt = 0; + kref_init(&rport_ptr->ref); + mutex_init(&rport_ptr->rport_lock_lhb2); + INIT_LIST_HEAD(&rport_ptr->resume_tx_port_list); + INIT_LIST_HEAD(&rport_ptr->conn_info_list); + list_add_tail(&rport_ptr->list, + &rt_entry->remote_port_list[key]); +out_create_rmt_port1: + kref_get(&rport_ptr->ref); +out_create_rmt_port2: + up_write(&rt_entry->lock_lha4); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return rport_ptr; +} + +/** + * msm_ipc_router_free_resume_tx_port() - Free the resume_tx ports + * @rport_ptr: Pointer to the remote port. + * + * This function deletes all the resume_tx ports associated with a remote port + * and frees the memory allocated to each resume_tx port. + * + * Must be called with rport_ptr->rport_lock_lhb2 locked. + */ +static void msm_ipc_router_free_resume_tx_port( + struct msm_ipc_router_remote_port *rport_ptr) +{ + struct msm_ipc_resume_tx_port *rtx_port, *tmp_rtx_port; + + list_for_each_entry_safe(rtx_port, tmp_rtx_port, + &rport_ptr->resume_tx_port_list, list) { + list_del(&rtx_port->list); + kfree(rtx_port); + } +} + +/** + * msm_ipc_router_lookup_resume_tx_port() - Lookup resume_tx port list + * @rport_ptr: Remote port whose resume_tx port list needs to be looked. + * @port_id: Port ID which needs to be looked from the list. + * + * return 1 if the port_id is found in the list, else 0. + * + * This function is used to lookup the existence of a local port in + * remote port's resume_tx list. This function is used to ensure that + * the same port is not added to the remote_port's resume_tx list repeatedly. + * + * Must be called with rport_ptr->rport_lock_lhb2 locked. + */ +static int msm_ipc_router_lookup_resume_tx_port( + struct msm_ipc_router_remote_port *rport_ptr, uint32_t port_id) +{ + struct msm_ipc_resume_tx_port *rtx_port; + + list_for_each_entry(rtx_port, &rport_ptr->resume_tx_port_list, list) { + if (port_id == rtx_port->port_id) + return 1; + } + return 0; +} + +/** + * ipc_router_dummy_write_space() - Dummy write space available callback + * @sk: Socket pointer for which the callback is called. + */ +void ipc_router_dummy_write_space(struct sock *sk) +{ +} + +/** + * post_resume_tx() - Post the resume_tx event + * @rport_ptr: Pointer to the remote port + * @pkt : The data packet that is received on a resume_tx event + * @msg: Out of band data to be passed to kernel drivers + * + * This function informs about the reception of the resume_tx message from a + * remote port pointed by rport_ptr to all the local ports that are in the + * resume_tx_ports_list of this remote port. On posting the information, this + * function sequentially deletes each entry in the resume_tx_port_list of the + * remote port. + * + * Must be called with rport_ptr->rport_lock_lhb2 locked. + */ +static void post_resume_tx(struct msm_ipc_router_remote_port *rport_ptr, + struct rr_packet *pkt, union rr_control_msg *msg) +{ + struct msm_ipc_resume_tx_port *rtx_port, *tmp_rtx_port; + struct msm_ipc_port *local_port; + struct sock *sk; + void (*write_space)(struct sock *sk) = NULL; + + list_for_each_entry_safe(rtx_port, tmp_rtx_port, + &rport_ptr->resume_tx_port_list, list) { + local_port = ipc_router_get_port_ref(rtx_port->port_id); + if (local_port && local_port->notify) { + wake_up(&local_port->port_tx_wait_q); + local_port->notify(IPC_ROUTER_CTRL_CMD_RESUME_TX, msg, + sizeof(*msg), local_port->priv); + } else if (local_port) { + wake_up(&local_port->port_tx_wait_q); + sk = ipc_port_sk(local_port->endpoint); + if (sk) { + read_lock(&sk->sk_callback_lock); + write_space = sk->sk_write_space; + read_unlock(&sk->sk_callback_lock); + } + if (write_space && + write_space != ipc_router_dummy_write_space) + write_space(sk); + else + post_pkt_to_port(local_port, pkt, 1); + } else { + IPC_RTR_ERR("%s: Local Port %d not Found", + __func__, rtx_port->port_id); + } + if (local_port) + kref_put(&local_port->ref, ipc_router_release_port); + list_del(&rtx_port->list); + kfree(rtx_port); + } +} + +/** + * signal_rport_exit() - Signal the local ports of remote port exit + * @rport_ptr: Remote port that is exiting. + * + * This function is used to signal the local ports that are waiting + * to resume transmission to a remote port that is exiting. + */ +static void signal_rport_exit(struct msm_ipc_router_remote_port *rport_ptr) +{ + struct msm_ipc_resume_tx_port *rtx_port, *tmp_rtx_port; + struct msm_ipc_port *local_port; + + mutex_lock(&rport_ptr->rport_lock_lhb2); + rport_ptr->status = RESET; + list_for_each_entry_safe(rtx_port, tmp_rtx_port, + &rport_ptr->resume_tx_port_list, list) { + local_port = ipc_router_get_port_ref(rtx_port->port_id); + if (local_port) { + wake_up(&local_port->port_tx_wait_q); + kref_put(&local_port->ref, ipc_router_release_port); + } + list_del(&rtx_port->list); + kfree(rtx_port); + } + mutex_unlock(&rport_ptr->rport_lock_lhb2); +} + +/** + * ipc_router_release_rport() - Cleanup and release the remote port + * @ref: Reference to the remote port. + * + * This function is called when all references to the remote port are released. + */ +static void ipc_router_release_rport(struct kref *ref) +{ + struct msm_ipc_router_remote_port *rport_ptr = + container_of(ref, struct msm_ipc_router_remote_port, ref); + + mutex_lock(&rport_ptr->rport_lock_lhb2); + msm_ipc_router_free_resume_tx_port(rport_ptr); + mutex_unlock(&rport_ptr->rport_lock_lhb2); + kfree(rport_ptr); +} + +/** + * ipc_router_destroy_rport() - Destroy the remote port + * @rport_ptr: Pointer to the remote port to be destroyed. + */ +static void ipc_router_destroy_rport( + struct msm_ipc_router_remote_port *rport_ptr) +{ + uint32_t node_id; + struct msm_ipc_routing_table_entry *rt_entry; + + if (!rport_ptr) + return; + + node_id = rport_ptr->node_id; + rt_entry = ipc_router_get_rtentry_ref(node_id); + if (!rt_entry) { + IPC_RTR_ERR("%s: Node %d is not up\n", __func__, node_id); + return; + } + down_write(&rt_entry->lock_lha4); + list_del(&rport_ptr->list); + up_write(&rt_entry->lock_lha4); + signal_rport_exit(rport_ptr); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return; +} + +/** + * msm_ipc_router_lookup_server() - Lookup server information + * @service: Service ID of the server info to be looked up. + * @instance: Instance ID of the server info to be looked up. + * @node_id: Node/Processor ID in which the server is hosted. + * @port_id: Port ID within the node in which the server is hosted. + * + * @return: If found Pointer to server structure, else NULL. + * + * Note1: Lock the server_list_lock_lha2 before accessing this function. + * Note2: If the <node_id:port_id> are <0:0>, then the lookup is restricted + * to <service:instance>. Used only when a client wants to send a + * message to any QMI server. + */ +static struct msm_ipc_server *msm_ipc_router_lookup_server( + uint32_t service, + uint32_t instance, + uint32_t node_id, + uint32_t port_id) +{ + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int key = (service & (SRV_HASH_SIZE - 1)); + + list_for_each_entry(server, &server_list[key], list) { + if ((server->name.service != service) || + (server->name.instance != instance)) + continue; + if ((node_id == 0) && (port_id == 0)) + return server; + list_for_each_entry(server_port, &server->server_port_list, + list) { + if ((server_port->server_addr.node_id == node_id) && + (server_port->server_addr.port_id == port_id)) + return server; + } + } + return NULL; +} + +/** + * ipc_router_get_server_ref() - Get reference to the server + * @svc: Service ID for which the reference is required. + * @ins: Instance ID for which the reference is required. + * @node_id: Node/Processor ID in which the server is hosted. + * @port_id: Port ID within the node in which the server is hosted. + * + * @return: If found return reference to server, else NULL. + */ +static struct msm_ipc_server *ipc_router_get_server_ref( + uint32_t svc, uint32_t ins, uint32_t node_id, uint32_t port_id) +{ + struct msm_ipc_server *server; + + down_read(&server_list_lock_lha2); + server = msm_ipc_router_lookup_server(svc, ins, node_id, port_id); + if (server) + kref_get(&server->ref); + up_read(&server_list_lock_lha2); + return server; +} + +/** + * ipc_router_release_server() - Cleanup and release the server + * @ref: Reference to the server. + * + * This function is called when all references to the server are released. + */ +static void ipc_router_release_server(struct kref *ref) +{ + struct msm_ipc_server *server = + container_of(ref, struct msm_ipc_server, ref); + + kfree(server); +} + +/** + * msm_ipc_router_create_server() - Add server info to hash table + * @service: Service ID of the server info to be created. + * @instance: Instance ID of the server info to be created. + * @node_id: Node/Processor ID in which the server is hosted. + * @port_id: Port ID within the node in which the server is hosted. + * @xprt_info: XPRT through which the node hosting the server is reached. + * + * @return: Pointer to server structure on success, else NULL. + * + * This function adds the server info to the hash table. If the same + * server(i.e. <service_id:instance_id>) is hosted in different nodes, + * they are maintained as list of "server_port" under "server" structure. + */ +static struct msm_ipc_server *msm_ipc_router_create_server( + uint32_t service, + uint32_t instance, + uint32_t node_id, + uint32_t port_id, + struct msm_ipc_router_xprt_info *xprt_info) +{ + struct msm_ipc_server *server = NULL; + struct msm_ipc_server_port *server_port; + struct platform_device *pdev; + int key = (service & (SRV_HASH_SIZE - 1)); + + down_write(&server_list_lock_lha2); + server = msm_ipc_router_lookup_server(service, instance, 0, 0); + if (server) { + list_for_each_entry(server_port, &server->server_port_list, + list) { + if ((server_port->server_addr.node_id == node_id) && + (server_port->server_addr.port_id == port_id)) + goto return_server; + } + goto create_srv_port; + } + + server = kzalloc(sizeof(struct msm_ipc_server), GFP_KERNEL); + if (!server) { + up_write(&server_list_lock_lha2); + IPC_RTR_ERR("%s: Server allocation failed\n", __func__); + return NULL; + } + server->name.service = service; + server->name.instance = instance; + server->synced_sec_rule = 0; + INIT_LIST_HEAD(&server->server_port_list); + kref_init(&server->ref); + list_add_tail(&server->list, &server_list[key]); + scnprintf(server->pdev_name, sizeof(server->pdev_name), + "SVC%08x:%08x", service, instance); + server->next_pdev_id = 1; + +create_srv_port: + server_port = kzalloc(sizeof(struct msm_ipc_server_port), GFP_KERNEL); + pdev = platform_device_alloc(server->pdev_name, server->next_pdev_id); + if (!server_port || !pdev) { + kfree(server_port); + if (pdev) + platform_device_put(pdev); + if (list_empty(&server->server_port_list)) { + list_del(&server->list); + kfree(server); + } + up_write(&server_list_lock_lha2); + IPC_RTR_ERR("%s: Server Port allocation failed\n", __func__); + return NULL; + } + server_port->pdev = pdev; + server_port->server_addr.node_id = node_id; + server_port->server_addr.port_id = port_id; + server_port->xprt_info = xprt_info; + list_add_tail(&server_port->list, &server->server_port_list); + server->next_pdev_id++; + platform_device_add(server_port->pdev); + +return_server: + /* Add a reference so that the caller can put it back */ + kref_get(&server->ref); + up_write(&server_list_lock_lha2); + return server; +} + +/** + * ipc_router_destroy_server_nolock() - Remove server info from hash table + * @server: Server info to be removed. + * @node_id: Node/Processor ID in which the server is hosted. + * @port_id: Port ID within the node in which the server is hosted. + * + * This function removes the server_port identified using <node_id:port_id> + * from the server structure. If the server_port list under server structure + * is empty after removal, then remove the server structure from the server + * hash table. This function must be called with server_list_lock_lha2 locked. + */ +static void ipc_router_destroy_server_nolock(struct msm_ipc_server *server, + uint32_t node_id, uint32_t port_id) +{ + struct msm_ipc_server_port *server_port; + bool server_port_found = false; + + if (!server) + return; + + list_for_each_entry(server_port, &server->server_port_list, list) { + if ((server_port->server_addr.node_id == node_id) && + (server_port->server_addr.port_id == port_id)) { + server_port_found = true; + break; + } + } + if (server_port_found && server_port) { + platform_device_unregister(server_port->pdev); + list_del(&server_port->list); + kfree(server_port); + } + if (list_empty(&server->server_port_list)) { + list_del(&server->list); + kref_put(&server->ref, ipc_router_release_server); + } + return; +} + +/** + * ipc_router_destroy_server() - Remove server info from hash table + * @server: Server info to be removed. + * @node_id: Node/Processor ID in which the server is hosted. + * @port_id: Port ID within the node in which the server is hosted. + * + * This function removes the server_port identified using <node_id:port_id> + * from the server structure. If the server_port list under server structure + * is empty after removal, then remove the server structure from the server + * hash table. + */ +static void ipc_router_destroy_server(struct msm_ipc_server *server, + uint32_t node_id, uint32_t port_id) +{ + down_write(&server_list_lock_lha2); + ipc_router_destroy_server_nolock(server, node_id, port_id); + up_write(&server_list_lock_lha2); + return; +} + +static int ipc_router_send_ctl_msg( + struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg, + uint32_t dst_node_id) +{ + struct rr_packet *pkt; + struct sk_buff *ipc_rtr_pkt; + struct rr_header_v1 *hdr; + int pkt_size; + void *data; + int ret = -EINVAL; + + pkt = create_pkt(NULL); + if (!pkt) { + IPC_RTR_ERR("%s: pkt alloc failed\n", __func__); + return -ENOMEM; + } + + pkt_size = IPC_ROUTER_HDR_SIZE + sizeof(*msg); + ipc_rtr_pkt = alloc_skb(pkt_size, GFP_KERNEL); + if (!ipc_rtr_pkt) { + IPC_RTR_ERR("%s: ipc_rtr_pkt alloc failed\n", __func__); + release_pkt(pkt); + return -ENOMEM; + } + + skb_reserve(ipc_rtr_pkt, IPC_ROUTER_HDR_SIZE); + data = skb_put(ipc_rtr_pkt, sizeof(*msg)); + memcpy(data, msg, sizeof(*msg)); + skb_queue_tail(pkt->pkt_fragment_q, ipc_rtr_pkt); + pkt->length = sizeof(*msg); + + hdr = &(pkt->hdr); + hdr->version = IPC_ROUTER_V1; + hdr->type = msg->cmd; + hdr->src_node_id = IPC_ROUTER_NID_LOCAL; + hdr->src_port_id = IPC_ROUTER_ADDRESS; + hdr->control_flag = 0; + hdr->size = sizeof(*msg); + if (hdr->type == IPC_ROUTER_CTRL_CMD_RESUME_TX || + (!xprt_info && dst_node_id == IPC_ROUTER_NID_LOCAL)) + hdr->dst_node_id = dst_node_id; + else if (xprt_info) + hdr->dst_node_id = xprt_info->remote_node_id; + hdr->dst_port_id = IPC_ROUTER_ADDRESS; + + if (dst_node_id == IPC_ROUTER_NID_LOCAL && + msg->cmd != IPC_ROUTER_CTRL_CMD_RESUME_TX) { + ipc_router_log_msg(local_log_ctx, + IPC_ROUTER_LOG_EVENT_TX, msg, hdr, NULL, NULL); + ret = post_control_ports(pkt); + } else if (dst_node_id == IPC_ROUTER_NID_LOCAL && + msg->cmd == IPC_ROUTER_CTRL_CMD_RESUME_TX) { + ipc_router_log_msg(local_log_ctx, + IPC_ROUTER_LOG_EVENT_TX, msg, hdr, NULL, NULL); + ret = process_resume_tx_msg(msg, pkt); + } else if (xprt_info && (msg->cmd == IPC_ROUTER_CTRL_CMD_HELLO || + xprt_info->initialized)) { + mutex_lock(&xprt_info->tx_lock_lhb2); + ipc_router_log_msg(xprt_info->log_ctx, + IPC_ROUTER_LOG_EVENT_TX, msg, hdr, NULL, NULL); + ret = prepend_header(pkt, xprt_info); + if (ret < 0) { + mutex_unlock(&xprt_info->tx_lock_lhb2); + IPC_RTR_ERR("%s: Prepend Header failed\n", __func__); + release_pkt(pkt); + return ret; + } + + ret = xprt_info->xprt->write(pkt, pkt->length, xprt_info->xprt); + mutex_unlock(&xprt_info->tx_lock_lhb2); + } + + release_pkt(pkt); + return ret; +} + +static int msm_ipc_router_send_server_list(uint32_t node_id, + struct msm_ipc_router_xprt_info *xprt_info) +{ + union rr_control_msg ctl; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int i; + + if (!xprt_info || !xprt_info->initialized) { + IPC_RTR_ERR("%s: Xprt info not initialized\n", __func__); + return -EINVAL; + } + + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = IPC_ROUTER_CTRL_CMD_NEW_SERVER; + + for (i = 0; i < SRV_HASH_SIZE; i++) { + list_for_each_entry(server, &server_list[i], list) { + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + list_for_each_entry(server_port, + &server->server_port_list, list) { + if (server_port->server_addr.node_id != + node_id) + continue; + + ctl.srv.node_id = + server_port->server_addr.node_id; + ctl.srv.port_id = + server_port->server_addr.port_id; + ipc_router_send_ctl_msg(xprt_info, + &ctl, IPC_ROUTER_DUMMY_DEST_NODE); + } + } + } + + return 0; +} + +static int broadcast_ctl_msg_locally(union rr_control_msg *msg) +{ + return ipc_router_send_ctl_msg(NULL, msg, IPC_ROUTER_NID_LOCAL); +} + +static int broadcast_ctl_msg(union rr_control_msg *ctl) +{ + struct msm_ipc_router_xprt_info *xprt_info; + + down_read(&xprt_info_list_lock_lha5); + list_for_each_entry(xprt_info, &xprt_info_list, list) { + ipc_router_send_ctl_msg(xprt_info, ctl, + IPC_ROUTER_DUMMY_DEST_NODE); + } + up_read(&xprt_info_list_lock_lha5); + broadcast_ctl_msg_locally(ctl); + + return 0; +} + +static int relay_ctl_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *ctl) +{ + struct msm_ipc_router_xprt_info *fwd_xprt_info; + + if (!xprt_info || !ctl) + return -EINVAL; + + down_read(&xprt_info_list_lock_lha5); + list_for_each_entry(fwd_xprt_info, &xprt_info_list, list) { + if (xprt_info->xprt->link_id != fwd_xprt_info->xprt->link_id) + ipc_router_send_ctl_msg(fwd_xprt_info, ctl, + IPC_ROUTER_DUMMY_DEST_NODE); + } + up_read(&xprt_info_list_lock_lha5); + + return 0; +} + +static int forward_msg(struct msm_ipc_router_xprt_info *xprt_info, + struct rr_packet *pkt) +{ + struct rr_header_v1 *hdr; + struct msm_ipc_router_xprt_info *fwd_xprt_info; + struct msm_ipc_routing_table_entry *rt_entry; + int ret = 0; + int fwd_xprt_option; + + if (!xprt_info || !pkt) + return -EINVAL; + + hdr = &(pkt->hdr); + rt_entry = ipc_router_get_rtentry_ref(hdr->dst_node_id); + if (!(rt_entry) || !(rt_entry->xprt_info)) { + IPC_RTR_ERR("%s: Routing table not initialized\n", __func__); + ret = -ENODEV; + goto fm_error1; + } + + down_read(&rt_entry->lock_lha4); + fwd_xprt_info = rt_entry->xprt_info; + ret = ipc_router_get_xprt_info_ref(fwd_xprt_info); + if (ret < 0) { + IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__); + goto fm_error_xprt; + } + ret = prepend_header(pkt, fwd_xprt_info); + if (ret < 0) { + IPC_RTR_ERR("%s: Prepend Header failed\n", __func__); + goto fm_error2; + } + fwd_xprt_option = fwd_xprt_info->xprt->get_option(fwd_xprt_info->xprt); + if (!(fwd_xprt_option & FRAG_PKT_WRITE_ENABLE)) { + ret = defragment_pkt(pkt); + if (ret < 0) + goto fm_error2; + } + + mutex_lock(&fwd_xprt_info->tx_lock_lhb2); + if (xprt_info->remote_node_id == fwd_xprt_info->remote_node_id) { + IPC_RTR_ERR("%s: Discarding Command to route back\n", __func__); + ret = -EINVAL; + goto fm_error3; + } + + if (xprt_info->xprt->link_id == fwd_xprt_info->xprt->link_id) { + IPC_RTR_ERR("%s: DST in the same cluster\n", __func__); + ret = 0; + goto fm_error3; + } + fwd_xprt_info->xprt->write(pkt, pkt->length, fwd_xprt_info->xprt); + IPC_RTR_INFO(fwd_xprt_info->log_ctx, + "%s %s Len:0x%x T:0x%x CF:0x%x SRC:<0x%x:0x%x> DST:<0x%x:0x%x>\n", + "FWD", "TX", hdr->size, hdr->type, hdr->control_flag, + hdr->src_node_id, hdr->src_port_id, + hdr->dst_node_id, hdr->dst_port_id); + +fm_error3: + mutex_unlock(&fwd_xprt_info->tx_lock_lhb2); +fm_error2: + ipc_router_put_xprt_info_ref(fwd_xprt_info); +fm_error_xprt: + up_read(&rt_entry->lock_lha4); +fm_error1: + if (rt_entry) + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return ret; +} + +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) { + IPC_RTR_ERR("%s: NULL mode_info\n", __func__); + return -EINVAL; + } + mode = mode_info->mode; + xprt_info = mode_info->xprt_info; + + memset(&msg, 0, sizeof(msg)); + 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) { + down_read(&xprt_info_list_lock_lha5); + list_for_each_entry(tmp_xprt_info, &xprt_info_list, list) { + if (tmp_xprt_info != xprt_info) + continue; + ipc_router_send_ctl_msg(tmp_xprt_info, &msg, + IPC_ROUTER_DUMMY_DEST_NODE); + break; + } + up_read(&xprt_info_list_lock_lha5); + } else if ((mode == SINGLE_LINK_MODE) && !xprt_info) { + broadcast_ctl_msg_locally(&msg); + } else if (mode == MULTI_LINK_MODE) { + broadcast_ctl_msg(&msg); + } else if (mode != NULL_MODE) { + IPC_RTR_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) { + IPC_RTR_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; +} + +/** + * cleanup_rmt_server() - Cleanup server hosted in the remote port + * @xprt_info: XPRT through which this cleanup event is handled. + * @rport_ptr: Remote port that is being cleaned up. + * @server: Server that is hosted in the remote port. + */ +static void cleanup_rmt_server(struct msm_ipc_router_xprt_info *xprt_info, + struct msm_ipc_router_remote_port *rport_ptr, + struct msm_ipc_server *server) +{ + union rr_control_msg ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + ctl.srv.node_id = rport_ptr->node_id; + ctl.srv.port_id = rport_ptr->port_id; + if (xprt_info) + relay_ctl_msg(xprt_info, &ctl); + broadcast_ctl_msg_locally(&ctl); + ipc_router_destroy_server_nolock(server, + rport_ptr->node_id, rport_ptr->port_id); +} + +static void cleanup_rmt_ports(struct msm_ipc_router_xprt_info *xprt_info, + struct msm_ipc_routing_table_entry *rt_entry) +{ + struct msm_ipc_router_remote_port *rport_ptr, *tmp_rport_ptr; + struct msm_ipc_server *server; + union rr_control_msg ctl; + int j; + + memset(&ctl, 0, sizeof(ctl)); + for (j = 0; j < RP_HASH_SIZE; j++) { + list_for_each_entry_safe(rport_ptr, tmp_rport_ptr, + &rt_entry->remote_port_list[j], list) { + list_del(&rport_ptr->list); + mutex_lock(&rport_ptr->rport_lock_lhb2); + server = rport_ptr->server; + rport_ptr->server = NULL; + mutex_unlock(&rport_ptr->rport_lock_lhb2); + ipc_router_reset_conn(rport_ptr); + if (server) { + cleanup_rmt_server(xprt_info, rport_ptr, + server); + server = NULL; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT; + ctl.cli.node_id = rport_ptr->node_id; + ctl.cli.port_id = rport_ptr->port_id; + kref_put(&rport_ptr->ref, ipc_router_release_rport); + + relay_ctl_msg(xprt_info, &ctl); + broadcast_ctl_msg_locally(&ctl); + } + } +} + +static void msm_ipc_cleanup_routing_table( + struct msm_ipc_router_xprt_info *xprt_info) +{ + int i; + struct msm_ipc_routing_table_entry *rt_entry, *tmp_rt_entry; + + if (!xprt_info) { + IPC_RTR_ERR("%s: Invalid xprt_info\n", __func__); + return; + } + + down_write(&server_list_lock_lha2); + down_write(&routing_table_lock_lha3); + for (i = 0; i < RT_HASH_SIZE; i++) { + list_for_each_entry_safe(rt_entry, tmp_rt_entry, + &routing_table[i], list) { + down_write(&rt_entry->lock_lha4); + if (rt_entry->xprt_info != xprt_info) { + up_write(&rt_entry->lock_lha4); + continue; + } + cleanup_rmt_ports(xprt_info, rt_entry); + rt_entry->xprt_info = NULL; + up_write(&rt_entry->lock_lha4); + list_del(&rt_entry->list); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + } + } + up_write(&routing_table_lock_lha3); + up_write(&server_list_lock_lha2); +} + +/** + * sync_sec_rule() - Synchrnoize the security rule into the server structure + * @server: Server structure where the rule has to be synchronized. + * @rule: Security tule to be synchronized. + * + * This function is used to update the server structure with the security + * rule configured for the <service:instance> corresponding to that server. + */ +static void sync_sec_rule(struct msm_ipc_server *server, void *rule) +{ + struct msm_ipc_server_port *server_port; + struct msm_ipc_router_remote_port *rport_ptr = NULL; + + list_for_each_entry(server_port, &server->server_port_list, list) { + rport_ptr = ipc_router_get_rport_ref( + server_port->server_addr.node_id, + server_port->server_addr.port_id); + if (!rport_ptr) + continue; + rport_ptr->sec_rule = rule; + kref_put(&rport_ptr->ref, ipc_router_release_rport); + } + server->synced_sec_rule = 1; +} + +/** + * msm_ipc_sync_sec_rule() - Sync the security rule to the service + * @service: Service for which the rule has to be synchronized. + * @instance: Instance for which the rule has to be synchronized. + * @rule: Security rule to be synchronized. + * + * This function is used to syncrhonize the security rule with the server + * hash table, if the user-space script configures the rule after the service + * has come up. This function is used to synchronize the security rule to a + * specific service and optionally a specific instance. + */ +void msm_ipc_sync_sec_rule(uint32_t service, uint32_t instance, void *rule) +{ + int key = (service & (SRV_HASH_SIZE - 1)); + struct msm_ipc_server *server; + + down_write(&server_list_lock_lha2); + list_for_each_entry(server, &server_list[key], list) { + if (server->name.service != service) + continue; + + if (server->name.instance != instance && + instance != ALL_INSTANCE) + continue; + + /* If the rule applies to all instances and if the specific + * instance of a service has a rule synchronized already, + * do not apply the rule for that specific instance. + */ + if (instance == ALL_INSTANCE && server->synced_sec_rule) + continue; + + sync_sec_rule(server, rule); + } + up_write(&server_list_lock_lha2); +} + +/** + * msm_ipc_sync_default_sec_rule() - Default security rule to all services + * @rule: Security rule to be synchronized. + * + * This function is used to syncrhonize the security rule with the server + * hash table, if the user-space script configures the rule after the service + * has come up. This function is used to synchronize the security rule that + * applies to all services, if the concerned service do not have any rule + * defined. + */ +void msm_ipc_sync_default_sec_rule(void *rule) +{ + int key; + struct msm_ipc_server *server; + + down_write(&server_list_lock_lha2); + for (key = 0; key < SRV_HASH_SIZE; key++) { + list_for_each_entry(server, &server_list[key], list) { + if (server->synced_sec_rule) + continue; + + sync_sec_rule(server, rule); + } + } + up_write(&server_list_lock_lha2); +} + +/** + * ipc_router_reset_conn() - Reset the connection to remote port + * @rport_ptr: Pointer to the remote port to be disconnected. + * + * This function is used to reset all the local ports that are connected to + * the remote port being passed. + */ +static void ipc_router_reset_conn(struct msm_ipc_router_remote_port *rport_ptr) +{ + struct msm_ipc_port *port_ptr; + struct ipc_router_conn_info *conn_info, *tmp_conn_info; + + mutex_lock(&rport_ptr->rport_lock_lhb2); + list_for_each_entry_safe(conn_info, tmp_conn_info, + &rport_ptr->conn_info_list, list) { + port_ptr = ipc_router_get_port_ref(conn_info->port_id); + if (port_ptr) { + mutex_lock(&port_ptr->port_lock_lhc3); + port_ptr->conn_status = CONNECTION_RESET; + mutex_unlock(&port_ptr->port_lock_lhc3); + wake_up(&port_ptr->port_rx_wait_q); + kref_put(&port_ptr->ref, ipc_router_release_port); + } + + list_del(&conn_info->list); + kfree(conn_info); + } + mutex_unlock(&rport_ptr->rport_lock_lhb2); +} + +/** + * ipc_router_set_conn() - Set the connection by initializing dest address + * @port_ptr: Local port in which the connection has to be set. + * @addr: Destination address of the connection. + * + * @return: 0 on success, standard Linux error codes on failure. + */ +int ipc_router_set_conn(struct msm_ipc_port *port_ptr, + struct msm_ipc_addr *addr) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct ipc_router_conn_info *conn_info; + + if (unlikely(!port_ptr || !addr)) + return -EINVAL; + + if (addr->addrtype != MSM_IPC_ADDR_ID) { + IPC_RTR_ERR("%s: Invalid Address type\n", __func__); + return -EINVAL; + } + + if (port_ptr->type == SERVER_PORT) { + IPC_RTR_ERR("%s: Connection refused on a server port\n", + __func__); + return -ECONNREFUSED; + } + + if (port_ptr->conn_status == CONNECTED) { + IPC_RTR_ERR("%s: Port %08x already connected\n", + __func__, port_ptr->this_port.port_id); + return -EISCONN; + } + + conn_info = kzalloc(sizeof(struct ipc_router_conn_info), GFP_KERNEL); + if (!conn_info) { + IPC_RTR_ERR("%s: Error allocating conn_info\n", __func__); + return -ENOMEM; + } + INIT_LIST_HEAD(&conn_info->list); + conn_info->port_id = port_ptr->this_port.port_id; + + rport_ptr = ipc_router_get_rport_ref(addr->addr.port_addr.node_id, + addr->addr.port_addr.port_id); + if (!rport_ptr) { + IPC_RTR_ERR("%s: Invalid remote endpoint\n", __func__); + kfree(conn_info); + return -ENODEV; + } + mutex_lock(&rport_ptr->rport_lock_lhb2); + list_add_tail(&conn_info->list, &rport_ptr->conn_info_list); + mutex_unlock(&rport_ptr->rport_lock_lhb2); + + mutex_lock(&port_ptr->port_lock_lhc3); + memcpy(&port_ptr->dest_addr, &addr->addr.port_addr, + sizeof(struct msm_ipc_port_addr)); + port_ptr->conn_status = CONNECTED; + mutex_unlock(&port_ptr->port_lock_lhc3); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + return 0; +} + +/** + * do_version_negotiation() - perform a version negotiation and set the version + * @xprt_info: Pointer to the IPC Router transport info structure. + * @msg: Pointer to the IPC Router HELLO message. + * + * This function performs the version negotiation by verifying the computed + * checksum first. If the checksum matches with the magic number, it sets the + * negotiated IPC Router version in transport. + */ +static void do_version_negotiation(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg) +{ + uint32_t magic; + unsigned version; + + if (!xprt_info) + return; + magic = ipc_router_calc_checksum(msg); + if (magic == IPC_ROUTER_HELLO_MAGIC) { + version = fls(msg->hello.versions & IPC_ROUTER_VER_BITMASK) - 1; + /*Bit 0 & 31 are reserved for future usage*/ + if ((version > 0) && + (version != (sizeof(version) * BITS_PER_BYTE - 1)) && + xprt_info->xprt->set_version) + xprt_info->xprt->set_version(xprt_info->xprt, version); + } +} + +static int process_hello_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg, + struct rr_header_v1 *hdr) +{ + int i, rc = 0; + union rr_control_msg ctl; + struct msm_ipc_routing_table_entry *rt_entry; + + if (!hdr) + return -EINVAL; + + xprt_info->remote_node_id = hdr->src_node_id; + rt_entry = create_routing_table_entry(hdr->src_node_id, xprt_info); + if (!rt_entry) { + IPC_RTR_ERR("%s: rt_entry allocation failed\n", __func__); + return -ENOMEM; + } + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + + do_version_negotiation(xprt_info, msg); + /* Send a reply HELLO message */ + memset(&ctl, 0, sizeof(ctl)); + ctl.hello.cmd = IPC_ROUTER_CTRL_CMD_HELLO; + ctl.hello.checksum = IPC_ROUTER_HELLO_MAGIC; + ctl.hello.versions = (uint32_t)IPC_ROUTER_VER_BITMASK; + ctl.hello.checksum = ipc_router_calc_checksum(&ctl); + rc = ipc_router_send_ctl_msg(xprt_info, &ctl, + IPC_ROUTER_DUMMY_DEST_NODE); + if (rc < 0) { + IPC_RTR_ERR("%s: Error sending reply HELLO message\n", + __func__); + return rc; + } + xprt_info->initialized = 1; + + /* Send list of servers from the local node and from nodes + * outside the mesh network in which this XPRT is part of. + */ + down_read(&server_list_lock_lha2); + down_read(&routing_table_lock_lha3); + for (i = 0; i < RT_HASH_SIZE; i++) { + list_for_each_entry(rt_entry, &routing_table[i], list) { + if ((rt_entry->node_id != IPC_ROUTER_NID_LOCAL) && + (!rt_entry->xprt_info || + (rt_entry->xprt_info->xprt->link_id == + xprt_info->xprt->link_id))) + continue; + rc = msm_ipc_router_send_server_list(rt_entry->node_id, + xprt_info); + if (rc < 0) { + up_read(&routing_table_lock_lha3); + up_read(&server_list_lock_lha2); + return rc; + } + } + } + up_read(&routing_table_lock_lha3); + up_read(&server_list_lock_lha2); + return rc; +} + +static int process_resume_tx_msg(union rr_control_msg *msg, + struct rr_packet *pkt) +{ + struct msm_ipc_router_remote_port *rport_ptr; + + + rport_ptr = ipc_router_get_rport_ref(msg->cli.node_id, + msg->cli.port_id); + if (!rport_ptr) { + IPC_RTR_ERR("%s: Unable to resume client\n", __func__); + return -ENODEV; + } + mutex_lock(&rport_ptr->rport_lock_lhb2); + rport_ptr->tx_quota_cnt = 0; + post_resume_tx(rport_ptr, pkt, msg); + mutex_unlock(&rport_ptr->rport_lock_lhb2); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + return 0; +} + +static int process_new_server_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg, struct rr_packet *pkt) +{ + struct msm_ipc_routing_table_entry *rt_entry; + struct msm_ipc_server *server; + struct msm_ipc_router_remote_port *rport_ptr; + + if (msg->srv.instance == 0) { + IPC_RTR_ERR("%s: Server %08x create rejected, version = 0\n", + __func__, msg->srv.service); + return -EINVAL; + } + + rt_entry = ipc_router_get_rtentry_ref(msg->srv.node_id); + if (!rt_entry) { + rt_entry = create_routing_table_entry(msg->srv.node_id, + xprt_info); + if (!rt_entry) { + IPC_RTR_ERR("%s: rt_entry allocation failed\n", + __func__); + return -ENOMEM; + } + } + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + + /* If the service already exists in the table, create_server returns + * a reference to it. + */ + rport_ptr = ipc_router_create_rport(msg->srv.node_id, + msg->srv.port_id, xprt_info); + if (!rport_ptr) + return -ENOMEM; + + server = msm_ipc_router_create_server( + msg->srv.service, msg->srv.instance, + msg->srv.node_id, msg->srv.port_id, xprt_info); + if (!server) { + IPC_RTR_ERR("%s: Server %08x:%08x Create failed\n", + __func__, msg->srv.service, msg->srv.instance); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + ipc_router_destroy_rport(rport_ptr); + return -ENOMEM; + } + mutex_lock(&rport_ptr->rport_lock_lhb2); + rport_ptr->server = server; + mutex_unlock(&rport_ptr->rport_lock_lhb2); + rport_ptr->sec_rule = msm_ipc_get_security_rule( + msg->srv.service, msg->srv.instance); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + kref_put(&server->ref, ipc_router_release_server); + + /* Relay the new server message to other subsystems that do not belong + * to the cluster from which this message is received. Notify the + * local clients waiting for this service. + */ + relay_ctl_msg(xprt_info, msg); + post_control_ports(pkt); + return 0; +} + +static int process_rmv_server_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg, struct rr_packet *pkt) +{ + struct msm_ipc_server *server; + struct msm_ipc_router_remote_port *rport_ptr; + + server = ipc_router_get_server_ref(msg->srv.service, msg->srv.instance, + msg->srv.node_id, msg->srv.port_id); + rport_ptr = ipc_router_get_rport_ref(msg->srv.node_id, + msg->srv.port_id); + if (rport_ptr) { + mutex_lock(&rport_ptr->rport_lock_lhb2); + if (rport_ptr->server == server) + rport_ptr->server = NULL; + mutex_unlock(&rport_ptr->rport_lock_lhb2); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + } + + if (server) { + kref_put(&server->ref, ipc_router_release_server); + ipc_router_destroy_server(server, msg->srv.node_id, + msg->srv.port_id); + /* + * Relay the new server message to other subsystems that do not + * belong to the cluster from which this message is received. + * Notify the local clients communicating with the service. + */ + relay_ctl_msg(xprt_info, msg); + post_control_ports(pkt); + } + return 0; +} + +static int process_rmv_client_msg(struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg, struct rr_packet *pkt) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_server *server; + + rport_ptr = ipc_router_get_rport_ref(msg->cli.node_id, + msg->cli.port_id); + if (rport_ptr) { + mutex_lock(&rport_ptr->rport_lock_lhb2); + server = rport_ptr->server; + rport_ptr->server = NULL; + mutex_unlock(&rport_ptr->rport_lock_lhb2); + ipc_router_reset_conn(rport_ptr); + down_write(&server_list_lock_lha2); + if (server) + cleanup_rmt_server(NULL, rport_ptr, server); + up_write(&server_list_lock_lha2); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + ipc_router_destroy_rport(rport_ptr); + } + + relay_ctl_msg(xprt_info, msg); + post_control_ports(pkt); + return 0; +} + +static int process_control_msg(struct msm_ipc_router_xprt_info *xprt_info, + struct rr_packet *pkt) +{ + union rr_control_msg *msg; + int rc = 0; + struct rr_header_v1 *hdr; + + if (pkt->length != sizeof(*msg)) { + IPC_RTR_ERR("%s: r2r msg size %d != %zu\n", + __func__, pkt->length, sizeof(*msg)); + return -EINVAL; + } + + hdr = &(pkt->hdr); + msg = msm_ipc_router_skb_to_buf(pkt->pkt_fragment_q, sizeof(*msg)); + if (!msg) { + IPC_RTR_ERR("%s: Error extracting control msg\n", __func__); + return -ENOMEM; + } + + ipc_router_log_msg(xprt_info->log_ctx, IPC_ROUTER_LOG_EVENT_RX, + msg, hdr, NULL, NULL); + + switch (msg->cmd) { + case IPC_ROUTER_CTRL_CMD_HELLO: + rc = process_hello_msg(xprt_info, msg, hdr); + break; + case IPC_ROUTER_CTRL_CMD_RESUME_TX: + rc = process_resume_tx_msg(msg, pkt); + break; + case IPC_ROUTER_CTRL_CMD_NEW_SERVER: + rc = process_new_server_msg(xprt_info, msg, pkt); + break; + case IPC_ROUTER_CTRL_CMD_REMOVE_SERVER: + rc = process_rmv_server_msg(xprt_info, msg, pkt); + break; + case IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT: + rc = process_rmv_client_msg(xprt_info, msg, pkt); + break; + default: + rc = -ENOSYS; + } + kfree(msg); + return rc; +} + +static void do_read_data(struct work_struct *work) +{ + struct rr_header_v1 *hdr; + struct rr_packet *pkt = NULL; + struct msm_ipc_port *port_ptr; + struct msm_ipc_router_remote_port *rport_ptr; + + struct msm_ipc_router_xprt_info *xprt_info = + container_of(work, + struct msm_ipc_router_xprt_info, + read_data); + + while ((pkt = rr_read(xprt_info)) != NULL) { + + hdr = &(pkt->hdr); + + if ((hdr->dst_node_id != IPC_ROUTER_NID_LOCAL) && + ((hdr->type == IPC_ROUTER_CTRL_CMD_RESUME_TX) || + (hdr->type == IPC_ROUTER_CTRL_CMD_DATA))) { + IPC_RTR_INFO(xprt_info->log_ctx, + "%s %s Len:0x%x T:0x%x CF:0x%x SRC:<0x%x:0x%x> DST:<0x%x:0x%x>\n", + "FWD", "RX", hdr->size, hdr->type, hdr->control_flag, + hdr->src_node_id, hdr->src_port_id, + hdr->dst_node_id, hdr->dst_port_id); + forward_msg(xprt_info, pkt); + goto read_next_pkt1; + } + + if (hdr->type != IPC_ROUTER_CTRL_CMD_DATA) { + process_control_msg(xprt_info, pkt); + goto read_next_pkt1; + } + + if (msm_ipc_router_debug_mask & SMEM_LOG) { + smem_log_event((SMEM_LOG_PROC_ID_APPS | + SMEM_LOG_IPC_ROUTER_EVENT_BASE | + IPC_ROUTER_LOG_EVENT_RX), + (hdr->src_node_id << 24) | + (hdr->src_port_id & 0xffffff), + (hdr->dst_node_id << 24) | + (hdr->dst_port_id & 0xffffff), + (hdr->type << 24) | (hdr->control_flag << 16) | + (hdr->size & 0xffff)); + } + + port_ptr = ipc_router_get_port_ref(hdr->dst_port_id); + if (!port_ptr) { + IPC_RTR_ERR("%s: No local port id %08x\n", __func__, + hdr->dst_port_id); + goto read_next_pkt1; + } + + rport_ptr = ipc_router_get_rport_ref(hdr->src_node_id, + hdr->src_port_id); + if (!rport_ptr) { + rport_ptr = ipc_router_create_rport(hdr->src_node_id, + hdr->src_port_id, xprt_info); + if (!rport_ptr) { + IPC_RTR_ERR( + "%s: Rmt Prt %08x:%08x create failed\n", + __func__, hdr->src_node_id, hdr->src_port_id); + goto read_next_pkt2; + } + } + + ipc_router_log_msg(xprt_info->log_ctx, IPC_ROUTER_LOG_EVENT_RX, + pkt, hdr, port_ptr, rport_ptr); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + post_pkt_to_port(port_ptr, pkt, 0); + kref_put(&port_ptr->ref, ipc_router_release_port); + continue; +read_next_pkt2: + kref_put(&port_ptr->ref, ipc_router_release_port); +read_next_pkt1: + release_pkt(pkt); + } +} + +int msm_ipc_router_register_server(struct msm_ipc_port *port_ptr, + struct msm_ipc_addr *name) +{ + struct msm_ipc_server *server; + union rr_control_msg ctl; + struct msm_ipc_router_remote_port *rport_ptr; + + if (!port_ptr || !name) + return -EINVAL; + + if (port_ptr->type != CLIENT_PORT) + return -EINVAL; + + if (name->addrtype != MSM_IPC_ADDR_NAME) + return -EINVAL; + + rport_ptr = ipc_router_create_rport(IPC_ROUTER_NID_LOCAL, + port_ptr->this_port.port_id, NULL); + if (!rport_ptr) { + IPC_RTR_ERR("%s: RPort %08x:%08x creation failed\n", __func__, + IPC_ROUTER_NID_LOCAL, port_ptr->this_port.port_id); + return -ENOMEM; + } + + server = msm_ipc_router_create_server(name->addr.port_name.service, + name->addr.port_name.instance, + IPC_ROUTER_NID_LOCAL, + port_ptr->this_port.port_id, + NULL); + if (!server) { + IPC_RTR_ERR("%s: Server %08x:%08x Create failed\n", + __func__, name->addr.port_name.service, + name->addr.port_name.instance); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + ipc_router_destroy_rport(rport_ptr); + return -ENOMEM; + } + + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = IPC_ROUTER_CTRL_CMD_NEW_SERVER; + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + ctl.srv.node_id = IPC_ROUTER_NID_LOCAL; + ctl.srv.port_id = port_ptr->this_port.port_id; + broadcast_ctl_msg(&ctl); + mutex_lock(&port_ptr->port_lock_lhc3); + 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; + port_ptr->rport_info = rport_ptr; + mutex_unlock(&port_ptr->port_lock_lhc3); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + kref_put(&server->ref, ipc_router_release_server); + return 0; +} + +int msm_ipc_router_unregister_server(struct msm_ipc_port *port_ptr) +{ + struct msm_ipc_server *server; + union rr_control_msg ctl; + struct msm_ipc_router_remote_port *rport_ptr; + + if (!port_ptr) + return -EINVAL; + + if (port_ptr->type != SERVER_PORT) { + IPC_RTR_ERR("%s: Trying to unregister a non-server port\n", + __func__); + return -EINVAL; + } + + if (port_ptr->this_port.node_id != IPC_ROUTER_NID_LOCAL) { + IPC_RTR_ERR( + "%s: Trying to unregister a remote server locally\n", + __func__); + return -EINVAL; + } + + server = ipc_router_get_server_ref(port_ptr->port_name.service, + port_ptr->port_name.instance, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + if (!server) { + IPC_RTR_ERR("%s: Server lookup failed\n", __func__); + return -ENODEV; + } + + mutex_lock(&port_ptr->port_lock_lhc3); + port_ptr->type = CLIENT_PORT; + rport_ptr = (struct msm_ipc_router_remote_port *)port_ptr->rport_info; + mutex_unlock(&port_ptr->port_lock_lhc3); + if (rport_ptr) + ipc_router_reset_conn(rport_ptr); + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + ctl.srv.node_id = IPC_ROUTER_NID_LOCAL; + ctl.srv.port_id = port_ptr->this_port.port_id; + kref_put(&server->ref, ipc_router_release_server); + ipc_router_destroy_server(server, port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + broadcast_ctl_msg(&ctl); + mutex_lock(&port_ptr->port_lock_lhc3); + port_ptr->type = CLIENT_PORT; + mutex_unlock(&port_ptr->port_lock_lhc3); + return 0; +} + +static int loopback_data(struct msm_ipc_port *src, + uint32_t port_id, + struct rr_packet *pkt) +{ + struct msm_ipc_port *port_ptr; + struct sk_buff *temp_skb; + int align_size; + + if (!pkt) { + IPC_RTR_ERR("%s: Invalid pkt pointer\n", __func__); + return -EINVAL; + } + + temp_skb = skb_peek_tail(pkt->pkt_fragment_q); + if (!temp_skb) { + IPC_RTR_ERR("%s: Empty skb\n", __func__); + return -EINVAL; + } + align_size = ALIGN_SIZE(pkt->length); + skb_put(temp_skb, align_size); + pkt->length += align_size; + + port_ptr = ipc_router_get_port_ref(port_id); + if (!port_ptr) { + IPC_RTR_ERR("%s: Local port %d not present\n", + __func__, port_id); + return -ENODEV; + } + post_pkt_to_port(port_ptr, pkt, 1); + update_comm_mode_info(&src->mode_info, NULL); + kref_put(&port_ptr->ref, ipc_router_release_port); + + return pkt->hdr.size; +} + +static int ipc_router_tx_wait(struct msm_ipc_port *src, + struct msm_ipc_router_remote_port *rport_ptr, + uint32_t *set_confirm_rx, + long timeout) +{ + struct msm_ipc_resume_tx_port *resume_tx_port; + int ret; + + if (unlikely(!src || !rport_ptr)) + return -EINVAL; + + for (;;) { + mutex_lock(&rport_ptr->rport_lock_lhb2); + if (rport_ptr->status == RESET) { + mutex_unlock(&rport_ptr->rport_lock_lhb2); + IPC_RTR_ERR("%s: RPort %08x:%08x is in reset state\n", + __func__, rport_ptr->node_id, rport_ptr->port_id); + return -ENETRESET; + } + + if (rport_ptr->tx_quota_cnt < IPC_ROUTER_HIGH_RX_QUOTA) + break; + + if (msm_ipc_router_lookup_resume_tx_port( + rport_ptr, src->this_port.port_id)) + goto check_timeo; + + resume_tx_port = + kzalloc(sizeof(struct msm_ipc_resume_tx_port), + GFP_KERNEL); + if (!resume_tx_port) { + IPC_RTR_ERR("%s: Resume_Tx port allocation failed\n", + __func__); + mutex_unlock(&rport_ptr->rport_lock_lhb2); + return -ENOMEM; + } + INIT_LIST_HEAD(&resume_tx_port->list); + resume_tx_port->port_id = src->this_port.port_id; + resume_tx_port->node_id = src->this_port.node_id; + list_add_tail(&resume_tx_port->list, + &rport_ptr->resume_tx_port_list); +check_timeo: + mutex_unlock(&rport_ptr->rport_lock_lhb2); + if (!timeout) { + return -EAGAIN; + } else if (timeout < 0) { + ret = wait_event_interruptible(src->port_tx_wait_q, + (rport_ptr->tx_quota_cnt != + IPC_ROUTER_HIGH_RX_QUOTA || + rport_ptr->status == RESET)); + if (ret) + return ret; + } else { + ret = wait_event_interruptible_timeout( + src->port_tx_wait_q, + (rport_ptr->tx_quota_cnt != + IPC_ROUTER_HIGH_RX_QUOTA || + rport_ptr->status == RESET), + msecs_to_jiffies(timeout)); + if (ret < 0) { + return ret; + } else if (ret == 0) { + IPC_RTR_ERR("%s: Resume_tx Timeout %08x:%08x\n", + __func__, rport_ptr->node_id, + rport_ptr->port_id); + return -ETIMEDOUT; + } + } + } + rport_ptr->tx_quota_cnt++; + if (rport_ptr->tx_quota_cnt == IPC_ROUTER_LOW_RX_QUOTA) + *set_confirm_rx = 1; + mutex_unlock(&rport_ptr->rport_lock_lhb2); + return 0; +} + +static int msm_ipc_router_write_pkt(struct msm_ipc_port *src, + struct msm_ipc_router_remote_port *rport_ptr, + struct rr_packet *pkt, + long timeout) +{ + struct rr_header_v1 *hdr; + struct msm_ipc_router_xprt_info *xprt_info; + struct msm_ipc_routing_table_entry *rt_entry; + struct sk_buff *temp_skb; + int xprt_option; + int ret; + int align_size; + uint32_t set_confirm_rx = 0; + + if (!rport_ptr || !src || !pkt) + return -EINVAL; + + hdr = &(pkt->hdr); + hdr->version = IPC_ROUTER_V1; + hdr->type = IPC_ROUTER_CTRL_CMD_DATA; + hdr->src_node_id = src->this_port.node_id; + hdr->src_port_id = src->this_port.port_id; + hdr->size = pkt->length; + hdr->control_flag = 0; + hdr->dst_node_id = rport_ptr->node_id; + hdr->dst_port_id = rport_ptr->port_id; + + ret = ipc_router_tx_wait(src, rport_ptr, &set_confirm_rx, timeout); + if (ret < 0) + return ret; + if (set_confirm_rx) + hdr->control_flag |= CONTROL_FLAG_CONFIRM_RX; + + if (hdr->dst_node_id == IPC_ROUTER_NID_LOCAL) { + ipc_router_log_msg(local_log_ctx, + IPC_ROUTER_LOG_EVENT_TX, pkt, hdr, src, rport_ptr); + ret = loopback_data(src, hdr->dst_port_id, pkt); + return ret; + } + + rt_entry = ipc_router_get_rtentry_ref(hdr->dst_node_id); + if (!rt_entry) { + IPC_RTR_ERR("%s: Remote node %d not up\n", + __func__, hdr->dst_node_id); + return -ENODEV; + } + down_read(&rt_entry->lock_lha4); + xprt_info = rt_entry->xprt_info; + ret = ipc_router_get_xprt_info_ref(xprt_info); + if (ret < 0) { + IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__); + up_read(&rt_entry->lock_lha4); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return ret; + } + ret = prepend_header(pkt, xprt_info); + if (ret < 0) { + IPC_RTR_ERR("%s: Prepend Header failed\n", __func__); + goto out_write_pkt; + } + xprt_option = xprt_info->xprt->get_option(xprt_info->xprt); + if (!(xprt_option & FRAG_PKT_WRITE_ENABLE)) { + ret = defragment_pkt(pkt); + if (ret < 0) + goto out_write_pkt; + } + + temp_skb = skb_peek_tail(pkt->pkt_fragment_q); + if (!temp_skb) { + IPC_RTR_ERR("%s: Abort invalid pkt\n", __func__); + ret = -EINVAL; + goto out_write_pkt; + } + align_size = ALIGN_SIZE(pkt->length); + skb_put(temp_skb, align_size); + pkt->length += align_size; + mutex_lock(&xprt_info->tx_lock_lhb2); + ret = xprt_info->xprt->write(pkt, pkt->length, xprt_info->xprt); + mutex_unlock(&xprt_info->tx_lock_lhb2); +out_write_pkt: + up_read(&rt_entry->lock_lha4); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + + if (ret < 0) { + IPC_RTR_ERR("%s: Write on XPRT failed\n", __func__); + ipc_router_log_msg(xprt_info->log_ctx, + IPC_ROUTER_LOG_EVENT_TX_ERR, pkt, hdr, src, rport_ptr); + + ipc_router_put_xprt_info_ref(xprt_info); + return ret; + } + update_comm_mode_info(&src->mode_info, xprt_info); + ipc_router_log_msg(xprt_info->log_ctx, + IPC_ROUTER_LOG_EVENT_TX, pkt, hdr, src, rport_ptr); + if (msm_ipc_router_debug_mask & SMEM_LOG) { + smem_log_event((SMEM_LOG_PROC_ID_APPS | + SMEM_LOG_IPC_ROUTER_EVENT_BASE | + IPC_ROUTER_LOG_EVENT_TX), + (hdr->src_node_id << 24) | + (hdr->src_port_id & 0xffffff), + (hdr->dst_node_id << 24) | + (hdr->dst_port_id & 0xffffff), + (hdr->type << 24) | (hdr->control_flag << 16) | + (hdr->size & 0xffff)); + } + + ipc_router_put_xprt_info_ref(xprt_info); + return hdr->size; +} + +int msm_ipc_router_send_to(struct msm_ipc_port *src, + struct sk_buff_head *data, + struct msm_ipc_addr *dest, + long timeout) +{ + uint32_t dst_node_id = 0, dst_port_id = 0; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + struct msm_ipc_router_remote_port *rport_ptr = NULL; + struct msm_ipc_router_remote_port *src_rport_ptr = NULL; + struct rr_packet *pkt; + int ret; + + if (!src || !data || !dest) { + IPC_RTR_ERR("%s: Invalid Parameters\n", __func__); + return -EINVAL; + } + + /* Resolve Address*/ + if (dest->addrtype == MSM_IPC_ADDR_ID) { + dst_node_id = dest->addr.port_addr.node_id; + dst_port_id = dest->addr.port_addr.port_id; + } else if (dest->addrtype == MSM_IPC_ADDR_NAME) { + server = ipc_router_get_server_ref( + dest->addr.port_name.service, + dest->addr.port_name.instance, + 0, 0); + if (!server) { + IPC_RTR_ERR("%s: Destination not reachable\n", + __func__); + return -ENODEV; + } + server_port = list_first_entry(&server->server_port_list, + struct msm_ipc_server_port, + list); + dst_node_id = server_port->server_addr.node_id; + dst_port_id = server_port->server_addr.port_id; + kref_put(&server->ref, ipc_router_release_server); + } + + rport_ptr = ipc_router_get_rport_ref(dst_node_id, dst_port_id); + if (!rport_ptr) { + IPC_RTR_ERR("%s: Remote port not found\n", __func__); + return -ENODEV; + } + + if (src->check_send_permissions) { + ret = src->check_send_permissions(rport_ptr->sec_rule); + if (ret <= 0) { + kref_put(&rport_ptr->ref, ipc_router_release_rport); + IPC_RTR_ERR("%s: permission failure for %s\n", + __func__, current->comm); + return -EPERM; + } + } + + if (dst_node_id == IPC_ROUTER_NID_LOCAL && !src->rport_info) { + src_rport_ptr = ipc_router_create_rport(IPC_ROUTER_NID_LOCAL, + src->this_port.port_id, NULL); + if (!src_rport_ptr) { + kref_put(&rport_ptr->ref, ipc_router_release_rport); + IPC_RTR_ERR("%s: RPort creation failed\n", __func__); + return -ENOMEM; + } + mutex_lock(&src->port_lock_lhc3); + src->rport_info = src_rport_ptr; + mutex_unlock(&src->port_lock_lhc3); + kref_put(&src_rport_ptr->ref, ipc_router_release_rport); + } + + pkt = create_pkt(data); + if (!pkt) { + kref_put(&rport_ptr->ref, ipc_router_release_rport); + IPC_RTR_ERR("%s: Pkt creation failed\n", __func__); + return -ENOMEM; + } + + ret = msm_ipc_router_write_pkt(src, rport_ptr, pkt, timeout); + kref_put(&rport_ptr->ref, ipc_router_release_rport); + if (ret < 0) + pkt->pkt_fragment_q = NULL; + release_pkt(pkt); + + return ret; +} + +int msm_ipc_router_send_msg(struct msm_ipc_port *src, + struct msm_ipc_addr *dest, + void *data, unsigned int data_len) +{ + struct sk_buff_head *out_skb_head; + int ret; + + out_skb_head = msm_ipc_router_buf_to_skb(data, data_len); + if (!out_skb_head) { + IPC_RTR_ERR("%s: SKB conversion failed\n", __func__); + return -EFAULT; + } + + ret = msm_ipc_router_send_to(src, out_skb_head, dest, 0); + if (ret < 0) { + if (ret != -EAGAIN) + IPC_RTR_ERR( + "%s: msm_ipc_router_send_to failed - ret: %d\n", + __func__, ret); + msm_ipc_router_free_skb(out_skb_head); + return ret; + } + return 0; +} + +/** + * msm_ipc_router_send_resume_tx() - Send Resume_Tx message + * @data: Pointer to received data packet that has confirm_rx bit set + * + * @return: On success, number of bytes transferred is returned, else + * standard linux error code is returned. + * + * This function sends the Resume_Tx event to the remote node that + * sent the data with confirm_rx field set. In case of a multi-hop + * scenario also, this function makes sure that the destination node_id + * to which the resume_tx event should reach is right. + */ +static int msm_ipc_router_send_resume_tx(void *data) +{ + union rr_control_msg msg; + struct rr_header_v1 *hdr = (struct rr_header_v1 *)data; + struct msm_ipc_routing_table_entry *rt_entry; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.cmd = IPC_ROUTER_CTRL_CMD_RESUME_TX; + msg.cli.node_id = hdr->dst_node_id; + msg.cli.port_id = hdr->dst_port_id; + rt_entry = ipc_router_get_rtentry_ref(hdr->src_node_id); + if (!rt_entry) { + IPC_RTR_ERR("%s: %d Node is not present", + __func__, hdr->src_node_id); + return -ENODEV; + } + ret = ipc_router_get_xprt_info_ref(rt_entry->xprt_info); + if (ret < 0) { + IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + return ret; + } + ret = ipc_router_send_ctl_msg(rt_entry->xprt_info, &msg, + hdr->src_node_id); + ipc_router_put_xprt_info_ref(rt_entry->xprt_info); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + if (ret < 0) + IPC_RTR_ERR( + "%s: Send Resume_Tx Failed SRC_NODE: %d SRC_PORT: %d DEST_NODE: %d", + __func__, hdr->dst_node_id, hdr->dst_port_id, + hdr->src_node_id); + + return ret; +} + +int msm_ipc_router_read(struct msm_ipc_port *port_ptr, + struct rr_packet **read_pkt, + size_t buf_len) +{ + struct rr_packet *pkt; + + if (!port_ptr || !read_pkt) + return -EINVAL; + + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + if (list_empty(&port_ptr->port_rx_q)) { + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + return -EAGAIN; + } + + pkt = list_first_entry(&port_ptr->port_rx_q, struct rr_packet, list); + if ((buf_len) && (pkt->hdr.size > buf_len)) { + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + return -ETOOSMALL; + } + list_del(&pkt->list); + if (list_empty(&port_ptr->port_rx_q)) + __pm_relax(port_ptr->port_rx_ws); + *read_pkt = pkt; + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + if (pkt->hdr.control_flag & CONTROL_FLAG_CONFIRM_RX) + msm_ipc_router_send_resume_tx(&pkt->hdr); + + return pkt->length; +} + +/** + * msm_ipc_router_rx_data_wait() - Wait for new message destined to a local port. + * @port_ptr: Pointer to the local port + * @timeout: < 0 timeout indicates infinite wait till a message arrives. + * > 0 timeout indicates the wait time. + * 0 indicates that we do not wait. + * @return: 0 if there are pending messages to read, + * standard Linux error code otherwise. + * + * Checks for the availability of messages that are destined to a local port. + * If no messages are present then waits as per @timeout. + */ +int msm_ipc_router_rx_data_wait(struct msm_ipc_port *port_ptr, long timeout) +{ + int ret = 0; + + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + while (list_empty(&port_ptr->port_rx_q)) { + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + if (timeout < 0) { + ret = wait_event_interruptible( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q)); + if (ret) + return ret; + } else if (timeout > 0) { + timeout = wait_event_interruptible_timeout( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q), + timeout); + if (timeout < 0) + return -EFAULT; + } + if (timeout == 0) + return -ENOMSG; + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + } + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + + return ret; +} + +/** + * msm_ipc_router_recv_from() - Recieve messages destined to a local port. + * @port_ptr: Pointer to the local port + * @pkt : Pointer to the router-to-router packet + * @src: Pointer to local port address + * @timeout: < 0 timeout indicates infinite wait till a message arrives. + * > 0 timeout indicates the wait time. + * 0 indicates that we do not wait. + * @return: = Number of bytes read(On successful read operation). + * = -ENOMSG (If there are no pending messages and timeout is 0). + * = -EINVAL (If either of the arguments, port_ptr or data is invalid) + * = -EFAULT (If there are no pending messages when timeout is > 0 + * and the wait_event_interruptible_timeout has returned value > 0) + * = -ERESTARTSYS (If there are no pending messages when timeout + * is < 0 and wait_event_interruptible was interrupted by a signal) + * + * This function reads the messages that are destined for a local port. It + * is used by modules that exist with-in the kernel and use IPC Router for + * transport. The function checks if there are any messages that are already + * received. If yes, it reads them, else it waits as per the timeout value. + * On a successful read, the return value of the function indicates the number + * of bytes that are read. + */ +int msm_ipc_router_recv_from(struct msm_ipc_port *port_ptr, + struct rr_packet **pkt, + struct msm_ipc_addr *src, + long timeout) +{ + int ret, data_len, align_size; + struct sk_buff *temp_skb; + struct rr_header_v1 *hdr = NULL; + + if (!port_ptr || !pkt) { + IPC_RTR_ERR("%s: Invalid pointers being passed\n", __func__); + return -EINVAL; + } + + *pkt = NULL; + + ret = msm_ipc_router_rx_data_wait(port_ptr, timeout); + if (ret) + return ret; + + ret = msm_ipc_router_read(port_ptr, pkt, 0); + if (ret <= 0 || !(*pkt)) + return ret; + + hdr = &((*pkt)->hdr); + if (src) { + src->addrtype = MSM_IPC_ADDR_ID; + src->addr.port_addr.node_id = hdr->src_node_id; + src->addr.port_addr.port_id = hdr->src_port_id; + } + + data_len = hdr->size; + align_size = ALIGN_SIZE(data_len); + if (align_size) { + temp_skb = skb_peek_tail((*pkt)->pkt_fragment_q); + if (temp_skb) + skb_trim(temp_skb, (temp_skb->len - align_size)); + } + return data_len; +} + +int msm_ipc_router_read_msg(struct msm_ipc_port *port_ptr, + struct msm_ipc_addr *src, + unsigned char **data, + unsigned int *len) +{ + struct rr_packet *pkt; + int ret; + + ret = msm_ipc_router_recv_from(port_ptr, &pkt, src, 0); + if (ret < 0) { + if (ret != -ENOMSG) + IPC_RTR_ERR( + "%s: msm_ipc_router_recv_from failed - ret: %d\n", + __func__, ret); + return ret; + } + + *data = msm_ipc_router_skb_to_buf(pkt->pkt_fragment_q, ret); + if (!(*data)) { + IPC_RTR_ERR("%s: Buf conversion failed\n", __func__); + release_pkt(pkt); + return -ENOMEM; + } + + *len = ret; + release_pkt(pkt); + return 0; +} + +/** + * msm_ipc_router_create_port() - Create a IPC Router port/endpoint + * @notify: Callback function to notify any event on the port. + * @event: Event ID to be handled. + * @oob_data: Any out-of-band data associated with the event. + * @oob_data_len: Size of the out-of-band data, if valid. + * @priv: Private data registered during the port creation. + * @priv: Private info to be passed while the notification is generated. + * + * @return: Pointer to the port on success, NULL on error. + */ +struct msm_ipc_port *msm_ipc_router_create_port( + void (*notify)(unsigned event, void *oob_data, + size_t oob_data_len, void *priv), + void *priv) +{ + struct msm_ipc_port *port_ptr; + int ret; + + ret = ipc_router_core_init(); + if (ret < 0) { + IPC_RTR_ERR("%s: Error %d initializing IPC Router\n", + __func__, ret); + return NULL; + } + + port_ptr = msm_ipc_router_create_raw_port(NULL, notify, priv); + if (!port_ptr) + IPC_RTR_ERR("%s: port_ptr alloc failed\n", __func__); + + return port_ptr; +} + +int msm_ipc_router_close_port(struct msm_ipc_port *port_ptr) +{ + union rr_control_msg msg; + struct msm_ipc_server *server; + struct msm_ipc_router_remote_port *rport_ptr; + + if (!port_ptr) + return -EINVAL; + + if (port_ptr->type == SERVER_PORT || port_ptr->type == CLIENT_PORT) { + down_write(&local_ports_lock_lhc2); + list_del(&port_ptr->list); + up_write(&local_ports_lock_lhc2); + + mutex_lock(&port_ptr->port_lock_lhc3); + rport_ptr = (struct msm_ipc_router_remote_port *) + port_ptr->rport_info; + port_ptr->rport_info = NULL; + mutex_unlock(&port_ptr->port_lock_lhc3); + if (rport_ptr) { + ipc_router_reset_conn(rport_ptr); + ipc_router_destroy_rport(rport_ptr); + } + + if (port_ptr->type == SERVER_PORT) { + memset(&msg, 0, sizeof(msg)); + msg.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + msg.srv.service = port_ptr->port_name.service; + msg.srv.instance = port_ptr->port_name.instance; + msg.srv.node_id = port_ptr->this_port.node_id; + msg.srv.port_id = port_ptr->this_port.port_id; + broadcast_ctl_msg(&msg); + } + + /* Server port could have been a client port earlier. + * Send REMOVE_CLIENT message in either case. + */ + 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) { + down_write(&control_ports_lock_lha5); + list_del(&port_ptr->list); + up_write(&control_ports_lock_lha5); + } else if (port_ptr->type == IRSC_PORT) { + down_write(&local_ports_lock_lhc2); + list_del(&port_ptr->list); + up_write(&local_ports_lock_lhc2); + signal_irsc_completion(); + } + + if (port_ptr->type == SERVER_PORT) { + server = ipc_router_get_server_ref( + port_ptr->port_name.service, + port_ptr->port_name.instance, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + if (server) { + kref_put(&server->ref, ipc_router_release_server); + ipc_router_destroy_server(server, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + } + } + + mutex_lock(&port_ptr->port_lock_lhc3); + rport_ptr = (struct msm_ipc_router_remote_port *)port_ptr->rport_info; + port_ptr->rport_info = NULL; + mutex_unlock(&port_ptr->port_lock_lhc3); + if (rport_ptr) + ipc_router_destroy_rport(rport_ptr); + + kref_put(&port_ptr->ref, ipc_router_release_port); + return 0; +} + +int msm_ipc_router_get_curr_pkt_size(struct msm_ipc_port *port_ptr) +{ + struct rr_packet *pkt; + int rc = 0; + + if (!port_ptr) + return -EINVAL; + + mutex_lock(&port_ptr->port_rx_q_lock_lhc3); + if (!list_empty(&port_ptr->port_rx_q)) { + pkt = list_first_entry(&port_ptr->port_rx_q, + struct rr_packet, list); + rc = pkt->hdr.size; + } + mutex_unlock(&port_ptr->port_rx_q_lock_lhc3); + + return rc; +} + +int msm_ipc_router_bind_control_port(struct msm_ipc_port *port_ptr) +{ + if (unlikely(!port_ptr || port_ptr->type != CLIENT_PORT)) + return -EINVAL; + + down_write(&local_ports_lock_lhc2); + list_del(&port_ptr->list); + up_write(&local_ports_lock_lhc2); + port_ptr->type = CONTROL_PORT; + down_write(&control_ports_lock_lha5); + list_add_tail(&port_ptr->list, &control_ports); + up_write(&control_ports_lock_lha5); + + return 0; +} + +int msm_ipc_router_lookup_server_name(struct msm_ipc_port_name *srv_name, + struct msm_ipc_server_info *srv_info, + int num_entries_in_array, + uint32_t lookup_mask) +{ + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int key, i = 0; /*num_entries_found*/ + + if (!srv_name) { + IPC_RTR_ERR("%s: Invalid srv_name\n", __func__); + return -EINVAL; + } + + if (num_entries_in_array && !srv_info) { + IPC_RTR_ERR("%s: srv_info NULL\n", __func__); + return -EINVAL; + } + + down_read(&server_list_lock_lha2); + key = (srv_name->service & (SRV_HASH_SIZE - 1)); + list_for_each_entry(server, &server_list[key], list) { + if ((server->name.service != srv_name->service) || + ((server->name.instance & lookup_mask) != + srv_name->instance)) + continue; + + list_for_each_entry(server_port, + &server->server_port_list, list) { + if (i < num_entries_in_array) { + srv_info[i].node_id = + server_port->server_addr.node_id; + srv_info[i].port_id = + server_port->server_addr.port_id; + srv_info[i].service = server->name.service; + srv_info[i].instance = server->name.instance; + } + i++; + } + } + up_read(&server_list_lock_lha2); + + return i; +} + +int msm_ipc_router_close(void) +{ + struct msm_ipc_router_xprt_info *xprt_info, *tmp_xprt_info; + + down_write(&xprt_info_list_lock_lha5); + list_for_each_entry_safe(xprt_info, tmp_xprt_info, + &xprt_info_list, list) { + xprt_info->xprt->close(xprt_info->xprt); + list_del(&xprt_info->list); + kfree(xprt_info); + } + up_write(&xprt_info_list_lock_lha5); + return 0; +} + +/** + * pil_vote_load_worker() - Process vote to load the modem + * + * @work: Work item to process + * + * This function is called to process votes to load the modem that have been + * queued by msm_ipc_load_default_node(). + */ +static void pil_vote_load_worker(struct work_struct *work) +{ + struct pil_vote_info *vote_info; + + vote_info = container_of(work, struct pil_vote_info, load_work); + if (strlen(default_peripheral)) { + vote_info->pil_handle = subsystem_get(default_peripheral); + if (IS_ERR(vote_info->pil_handle)) { + IPC_RTR_ERR("%s: Failed to load %s\n", + __func__, default_peripheral); + vote_info->pil_handle = NULL; + } + } else { + vote_info->pil_handle = NULL; + } +} + +/** + * pil_vote_unload_worker() - Process vote to unload the modem + * + * @work: Work item to process + * + * This function is called to process votes to unload the modem that have been + * queued by msm_ipc_unload_default_node(). + */ +static void pil_vote_unload_worker(struct work_struct *work) +{ + struct pil_vote_info *vote_info; + + vote_info = container_of(work, struct pil_vote_info, unload_work); + + if (vote_info->pil_handle) { + subsystem_put(vote_info->pil_handle); + vote_info->pil_handle = NULL; + } + kfree(vote_info); +} + +/** + * msm_ipc_load_default_node() - Queue a vote to load the modem. + * + * @return: PIL vote info structure on success, NULL on failure. + * + * This function places a work item that loads the modem on the + * single-threaded workqueue used for processing PIL votes to load + * or unload the modem. + */ +void *msm_ipc_load_default_node(void) +{ + struct pil_vote_info *vote_info; + + vote_info = kmalloc(sizeof(*vote_info), GFP_KERNEL); + if (!vote_info) + return vote_info; + + INIT_WORK(&vote_info->load_work, pil_vote_load_worker); + queue_work(msm_ipc_router_workqueue, &vote_info->load_work); + + return vote_info; +} + +/** + * msm_ipc_unload_default_node() - Queue a vote to unload the modem. + * + * @pil_vote: PIL vote info structure, containing the PIL handle + * and work structure. + * + * This function places a work item that unloads the modem on the + * single-threaded workqueue used for processing PIL votes to load + * or unload the modem. + */ +void msm_ipc_unload_default_node(void *pil_vote) +{ + struct pil_vote_info *vote_info; + + if (pil_vote) { + vote_info = (struct pil_vote_info *)pil_vote; + INIT_WORK(&vote_info->unload_work, pil_vote_unload_worker); + queue_work(msm_ipc_router_workqueue, &vote_info->unload_work); + } +} + +#if defined(CONFIG_DEBUG_FS) +static void dump_routing_table(struct seq_file *s) +{ + int j; + struct msm_ipc_routing_table_entry *rt_entry; + + seq_printf(s, "%-10s|%-20s|%-10s|\n", + "Node Id", "XPRT Name", "Next Hop"); + seq_puts(s, "----------------------------------------------\n"); + for (j = 0; j < RT_HASH_SIZE; j++) { + down_read(&routing_table_lock_lha3); + list_for_each_entry(rt_entry, &routing_table[j], list) { + down_read(&rt_entry->lock_lha4); + seq_printf(s, "0x%08x|", rt_entry->node_id); + if (rt_entry->node_id == IPC_ROUTER_NID_LOCAL) + seq_printf(s, "%-20s|0x%08x|\n", + "Loopback", rt_entry->node_id); + else + seq_printf(s, "%-20s|0x%08x|\n", + rt_entry->xprt_info->xprt->name, + rt_entry->node_id); + up_read(&rt_entry->lock_lha4); + } + up_read(&routing_table_lock_lha3); + } +} + +static void dump_xprt_info(struct seq_file *s) +{ + struct msm_ipc_router_xprt_info *xprt_info; + + seq_printf(s, "%-20s|%-10s|%-12s|%-15s|\n", + "XPRT Name", "Link ID", + "Initialized", "Remote Node Id"); + seq_puts(s, "------------------------------------------------------------\n"); + down_read(&xprt_info_list_lock_lha5); + list_for_each_entry(xprt_info, &xprt_info_list, list) + seq_printf(s, "%-20s|0x%08x|%-12s|0x%08x|\n", + xprt_info->xprt->name, + xprt_info->xprt->link_id, + (xprt_info->initialized ? "Y" : "N"), + xprt_info->remote_node_id); + up_read(&xprt_info_list_lock_lha5); +} + +static void dump_servers(struct seq_file *s) +{ + int j; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + + seq_printf(s, "%-11s|%-11s|%-11s|%-11s|\n", + "Service", "Instance", "Node_id", "Port_id"); + seq_puts(s, "------------------------------------------------------------\n"); + down_read(&server_list_lock_lha2); + for (j = 0; j < SRV_HASH_SIZE; j++) { + list_for_each_entry(server, &server_list[j], list) { + list_for_each_entry(server_port, + &server->server_port_list, + list) + seq_printf(s, "0x%08x |0x%08x |0x%08x |0x%08x |\n", + server->name.service, + server->name.instance, + server_port->server_addr.node_id, + server_port->server_addr.port_id); + } + } + up_read(&server_list_lock_lha2); +} + +static void dump_remote_ports(struct seq_file *s) +{ + int j, k; + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + + seq_printf(s, "%-11s|%-11s|%-10s|\n", + "Node_id", "Port_id", "Quota_cnt"); + seq_puts(s, "------------------------------------------------------------\n"); + for (j = 0; j < RT_HASH_SIZE; j++) { + down_read(&routing_table_lock_lha3); + list_for_each_entry(rt_entry, &routing_table[j], list) { + down_read(&rt_entry->lock_lha4); + for (k = 0; k < RP_HASH_SIZE; k++) { + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[k], + list) + seq_printf(s, "0x%08x |0x%08x |0x%08x|\n", + rport_ptr->node_id, + rport_ptr->port_id, + rport_ptr->tx_quota_cnt); + } + up_read(&rt_entry->lock_lha4); + } + up_read(&routing_table_lock_lha3); + } +} + +static void dump_control_ports(struct seq_file *s) +{ + struct msm_ipc_port *port_ptr; + + seq_printf(s, "%-11s|%-11s|\n", + "Node_id", "Port_id"); + seq_puts(s, "------------------------------------------------------------\n"); + down_read(&control_ports_lock_lha5); + list_for_each_entry(port_ptr, &control_ports, list) + seq_printf(s, "0x%08x |0x%08x |\n", + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + up_read(&control_ports_lock_lha5); +} + +static void dump_local_ports(struct seq_file *s) +{ + int j; + struct msm_ipc_port *port_ptr; + + seq_printf(s, "%-11s|%-11s|%-32s|%-11s|\n", + "Node_id", "Port_id", "Wakelock", "Last SVCID"); + seq_puts(s, "------------------------------------------------------------\n"); + down_read(&local_ports_lock_lhc2); + for (j = 0; j < LP_HASH_SIZE; j++) { + list_for_each_entry(port_ptr, &local_ports[j], list) { + mutex_lock(&port_ptr->port_lock_lhc3); + seq_printf(s, "0x%08x |0x%08x |%-32s|0x%08x |\n", + port_ptr->this_port.node_id, + port_ptr->this_port.port_id, + port_ptr->rx_ws_name, + port_ptr->last_served_svc_id); + mutex_unlock(&port_ptr->port_lock_lhc3); + } + } + up_read(&local_ports_lock_lhc2); +} + +static int debugfs_show(struct seq_file *s, void *data) +{ + void (*show)(struct seq_file *) = s->private; + show(s); + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_show, inode->i_private); +} + +static const struct file_operations debug_ops = { + .open = debug_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static void debug_create(const char *name, struct dentry *dent, + void (*show)(struct seq_file *)) +{ + debugfs_create_file(name, 0444, dent, show, &debug_ops); +} + +static void debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("msm_ipc_router", 0); + if (IS_ERR(dent)) + return; + + debug_create("dump_local_ports", dent, dump_local_ports); + debug_create("dump_remote_ports", dent, dump_remote_ports); + debug_create("dump_control_ports", dent, dump_control_ports); + debug_create("dump_servers", dent, dump_servers); + debug_create("dump_xprt_info", dent, dump_xprt_info); + debug_create("dump_routing_table", dent, dump_routing_table); +} + +#else +static void debugfs_init(void) {} +#endif + +/** + * ipc_router_create_log_ctx() - Create and add the log context based on transport + * @name: subsystem name + * + * Return: a reference to the log context created + * + * This function creates ipc log context based on transport and adds it to a + * global list. This log context can be reused from the list in case of a + * subsystem restart. + */ +static void *ipc_router_create_log_ctx(char *name) +{ + struct ipc_rtr_log_ctx *sub_log_ctx; + + sub_log_ctx = kmalloc(sizeof(struct ipc_rtr_log_ctx), + GFP_KERNEL); + if (!sub_log_ctx) + return NULL; + sub_log_ctx->log_ctx = ipc_log_context_create( + IPC_RTR_INFO_PAGES, name, 0); + if (!sub_log_ctx->log_ctx) { + IPC_RTR_ERR("%s: Unable to create IPC logging for [%s]", + __func__, name); + kfree(sub_log_ctx); + return NULL; + } + strlcpy(sub_log_ctx->log_ctx_name, name, + LOG_CTX_NAME_LEN); + INIT_LIST_HEAD(&sub_log_ctx->list); + list_add_tail(&sub_log_ctx->list, &log_ctx_list); + return sub_log_ctx->log_ctx; +} + +static void ipc_router_log_ctx_init(void) +{ + mutex_lock(&log_ctx_list_lock_lha0); + local_log_ctx = ipc_router_create_log_ctx("local_IPCRTR"); + mutex_unlock(&log_ctx_list_lock_lha0); +} + +/** + * ipc_router_get_log_ctx() - Retrieves the ipc log context based on subsystem name. + * @sub_name: subsystem name + * + * Return: a reference to the log context + */ +static void *ipc_router_get_log_ctx(char *sub_name) +{ + void *log_ctx = NULL; + struct ipc_rtr_log_ctx *temp_log_ctx; + + mutex_lock(&log_ctx_list_lock_lha0); + list_for_each_entry(temp_log_ctx, &log_ctx_list, list) + if (!strcmp(temp_log_ctx->log_ctx_name, sub_name)) { + log_ctx = temp_log_ctx->log_ctx; + mutex_unlock(&log_ctx_list_lock_lha0); + return log_ctx; + } + log_ctx = ipc_router_create_log_ctx(sub_name); + mutex_unlock(&log_ctx_list_lock_lha0); + + return log_ctx; +} + +/** + * ipc_router_get_xprt_info_ref() - Get a reference to the xprt_info structure + * @xprt_info: pointer to the xprt_info. + * + * @return: Zero on success, -ENODEV on failure. + * + * This function is used to obtain a reference to the xprt_info structure + * corresponding to the requested @xprt_info pointer. + */ +static int ipc_router_get_xprt_info_ref( + struct msm_ipc_router_xprt_info *xprt_info) +{ + int ret = -ENODEV; + struct msm_ipc_router_xprt_info *tmp_xprt_info; + + if (!xprt_info) + return 0; + + down_read(&xprt_info_list_lock_lha5); + list_for_each_entry(tmp_xprt_info, &xprt_info_list, list) { + if (tmp_xprt_info == xprt_info) { + kref_get(&xprt_info->ref); + ret = 0; + break; + } + } + up_read(&xprt_info_list_lock_lha5); + + return ret; +} + +/** + * ipc_router_put_xprt_info_ref() - Put a reference to the xprt_info structure + * @xprt_info: pointer to the xprt_info. + * + * This function is used to put the reference to the xprt_info structure + * corresponding to the requested @xprt_info pointer. + */ +static void ipc_router_put_xprt_info_ref( + struct msm_ipc_router_xprt_info *xprt_info) +{ + if (xprt_info) + kref_put(&xprt_info->ref, ipc_router_release_xprt_info_ref); +} + +/** + * ipc_router_release_xprt_info_ref() - release the xprt_info last reference + * @ref: Reference to the xprt_info structure. + * + * This function is called when all references to the xprt_info structure + * are released. + */ +static void ipc_router_release_xprt_info_ref(struct kref *ref) +{ + struct msm_ipc_router_xprt_info *xprt_info = + container_of(ref, struct msm_ipc_router_xprt_info, ref); + + complete_all(&xprt_info->ref_complete); +} + +static int msm_ipc_router_add_xprt(struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_xprt_info *xprt_info; + + xprt_info = kmalloc(sizeof(struct msm_ipc_router_xprt_info), + GFP_KERNEL); + if (!xprt_info) + return -ENOMEM; + + xprt_info->xprt = xprt; + xprt_info->initialized = 0; + xprt_info->remote_node_id = -1; + INIT_LIST_HEAD(&xprt_info->pkt_list); + mutex_init(&xprt_info->rx_lock_lhb2); + mutex_init(&xprt_info->tx_lock_lhb2); + wakeup_source_init(&xprt_info->ws, xprt->name); + xprt_info->need_len = 0; + xprt_info->abort_data_read = 0; + INIT_WORK(&xprt_info->read_data, do_read_data); + INIT_LIST_HEAD(&xprt_info->list); + kref_init(&xprt_info->ref); + init_completion(&xprt_info->ref_complete); + xprt_info->dynamic_ws = 0; + if (xprt->get_ws_info) + xprt_info->dynamic_ws = xprt->get_ws_info(xprt); + + xprt_info->workqueue = create_singlethread_workqueue(xprt->name); + if (!xprt_info->workqueue) { + kfree(xprt_info); + return -ENOMEM; + } + + xprt_info->log_ctx = ipc_router_get_log_ctx(xprt->name); + + if (!strcmp(xprt->name, "msm_ipc_router_loopback_xprt")) { + xprt_info->remote_node_id = IPC_ROUTER_NID_LOCAL; + xprt_info->initialized = 1; + } + + IPC_RTR_INFO(xprt_info->log_ctx, "Adding xprt: [%s]\n", + xprt->name); + down_write(&xprt_info_list_lock_lha5); + list_add_tail(&xprt_info->list, &xprt_info_list); + up_write(&xprt_info_list_lock_lha5); + + down_write(&routing_table_lock_lha3); + if (!routing_table_inited) { + init_routing_table(); + routing_table_inited = 1; + } + up_write(&routing_table_lock_lha3); + + xprt->priv = xprt_info; + + return 0; +} + +static void msm_ipc_router_remove_xprt(struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_xprt_info *xprt_info; + struct rr_packet *temp_pkt, *pkt; + + if (xprt && xprt->priv) { + xprt_info = xprt->priv; + + IPC_RTR_INFO(xprt_info->log_ctx, "Removing xprt: [%s]\n", + xprt->name); + mutex_lock(&xprt_info->rx_lock_lhb2); + xprt_info->abort_data_read = 1; + mutex_unlock(&xprt_info->rx_lock_lhb2); + flush_workqueue(xprt_info->workqueue); + destroy_workqueue(xprt_info->workqueue); + mutex_lock(&xprt_info->rx_lock_lhb2); + list_for_each_entry_safe(pkt, temp_pkt, + &xprt_info->pkt_list, list) { + list_del(&pkt->list); + release_pkt(pkt); + } + mutex_unlock(&xprt_info->rx_lock_lhb2); + + down_write(&xprt_info_list_lock_lha5); + list_del(&xprt_info->list); + up_write(&xprt_info_list_lock_lha5); + + msm_ipc_cleanup_routing_table(xprt_info); + + wakeup_source_trash(&xprt_info->ws); + + ipc_router_put_xprt_info_ref(xprt_info); + wait_for_completion(&xprt_info->ref_complete); + + xprt->priv = 0; + kfree(xprt_info); + } +} + + +struct msm_ipc_router_xprt_work { + struct msm_ipc_router_xprt *xprt; + struct work_struct work; +}; + +static void xprt_open_worker(struct work_struct *work) +{ + struct msm_ipc_router_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_xprt_work, work); + + msm_ipc_router_add_xprt(xprt_work->xprt); + kfree(xprt_work); +} + +static void xprt_close_worker(struct work_struct *work) +{ + struct msm_ipc_router_xprt_work *xprt_work = + container_of(work, struct msm_ipc_router_xprt_work, work); + + msm_ipc_router_remove_xprt(xprt_work->xprt); + xprt_work->xprt->sft_close_done(xprt_work->xprt); + kfree(xprt_work); +} + +void msm_ipc_router_xprt_notify(struct msm_ipc_router_xprt *xprt, + unsigned event, + void *data) +{ + struct msm_ipc_router_xprt_info *xprt_info = xprt->priv; + struct msm_ipc_router_xprt_work *xprt_work; + struct msm_ipc_router_remote_port *rport_ptr = NULL; + struct rr_packet *pkt; + int ret; + + ret = ipc_router_core_init(); + if (ret < 0) { + IPC_RTR_ERR("%s: Error %d initializing IPC Router\n", + __func__, ret); + return; + } + + switch (event) { + case IPC_ROUTER_XPRT_EVENT_OPEN: + xprt_work = kmalloc(sizeof(struct msm_ipc_router_xprt_work), + GFP_ATOMIC); + if (xprt_work) { + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_open_worker); + queue_work(msm_ipc_router_workqueue, &xprt_work->work); + } else { + IPC_RTR_ERR( + "%s: malloc failure - Couldn't notify OPEN event", + __func__); + } + break; + + case IPC_ROUTER_XPRT_EVENT_CLOSE: + xprt_work = kmalloc(sizeof(struct msm_ipc_router_xprt_work), + GFP_ATOMIC); + if (xprt_work) { + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_close_worker); + queue_work(msm_ipc_router_workqueue, &xprt_work->work); + } else { + IPC_RTR_ERR( + "%s: malloc failure - Couldn't notify CLOSE event", + __func__); + } + break; + } + + if (!data) + return; + + while (!xprt_info) { + msleep(100); + xprt_info = xprt->priv; + } + + pkt = clone_pkt((struct rr_packet *)data); + if (!pkt) + return; + + if (pkt->length < calc_rx_header_size(xprt_info) || + pkt->length > MAX_IPC_PKT_SIZE) { + IPC_RTR_ERR("%s: Invalid pkt length %d\n", + __func__, pkt->length); + release_pkt(pkt); + return; + } + + ret = extract_header(pkt); + if (ret < 0) { + release_pkt(pkt); + return; + } + + pkt->ws_need = false; + + if (pkt->hdr.type == IPC_ROUTER_CTRL_CMD_DATA) + rport_ptr = ipc_router_get_rport_ref(pkt->hdr.src_node_id, + pkt->hdr.src_port_id); + + mutex_lock(&xprt_info->rx_lock_lhb2); + list_add_tail(&pkt->list, &xprt_info->pkt_list); + /* check every pkt is from SENSOR services or not and + * avoid holding both edge and port specific wake-up sources + */ + if (!is_sensor_port(rport_ptr)) { + if (!xprt_info->dynamic_ws) { + __pm_stay_awake(&xprt_info->ws); + pkt->ws_need = true; + } else { + if (is_wakeup_source_allowed) { + __pm_stay_awake(&xprt_info->ws); + pkt->ws_need = true; + } + } + } + mutex_unlock(&xprt_info->rx_lock_lhb2); + queue_work(xprt_info->workqueue, &xprt_info->read_data); +} + +/** + * parse_devicetree() - parse device tree binding + * + * @node: pointer to device tree node + * + * @return: 0 on success, -ENODEV on failure. + */ +static int parse_devicetree(struct device_node *node) +{ + char *key; + const char *peripheral = NULL; + + key = "qcom,default-peripheral"; + peripheral = of_get_property(node, key, NULL); + if (peripheral) + strlcpy(default_peripheral, peripheral, PIL_SUBSYSTEM_NAME_LEN); + + return 0; +} + +/** + * ipc_router_probe() - Probe the IPC Router + * + * @pdev: Platform device corresponding to IPC Router. + * + * @return: 0 on success, standard Linux error codes on error. + * + * This function is called when the underlying device tree driver registers + * a platform device, mapped to IPC Router. + */ +static int ipc_router_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (pdev && pdev->dev.of_node) { + ret = parse_devicetree(pdev->dev.of_node); + if (ret) + IPC_RTR_ERR("%s: Failed to parse device tree\n", + __func__); + } + return ret; +} + +static struct of_device_id ipc_router_match_table[] = { + { .compatible = "qcom,ipc_router" }, + {}, +}; + +static struct platform_driver ipc_router_driver = { + .probe = ipc_router_probe, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = ipc_router_match_table, + }, +}; + +/** + * ipc_router_core_init() - Initialize all IPC Router core data structures + * + * Return: 0 on Success or Standard error code otherwise. + * + * This function only initializes all the core data structures to the IPC Router + * module. The remaining initialization is done inside msm_ipc_router_init(). + */ +static int ipc_router_core_init(void) +{ + int i; + int ret; + struct msm_ipc_routing_table_entry *rt_entry; + + mutex_lock(&ipc_router_init_lock); + if (likely(is_ipc_router_inited)) { + mutex_unlock(&ipc_router_init_lock); + return 0; + } + + debugfs_init(); + + for (i = 0; i < SRV_HASH_SIZE; i++) + INIT_LIST_HEAD(&server_list[i]); + + for (i = 0; i < LP_HASH_SIZE; i++) + INIT_LIST_HEAD(&local_ports[i]); + + down_write(&routing_table_lock_lha3); + if (!routing_table_inited) { + init_routing_table(); + routing_table_inited = 1; + } + up_write(&routing_table_lock_lha3); + rt_entry = create_routing_table_entry(IPC_ROUTER_NID_LOCAL, NULL); + kref_put(&rt_entry->ref, ipc_router_release_rtentry); + + msm_ipc_router_workqueue = + create_singlethread_workqueue("msm_ipc_router"); + if (!msm_ipc_router_workqueue) { + mutex_unlock(&ipc_router_init_lock); + return -ENOMEM; + } + + ret = msm_ipc_router_security_init(); + if (ret < 0) + IPC_RTR_ERR("%s: Security Init failed\n", __func__); + else + is_ipc_router_inited = true; + mutex_unlock(&ipc_router_init_lock); + + return ret; +} + +static int msm_ipc_router_init(void) +{ + int ret; + + ret = ipc_router_core_init(); + if (ret < 0) + return ret; + + ret = platform_driver_register(&ipc_router_driver); + if (ret) + IPC_RTR_ERR( + "%s: ipc_router_driver register failed %d\n", __func__, ret); + + ret = msm_ipc_router_init_sockets(); + if (ret < 0) + IPC_RTR_ERR("%s: Init sockets failed\n", __func__); + + ipc_router_log_ctx_init(); + return ret; +} + +module_init(msm_ipc_router_init); +MODULE_DESCRIPTION("MSM IPC Router"); +MODULE_LICENSE("GPL v2"); diff --git a/net/ipc_router/ipc_router_private.h b/net/ipc_router/ipc_router_private.h new file mode 100644 index 000000000000..ce6c84070402 --- /dev/null +++ b/net/ipc_router/ipc_router_private.h @@ -0,0 +1,150 @@ +/* Copyright (c) 2011-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. + */ + +#ifndef _IPC_ROUTER_PRIVATE_H +#define _IPC_ROUTER_PRIVATE_H + +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/msm_ipc.h> +#include <linux/ipc_router.h> +#include <linux/ipc_router_xprt.h> + +#include <net/sock.h> + +/* definitions for the R2R wire protcol */ +#define IPC_ROUTER_V1 1 +/* + * Ambiguous definition but will enable multiplexing IPC_ROUTER_V2 packets + * with an existing alternate transport in user-space, if needed. + */ +#define IPC_ROUTER_V2 3 +#define IPC_ROUTER_VER_BITMASK ((BIT(IPC_ROUTER_V1)) | (BIT(IPC_ROUTER_V2))) +#define IPC_ROUTER_HELLO_MAGIC 0xE110 +#define IPC_ROUTER_CHECKSUM_MASK 0xFFFF + +#define IPC_ROUTER_ADDRESS 0x0000FFFF + +#define IPC_ROUTER_NID_LOCAL 1 +#define MAX_IPC_PKT_SIZE 66000 + +#define IPC_ROUTER_LOW_RX_QUOTA 5 +#define IPC_ROUTER_HIGH_RX_QUOTA 10 + +#define IPC_ROUTER_INFINITY -1 +#define DEFAULT_RCV_TIMEO IPC_ROUTER_INFINITY +#define DEFAULT_SND_TIMEO IPC_ROUTER_INFINITY + +#define ALIGN_SIZE(x) ((4 - ((x) & 3)) & 3) + +#define ALL_SERVICE 0xFFFFFFFF +#define ALL_INSTANCE 0xFFFFFFFF + +#define CONTROL_FLAG_CONFIRM_RX 0x1 +#define CONTROL_FLAG_OPT_HDR 0x2 + +enum { + CLIENT_PORT, + SERVER_PORT, + CONTROL_PORT, + IRSC_PORT, +}; + +enum { + NULL_MODE, + SINGLE_LINK_MODE, + MULTI_LINK_MODE, +}; + +enum { + CONNECTION_RESET = -1, + NOT_CONNECTED, + CONNECTED, +}; + +struct msm_ipc_sock { + struct sock sk; + struct msm_ipc_port *port; + void *default_node_vote_info; +}; + +/** + * msm_ipc_router_create_raw_port() - Create an IPC Router port + * @endpoint: User-space space socket information to be cached. + * @notify: Function to notify incoming events on the port. + * @event: Event ID to be handled. + * @oob_data: Any out-of-band data associated with the event. + * @oob_data_len: Size of the out-of-band data, if valid. + * @priv: Private data registered during the port creation. + * @priv: Private Data to be passed during the event notification. + * + * @return: Valid pointer to port on success, NULL on failure. + * + * This function is used to create an IPC Router port. The port is used for + * communication locally or outside the subsystem. + */ +struct msm_ipc_port *msm_ipc_router_create_raw_port(void *endpoint, + void (*notify)(unsigned event, void *oob_data, + size_t oob_data_len, void *priv), + void *priv); +int msm_ipc_router_send_to(struct msm_ipc_port *src, + struct sk_buff_head *data, + struct msm_ipc_addr *dest, + long timeout); +int msm_ipc_router_read(struct msm_ipc_port *port_ptr, + struct rr_packet **pkt, + size_t buf_len); + +int msm_ipc_router_recv_from(struct msm_ipc_port *port_ptr, + struct rr_packet **pkt, + struct msm_ipc_addr *src_addr, + long timeout); +int msm_ipc_router_register_server(struct msm_ipc_port *server_port, + struct msm_ipc_addr *name); +int msm_ipc_router_unregister_server(struct msm_ipc_port *server_port); + +int msm_ipc_router_init_sockets(void); +void msm_ipc_router_exit_sockets(void); + +void msm_ipc_sync_sec_rule(uint32_t service, uint32_t instance, void *rule); + +void msm_ipc_sync_default_sec_rule(void *rule); + +int msm_ipc_router_rx_data_wait(struct msm_ipc_port *port_ptr, long timeout); + +void msm_ipc_router_free_skb(struct sk_buff_head *skb_head); + +/** + * ipc_router_set_conn() - Set the connection by initializing dest address + * @port_ptr: Local port in which the connection has to be set. + * @addr: Destination address of the connection. + * + * @return: 0 on success, standard Linux error codes on failure. + */ +int ipc_router_set_conn(struct msm_ipc_port *port_ptr, + struct msm_ipc_addr *addr); + +void *msm_ipc_load_default_node(void); + +void msm_ipc_unload_default_node(void *pil); + +/** + * ipc_router_dummy_write_space() - Dummy write space available callback + * @sk: Socket pointer for which the callback is called. + */ +void ipc_router_dummy_write_space(struct sock *sk); + +#endif diff --git a/net/ipc_router/ipc_router_security.c b/net/ipc_router/ipc_router_security.c new file mode 100644 index 000000000000..ab4e5003c05d --- /dev/null +++ b/net/ipc_router/ipc_router_security.c @@ -0,0 +1,328 @@ +/* Copyright (c) 2012-2014,2020, 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/module.h> +#include <linux/types.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/fcntl.h> +#include <linux/gfp.h> +#include <linux/uaccess.h> +#include <linux/kernel.h> +#include <linux/msm_ipc.h> +#include <linux/rwsem.h> +#include <linux/uaccess.h> + +#include <net/sock.h> +#include "ipc_router_private.h" +#include "ipc_router_security.h" + +#define IRSC_COMPLETION_TIMEOUT_MS 30000 +#define SEC_RULES_HASH_SZ 32 + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +struct security_rule { + struct list_head list; + uint32_t service_id; + uint32_t instance_id; + unsigned reserved; + int num_group_info; + kgid_t *group_id; +}; + +static DECLARE_RWSEM(security_rules_lock_lha4); +static struct list_head security_rules[SEC_RULES_HASH_SZ]; +static DECLARE_COMPLETION(irsc_completion); + +/** + * wait_for_irsc_completion() - Wait for IPC Router Security Configuration + * (IRSC) to complete + */ +void wait_for_irsc_completion(void) +{ + unsigned long rem_jiffies; + do { + rem_jiffies = wait_for_completion_timeout(&irsc_completion, + msecs_to_jiffies(IRSC_COMPLETION_TIMEOUT_MS)); + if (rem_jiffies) + return; + pr_err("%s: waiting for IPC Security Conf.\n", __func__); + } while (1); +} + +/** + * signal_irsc_completion() - Signal the completion of IRSC + */ +void signal_irsc_completion(void) +{ + complete_all(&irsc_completion); +} + +/** + * check_permisions() - Check whether the process has permissions to + * create an interface handle with IPC Router + * + * @return: true if the process has permissions, else false. + */ +int check_permissions(void) +{ + int rc = 0; + if (capable(CAP_NET_RAW) || capable(CAP_NET_BIND_SERVICE)) + rc = 1; + return rc; +} +EXPORT_SYMBOL(check_permissions); + +/** + * msm_ipc_config_sec_rules() - Add a security rule to the database + * @arg: Pointer to the buffer containing the rule. + * + * @return: 0 if successfully added, < 0 for error. + * + * A security rule is defined using <Service_ID: Group_ID> tuple. The rule + * implies that a user-space process in order to send a QMI message to + * service Service_ID should belong to the Linux group Group_ID. + */ +int msm_ipc_config_sec_rules(void *arg) +{ + struct config_sec_rules_args sec_rules_arg; + struct security_rule *rule; + int key; + size_t kgroup_info_sz; + int ret; + size_t group_info_sz; + gid_t *group_id = NULL; + int loop; + + if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) + return -EPERM; + + ret = copy_from_user(&sec_rules_arg, (void *)arg, + sizeof(sec_rules_arg)); + if (ret) + return -EFAULT; + + /* Default rule change from config util not allowed */ + if (sec_rules_arg.service_id == ALL_SERVICE) + return -EINVAL; + + if (sec_rules_arg.num_group_info <= 0) + return -EINVAL; + + if (sec_rules_arg.num_group_info > (SIZE_MAX / sizeof(gid_t))) { + pr_err("%s: Integer Overflow %zu * %d\n", __func__, + sizeof(gid_t), sec_rules_arg.num_group_info); + return -EINVAL; + } + group_info_sz = sec_rules_arg.num_group_info * sizeof(gid_t); + + if (sec_rules_arg.num_group_info > (SIZE_MAX / sizeof(kgid_t))) { + pr_err("%s: Integer Overflow %zu * %d\n", __func__, + sizeof(kgid_t), sec_rules_arg.num_group_info); + return -EINVAL; + } + kgroup_info_sz = sec_rules_arg.num_group_info * sizeof(kgid_t); + + rule = kzalloc(sizeof(struct security_rule), GFP_KERNEL); + if (!rule) { + pr_err("%s: security_rule alloc failed\n", __func__); + return -ENOMEM; + } + + rule->group_id = kzalloc(kgroup_info_sz, GFP_KERNEL); + if (!rule->group_id) { + pr_err("%s: kgroup_id alloc failed\n", __func__); + kfree(rule); + return -ENOMEM; + } + + group_id = kzalloc(group_info_sz, GFP_KERNEL); + if (!group_id) { + pr_err("%s: group_id alloc failed\n", __func__); + kfree(rule->group_id); + kfree(rule); + return -ENOMEM; + } + + rule->service_id = sec_rules_arg.service_id; + rule->instance_id = sec_rules_arg.instance_id; + rule->reserved = sec_rules_arg.reserved; + rule->num_group_info = sec_rules_arg.num_group_info; + ret = copy_from_user(group_id, ((void *)(arg + sizeof(sec_rules_arg))), + group_info_sz); + if (ret) { + kfree(group_id); + kfree(rule->group_id); + kfree(rule); + return -EFAULT; + } + for (loop = 0; loop < rule->num_group_info; loop++) + rule->group_id[loop] = make_kgid(current_user_ns(), + group_id[loop]); + kfree(group_id); + + key = rule->service_id & (SEC_RULES_HASH_SZ - 1); + down_write(&security_rules_lock_lha4); + list_add_tail(&rule->list, &security_rules[key]); + up_write(&security_rules_lock_lha4); + + msm_ipc_sync_sec_rule(rule->service_id, + rule->instance_id, (void *)rule); + + return 0; +} +EXPORT_SYMBOL(msm_ipc_config_sec_rules); + +/** + * msm_ipc_add_default_rule() - Add default security rule + * + * @return: 0 on success, < 0 on error/ + * + * This function is used to ensure the basic security, if there is no + * security rule defined for a service. It can be overwritten by the + * default security rule from user-space script. + */ +static int msm_ipc_add_default_rule(void) +{ + struct security_rule *rule; + int key; + + rule = kzalloc(sizeof(struct security_rule), GFP_KERNEL); + if (!rule) { + pr_err("%s: security_rule alloc failed\n", __func__); + return -ENOMEM; + } + + rule->group_id = kzalloc(sizeof(*(rule->group_id)), GFP_KERNEL); + if (!rule->group_id) { + pr_err("%s: group_id alloc failed\n", __func__); + kfree(rule); + return -ENOMEM; + } + + rule->service_id = ALL_SERVICE; + rule->instance_id = ALL_INSTANCE; + rule->num_group_info = 1; + *(rule->group_id) = AID_NET_RAW; + down_write(&security_rules_lock_lha4); + key = (ALL_SERVICE & (SEC_RULES_HASH_SZ - 1)); + list_add_tail(&rule->list, &security_rules[key]); + up_write(&security_rules_lock_lha4); + return 0; +} + +/** + * msm_ipc_get_security_rule() - Get the security rule corresponding to a + * service + * @service_id: Service ID for which the rule has to be got. + * @instance_id: Instance ID for which the rule has to be got. + * + * @return: Returns the rule info on success, NULL on error. + * + * This function is used when the service comes up and gets registered with + * the IPC Router. + */ +void *msm_ipc_get_security_rule(uint32_t service_id, uint32_t instance_id) +{ + int key; + struct security_rule *rule; + + key = (service_id & (SEC_RULES_HASH_SZ - 1)); + down_read(&security_rules_lock_lha4); + /* Return the rule for a specific <service:instance>, if found. */ + list_for_each_entry(rule, &security_rules[key], list) { + if ((rule->service_id == service_id) && + (rule->instance_id == instance_id)) { + up_read(&security_rules_lock_lha4); + return (void *)rule; + } + } + + /* Return the rule for a specific service, if found. */ + list_for_each_entry(rule, &security_rules[key], list) { + if ((rule->service_id == service_id) && + (rule->instance_id == ALL_INSTANCE)) { + up_read(&security_rules_lock_lha4); + return (void *)rule; + } + } + + /* Return the default rule, if no rule defined for a service. */ + key = (ALL_SERVICE & (SEC_RULES_HASH_SZ - 1)); + list_for_each_entry(rule, &security_rules[key], list) { + if ((rule->service_id == ALL_SERVICE) && + (rule->instance_id == ALL_INSTANCE)) { + up_read(&security_rules_lock_lha4); + return (void *)rule; + } + } + up_read(&security_rules_lock_lha4); + return NULL; +} +EXPORT_SYMBOL(msm_ipc_get_security_rule); + +/** + * msm_ipc_check_send_permissions() - Check if the sendng process has + * permissions specified as per the rule + * @data: Security rule to be checked. + * + * @return: true if the process has permissions, else false. + * + * This function is used to check if the current executing process has + * permissions to send message to the remote entity. The security rule + * corresponding to the remote entity is specified by "data" parameter + */ +int msm_ipc_check_send_permissions(void *data) +{ + int i; + struct security_rule *rule = (struct security_rule *)data; + + /* Source/Sender is Root user */ + if (uid_eq(current_euid(), GLOBAL_ROOT_UID)) + return 1; + + /* Destination has no rules defined, possibly a client. */ + if (!rule) + return 1; + + for (i = 0; i < rule->num_group_info; i++) { + if (!gid_valid(rule->group_id[i])) + continue; + if (in_egroup_p(rule->group_id[i])) + return 1; + } + return 0; +} +EXPORT_SYMBOL(msm_ipc_check_send_permissions); + +/** + * msm_ipc_router_security_init() - Initialize the security rule database + * + * @return: 0 if successful, < 0 for error. + */ +int msm_ipc_router_security_init(void) +{ + int i; + + for (i = 0; i < SEC_RULES_HASH_SZ; i++) + INIT_LIST_HEAD(&security_rules[i]); + + msm_ipc_add_default_rule(); + return 0; +} +EXPORT_SYMBOL(msm_ipc_router_security_init); diff --git a/net/ipc_router/ipc_router_security.h b/net/ipc_router/ipc_router_security.h new file mode 100644 index 000000000000..002ae84d0b95 --- /dev/null +++ b/net/ipc_router/ipc_router_security.h @@ -0,0 +1,120 @@ +/* Copyright (c) 2012-2014, 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. + */ + +#ifndef _IPC_ROUTER_SECURITY_H +#define _IPC_ROUTER_SECURITY_H + +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/errno.h> + +#ifdef CONFIG_IPC_ROUTER_SECURITY +#include <linux/android_aid.h> + +/** + * check_permisions() - Check whether the process has permissions to + * create an interface handle with IPC Router + * + * @return: true if the process has permissions, else false. + */ +int check_permissions(void); + +/** + * msm_ipc_config_sec_rules() - Add a security rule to the database + * @arg: Pointer to the buffer containing the rule. + * + * @return: 0 if successfully added, < 0 for error. + * + * A security rule is defined using <Service_ID: Group_ID> tuple. The rule + * implies that a user-space process in order to send a QMI message to + * service Service_ID should belong to the Linux group Group_ID. + */ +int msm_ipc_config_sec_rules(void *arg); + +/** + * msm_ipc_get_security_rule() - Get the security rule corresponding to a + * service + * @service_id: Service ID for which the rule has to be got. + * @instance_id: Instance ID for which the rule has to be got. + * + * @return: Returns the rule info on success, NULL on error. + * + * This function is used when the service comes up and gets registered with + * the IPC Router. + */ +void *msm_ipc_get_security_rule(uint32_t service_id, uint32_t instance_id); + +/** + * msm_ipc_check_send_permissions() - Check if the sendng process has + * permissions specified as per the rule + * @data: Security rule to be checked. + * + * @return: true if the process has permissions, else false. + * + * This function is used to check if the current executing process has + * permissions to send message to the remote entity. The security rule + * corresponding to the remote entity is specified by "data" parameter + */ +int msm_ipc_check_send_permissions(void *data); + +/** + * msm_ipc_router_security_init() - Initialize the security rule database + * + * @return: 0 if successful, < 0 for error. + */ +int msm_ipc_router_security_init(void); + +/** + * wait_for_irsc_completion() - Wait for IPC Router Security Configuration + * (IRSC) to complete + */ +void wait_for_irsc_completion(void); + +/** + * signal_irsc_completion() - Signal the completion of IRSC + */ +void signal_irsc_completion(void); + +#else + +static inline int check_permissions(void) +{ + return 1; +} + +static inline int msm_ipc_config_sec_rules(void *arg) +{ + return -ENODEV; +} + +static inline void *msm_ipc_get_security_rule(uint32_t service_id, + uint32_t instance_id) +{ + return NULL; +} + +static inline int msm_ipc_check_send_permissions(void *data) +{ + return 1; +} + +static inline int msm_ipc_router_security_init(void) +{ + return 0; +} + +static inline void wait_for_irsc_completion(void) { } + +static inline void signal_irsc_completion(void) { } + +#endif +#endif diff --git a/net/ipc_router/ipc_router_socket.c b/net/ipc_router/ipc_router_socket.c new file mode 100644 index 000000000000..7c82f7ff8874 --- /dev/null +++ b/net/ipc_router/ipc_router_socket.c @@ -0,0 +1,693 @@ +/* Copyright (c) 2011-2018, 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/module.h> +#include <linux/types.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <linux/errno.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/fcntl.h> +#include <linux/gfp.h> +#include <linux/msm_ipc.h> +#include <linux/sched.h> +#include <linux/thread_info.h> +#include <linux/slab.h> +#include <linux/kmemleak.h> +#include <linux/ipc_logging.h> +#include <linux/string.h> +#include <linux/atomic.h> +#include <linux/ipc_router.h> + +#include <net/sock.h> + +#include "ipc_router_private.h" +#include "ipc_router_security.h" + +#define msm_ipc_sk(sk) ((struct msm_ipc_sock *)(sk)) +#define msm_ipc_sk_port(sk) ((struct msm_ipc_port *)(msm_ipc_sk(sk)->port)) + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +static int sockets_enabled; +static struct proto msm_ipc_proto; +static const struct proto_ops msm_ipc_proto_ops; +static RAW_NOTIFIER_HEAD(ipcrtr_af_init_chain); +static DEFINE_MUTEX(ipcrtr_af_init_lock); + +static struct sk_buff_head *msm_ipc_router_build_msg(struct msghdr *m, + size_t total_len) +{ + struct sk_buff_head *msg_head; + struct sk_buff *msg; + int first = 1; + int last = 1; + size_t data_size = 0; + size_t alloc_size, align_size; + void *data; + size_t total_copied_size = 0, copied_size; + + if (iov_iter_count(&m->msg_iter) == total_len) + data_size = total_len; + + if (!data_size) + return NULL; + align_size = ALIGN_SIZE(data_size); + + msg_head = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!msg_head) { + IPC_RTR_ERR("%s: cannot allocate skb_head\n", __func__); + return NULL; + } + skb_queue_head_init(msg_head); + + while (total_copied_size < total_len) { + alloc_size = data_size; + if (first) + alloc_size += IPC_ROUTER_HDR_SIZE; + if (last) + alloc_size += align_size; + + msg = alloc_skb(alloc_size, GFP_KERNEL); + if (!msg) { + if (alloc_size <= (PAGE_SIZE/2)) { + IPC_RTR_ERR("%s: cannot allocated skb\n", + __func__); + goto msg_build_failure; + } + data_size = data_size / 2; + last = 0; + continue; + } + + if (first) { + skb_reserve(msg, IPC_ROUTER_HDR_SIZE); + first = 0; + } + + data = skb_put(msg, data_size); + copied_size = copy_from_iter(msg->data, data_size, &m->msg_iter); + if (copied_size != data_size) { + IPC_RTR_ERR("%s: copy_from_iter failed %zu %zu %zu\n", __func__, alloc_size, data_size, copied_size); + kfree_skb(msg); + goto msg_build_failure; + } + skb_queue_tail(msg_head, msg); + total_copied_size += data_size; + data_size = total_len - total_copied_size; + last = 1; + } + return msg_head; + +msg_build_failure: + while (!skb_queue_empty(msg_head)) { + msg = skb_dequeue(msg_head); + kfree_skb(msg); + } + kfree(msg_head); + return NULL; +} + +static int msm_ipc_router_extract_msg(struct msghdr *m, + struct rr_packet *pkt) +{ + struct sockaddr_msm_ipc *addr; + struct rr_header_v1 *hdr; + struct sk_buff *temp; + union rr_control_msg *ctl_msg; + int offset = 0, data_len = 0, copy_len, copied_len; + + if (!m || !pkt) { + IPC_RTR_ERR("%s: Invalid pointers passed\n", __func__); + return -EINVAL; + } + addr = (struct sockaddr_msm_ipc *)m->msg_name; + + hdr = &(pkt->hdr); + if (addr && (hdr->type == IPC_ROUTER_CTRL_CMD_RESUME_TX)) { + temp = skb_peek(pkt->pkt_fragment_q); + if (!temp || !temp->data) { + IPC_RTR_ERR("%s: Invalid skb\n", __func__); + return -EINVAL; + } + ctl_msg = (union rr_control_msg *)(temp->data); + memset(addr, 0x0, sizeof(*addr)); + addr->family = AF_MSM_IPC; + addr->address.addrtype = MSM_IPC_ADDR_ID; + addr->address.addr.port_addr.node_id = ctl_msg->cli.node_id; + addr->address.addr.port_addr.port_id = ctl_msg->cli.port_id; + m->msg_namelen = sizeof(struct sockaddr_msm_ipc); + return offset; + } + if (addr && (hdr->type == IPC_ROUTER_CTRL_CMD_DATA)) { + memset(addr, 0x0, sizeof(*addr)); + addr->family = AF_MSM_IPC; + addr->address.addrtype = MSM_IPC_ADDR_ID; + addr->address.addr.port_addr.node_id = hdr->src_node_id; + addr->address.addr.port_addr.port_id = hdr->src_port_id; + m->msg_namelen = sizeof(struct sockaddr_msm_ipc); + } + + data_len = hdr->size; + skb_queue_walk(pkt->pkt_fragment_q, temp) { + copy_len = data_len < temp->len ? data_len : temp->len; + copied_len = copy_to_iter(temp->data, copy_len, &m->msg_iter); + if (copy_len != copied_len) { + IPC_RTR_ERR("%s: Copy to user failed\n", __func__); + return -EFAULT; + } + offset += copy_len; + data_len -= copy_len; + } + return offset; +} + +static int msm_ipc_router_create(struct net *net, + struct socket *sock, + int protocol, + int kern) +{ + struct sock *sk; + struct msm_ipc_port *port_ptr; + + if (unlikely(protocol != 0)) { + IPC_RTR_ERR("%s: Protocol not supported\n", __func__); + return -EPROTONOSUPPORT; + } + + switch (sock->type) { + case SOCK_DGRAM: + break; + default: + IPC_RTR_ERR("%s: Protocol type not supported\n", __func__); + return -EPROTOTYPE; + } + + sk = sk_alloc(net, AF_MSM_IPC, GFP_KERNEL, &msm_ipc_proto, kern); + if (!sk) { + IPC_RTR_ERR("%s: sk_alloc failed\n", __func__); + return -ENOMEM; + } + + sock->ops = &msm_ipc_proto_ops; + sock_init_data(sock, sk); + sk->sk_data_ready = NULL; + sk->sk_write_space = ipc_router_dummy_write_space; + sk->sk_rcvtimeo = DEFAULT_RCV_TIMEO; + sk->sk_sndtimeo = DEFAULT_SND_TIMEO; + + port_ptr = msm_ipc_router_create_raw_port(sk, NULL, NULL); + if (!port_ptr) { + IPC_RTR_ERR("%s: port_ptr alloc failed\n", __func__); + sock_put(sk); + sock->sk = NULL; + return -ENOMEM; + } + + port_ptr->check_send_permissions = msm_ipc_check_send_permissions; + msm_ipc_sk(sk)->port = port_ptr; + msm_ipc_sk(sk)->default_node_vote_info = NULL; + + return 0; +} + +int msm_ipc_router_bind(struct socket *sock, struct sockaddr *uaddr, + int uaddr_len) +{ + struct sockaddr_msm_ipc *addr = (struct sockaddr_msm_ipc *)uaddr; + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + int ret; + + if (!sk) + return -EINVAL; + + if (!check_permissions()) { + IPC_RTR_ERR("%s: %s Do not have permissions\n", + __func__, current->comm); + return -EPERM; + } + + if (!uaddr_len) { + IPC_RTR_ERR("%s: Invalid address length\n", __func__); + return -EINVAL; + } + + if (addr->family != AF_MSM_IPC) { + IPC_RTR_ERR("%s: Address family is incorrect\n", __func__); + return -EAFNOSUPPORT; + } + + if (addr->address.addrtype != MSM_IPC_ADDR_NAME) { + IPC_RTR_ERR("%s: Address type is incorrect\n", __func__); + return -EINVAL; + } + + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) + return -ENODEV; + + if (!msm_ipc_sk(sk)->default_node_vote_info) + msm_ipc_sk(sk)->default_node_vote_info = + msm_ipc_load_default_node(); + lock_sock(sk); + + ret = msm_ipc_router_register_server(port_ptr, &addr->address); + + release_sock(sk); + return ret; +} + +static int ipc_router_connect(struct socket *sock, struct sockaddr *uaddr, + int uaddr_len, int flags) +{ + struct sockaddr_msm_ipc *addr = (struct sockaddr_msm_ipc *)uaddr; + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + int ret; + + if (!sk) + return -EINVAL; + + if (uaddr_len <= 0) { + IPC_RTR_ERR("%s: Invalid address length\n", __func__); + return -EINVAL; + } + + if (!addr) { + IPC_RTR_ERR("%s: Invalid address\n", __func__); + return -EINVAL; + } + + if (addr->family != AF_MSM_IPC) { + IPC_RTR_ERR("%s: Address family is incorrect\n", __func__); + return -EAFNOSUPPORT; + } + + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) + return -ENODEV; + + lock_sock(sk); + ret = ipc_router_set_conn(port_ptr, &addr->address); + release_sock(sk); + return ret; +} + +static int msm_ipc_router_sendmsg(struct socket *sock, + struct msghdr *m, size_t total_len) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk); + struct sockaddr_msm_ipc *dest = (struct sockaddr_msm_ipc *)m->msg_name; + struct sk_buff_head *msg; + int ret; + struct msm_ipc_addr dest_addr = {0}; + long timeout; + + if (dest) { + if (m->msg_namelen < sizeof(*dest) || + dest->family != AF_MSM_IPC) + return -EINVAL; + memcpy(&dest_addr, &dest->address, sizeof(dest_addr)); + } else { + if (port_ptr->conn_status == NOT_CONNECTED) { + return -EDESTADDRREQ; + } else if (port_ptr->conn_status < CONNECTION_RESET) { + return -ENETRESET; + } else { + memcpy(&dest_addr.addr.port_addr, &port_ptr->dest_addr, + sizeof(struct msm_ipc_port_addr)); + dest_addr.addrtype = MSM_IPC_ADDR_ID; + } + } + + if (total_len > MAX_IPC_PKT_SIZE) + return -EINVAL; + + lock_sock(sk); + timeout = sock_sndtimeo(sk, m->msg_flags & MSG_DONTWAIT); + msg = msm_ipc_router_build_msg(m, total_len); + if (!msg) { + IPC_RTR_ERR("%s: Msg build failure\n", __func__); + ret = -ENOMEM; + goto out_sendmsg; + } + kmemleak_not_leak(msg); + + if (port_ptr->type == CLIENT_PORT) + wait_for_irsc_completion(); + ret = msm_ipc_router_send_to(port_ptr, msg, &dest_addr, timeout); + if (ret != total_len) { + if (ret < 0) { + if (ret != -EAGAIN) + IPC_RTR_ERR("%s: Send_to failure %d\n", + __func__, ret); + msm_ipc_router_free_skb(msg); + } else if (ret >= 0) { + ret = -EFAULT; + } + } + +out_sendmsg: + release_sock(sk); + return ret; +} + +static int msm_ipc_router_recvmsg(struct socket *sock, + struct msghdr *m, size_t buf_len, int flags) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk); + struct rr_packet *pkt; + long timeout; + int ret; + + lock_sock(sk); + if (!buf_len) { + if (flags & MSG_PEEK) + ret = msm_ipc_router_get_curr_pkt_size(port_ptr); + else + ret = -EINVAL; + release_sock(sk); + return ret; + } + timeout = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + + ret = msm_ipc_router_rx_data_wait(port_ptr, timeout); + if (ret) { + release_sock(sk); + if (ret == -ENOMSG) + m->msg_namelen = 0; + return ret; + } + + ret = msm_ipc_router_read(port_ptr, &pkt, buf_len); + if (ret <= 0 || !pkt) { + release_sock(sk); + return ret; + } + + ret = msm_ipc_router_extract_msg(m, pkt); + release_pkt(pkt); + release_sock(sk); + return ret; +} + +static int msm_ipc_router_ioctl(struct socket *sock, + unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + struct server_lookup_args server_arg; + struct msm_ipc_server_info *srv_info = NULL; + unsigned int n; + size_t srv_info_sz = 0; + int ret; + + if (!sk) + return -EINVAL; + + lock_sock(sk); + port_ptr = msm_ipc_sk_port(sock->sk); + if (!port_ptr) { + release_sock(sk); + return -EINVAL; + } + + switch (cmd) { + case IPC_ROUTER_IOCTL_GET_VERSION: + n = IPC_ROUTER_V1; + ret = put_user(n, (unsigned int *)arg); + break; + + case IPC_ROUTER_IOCTL_GET_MTU: + n = (MAX_IPC_PKT_SIZE - IPC_ROUTER_HDR_SIZE); + ret = put_user(n, (unsigned int *)arg); + break; + + case IPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE: + ret = msm_ipc_router_get_curr_pkt_size(port_ptr); + break; + + case IPC_ROUTER_IOCTL_LOOKUP_SERVER: + if (!msm_ipc_sk(sk)->default_node_vote_info) + msm_ipc_sk(sk)->default_node_vote_info = + msm_ipc_load_default_node(); + + ret = copy_from_user(&server_arg, (void *)arg, + sizeof(server_arg)); + if (ret) { + ret = -EFAULT; + break; + } + + if (server_arg.num_entries_in_array < 0) { + ret = -EINVAL; + break; + } + if (server_arg.num_entries_in_array) { + if (server_arg.num_entries_in_array > + (SIZE_MAX / sizeof(*srv_info))) { + IPC_RTR_ERR("%s: Integer Overflow %zu * %d\n", + __func__, sizeof(*srv_info), + server_arg.num_entries_in_array); + ret = -EINVAL; + break; + } + srv_info_sz = server_arg.num_entries_in_array * + sizeof(*srv_info); + srv_info = kmalloc(srv_info_sz, GFP_KERNEL); + if (!srv_info) { + ret = -ENOMEM; + break; + } + } + ret = msm_ipc_router_lookup_server_name(&server_arg.port_name, + srv_info, server_arg.num_entries_in_array, + server_arg.lookup_mask); + if (ret < 0) { + IPC_RTR_ERR("%s: Server not found\n", __func__); + ret = -ENODEV; + kfree(srv_info); + break; + } + server_arg.num_entries_found = ret; + + ret = copy_to_user((void *)arg, &server_arg, + sizeof(server_arg)); + + n = min(server_arg.num_entries_found, + server_arg.num_entries_in_array); + + if (ret == 0 && n) { + ret = copy_to_user((void *)(arg + sizeof(server_arg)), + srv_info, n * sizeof(*srv_info)); + } + + if (ret) + ret = -EFAULT; + kfree(srv_info); + break; + + case IPC_ROUTER_IOCTL_BIND_CONTROL_PORT: + ret = msm_ipc_router_bind_control_port(port_ptr); + break; + + case IPC_ROUTER_IOCTL_CONFIG_SEC_RULES: + ret = msm_ipc_config_sec_rules((void *)arg); + if (ret != -EPERM) + port_ptr->type = IRSC_PORT; + break; + + default: + ret = -EINVAL; + } + release_sock(sk); + return ret; +} + +static unsigned int msm_ipc_router_poll(struct file *file, + struct socket *sock, poll_table *wait) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + uint32_t mask = 0; + + if (!sk) + return -EINVAL; + + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) + return -EINVAL; + + poll_wait(file, &port_ptr->port_rx_wait_q, wait); + + if (!list_empty(&port_ptr->port_rx_q)) + mask |= (POLLRDNORM | POLLIN); + + if (port_ptr->conn_status == CONNECTION_RESET) + mask |= (POLLHUP | POLLERR); + + return mask; +} + +static int msm_ipc_router_close(struct socket *sock) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + int ret; + + if (!sk) + return -EINVAL; + + lock_sock(sk); + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) { + release_sock(sk); + return -EINVAL; + } + ret = msm_ipc_router_close_port(port_ptr); + msm_ipc_unload_default_node(msm_ipc_sk(sk)->default_node_vote_info); + release_sock(sk); + sock_put(sk); + sock->sk = NULL; + + return ret; +} + +/** + * register_ipcrtr_af_init_notifier() - Register for ipc router socket + * address family initialization callback + * @nb: Notifier block which will be notified when address family is + * initialized. + * + * Return: 0 on success, standard error code otherwise. + */ +int register_ipcrtr_af_init_notifier(struct notifier_block *nb) +{ + int ret; + + if (!nb) + return -EINVAL; + mutex_lock(&ipcrtr_af_init_lock); + if (sockets_enabled) + nb->notifier_call(nb, IPCRTR_AF_INIT, NULL); + ret = raw_notifier_chain_register(&ipcrtr_af_init_chain, nb); + mutex_unlock(&ipcrtr_af_init_lock); + return ret; +} +EXPORT_SYMBOL(register_ipcrtr_af_init_notifier); + +/** + * unregister_ipcrtr_af_init_notifier() - Unregister for ipc router socket + * address family initialization callback + * @nb: Notifier block which will be notified once address family is + * initialized. + * + * Return: 0 on success, standard error code otherwise. + */ +int unregister_ipcrtr_af_init_notifier(struct notifier_block *nb) +{ + int ret; + + if (!nb) + return -EINVAL; + ret = raw_notifier_chain_unregister(&ipcrtr_af_init_chain, nb); + return ret; +} +EXPORT_SYMBOL(unregister_ipcrtr_af_init_notifier); + +static const struct net_proto_family msm_ipc_family_ops = { + .owner = THIS_MODULE, + .family = AF_MSM_IPC, + .create = msm_ipc_router_create +}; + +static const struct proto_ops msm_ipc_proto_ops = { + .family = AF_MSM_IPC, + .owner = THIS_MODULE, + .release = msm_ipc_router_close, + .bind = msm_ipc_router_bind, + .connect = ipc_router_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .getname = sock_no_getname, + .poll = msm_ipc_router_poll, + .ioctl = msm_ipc_router_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = msm_ipc_router_ioctl, +#endif + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, +#ifdef CONFIG_COMPAT + .compat_setsockopt = sock_no_setsockopt, + .compat_getsockopt = sock_no_getsockopt, +#endif + .sendmsg = msm_ipc_router_sendmsg, + .recvmsg = msm_ipc_router_recvmsg, + .mmap = sock_no_mmap, + .sendpage = sock_no_sendpage, +}; + +static struct proto msm_ipc_proto = { + .name = "MSM_IPC", + .owner = THIS_MODULE, + .obj_size = sizeof(struct msm_ipc_sock), +}; + +int msm_ipc_router_init_sockets(void) +{ + int ret; + + ret = proto_register(&msm_ipc_proto, 1); + if (ret) { + IPC_RTR_ERR("%s: Failed to register MSM_IPC protocol type\n", + __func__); + goto out_init_sockets; + } + + ret = sock_register(&msm_ipc_family_ops); + if (ret) { + IPC_RTR_ERR("%s: Failed to register MSM_IPC socket type\n", + __func__); + proto_unregister(&msm_ipc_proto); + goto out_init_sockets; + } + + mutex_lock(&ipcrtr_af_init_lock); + sockets_enabled = 1; + raw_notifier_call_chain(&ipcrtr_af_init_chain, + IPCRTR_AF_INIT, NULL); + mutex_unlock(&ipcrtr_af_init_lock); +out_init_sockets: + return ret; +} + +void msm_ipc_router_exit_sockets(void) +{ + if (!sockets_enabled) + return; + + sock_unregister(msm_ipc_family_ops.family); + proto_unregister(&msm_ipc_proto); + mutex_lock(&ipcrtr_af_init_lock); + sockets_enabled = 0; + raw_notifier_call_chain(&ipcrtr_af_init_chain, + IPCRTR_AF_DEINIT, NULL); + mutex_unlock(&ipcrtr_af_init_lock); +} |