OSDN Git Service

soc: qcom: Add snapshot of ipc_router_smd_xprt
authorKarthikeyan Ramasubramanian <kramasub@codeaurora.org>
Wed, 3 Feb 2016 19:45:04 +0000 (12:45 -0700)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 18:08:03 +0000 (11:08 -0700)
This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of
kernel.lnx.3.18-151201.)

Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
drivers/soc/qcom/Kconfig
drivers/soc/qcom/Makefile
drivers/soc/qcom/ipc_router_smd_xprt.c [new file with mode: 0644]

index 9250d74..9c47a08 100644 (file)
@@ -161,6 +161,16 @@ config QCOM_SMEM
          The driver provides an interface to items in a heap shared among all
          processors in a Qualcomm platform.
 
+config MSM_IPC_ROUTER_SMD_XPRT
+       depends on MSM_SMD
+       depends on IPC_ROUTER
+       bool "MSM SMD XPRT Layer"
+       help
+         SMD Transport Layer that enables IPC Router communication within
+         a System-on-Chip(SoC). When the SMD channels become available,
+         this layer registers a transport with IPC Router and enable
+         message exchange.
+
 config QCOM_SMD
        tristate "Qualcomm Shared Memory Driver (SMD)"
        depends on QCOM_SMEM
index 4836a12..58d3930 100644 (file)
@@ -13,6 +13,7 @@ obj-$(CONFIG_MSM_RPM_SMD)     +=      rpm-smd.o
 ifdef CONFIG_DEBUG_FS
 obj-$(CONFIG_MSM_RPM_SMD)      +=      rpm-smd-debug.o
 endif
+obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT)  +=      ipc_router_smd_xprt.o
 
 obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o
 obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o
diff --git a/drivers/soc/qcom/ipc_router_smd_xprt.c b/drivers/soc/qcom/ipc_router_smd_xprt.c
new file mode 100644 (file)
index 0000000..60c4508
--- /dev/null
@@ -0,0 +1,845 @@
+/* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * IPC ROUTER SMD XPRT module.
+ */
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/ipc_router_xprt.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include <soc/qcom/smd.h>
+#include <soc/qcom/smsm.h>
+#include <soc/qcom/subsystem_restart.h>
+
+static int msm_ipc_router_smd_xprt_debug_mask;
+module_param_named(debug_mask, msm_ipc_router_smd_xprt_debug_mask,
+                  int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#if defined(DEBUG)
+#define D(x...) do { \
+if (msm_ipc_router_smd_xprt_debug_mask) \
+       pr_info(x); \
+} while (0)
+#else
+#define D(x...) do { } while (0)
+#endif
+
+#define MIN_FRAG_SZ (IPC_ROUTER_HDR_SIZE + sizeof(union rr_control_msg))
+
+#define NUM_SMD_XPRTS 4
+#define XPRT_NAME_LEN (SMD_MAX_CH_NAME_LEN + 12)
+
+/**
+ * msm_ipc_router_smd_xprt - IPC Router's SMD XPRT structure
+ * @list: IPC router's SMD XPRTs list.
+ * @ch_name: Name of the HSIC endpoint exported by ipc_bridge driver.
+ * @xprt_name: Name of the XPRT to be registered with IPC Router.
+ * @edge: SMD channel edge.
+ * @driver: Platform drivers register by this XPRT.
+ * @xprt: IPC Router XPRT structure to contain XPRT specific info.
+ * @channel: SMD channel specific info.
+ * @smd_xprt_wq: Workqueue to queue read & other XPRT related works.
+ * @write_avail_wait_q: wait queue for writer thread.
+ * @in_pkt: Pointer to any partially read packet.
+ * @is_partial_in_pkt: check pkt completion.
+ * @read_work: Read Work to perform read operation from SMD.
+ * @ss_reset_lock: Lock to protect access to the ss_reset flag.
+ * @ss_reset: flag used to check SSR state.
+ * @pil: handle to the remote subsystem.
+ * @sft_close_complete: Variable to indicate completion of SSR handling
+ *                      by IPC Router.
+ * @xprt_version: IPC Router header version supported by this XPRT.
+ * @xprt_option: XPRT specific options to be handled by IPC Router.
+ * @disable_pil_loading: Disable PIL Loading of the subsystem.
+ */
+struct msm_ipc_router_smd_xprt {
+       struct list_head list;
+       char ch_name[SMD_MAX_CH_NAME_LEN];
+       char xprt_name[XPRT_NAME_LEN];
+       uint32_t edge;
+       struct platform_driver driver;
+       struct msm_ipc_router_xprt xprt;
+       smd_channel_t *channel;
+       struct workqueue_struct *smd_xprt_wq;
+       wait_queue_head_t write_avail_wait_q;
+       struct rr_packet *in_pkt;
+       int is_partial_in_pkt;
+       struct delayed_work read_work;
+       spinlock_t ss_reset_lock;       /*Subsystem reset lock*/
+       int ss_reset;
+       void *pil;
+       struct completion sft_close_complete;
+       unsigned xprt_version;
+       unsigned xprt_option;
+       bool disable_pil_loading;
+};
+
+struct msm_ipc_router_smd_xprt_work {
+       struct msm_ipc_router_xprt *xprt;
+       struct work_struct work;
+};
+
+static void smd_xprt_read_data(struct work_struct *work);
+static void smd_xprt_open_event(struct work_struct *work);
+static void smd_xprt_close_event(struct work_struct *work);
+
+/**
+ * msm_ipc_router_smd_xprt_config - Config. Info. of each SMD XPRT
+ * @ch_name: Name of the SMD endpoint exported by SMD driver.
+ * @xprt_name: Name of the XPRT to be registered with IPC Router.
+ * @edge: ID to differentiate among multiple SMD endpoints.
+ * @link_id: Network Cluster ID to which this XPRT belongs to.
+ * @xprt_version: IPC Router header version supported by this XPRT.
+ * @disable_pil_loading: Disable PIL Loading of the subsystem.
+ */
+struct msm_ipc_router_smd_xprt_config {
+       char ch_name[SMD_MAX_CH_NAME_LEN];
+       char xprt_name[XPRT_NAME_LEN];
+       uint32_t edge;
+       uint32_t link_id;
+       unsigned xprt_version;
+       unsigned xprt_option;
+       bool disable_pil_loading;
+};
+
+struct msm_ipc_router_smd_xprt_config smd_xprt_cfg[] = {
+       {"RPCRPY_CNTL", "ipc_rtr_smd_rpcrpy_cntl", SMD_APPS_MODEM, 1, 1},
+       {"IPCRTR", "ipc_rtr_smd_ipcrtr", SMD_APPS_MODEM, 1, 1},
+       {"IPCRTR", "ipc_rtr_q6_ipcrtr", SMD_APPS_QDSP, 1, 1},
+       {"IPCRTR", "ipc_rtr_wcnss_ipcrtr", SMD_APPS_WCNSS, 1, 1},
+};
+
+#define MODULE_NAME "ipc_router_smd_xprt"
+#define IPC_ROUTER_SMD_XPRT_WAIT_TIMEOUT 3000
+static int ipc_router_smd_xprt_probe_done;
+static struct delayed_work ipc_router_smd_xprt_probe_work;
+static DEFINE_MUTEX(smd_remote_xprt_list_lock_lha1);
+static LIST_HEAD(smd_remote_xprt_list);
+
+static bool is_pil_loading_disabled(uint32_t edge);
+
+static int msm_ipc_router_smd_get_xprt_version(
+       struct msm_ipc_router_xprt *xprt)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+       if (!xprt)
+               return -EINVAL;
+       smd_xprtp = container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       return (int)smd_xprtp->xprt_version;
+}
+
+static int msm_ipc_router_smd_get_xprt_option(
+       struct msm_ipc_router_xprt *xprt)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+       if (!xprt)
+               return -EINVAL;
+       smd_xprtp = container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       return (int)smd_xprtp->xprt_option;
+}
+
+static int msm_ipc_router_smd_remote_write_avail(
+       struct msm_ipc_router_xprt *xprt)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       return smd_write_avail(smd_xprtp->channel);
+}
+
+static int msm_ipc_router_smd_remote_write(void *data,
+                                          uint32_t len,
+                                          struct msm_ipc_router_xprt *xprt)
+{
+       struct rr_packet *pkt = (struct rr_packet *)data;
+       struct sk_buff *ipc_rtr_pkt;
+       int offset, sz_written = 0;
+       int ret, num_retries = 0;
+       unsigned long flags;
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       if (!pkt)
+               return -EINVAL;
+
+       if (!len || pkt->length != len)
+               return -EINVAL;
+
+       do {
+               spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
+               if (smd_xprtp->ss_reset) {
+                       spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock,
+                                               flags);
+                       IPC_RTR_ERR("%s: %s chnl reset\n",
+                                       __func__, xprt->name);
+                       return -ENETRESET;
+               }
+               spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
+               ret = smd_write_start(smd_xprtp->channel, len);
+               if (ret < 0 && num_retries >= 5) {
+                       IPC_RTR_ERR("%s: Error %d @smd_write_start for %s\n",
+                               __func__, ret, xprt->name);
+                       return ret;
+               } else if (ret < 0) {
+                       msleep(50);
+                       num_retries++;
+               }
+       } while (ret < 0);
+
+       D("%s: Ready to write %d bytes\n", __func__, len);
+       skb_queue_walk(pkt->pkt_fragment_q, ipc_rtr_pkt) {
+               offset = 0;
+               while (offset < ipc_rtr_pkt->len) {
+                       if (!smd_write_segment_avail(smd_xprtp->channel))
+                               smd_enable_read_intr(smd_xprtp->channel);
+
+                       wait_event(smd_xprtp->write_avail_wait_q,
+                               (smd_write_segment_avail(smd_xprtp->channel) ||
+                               smd_xprtp->ss_reset));
+                       smd_disable_read_intr(smd_xprtp->channel);
+                       spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
+                       if (smd_xprtp->ss_reset) {
+                               spin_unlock_irqrestore(
+                                       &smd_xprtp->ss_reset_lock, flags);
+                               IPC_RTR_ERR("%s: %s chnl reset\n",
+                                       __func__, xprt->name);
+                               return -ENETRESET;
+                       }
+                       spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock,
+                                               flags);
+
+                       sz_written = smd_write_segment(smd_xprtp->channel,
+                                       ipc_rtr_pkt->data + offset,
+                                       (ipc_rtr_pkt->len - offset));
+                       offset += sz_written;
+                       sz_written = 0;
+               }
+               D("%s: Wrote %d bytes over %s\n",
+                 __func__, offset, xprt->name);
+       }
+
+       if (!smd_write_end(smd_xprtp->channel))
+               D("%s: Finished writing\n", __func__);
+       return len;
+}
+
+static int msm_ipc_router_smd_remote_close(struct msm_ipc_router_xprt *xprt)
+{
+       int rc;
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       rc = smd_close(smd_xprtp->channel);
+       if (smd_xprtp->pil) {
+               subsystem_put(smd_xprtp->pil);
+               smd_xprtp->pil = NULL;
+       }
+       return rc;
+}
+
+static void smd_xprt_sft_close_done(struct msm_ipc_router_xprt *xprt)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt, struct msm_ipc_router_smd_xprt, xprt);
+
+       complete_all(&smd_xprtp->sft_close_complete);
+}
+
+static void smd_xprt_read_data(struct work_struct *work)
+{
+       int pkt_size, sz_read, sz;
+       struct sk_buff *ipc_rtr_pkt;
+       void *data;
+       unsigned long flags;
+       struct delayed_work *rwork = to_delayed_work(work);
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(rwork, struct msm_ipc_router_smd_xprt, read_work);
+
+       spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
+       if (smd_xprtp->ss_reset) {
+               spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
+               if (smd_xprtp->in_pkt)
+                       release_pkt(smd_xprtp->in_pkt);
+               smd_xprtp->is_partial_in_pkt = 0;
+               IPC_RTR_ERR("%s: %s channel reset\n",
+                       __func__, smd_xprtp->xprt.name);
+               return;
+       }
+       spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
+
+       D("%s pkt_size: %d, read_avail: %d\n", __func__,
+               smd_cur_packet_size(smd_xprtp->channel),
+               smd_read_avail(smd_xprtp->channel));
+       while ((pkt_size = smd_cur_packet_size(smd_xprtp->channel)) &&
+               smd_read_avail(smd_xprtp->channel)) {
+               if (!smd_xprtp->is_partial_in_pkt) {
+                       smd_xprtp->in_pkt = create_pkt(NULL);
+                       if (!smd_xprtp->in_pkt) {
+                               IPC_RTR_ERR("%s: Couldn't alloc rr_packet\n",
+                                       __func__);
+                               return;
+                       }
+                       smd_xprtp->is_partial_in_pkt = 1;
+                       D("%s: Allocated rr_packet\n", __func__);
+               }
+
+               if (((pkt_size >= MIN_FRAG_SZ) &&
+                    (smd_read_avail(smd_xprtp->channel) < MIN_FRAG_SZ)) ||
+                   ((pkt_size < MIN_FRAG_SZ) &&
+                    (smd_read_avail(smd_xprtp->channel) < pkt_size)))
+                       return;
+
+               sz = smd_read_avail(smd_xprtp->channel);
+               do {
+                       ipc_rtr_pkt = alloc_skb(sz, GFP_KERNEL);
+                       if (!ipc_rtr_pkt) {
+                               if (sz <= (PAGE_SIZE/2)) {
+                                       queue_delayed_work(
+                                               smd_xprtp->smd_xprt_wq,
+                                               &smd_xprtp->read_work,
+                                               msecs_to_jiffies(100));
+                                       return;
+                               }
+                               sz = sz / 2;
+                       }
+               } while (!ipc_rtr_pkt);
+
+               D("%s: Allocated the sk_buff of size %d\n", __func__, sz);
+               data = skb_put(ipc_rtr_pkt, sz);
+               sz_read = smd_read(smd_xprtp->channel, data, sz);
+               if (sz_read != sz) {
+                       IPC_RTR_ERR("%s: Couldn't read %s completely\n",
+                               __func__, smd_xprtp->xprt.name);
+                       kfree_skb(ipc_rtr_pkt);
+                       release_pkt(smd_xprtp->in_pkt);
+                       smd_xprtp->is_partial_in_pkt = 0;
+                       return;
+               }
+               skb_queue_tail(smd_xprtp->in_pkt->pkt_fragment_q, ipc_rtr_pkt);
+               smd_xprtp->in_pkt->length += sz_read;
+               if (sz_read != pkt_size)
+                       smd_xprtp->is_partial_in_pkt = 1;
+               else
+                       smd_xprtp->is_partial_in_pkt = 0;
+
+               if (!smd_xprtp->is_partial_in_pkt) {
+                       D("%s: Packet size read %d\n",
+                         __func__, smd_xprtp->in_pkt->length);
+                       msm_ipc_router_xprt_notify(&smd_xprtp->xprt,
+                                               IPC_ROUTER_XPRT_EVENT_DATA,
+                                               (void *)smd_xprtp->in_pkt);
+                       release_pkt(smd_xprtp->in_pkt);
+                       smd_xprtp->in_pkt = NULL;
+               }
+       }
+}
+
+static void smd_xprt_open_event(struct work_struct *work)
+{
+       struct msm_ipc_router_smd_xprt_work *xprt_work =
+               container_of(work, struct msm_ipc_router_smd_xprt_work, work);
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt_work->xprt,
+                            struct msm_ipc_router_smd_xprt, xprt);
+       unsigned long flags;
+
+       spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
+       smd_xprtp->ss_reset = 0;
+       spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
+       msm_ipc_router_xprt_notify(xprt_work->xprt,
+                               IPC_ROUTER_XPRT_EVENT_OPEN, NULL);
+       D("%s: Notified IPC Router of %s OPEN\n",
+          __func__, xprt_work->xprt->name);
+       kfree(xprt_work);
+}
+
+static void smd_xprt_close_event(struct work_struct *work)
+{
+       struct msm_ipc_router_smd_xprt_work *xprt_work =
+               container_of(work, struct msm_ipc_router_smd_xprt_work, work);
+       struct msm_ipc_router_smd_xprt *smd_xprtp =
+               container_of(xprt_work->xprt,
+                            struct msm_ipc_router_smd_xprt, xprt);
+
+       if (smd_xprtp->in_pkt) {
+               release_pkt(smd_xprtp->in_pkt);
+               smd_xprtp->in_pkt = NULL;
+       }
+       smd_xprtp->is_partial_in_pkt = 0;
+       init_completion(&smd_xprtp->sft_close_complete);
+       msm_ipc_router_xprt_notify(xprt_work->xprt,
+                               IPC_ROUTER_XPRT_EVENT_CLOSE, NULL);
+       D("%s: Notified IPC Router of %s CLOSE\n",
+          __func__, xprt_work->xprt->name);
+       wait_for_completion(&smd_xprtp->sft_close_complete);
+       kfree(xprt_work);
+}
+
+static void msm_ipc_router_smd_remote_notify(void *_dev, unsigned event)
+{
+       unsigned long flags;
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+       struct msm_ipc_router_smd_xprt_work *xprt_work;
+
+       smd_xprtp = (struct msm_ipc_router_smd_xprt *)_dev;
+       if (!smd_xprtp)
+               return;
+
+       switch (event) {
+       case SMD_EVENT_DATA:
+               if (smd_read_avail(smd_xprtp->channel))
+                       queue_delayed_work(smd_xprtp->smd_xprt_wq,
+                                          &smd_xprtp->read_work, 0);
+               if (smd_write_segment_avail(smd_xprtp->channel))
+                       wake_up(&smd_xprtp->write_avail_wait_q);
+               break;
+
+       case SMD_EVENT_OPEN:
+               xprt_work = kmalloc(sizeof(struct msm_ipc_router_smd_xprt_work),
+                                   GFP_ATOMIC);
+               if (!xprt_work) {
+                       IPC_RTR_ERR(
+                       "%s: Couldn't notify %d event to IPC Router\n",
+                               __func__, event);
+                       return;
+               }
+               xprt_work->xprt = &smd_xprtp->xprt;
+               INIT_WORK(&xprt_work->work, smd_xprt_open_event);
+               queue_work(smd_xprtp->smd_xprt_wq, &xprt_work->work);
+               break;
+
+       case SMD_EVENT_CLOSE:
+               spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
+               smd_xprtp->ss_reset = 1;
+               spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
+               wake_up(&smd_xprtp->write_avail_wait_q);
+               xprt_work = kmalloc(sizeof(struct msm_ipc_router_smd_xprt_work),
+                                   GFP_ATOMIC);
+               if (!xprt_work) {
+                       IPC_RTR_ERR(
+                       "%s: Couldn't notify %d event to IPC Router\n",
+                               __func__, event);
+                       return;
+               }
+               xprt_work->xprt = &smd_xprtp->xprt;
+               INIT_WORK(&xprt_work->work, smd_xprt_close_event);
+               queue_work(smd_xprtp->smd_xprt_wq, &xprt_work->work);
+               break;
+       }
+}
+
+static void *msm_ipc_load_subsystem(uint32_t edge)
+{
+       void *pil = NULL;
+       const char *peripheral;
+       bool loading_disabled;
+
+       loading_disabled = is_pil_loading_disabled(edge);
+       peripheral = smd_edge_to_pil_str(edge);
+       if (!IS_ERR_OR_NULL(peripheral) && !loading_disabled) {
+               pil = subsystem_get(peripheral);
+               if (IS_ERR(pil)) {
+                       IPC_RTR_ERR("%s: Failed to load %s\n",
+                               __func__, peripheral);
+                       pil = NULL;
+               }
+       }
+       return pil;
+}
+
+/**
+ * find_smd_xprt_list() - Find xprt item specific to an HSIC endpoint
+ * @pdev: Platform device registered by HSIC's ipc_bridge driver
+ *
+ * @return: pointer to msm_ipc_router_smd_xprt if matching endpoint is found,
+ *             else NULL.
+ *
+ * This function is used to find specific xprt item from the global xprt list
+ */
+static struct msm_ipc_router_smd_xprt *
+               find_smd_xprt_list(struct platform_device *pdev)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+
+       mutex_lock(&smd_remote_xprt_list_lock_lha1);
+       list_for_each_entry(smd_xprtp, &smd_remote_xprt_list, list) {
+               if (!strcmp(pdev->name, smd_xprtp->ch_name)
+                               && (pdev->id == smd_xprtp->edge)) {
+                       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+                       return smd_xprtp;
+               }
+       }
+       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+       return NULL;
+}
+
+/**
+ * is_pil_loading_disabled() - Check if pil loading a subsystem is disabled
+ * @edge: Edge that points to the remote subsystem.
+ *
+ * @return: true if disabled, false if enabled.
+ */
+static bool is_pil_loading_disabled(uint32_t edge)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+
+       mutex_lock(&smd_remote_xprt_list_lock_lha1);
+       list_for_each_entry(smd_xprtp, &smd_remote_xprt_list, list) {
+               if (smd_xprtp->edge == edge) {
+                       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+                       return smd_xprtp->disable_pil_loading;
+               }
+       }
+       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+       return true;
+}
+
+/**
+ * msm_ipc_router_smd_remote_probe() - Probe an SMD endpoint
+ *
+ * @pdev: Platform device corresponding to SMD endpoint.
+ *
+ * @return: 0 on success, standard Linux error codes on error.
+ *
+ * This function is called when the underlying SMD driver registers
+ * a platform device, mapped to SMD endpoint.
+ */
+static int msm_ipc_router_smd_remote_probe(struct platform_device *pdev)
+{
+       int rc;
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+
+       smd_xprtp = find_smd_xprt_list(pdev);
+       if (!smd_xprtp) {
+               IPC_RTR_ERR("%s No device with name %s\n",
+                                       __func__, pdev->name);
+               return -EPROBE_DEFER;
+       }
+       if (strcmp(pdev->name, smd_xprtp->ch_name)
+                       || (pdev->id != smd_xprtp->edge)) {
+               IPC_RTR_ERR("%s wrong item name:%s edge:%d\n",
+                               __func__, smd_xprtp->ch_name, smd_xprtp->edge);
+               return -ENODEV;
+       }
+       smd_xprtp->smd_xprt_wq =
+               create_singlethread_workqueue(pdev->name);
+       if (!smd_xprtp->smd_xprt_wq) {
+               IPC_RTR_ERR("%s: WQ creation failed for %s\n",
+                       __func__, pdev->name);
+               return -EFAULT;
+       }
+
+       smd_xprtp->pil = msm_ipc_load_subsystem(
+                                       smd_xprtp->edge);
+       rc = smd_named_open_on_edge(smd_xprtp->ch_name,
+                                   smd_xprtp->edge,
+                                   &smd_xprtp->channel,
+                                   smd_xprtp,
+                                   msm_ipc_router_smd_remote_notify);
+       if (rc < 0) {
+               IPC_RTR_ERR("%s: Channel open failed for %s\n",
+                       __func__, smd_xprtp->ch_name);
+               if (smd_xprtp->pil) {
+                       subsystem_put(smd_xprtp->pil);
+                       smd_xprtp->pil = NULL;
+               }
+               destroy_workqueue(smd_xprtp->smd_xprt_wq);
+               return rc;
+       }
+
+       smd_disable_read_intr(smd_xprtp->channel);
+
+       smsm_change_state(SMSM_APPS_STATE, 0, SMSM_RPCINIT);
+
+       return 0;
+}
+
+/**
+ * msm_ipc_router_smd_driver_register() - register SMD XPRT drivers
+ *
+ * @smd_xprtp: pointer to Ipc router smd xprt structure.
+ *
+ * @return: 0 on success, standard Linux error codes on error.
+ *
+ * This function is called when a new XPRT is added to register platform
+ * drivers for new XPRT.
+ */
+static int msm_ipc_router_smd_driver_register(
+                       struct msm_ipc_router_smd_xprt *smd_xprtp)
+{
+       int ret;
+       struct msm_ipc_router_smd_xprt *item;
+       unsigned already_registered = 0;
+
+       mutex_lock(&smd_remote_xprt_list_lock_lha1);
+       list_for_each_entry(item, &smd_remote_xprt_list, list) {
+               if (!strcmp(smd_xprtp->ch_name, item->ch_name))
+                       already_registered = 1;
+       }
+       list_add(&smd_xprtp->list, &smd_remote_xprt_list);
+       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+
+       if (!already_registered) {
+               smd_xprtp->driver.driver.name = smd_xprtp->ch_name;
+               smd_xprtp->driver.driver.owner = THIS_MODULE;
+               smd_xprtp->driver.probe = msm_ipc_router_smd_remote_probe;
+
+               ret = platform_driver_register(&smd_xprtp->driver);
+               if (ret) {
+                       IPC_RTR_ERR(
+                       "%s: Failed to register platform driver [%s]\n",
+                                               __func__, smd_xprtp->ch_name);
+                       return ret;
+               }
+       } else {
+               IPC_RTR_ERR("%s Already driver registered %s\n",
+                                       __func__, smd_xprtp->ch_name);
+       }
+       return 0;
+}
+
+/**
+ * msm_ipc_router_smd_config_init() - init SMD xprt configs
+ *
+ * @smd_xprt_config: pointer to SMD xprt configurations.
+ *
+ * @return: 0 on success, standard Linux error codes on error.
+ *
+ * This function is called to initialize the SMD XPRT pointer with
+ * the SMD XPRT configurations either from device tree or static arrays.
+ */
+static int msm_ipc_router_smd_config_init(
+               struct msm_ipc_router_smd_xprt_config *smd_xprt_config)
+{
+       struct msm_ipc_router_smd_xprt *smd_xprtp;
+
+       smd_xprtp = kzalloc(sizeof(struct msm_ipc_router_smd_xprt), GFP_KERNEL);
+       if (IS_ERR_OR_NULL(smd_xprtp)) {
+               IPC_RTR_ERR("%s: kzalloc() failed for smd_xprtp id:%s\n",
+                               __func__, smd_xprt_config->ch_name);
+               return -ENOMEM;
+       }
+
+       smd_xprtp->xprt.link_id = smd_xprt_config->link_id;
+       smd_xprtp->xprt_version = smd_xprt_config->xprt_version;
+       smd_xprtp->edge = smd_xprt_config->edge;
+       smd_xprtp->xprt_option = smd_xprt_config->xprt_option;
+       smd_xprtp->disable_pil_loading = smd_xprt_config->disable_pil_loading;
+
+       strlcpy(smd_xprtp->ch_name, smd_xprt_config->ch_name,
+                                               SMD_MAX_CH_NAME_LEN);
+
+       strlcpy(smd_xprtp->xprt_name, smd_xprt_config->xprt_name,
+                                               XPRT_NAME_LEN);
+       smd_xprtp->xprt.name = smd_xprtp->xprt_name;
+
+       smd_xprtp->xprt.get_version =
+               msm_ipc_router_smd_get_xprt_version;
+       smd_xprtp->xprt.get_option =
+               msm_ipc_router_smd_get_xprt_option;
+       smd_xprtp->xprt.read_avail = NULL;
+       smd_xprtp->xprt.read = NULL;
+       smd_xprtp->xprt.write_avail =
+               msm_ipc_router_smd_remote_write_avail;
+       smd_xprtp->xprt.write = msm_ipc_router_smd_remote_write;
+       smd_xprtp->xprt.close = msm_ipc_router_smd_remote_close;
+       smd_xprtp->xprt.sft_close_done = smd_xprt_sft_close_done;
+       smd_xprtp->xprt.priv = NULL;
+
+       init_waitqueue_head(&smd_xprtp->write_avail_wait_q);
+       smd_xprtp->in_pkt = NULL;
+       smd_xprtp->is_partial_in_pkt = 0;
+       INIT_DELAYED_WORK(&smd_xprtp->read_work, smd_xprt_read_data);
+       spin_lock_init(&smd_xprtp->ss_reset_lock);
+       smd_xprtp->ss_reset = 0;
+
+       msm_ipc_router_smd_driver_register(smd_xprtp);
+
+       return 0;
+}
+
+/**
+ * parse_devicetree() - parse device tree binding
+ *
+ * @node: pointer to device tree node
+ * @smd_xprt_config: pointer to SMD XPRT configurations
+ *
+ * @return: 0 on success, -ENODEV on failure.
+ */
+static int parse_devicetree(struct device_node *node,
+               struct msm_ipc_router_smd_xprt_config *smd_xprt_config)
+{
+       int ret;
+       int edge;
+       int link_id;
+       int version;
+       char *key;
+       const char *ch_name;
+       const char *remote_ss;
+
+       key = "qcom,ch-name";
+       ch_name = of_get_property(node, key, NULL);
+       if (!ch_name)
+               goto error;
+       strlcpy(smd_xprt_config->ch_name, ch_name, SMD_MAX_CH_NAME_LEN);
+
+       key = "qcom,xprt-remote";
+       remote_ss = of_get_property(node, key, NULL);
+       if (!remote_ss)
+               goto error;
+       edge = smd_remote_ss_to_edge(remote_ss);
+       if (edge < 0)
+               goto error;
+       smd_xprt_config->edge = edge;
+
+       key = "qcom,xprt-linkid";
+       ret = of_property_read_u32(node, key, &link_id);
+       if (ret)
+               goto error;
+       smd_xprt_config->link_id = link_id;
+
+       key = "qcom,xprt-version";
+       ret = of_property_read_u32(node, key, &version);
+       if (ret)
+               goto error;
+       smd_xprt_config->xprt_version = version;
+
+       key = "qcom,fragmented-data";
+       smd_xprt_config->xprt_option = of_property_read_bool(node, key);
+
+       key = "qcom,disable-pil-loading";
+       smd_xprt_config->disable_pil_loading = of_property_read_bool(node, key);
+
+       scnprintf(smd_xprt_config->xprt_name, XPRT_NAME_LEN, "%s_%s",
+                       remote_ss, smd_xprt_config->ch_name);
+
+       return 0;
+
+error:
+       IPC_RTR_ERR("%s: missing key: %s\n", __func__, key);
+       return -ENODEV;
+}
+
+/**
+ * msm_ipc_router_smd_xprt_probe() - Probe an SMD xprt
+ *
+ * @pdev: Platform device corresponding to SMD xprt.
+ *
+ * @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 an SMD transport.
+ */
+static int msm_ipc_router_smd_xprt_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct msm_ipc_router_smd_xprt_config smd_xprt_config;
+
+       if (pdev) {
+               if (pdev->dev.of_node) {
+                       mutex_lock(&smd_remote_xprt_list_lock_lha1);
+                       ipc_router_smd_xprt_probe_done = 1;
+                       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+
+                       ret = parse_devicetree(pdev->dev.of_node,
+                                                       &smd_xprt_config);
+                       if (ret) {
+                               IPC_RTR_ERR("%s: Failed to parse device tree\n",
+                                                               __func__);
+                               return ret;
+                       }
+
+                       ret = msm_ipc_router_smd_config_init(&smd_xprt_config);
+                       if (ret) {
+                               IPC_RTR_ERR("%s init failed\n", __func__);
+                               return ret;
+                       }
+               }
+       }
+       return 0;
+}
+
+/**
+ * ipc_router_smd_xprt_probe_worker() - probe worker for non DT configurations
+ *
+ * @work: work item to process
+ *
+ * This function is called by schedule_delay_work after 3sec and check if
+ * device tree probe is done or not. If device tree probe fails the default
+ * configurations read from static array.
+ */
+static void ipc_router_smd_xprt_probe_worker(struct work_struct *work)
+{
+       int i, ret;
+
+       BUG_ON(ARRAY_SIZE(smd_xprt_cfg) != NUM_SMD_XPRTS);
+
+       mutex_lock(&smd_remote_xprt_list_lock_lha1);
+       if (!ipc_router_smd_xprt_probe_done) {
+               mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+               for (i = 0; i < ARRAY_SIZE(smd_xprt_cfg); i++) {
+                       ret = msm_ipc_router_smd_config_init(&smd_xprt_cfg[i]);
+                       if (ret)
+                               IPC_RTR_ERR(" %s init failed config idx %d\n",
+                                                       __func__, i);
+               }
+               mutex_lock(&smd_remote_xprt_list_lock_lha1);
+       }
+       mutex_unlock(&smd_remote_xprt_list_lock_lha1);
+}
+
+static struct of_device_id msm_ipc_router_smd_xprt_match_table[] = {
+       { .compatible = "qcom,ipc_router_smd_xprt" },
+       {},
+};
+
+static struct platform_driver msm_ipc_router_smd_xprt_driver = {
+       .probe = msm_ipc_router_smd_xprt_probe,
+       .driver = {
+               .name = MODULE_NAME,
+               .owner = THIS_MODULE,
+               .of_match_table = msm_ipc_router_smd_xprt_match_table,
+        },
+};
+
+static int __init msm_ipc_router_smd_xprt_init(void)
+{
+       int rc;
+
+       rc = platform_driver_register(&msm_ipc_router_smd_xprt_driver);
+       if (rc) {
+               IPC_RTR_ERR(
+               "%s: msm_ipc_router_smd_xprt_driver register failed %d\n",
+                                                               __func__, rc);
+               return rc;
+       }
+
+       INIT_DELAYED_WORK(&ipc_router_smd_xprt_probe_work,
+                                       ipc_router_smd_xprt_probe_worker);
+       schedule_delayed_work(&ipc_router_smd_xprt_probe_work,
+                       msecs_to_jiffies(IPC_ROUTER_SMD_XPRT_WAIT_TIMEOUT));
+       return 0;
+}
+
+module_init(msm_ipc_router_smd_xprt_init);
+MODULE_DESCRIPTION("IPC Router SMD XPRT");
+MODULE_LICENSE("GPL v2");