summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarthikeyan Ramasubramanian <kramasub@codeaurora.org>2016-02-03 12:59:45 -0700
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:08:06 -0700
commite6668df1e53106ca243e86f45d42c51467020a5d (patch)
tree3ea86ee349aa4a73c18859b1d9a080b8197cb1ac
parent963de668849a8885ae9d39115beefe53dec21648 (diff)
soc: qcom: Add snapshot of ipc_router_glink_xprt
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>
-rw-r--r--drivers/soc/qcom/Kconfig10
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/ipc_router_glink_xprt.c848
3 files changed, 859 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index c97fafe7c5b2..071ef53ebebc 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -191,6 +191,16 @@ config MSM_IPC_ROUTER_MHI_XPRT
registers the transport with IPC Router and enable message
exchange.
+config MSM_IPC_ROUTER_GLINK_XPRT
+ depends on MSM_GLINK
+ depends on IPC_ROUTER
+ bool "MSM GLINK XPRT Layer"
+ help
+ GLINK Transport Layer that enables IPC Router communication within
+ a System-on-Chip(SoC). When the GLINK 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
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index deed994a51df..d06bc5ddb261 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -16,6 +16,7 @@ endif
obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o
obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o
obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o
+obj-$(CONFIG_MSM_IPC_ROUTER_GLINK_XPRT) += ipc_router_glink_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_glink_xprt.c b/drivers/soc/qcom/ipc_router_glink_xprt.c
new file mode 100644
index 000000000000..5290d445b05f
--- /dev/null
+++ b/drivers/soc/qcom/ipc_router_glink_xprt.c
@@ -0,0 +1,848 @@
+/* Copyright (c) 2014-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 GLINK 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/glink.h>
+#include <soc/qcom/subsystem_restart.h>
+
+static int ipc_router_glink_xprt_debug_mask;
+module_param_named(debug_mask, ipc_router_glink_xprt_debug_mask,
+ int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+#if defined(DEBUG)
+#define D(x...) do { \
+if (ipc_router_glink_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 IPC_RTR_XPRT_NAME_LEN (2 * GLINK_NAME_SIZE)
+#define PIL_SUBSYSTEM_NAME_LEN 32
+#define DEFAULT_NUM_INTENTS 5
+#define DEFAULT_RX_INTENT_SIZE 2048
+/**
+ * ipc_router_glink_xprt - IPC Router's GLINK XPRT structure
+ * @list: IPC router's GLINK XPRT list.
+ * @ch_name: GLink Channel Name.
+ * @edge: Edge between the local node and the remote node.
+ * @transport: Physical Transport Name as identified by Glink.
+ * @pil_edge: Edge name understood by PIL.
+ * @ipc_rtr_xprt_name: XPRT Name to be registered with IPC Router.
+ * @xprt: IPC Router XPRT structure to contain XPRT specific info.
+ * @ch_hndl: Opaque Channel handle returned by GLink.
+ * @xprt_wq: Workqueue to queue read & other XPRT related works.
+ * @ss_reset_lock: Lock to protect access to the ss_reset flag.
+ * @ss_reset: flag used to check SSR state.
+ * @pil: 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 ipc_router_glink_xprt {
+ struct list_head list;
+ char ch_name[GLINK_NAME_SIZE];
+ char edge[GLINK_NAME_SIZE];
+ char transport[GLINK_NAME_SIZE];
+ char pil_edge[PIL_SUBSYSTEM_NAME_LEN];
+ char ipc_rtr_xprt_name[IPC_RTR_XPRT_NAME_LEN];
+ struct msm_ipc_router_xprt xprt;
+ void *ch_hndl;
+ struct workqueue_struct *xprt_wq;
+ struct mutex ss_reset_lock;
+ int ss_reset;
+ void *pil;
+ struct completion sft_close_complete;
+ unsigned xprt_version;
+ unsigned xprt_option;
+ bool disable_pil_loading;
+};
+
+struct ipc_router_glink_xprt_work {
+ struct ipc_router_glink_xprt *glink_xprtp;
+ struct work_struct work;
+};
+
+struct queue_rx_intent_work {
+ struct ipc_router_glink_xprt *glink_xprtp;
+ size_t intent_size;
+ struct work_struct work;
+};
+
+struct read_work {
+ struct ipc_router_glink_xprt *glink_xprtp;
+ void *iovec;
+ size_t iovec_size;
+ void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size);
+ void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size);
+ struct work_struct work;
+};
+
+static void glink_xprt_read_data(struct work_struct *work);
+static void glink_xprt_open_event(struct work_struct *work);
+static void glink_xprt_close_event(struct work_struct *work);
+
+/**
+ * ipc_router_glink_xprt_config - Config. Info. of each GLINK XPRT
+ * @ch_name: Name of the GLINK endpoint exported by GLINK driver.
+ * @edge: Edge between the local node and remote node.
+ * @transport: Physical Transport Name as identified by GLINK.
+ * @pil_edge: Edge name understood by PIL.
+ * @ipc_rtr_xprt_name: XPRT Name to be registered with IPC Router.
+ * @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 ipc_router_glink_xprt_config {
+ char ch_name[GLINK_NAME_SIZE];
+ char edge[GLINK_NAME_SIZE];
+ char transport[GLINK_NAME_SIZE];
+ char ipc_rtr_xprt_name[IPC_RTR_XPRT_NAME_LEN];
+ char pil_edge[PIL_SUBSYSTEM_NAME_LEN];
+ uint32_t link_id;
+ unsigned xprt_version;
+ unsigned xprt_option;
+ bool disable_pil_loading;
+};
+
+#define MODULE_NAME "ipc_router_glink_xprt"
+static DEFINE_MUTEX(glink_xprt_list_lock_lha1);
+static LIST_HEAD(glink_xprt_list);
+
+static struct workqueue_struct *glink_xprt_wq;
+
+static void glink_xprt_link_state_cb(struct glink_link_state_cb_info *cb_info,
+ void *priv);
+static struct glink_link_info glink_xprt_link_info = {
+ NULL, NULL, glink_xprt_link_state_cb};
+static void *glink_xprt_link_state_notif_handle;
+
+struct xprt_state_work_info {
+ char edge[GLINK_NAME_SIZE];
+ char transport[GLINK_NAME_SIZE];
+ uint32_t link_state;
+ struct work_struct work;
+};
+
+#define OVERFLOW_ADD_UNSIGNED(type, a, b) \
+ (((type)~0 - (a)) < (b) ? true : false)
+
+static void *glink_xprt_vbuf_provider(void *iovec, size_t offset,
+ size_t *buf_size)
+{
+ struct rr_packet *pkt = (struct rr_packet *)iovec;
+ struct sk_buff *skb;
+ size_t temp_size = 0;
+
+ if (unlikely(!pkt || !buf_size))
+ return NULL;
+
+ *buf_size = 0;
+ skb_queue_walk(pkt->pkt_fragment_q, skb) {
+ if (unlikely(OVERFLOW_ADD_UNSIGNED(size_t, temp_size,
+ skb->len)))
+ break;
+
+ temp_size += skb->len;
+ if (offset >= temp_size)
+ continue;
+
+ *buf_size = temp_size - offset;
+ return (void *)skb->data + skb->len - *buf_size;
+ }
+ return NULL;
+}
+
+static int ipc_router_glink_xprt_get_version(
+ struct msm_ipc_router_xprt *xprt)
+{
+ struct ipc_router_glink_xprt *glink_xprtp;
+ if (!xprt)
+ return -EINVAL;
+ glink_xprtp = container_of(xprt, struct ipc_router_glink_xprt, xprt);
+
+ return (int)glink_xprtp->xprt_version;
+}
+
+static int ipc_router_glink_xprt_get_option(
+ struct msm_ipc_router_xprt *xprt)
+{
+ struct ipc_router_glink_xprt *glink_xprtp;
+ if (!xprt)
+ return -EINVAL;
+ glink_xprtp = container_of(xprt, struct ipc_router_glink_xprt, xprt);
+
+ return (int)glink_xprtp->xprt_option;
+}
+
+static int ipc_router_glink_xprt_write(void *data, uint32_t len,
+ struct msm_ipc_router_xprt *xprt)
+{
+ struct rr_packet *pkt = (struct rr_packet *)data;
+ struct rr_packet *temp_pkt;
+ int ret;
+ struct ipc_router_glink_xprt *glink_xprtp =
+ container_of(xprt, struct ipc_router_glink_xprt, xprt);
+
+ if (!pkt)
+ return -EINVAL;
+
+ if (!len || pkt->length != len)
+ return -EINVAL;
+
+ temp_pkt = clone_pkt(pkt);
+ if (!temp_pkt) {
+ IPC_RTR_ERR("%s: Error cloning packet while tx\n", __func__);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&glink_xprtp->ss_reset_lock);
+ if (glink_xprtp->ss_reset) {
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ release_pkt(temp_pkt);
+ IPC_RTR_ERR("%s: %s chnl reset\n", __func__, xprt->name);
+ return -ENETRESET;
+ }
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+
+ D("%s: Ready to write %d bytes\n", __func__, len);
+ ret = glink_txv(glink_xprtp->ch_hndl, (void *)glink_xprtp,
+ (void *)temp_pkt, len, glink_xprt_vbuf_provider,
+ NULL, true);
+ if (ret < 0) {
+ release_pkt(temp_pkt);
+ IPC_RTR_ERR("%s: Error %d while tx\n", __func__, ret);
+ return ret;
+ }
+ D("%s:%s: TX Complete for %d bytes @ %p\n", __func__,
+ glink_xprtp->ipc_rtr_xprt_name, len, temp_pkt);
+ return len;
+}
+
+static int ipc_router_glink_xprt_close(struct msm_ipc_router_xprt *xprt)
+{
+ struct ipc_router_glink_xprt *glink_xprtp =
+ container_of(xprt, struct ipc_router_glink_xprt, xprt);
+
+ mutex_lock(&glink_xprtp->ss_reset_lock);
+ glink_xprtp->ss_reset = 1;
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ return glink_close(glink_xprtp->ch_hndl);
+}
+
+static void glink_xprt_sft_close_done(struct msm_ipc_router_xprt *xprt)
+{
+ struct ipc_router_glink_xprt *glink_xprtp =
+ container_of(xprt, struct ipc_router_glink_xprt, xprt);
+
+ complete_all(&glink_xprtp->sft_close_complete);
+}
+
+static struct rr_packet *glink_xprt_copy_data(struct read_work *rx_work)
+{
+ void *buf, *pbuf, *dest_buf;
+ size_t buf_size;
+ struct rr_packet *pkt;
+ struct sk_buff *skb;
+
+ pkt = create_pkt(NULL);
+ if (!pkt) {
+ IPC_RTR_ERR("%s: Couldn't alloc rr_packet\n", __func__);
+ return NULL;
+ }
+
+ do {
+ buf_size = 0;
+ if (rx_work->vbuf_provider) {
+ buf = rx_work->vbuf_provider(rx_work->iovec,
+ pkt->length, &buf_size);
+ } else {
+ pbuf = rx_work->pbuf_provider(rx_work->iovec,
+ pkt->length, &buf_size);
+ buf = phys_to_virt((unsigned long)pbuf);
+ }
+ if (!buf_size || !buf)
+ break;
+
+ skb = alloc_skb(buf_size, GFP_KERNEL);
+ if (!skb) {
+ IPC_RTR_ERR("%s: Couldn't alloc skb of size %zu\n",
+ __func__, buf_size);
+ release_pkt(pkt);
+ return NULL;
+ }
+ dest_buf = skb_put(skb, buf_size);
+ memcpy(dest_buf, buf, buf_size);
+ skb_queue_tail(pkt->pkt_fragment_q, skb);
+ pkt->length += buf_size;
+ } while (buf && buf_size);
+ return pkt;
+}
+
+static void glink_xprt_read_data(struct work_struct *work)
+{
+ struct rr_packet *pkt;
+ struct read_work *rx_work =
+ container_of(work, struct read_work, work);
+ struct ipc_router_glink_xprt *glink_xprtp = rx_work->glink_xprtp;
+ bool reuse_intent = false;
+
+ mutex_lock(&glink_xprtp->ss_reset_lock);
+ if (glink_xprtp->ss_reset) {
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ IPC_RTR_ERR("%s: %s channel reset\n",
+ __func__, glink_xprtp->xprt.name);
+ goto out_read_data;
+ }
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+
+ D("%s %zu bytes @ %p\n", __func__, rx_work->iovec_size, rx_work->iovec);
+ if (rx_work->iovec_size <= DEFAULT_RX_INTENT_SIZE)
+ reuse_intent = true;
+
+ pkt = glink_xprt_copy_data(rx_work);
+ if (!pkt) {
+ IPC_RTR_ERR("%s: Error copying data\n", __func__);
+ goto out_read_data;
+ }
+
+ msm_ipc_router_xprt_notify(&glink_xprtp->xprt,
+ IPC_ROUTER_XPRT_EVENT_DATA, pkt);
+ release_pkt(pkt);
+out_read_data:
+ glink_rx_done(glink_xprtp->ch_hndl, rx_work->iovec, reuse_intent);
+ kfree(rx_work);
+}
+
+static void glink_xprt_open_event(struct work_struct *work)
+{
+ struct ipc_router_glink_xprt_work *xprt_work =
+ container_of(work, struct ipc_router_glink_xprt_work, work);
+ struct ipc_router_glink_xprt *glink_xprtp = xprt_work->glink_xprtp;
+ int i;
+
+ msm_ipc_router_xprt_notify(&glink_xprtp->xprt,
+ IPC_ROUTER_XPRT_EVENT_OPEN, NULL);
+ D("%s: Notified IPC Router of %s OPEN\n",
+ __func__, glink_xprtp->xprt.name);
+ for (i = 0; i < DEFAULT_NUM_INTENTS; i++)
+ glink_queue_rx_intent(glink_xprtp->ch_hndl, (void *)glink_xprtp,
+ DEFAULT_RX_INTENT_SIZE);
+ kfree(xprt_work);
+}
+
+static void glink_xprt_close_event(struct work_struct *work)
+{
+ struct ipc_router_glink_xprt_work *xprt_work =
+ container_of(work, struct ipc_router_glink_xprt_work, work);
+ struct ipc_router_glink_xprt *glink_xprtp = xprt_work->glink_xprtp;
+
+ init_completion(&glink_xprtp->sft_close_complete);
+ msm_ipc_router_xprt_notify(&glink_xprtp->xprt,
+ IPC_ROUTER_XPRT_EVENT_CLOSE, NULL);
+ D("%s: Notified IPC Router of %s CLOSE\n",
+ __func__, glink_xprtp->xprt.name);
+ wait_for_completion(&glink_xprtp->sft_close_complete);
+ kfree(xprt_work);
+}
+
+static void glink_xprt_qrx_intent_worker(struct work_struct *work)
+{
+ struct queue_rx_intent_work *qrx_intent_work =
+ container_of(work, struct queue_rx_intent_work, work);
+ struct ipc_router_glink_xprt *glink_xprtp =
+ qrx_intent_work->glink_xprtp;
+
+ glink_queue_rx_intent(glink_xprtp->ch_hndl, (void *)glink_xprtp,
+ qrx_intent_work->intent_size);
+ kfree(qrx_intent_work);
+}
+
+static void msm_ipc_unload_subsystem(struct ipc_router_glink_xprt *glink_xprtp)
+{
+ if (glink_xprtp->pil) {
+ subsystem_put(glink_xprtp->pil);
+ glink_xprtp->pil = NULL;
+ }
+}
+
+static void *msm_ipc_load_subsystem(struct ipc_router_glink_xprt *glink_xprtp)
+{
+ void *pil = NULL;
+
+ if (!glink_xprtp->disable_pil_loading) {
+ pil = subsystem_get(glink_xprtp->pil_edge);
+ if (IS_ERR(pil)) {
+ pr_err("%s: Failed to load %s err = [0x%ld]\n",
+ __func__, glink_xprtp->pil_edge, PTR_ERR(pil));
+ pil = NULL;
+ }
+ }
+ return pil;
+}
+
+static void glink_xprt_notify_rxv(void *handle, const void *priv,
+ const void *pkt_priv, void *ptr, size_t size,
+ void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size),
+ void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size))
+{
+ struct ipc_router_glink_xprt *glink_xprtp =
+ (struct ipc_router_glink_xprt *)priv;
+ struct read_work *rx_work;
+
+ rx_work = kmalloc(sizeof(struct read_work), GFP_KERNEL);
+ if (!rx_work) {
+ IPC_RTR_ERR("%s: couldn't allocate read_work\n", __func__);
+ glink_rx_done(glink_xprtp->ch_hndl, ptr, true);
+ return;
+ }
+
+ rx_work->glink_xprtp = glink_xprtp;
+ rx_work->iovec = ptr;
+ rx_work->iovec_size = size;
+ rx_work->vbuf_provider = vbuf_provider;
+ rx_work->pbuf_provider = pbuf_provider;
+ INIT_WORK(&rx_work->work, glink_xprt_read_data);
+ queue_work(glink_xprtp->xprt_wq, &rx_work->work);
+}
+
+static void glink_xprt_notify_tx_done(void *handle, const void *priv,
+ const void *pkt_priv, const void *ptr)
+{
+ struct ipc_router_glink_xprt *glink_xprtp =
+ (struct ipc_router_glink_xprt *)priv;
+ struct rr_packet *temp_pkt = (struct rr_packet *)ptr;
+
+ D("%s:%s: @ %p\n", __func__, glink_xprtp->ipc_rtr_xprt_name, ptr);
+ release_pkt(temp_pkt);
+}
+
+static bool glink_xprt_notify_rx_intent_req(void *handle, const void *priv,
+ size_t sz)
+{
+ struct queue_rx_intent_work *qrx_intent_work;
+ struct ipc_router_glink_xprt *glink_xprtp =
+ (struct ipc_router_glink_xprt *)priv;
+
+ qrx_intent_work = kmalloc(sizeof(struct queue_rx_intent_work),
+ GFP_KERNEL);
+ if (!qrx_intent_work) {
+ IPC_RTR_ERR("%s: Couldn't queue rx_intent of %zu bytes\n",
+ __func__, sz);
+ return false;
+ }
+ qrx_intent_work->glink_xprtp = glink_xprtp;
+ qrx_intent_work->intent_size = sz;
+ INIT_WORK(&qrx_intent_work->work, glink_xprt_qrx_intent_worker);
+ queue_work(glink_xprtp->xprt_wq, &qrx_intent_work->work);
+ return true;
+}
+
+static void glink_xprt_notify_state(void *handle, const void *priv,
+ unsigned event)
+{
+ struct ipc_router_glink_xprt_work *xprt_work;
+ struct ipc_router_glink_xprt *glink_xprtp =
+ (struct ipc_router_glink_xprt *)priv;
+
+ D("%s: %s:%s - State %d\n",
+ __func__, glink_xprtp->edge, glink_xprtp->transport, event);
+ switch (event) {
+ case GLINK_CONNECTED:
+ if (IS_ERR_OR_NULL(glink_xprtp->ch_hndl))
+ glink_xprtp->ch_hndl = handle;
+ mutex_lock(&glink_xprtp->ss_reset_lock);
+ glink_xprtp->ss_reset = 0;
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ xprt_work = kmalloc(sizeof(struct ipc_router_glink_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->glink_xprtp = glink_xprtp;
+ INIT_WORK(&xprt_work->work, glink_xprt_open_event);
+ queue_work(glink_xprtp->xprt_wq, &xprt_work->work);
+ break;
+
+ case GLINK_LOCAL_DISCONNECTED:
+ case GLINK_REMOTE_DISCONNECTED:
+ mutex_lock(&glink_xprtp->ss_reset_lock);
+ if (glink_xprtp->ss_reset) {
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ break;
+ }
+ glink_xprtp->ss_reset = 1;
+ mutex_unlock(&glink_xprtp->ss_reset_lock);
+ xprt_work = kmalloc(sizeof(struct ipc_router_glink_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->glink_xprtp = glink_xprtp;
+ INIT_WORK(&xprt_work->work, glink_xprt_close_event);
+ queue_work(glink_xprtp->xprt_wq, &xprt_work->work);
+ break;
+ }
+}
+
+static void glink_xprt_ch_open(struct ipc_router_glink_xprt *glink_xprtp)
+{
+ struct glink_open_config open_cfg = {0};
+
+ if (!IS_ERR_OR_NULL(glink_xprtp->ch_hndl))
+ return;
+
+ open_cfg.transport = glink_xprtp->transport;
+ open_cfg.options |= GLINK_OPT_INITIAL_XPORT;
+ open_cfg.edge = glink_xprtp->edge;
+ open_cfg.name = glink_xprtp->ch_name;
+ open_cfg.notify_rx = NULL;
+ open_cfg.notify_rxv = glink_xprt_notify_rxv;
+ open_cfg.notify_tx_done = glink_xprt_notify_tx_done;
+ open_cfg.notify_state = glink_xprt_notify_state;
+ open_cfg.notify_rx_intent_req = glink_xprt_notify_rx_intent_req;
+ open_cfg.priv = glink_xprtp;
+
+ glink_xprtp->pil = msm_ipc_load_subsystem(glink_xprtp);
+ glink_xprtp->ch_hndl = glink_open(&open_cfg);
+ if (IS_ERR_OR_NULL(glink_xprtp->ch_hndl)) {
+ IPC_RTR_ERR("%s:%s:%s %s: unable to open channel\n",
+ open_cfg.transport, open_cfg.edge,
+ open_cfg.name, __func__);
+ msm_ipc_unload_subsystem(glink_xprtp);
+ }
+}
+
+/**
+ * glink_xprt_link_state_worker() - Function to handle link state updates
+ * @work: Pointer to the work item in the link_state_work_info.
+ *
+ * This worker function is scheduled when there is a link state update. Since
+ * the loopback server registers for all transports, it receives all link state
+ * updates about all transports that get registered in the system.
+ */
+static void glink_xprt_link_state_worker(struct work_struct *work)
+{
+ struct xprt_state_work_info *xs_info =
+ container_of(work, struct xprt_state_work_info, work);
+ struct ipc_router_glink_xprt *glink_xprtp;
+
+ if (xs_info->link_state == GLINK_LINK_STATE_UP) {
+ D("%s: LINK_STATE_UP %s:%s\n",
+ __func__, xs_info->edge, xs_info->transport);
+ mutex_lock(&glink_xprt_list_lock_lha1);
+ list_for_each_entry(glink_xprtp, &glink_xprt_list, list) {
+ if (strcmp(glink_xprtp->edge, xs_info->edge) ||
+ strcmp(glink_xprtp->transport, xs_info->transport))
+ continue;
+ glink_xprt_ch_open(glink_xprtp);
+ }
+ mutex_unlock(&glink_xprt_list_lock_lha1);
+ } else if (xs_info->link_state == GLINK_LINK_STATE_DOWN) {
+ D("%s: LINK_STATE_DOWN %s:%s\n",
+ __func__, xs_info->edge, xs_info->transport);
+ mutex_lock(&glink_xprt_list_lock_lha1);
+ list_for_each_entry(glink_xprtp, &glink_xprt_list, list) {
+ if (strcmp(glink_xprtp->edge, xs_info->edge) ||
+ strcmp(glink_xprtp->transport, xs_info->transport)
+ || IS_ERR_OR_NULL(glink_xprtp->ch_hndl))
+ continue;
+ glink_close(glink_xprtp->ch_hndl);
+ glink_xprtp->ch_hndl = NULL;
+ msm_ipc_unload_subsystem(glink_xprtp);
+ }
+ mutex_unlock(&glink_xprt_list_lock_lha1);
+
+ }
+ kfree(xs_info);
+ return;
+}
+
+/**
+ * glink_xprt_link_state_cb() - Callback to receive link state updates
+ * @cb_info: Information containing link & its state.
+ * @priv: Private data passed during the link state registration.
+ *
+ * This function is called by the GLINK core to notify the IPC Router
+ * regarding the link state updates. This function is registered with the
+ * GLINK core by IPC Router during glink_register_link_state_cb().
+ */
+static void glink_xprt_link_state_cb(struct glink_link_state_cb_info *cb_info,
+ void *priv)
+{
+ struct xprt_state_work_info *xs_info;
+
+ if (!cb_info)
+ return;
+
+ D("%s: %s:%s\n", __func__, cb_info->edge, cb_info->transport);
+ xs_info = kmalloc(sizeof(*xs_info), GFP_KERNEL);
+ if (!xs_info) {
+ IPC_RTR_ERR("%s: Error allocating xprt state info\n", __func__);
+ return;
+ }
+
+ strlcpy(xs_info->edge, cb_info->edge, GLINK_NAME_SIZE);
+ strlcpy(xs_info->transport, cb_info->transport, GLINK_NAME_SIZE);
+ xs_info->link_state = cb_info->link_state;
+ INIT_WORK(&xs_info->work, glink_xprt_link_state_worker);
+ queue_work(glink_xprt_wq, &xs_info->work);
+}
+
+/**
+ * ipc_router_glink_config_init() - init GLINK xprt configs
+ *
+ * @glink_xprt_config: pointer to GLINK Channel configurations.
+ *
+ * @return: 0 on success, standard Linux error codes on error.
+ *
+ * This function is called to initialize the GLINK XPRT pointer with
+ * the GLINK XPRT configurations either from device tree or static arrays.
+ */
+static int ipc_router_glink_config_init(
+ struct ipc_router_glink_xprt_config *glink_xprt_config)
+{
+ struct ipc_router_glink_xprt *glink_xprtp;
+ char xprt_wq_name[GLINK_NAME_SIZE];
+
+ glink_xprtp = kzalloc(sizeof(struct ipc_router_glink_xprt), GFP_KERNEL);
+ if (IS_ERR_OR_NULL(glink_xprtp)) {
+ IPC_RTR_ERR("%s:%s:%s:%s glink_xprtp alloc failed\n",
+ __func__, glink_xprt_config->ch_name,
+ glink_xprt_config->edge,
+ glink_xprt_config->transport);
+ return -ENOMEM;
+ }
+
+ glink_xprtp->xprt.link_id = glink_xprt_config->link_id;
+ glink_xprtp->xprt_version = glink_xprt_config->xprt_version;
+ glink_xprtp->xprt_option = glink_xprt_config->xprt_option;
+ glink_xprtp->disable_pil_loading =
+ glink_xprt_config->disable_pil_loading;
+
+ if (!glink_xprtp->disable_pil_loading)
+ strlcpy(glink_xprtp->pil_edge, glink_xprt_config->pil_edge,
+ PIL_SUBSYSTEM_NAME_LEN);
+ strlcpy(glink_xprtp->ch_name, glink_xprt_config->ch_name,
+ GLINK_NAME_SIZE);
+ strlcpy(glink_xprtp->edge, glink_xprt_config->edge, GLINK_NAME_SIZE);
+ strlcpy(glink_xprtp->transport,
+ glink_xprt_config->transport, GLINK_NAME_SIZE);
+ strlcpy(glink_xprtp->ipc_rtr_xprt_name,
+ glink_xprt_config->ipc_rtr_xprt_name, IPC_RTR_XPRT_NAME_LEN);
+ glink_xprtp->xprt.name = glink_xprtp->ipc_rtr_xprt_name;
+
+ glink_xprtp->xprt.get_version = ipc_router_glink_xprt_get_version;
+ glink_xprtp->xprt.get_option = ipc_router_glink_xprt_get_option;
+ glink_xprtp->xprt.read_avail = NULL;
+ glink_xprtp->xprt.read = NULL;
+ glink_xprtp->xprt.write_avail = NULL;
+ glink_xprtp->xprt.write = ipc_router_glink_xprt_write;
+ glink_xprtp->xprt.close = ipc_router_glink_xprt_close;
+ glink_xprtp->xprt.sft_close_done = glink_xprt_sft_close_done;
+ glink_xprtp->xprt.priv = NULL;
+
+ mutex_init(&glink_xprtp->ss_reset_lock);
+ glink_xprtp->ss_reset = 0;
+
+ scnprintf(xprt_wq_name, GLINK_NAME_SIZE, "%s_%s_%s",
+ glink_xprtp->ch_name, glink_xprtp->edge,
+ glink_xprtp->transport);
+ glink_xprtp->xprt_wq = create_singlethread_workqueue(xprt_wq_name);
+ if (IS_ERR_OR_NULL(glink_xprtp->xprt_wq)) {
+ IPC_RTR_ERR("%s:%s:%s:%s wq alloc failed\n",
+ __func__, glink_xprt_config->ch_name,
+ glink_xprt_config->edge,
+ glink_xprt_config->transport);
+ kfree(glink_xprtp);
+ return -EFAULT;
+ }
+
+ mutex_lock(&glink_xprt_list_lock_lha1);
+ list_add(&glink_xprtp->list, &glink_xprt_list);
+ mutex_unlock(&glink_xprt_list_lock_lha1);
+
+ glink_xprt_link_info.edge = glink_xprt_config->edge;
+ glink_xprt_link_state_notif_handle = glink_register_link_state_cb(
+ &glink_xprt_link_info, NULL);
+ return 0;
+}
+
+/**
+ * parse_devicetree() - parse device tree binding
+ *
+ * @node: pointer to device tree node
+ * @glink_xprt_config: pointer to GLINK XPRT configurations
+ *
+ * @return: 0 on success, -ENODEV on failure.
+ */
+static int parse_devicetree(struct device_node *node,
+ struct ipc_router_glink_xprt_config *glink_xprt_config)
+{
+ int ret;
+ int link_id;
+ int version;
+ char *key;
+ const char *ch_name;
+ const char *edge;
+ const char *transport;
+ const char *pil_edge;
+
+ key = "qcom,ch-name";
+ ch_name = of_get_property(node, key, NULL);
+ if (!ch_name)
+ goto error;
+ strlcpy(glink_xprt_config->ch_name, ch_name, GLINK_NAME_SIZE);
+
+ key = "qcom,xprt-remote";
+ edge = of_get_property(node, key, NULL);
+ if (!edge)
+ goto error;
+ strlcpy(glink_xprt_config->edge, edge, GLINK_NAME_SIZE);
+
+ key = "qcom,glink-xprt";
+ transport = of_get_property(node, key, NULL);
+ if (!transport)
+ goto error;
+ strlcpy(glink_xprt_config->transport, transport,
+ GLINK_NAME_SIZE);
+
+ key = "qcom,xprt-linkid";
+ ret = of_property_read_u32(node, key, &link_id);
+ if (ret)
+ goto error;
+ glink_xprt_config->link_id = link_id;
+
+ key = "qcom,xprt-version";
+ ret = of_property_read_u32(node, key, &version);
+ if (ret)
+ goto error;
+ glink_xprt_config->xprt_version = version;
+
+ key = "qcom,fragmented-data";
+ glink_xprt_config->xprt_option = of_property_read_bool(node, key);
+
+ key = "qcom,pil-label";
+ pil_edge = of_get_property(node, key, NULL);
+ if (pil_edge) {
+ strlcpy(glink_xprt_config->pil_edge,
+ pil_edge, PIL_SUBSYSTEM_NAME_LEN);
+ glink_xprt_config->disable_pil_loading = false;
+ } else {
+ glink_xprt_config->disable_pil_loading = true;
+ }
+ scnprintf(glink_xprt_config->ipc_rtr_xprt_name, IPC_RTR_XPRT_NAME_LEN,
+ "%s_%s", edge, ch_name);
+
+ return 0;
+
+error:
+ IPC_RTR_ERR("%s: missing key: %s\n", __func__, key);
+ return -ENODEV;
+}
+
+/**
+ * ipc_router_glink_xprt_probe() - Probe a GLINK xprt
+ *
+ * @pdev: Platform device corresponding to GLINK 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 a GLINK transport.
+ */
+static int ipc_router_glink_xprt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct ipc_router_glink_xprt_config glink_xprt_config;
+
+ if (pdev) {
+ if (pdev->dev.of_node) {
+ ret = parse_devicetree(pdev->dev.of_node,
+ &glink_xprt_config);
+ if (ret) {
+ IPC_RTR_ERR("%s: Failed to parse device tree\n",
+ __func__);
+ return ret;
+ }
+
+ ret = ipc_router_glink_config_init(&glink_xprt_config);
+ if (ret) {
+ IPC_RTR_ERR("%s init failed\n", __func__);
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static struct of_device_id ipc_router_glink_xprt_match_table[] = {
+ { .compatible = "qcom,ipc_router_glink_xprt" },
+ {},
+};
+
+static struct platform_driver ipc_router_glink_xprt_driver = {
+ .probe = ipc_router_glink_xprt_probe,
+ .driver = {
+ .name = MODULE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = ipc_router_glink_xprt_match_table,
+ },
+};
+
+static int __init ipc_router_glink_xprt_init(void)
+{
+ int rc;
+
+ glink_xprt_wq = create_singlethread_workqueue("glink_xprt_wq");
+ if (IS_ERR_OR_NULL(glink_xprt_wq)) {
+ pr_err("%s: create_singlethread_workqueue failed\n", __func__);
+ return -EFAULT;
+ }
+
+ rc = platform_driver_register(&ipc_router_glink_xprt_driver);
+ if (rc) {
+ IPC_RTR_ERR(
+ "%s: ipc_router_glink_xprt_driver register failed %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+module_init(ipc_router_glink_xprt_init);
+MODULE_DESCRIPTION("IPC Router GLINK XPRT");
+MODULE_LICENSE("GPL v2");