summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-09-09 14:34:38 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2017-09-09 14:34:38 -0700
commit7151202b64c8c5eb163e41fa0adcb8239eea64aa (patch)
tree00a8d47c830e1c47405808b4a3714fe41f3eea0d
parentd7efc352abb8903ccb8600e1148f59dd9164317e (diff)
parented43d098f9020d6669a00bd26fac807d5c19e202 (diff)
Merge tag 'rpmsg-v4.14' of git://github.com/andersson/remoteproc
Pull rpmsg updates from Bjorn Andersson: "This extends the Qualcomm GLINK implementation to support the additional features used for communicating with modem and DSP coprocessors in modern Qualcomm platforms. In addition to this there's support for placing virtio RPMSG buffers in non-System RAM" * tag 'rpmsg-v4.14' of git://github.com/andersson/remoteproc: (29 commits) rpmsg: glink: initialize ret to zero to ensure error status check is correct rpmsg: glink: fix null pointer dereference on a null intent dt-bindings: soc: qcom: Extend GLINK to cover SMEM remoteproc: qcom: adsp: Allow defining GLINK edge rpmsg: glink: Export symbols from common code rpmsg: glink: Release idr lock before returning on error rpmsg: glink: Handle remote rx done command rpmsg: glink: Request for intents when unavailable rpmsg: glink: Use the intents passed by remote rpmsg: glink: Receive and store the remote intent buffers rpmsg: glink: Add announce_create ops and preallocate intents rpmsg: glink: Add rx done command rpmsg: glink: Make RX FIFO peak accessor to take an offset rpmsg: glink: Use the local intents when receiving data rpmsg: glink: Add support for TX intents rpmsg: glink: Fix idr_lock from mutex to spinlock rpmsg: glink: Add support for transport version negotiation rpmsg: glink: Introduce glink smem based transport rpmsg: glink: Do a mbox_free_channel in remove rpmsg: glink: Return -EAGAIN when there is no FIFO space ...
-rw-r--r--Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt7
-rw-r--r--Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt5
-rw-r--r--Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt13
-rw-r--r--drivers/remoteproc/Kconfig1
-rw-r--r--drivers/remoteproc/qcom_adsp_pil.c3
-rw-r--r--drivers/remoteproc/qcom_common.c49
-rw-r--r--drivers/remoteproc/qcom_common.h11
-rw-r--r--drivers/rpmsg/Kconfig16
-rw-r--r--drivers/rpmsg/Makefile2
-rw-r--r--drivers/rpmsg/qcom_glink_native.c1612
-rw-r--r--drivers/rpmsg/qcom_glink_native.h45
-rw-r--r--drivers/rpmsg/qcom_glink_rpm.c1026
-rw-r--r--drivers/rpmsg/qcom_glink_smem.c316
-rw-r--r--drivers/rpmsg/qcom_smd.c1
-rw-r--r--drivers/rpmsg/virtio_rpmsg_bus.c47
-rw-r--r--include/linux/rpmsg/qcom_glink.h27
16 files changed, 2210 insertions, 971 deletions
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt
index 75ad7b8df0b1..728e4193f7a6 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt
@@ -63,9 +63,10 @@ on the Qualcomm ADSP Hexagon core.
= SUBNODES
-The adsp node may have an subnode named "smd-edge" that describes the SMD edge,
-channels and devices related to the ADSP. See ../soc/qcom/qcom,smd.txt for
-details on how to describe the SMD edge.
+The adsp node may have an subnode named either "smd-edge" or "glink-edge" that
+describes the communication edge, channels and devices related to the ADSP.
+See ../soc/qcom/qcom,smd.txt and ../soc/qcom/qcom,glink.txt for details on how
+to describe these.
= EXAMPLE
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt b/Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt
index 92347fe6890e..7ff3f7903f26 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt
@@ -90,6 +90,11 @@ the memory regions used by the Hexagon firmware. Each sub-node must contain:
Value type: <phandle>
Definition: reference to the reserved-memory for the region
+The Hexagon node may also have an subnode named either "smd-edge" or
+"glink-edge" that describes the communication edge, channels and devices
+related to the Hexagon. See ../soc/qcom/qcom,smd.txt and
+../soc/qcom/qcom,glink.txt for details on how to describe these.
+
= EXAMPLE
The following example describes the resources needed to boot control the
Hexagon, as it is found on MSM8974 boards.
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
index 50fc20c6ce91..b277eca861f7 100644
--- a/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,glink.txt
@@ -1,11 +1,12 @@
-Qualcomm RPM GLINK binding
+Qualcomm GLINK edge binding
-This binding describes the Qualcomm RPM GLINK, a fifo based mechanism for
-communication with the Resource Power Management system on various Qualcomm
-platforms.
+This binding describes a Qualcomm GLINK edge, a fifo based mechanism for
+communication between subsystem-pairs on various Qualcomm platforms. Two types
+of edges can be described by the binding; the GLINK RPM edge and a SMEM based
+edge.
- compatible:
- Usage: required
+ Usage: required for glink-rpm
Value type: <stringlist>
Definition: must be "qcom,glink-rpm"
@@ -16,7 +17,7 @@ platforms.
signal this processor about communication related events
- qcom,rpm-msg-ram:
- Usage: required
+ Usage: required for glink-rpm
Value type: <prop-encoded-array>
Definition: handle to RPM message memory resource
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 79f903af6504..df63e44526ac 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -92,6 +92,7 @@ config QCOM_ADSP_PIL
depends on OF && ARCH_QCOM
depends on QCOM_SMEM
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
+ depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
select MFD_SYSCON
select QCOM_MDT_LOADER
select QCOM_RPROC_COMMON
diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c
index d01a8daf948a..3f6af54dbc96 100644
--- a/drivers/remoteproc/qcom_adsp_pil.c
+++ b/drivers/remoteproc/qcom_adsp_pil.c
@@ -72,6 +72,7 @@ struct qcom_adsp {
void *mem_region;
size_t mem_size;
+ struct qcom_rproc_glink glink_subdev;
struct qcom_rproc_subdev smd_subdev;
struct qcom_rproc_ssr ssr_subdev;
};
@@ -400,6 +401,7 @@ static int adsp_probe(struct platform_device *pdev)
goto free_rproc;
}
+ qcom_add_glink_subdev(rproc, &adsp->glink_subdev);
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name);
@@ -422,6 +424,7 @@ static int adsp_remove(struct platform_device *pdev)
qcom_smem_state_put(adsp->state);
rproc_del(adsp->rproc);
+ qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev);
qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev);
qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
rproc_free(adsp->rproc);
diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c
index 257680f82b27..d487040b528b 100644
--- a/drivers/remoteproc/qcom_common.c
+++ b/drivers/remoteproc/qcom_common.c
@@ -20,11 +20,13 @@
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/remoteproc.h>
+#include <linux/rpmsg/qcom_glink.h>
#include <linux/rpmsg/qcom_smd.h>
#include "remoteproc_internal.h"
#include "qcom_common.h"
+#define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)
#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
@@ -49,6 +51,53 @@ struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
}
EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table);
+static int glink_subdev_probe(struct rproc_subdev *subdev)
+{
+ struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
+
+ glink->edge = qcom_glink_smem_register(glink->dev, glink->node);
+
+ return IS_ERR(glink->edge) ? PTR_ERR(glink->edge) : 0;
+}
+
+static void glink_subdev_remove(struct rproc_subdev *subdev)
+{
+ struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
+
+ qcom_glink_smem_unregister(glink->edge);
+ glink->edge = NULL;
+}
+
+/**
+ * qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc
+ * @rproc: rproc handle to parent the subdevice
+ * @glink: reference to a GLINK subdev context
+ */
+void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
+{
+ struct device *dev = &rproc->dev;
+
+ glink->node = of_get_child_by_name(dev->parent->of_node, "glink-edge");
+ if (!glink->node)
+ return;
+
+ glink->dev = dev;
+ rproc_add_subdev(rproc, &glink->subdev, glink_subdev_probe, glink_subdev_remove);
+}
+EXPORT_SYMBOL_GPL(qcom_add_glink_subdev);
+
+/**
+ * qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc
+ * @rproc: rproc handle
+ * @glink: reference to a GLINK subdev context
+ */
+void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
+{
+ rproc_remove_subdev(rproc, &glink->subdev);
+ of_node_put(glink->node);
+}
+EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);
+
static int smd_subdev_probe(struct rproc_subdev *subdev)
{
struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h
index fab28b64b8ea..4f8bc168473c 100644
--- a/drivers/remoteproc/qcom_common.h
+++ b/drivers/remoteproc/qcom_common.h
@@ -4,6 +4,14 @@
#include <linux/remoteproc.h>
#include "remoteproc_internal.h"
+struct qcom_rproc_glink {
+ struct rproc_subdev subdev;
+
+ struct device *dev;
+ struct device_node *node;
+ struct qcom_glink *edge;
+};
+
struct qcom_rproc_subdev {
struct rproc_subdev subdev;
@@ -22,6 +30,9 @@ struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc,
const struct firmware *fw,
int *tablesz);
+void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink);
+void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink);
+
void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index 1323a245763b..0fe6eac46512 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -13,9 +13,13 @@ config RPMSG_CHAR
in /dev. They make it possible for user-space programs to send and
receive rpmsg packets.
+config RPMSG_QCOM_GLINK_NATIVE
+ tristate
+ select RPMSG
+
config RPMSG_QCOM_GLINK_RPM
tristate "Qualcomm RPM Glink driver"
- select RPMSG
+ select RPMSG_QCOM_GLINK_NATIVE
depends on HAS_IOMEM
depends on MAILBOX
help
@@ -23,6 +27,16 @@ config RPMSG_QCOM_GLINK_RPM
which serves as a channel for communication with the RPM in GLINK
enabled systems.
+config RPMSG_QCOM_GLINK_SMEM
+ tristate "Qualcomm SMEM Glink driver"
+ select RPMSG_QCOM_GLINK_NATIVE
+ depends on MAILBOX
+ depends on QCOM_SMEM
+ help
+ Say y here to enable support for the GLINK SMEM communication driver,
+ which provides support for using the GLINK communication protocol
+ over SMEM.
+
config RPMSG_QCOM_SMD
tristate "Qualcomm Shared Memory Driver (SMD)"
depends on QCOM_SMEM
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index 28cc19088cc0..c71f4ab1ae17 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -1,5 +1,7 @@
obj-$(CONFIG_RPMSG) += rpmsg_core.o
obj-$(CONFIG_RPMSG_CHAR) += rpmsg_char.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
+obj-$(CONFIG_RPMSG_QCOM_GLINK_NATIVE) += qcom_glink_native.o
+obj-$(CONFIG_RPMSG_QCOM_GLINK_SMEM) += qcom_glink_smem.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c
new file mode 100644
index 000000000000..5a5e927ea50f
--- /dev/null
+++ b/drivers/rpmsg/qcom_glink_native.c
@@ -0,0 +1,1612 @@
+/*
+ * Copyright (c) 2016-2017, Linaro Ltd
+ *
+ * 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/idr.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rpmsg.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/mailbox_client.h>
+
+#include "rpmsg_internal.h"
+#include "qcom_glink_native.h"
+
+#define GLINK_NAME_SIZE 32
+#define GLINK_VERSION_1 1
+
+#define RPM_GLINK_CID_MIN 1
+#define RPM_GLINK_CID_MAX 65536
+
+struct glink_msg {
+ __le16 cmd;
+ __le16 param1;
+ __le32 param2;
+ u8 data[];
+} __packed;
+
+/**
+ * struct glink_defer_cmd - deferred incoming control message
+ * @node: list node
+ * @msg: message header
+ * data: payload of the message
+ *
+ * Copy of a received control message, to be added to @rx_queue and processed
+ * by @rx_work of @qcom_glink.
+ */
+struct glink_defer_cmd {
+ struct list_head node;
+
+ struct glink_msg msg;
+ u8 data[];
+};
+
+/**
+ * struct glink_core_rx_intent - RX intent
+ * RX intent
+ *
+ * data: pointer to the data (may be NULL for zero-copy)
+ * id: remote or local intent ID
+ * size: size of the original intent (do not modify)
+ * reuse: To mark if the intent can be reused after first use
+ * in_use: To mark if intent is already in use for the channel
+ * offset: next write offset (initially 0)
+ */
+struct glink_core_rx_intent {
+ void *data;
+ u32 id;
+ size_t size;
+ bool reuse;
+ bool in_use;
+ u32 offset;
+
+ struct list_head node;
+};
+
+/**
+ * struct qcom_glink - driver context, relates to one remote subsystem
+ * @dev: reference to the associated struct device
+ * @mbox_client: mailbox client
+ * @mbox_chan: mailbox channel
+ * @rx_pipe: pipe object for receive FIFO
+ * @tx_pipe: pipe object for transmit FIFO
+ * @irq: IRQ for signaling incoming events
+ * @rx_work: worker for handling received control messages
+ * @rx_lock: protects the @rx_queue
+ * @rx_queue: queue of received control messages to be processed in @rx_work
+ * @tx_lock: synchronizes operations on the tx fifo
+ * @idr_lock: synchronizes @lcids and @rcids modifications
+ * @lcids: idr of all channels with a known local channel id
+ * @rcids: idr of all channels with a known remote channel id
+ */
+struct qcom_glink {
+ struct device *dev;
+
+ struct mbox_client mbox_client;
+ struct mbox_chan *mbox_chan;
+
+ struct qcom_glink_pipe *rx_pipe;
+ struct qcom_glink_pipe *tx_pipe;
+
+ int irq;
+
+ struct work_struct rx_work;
+ spinlock_t rx_lock;
+ struct list_head rx_queue;
+
+ struct mutex tx_lock;
+
+ spinlock_t idr_lock;
+ struct idr lcids;
+ struct idr rcids;
+ unsigned long features;
+
+ bool intentless;
+};
+
+enum {
+ GLINK_STATE_CLOSED,
+ GLINK_STATE_OPENING,
+ GLINK_STATE_OPEN,
+ GLINK_STATE_CLOSING,
+};
+
+/**
+ * struct glink_channel - internal representation of a channel
+ * @rpdev: rpdev reference, only used for primary endpoints
+ * @ept: rpmsg endpoint this channel is associated with
+ * @glink: qcom_glink context handle
+ * @refcount: refcount for the channel object
+ * @recv_lock: guard for @ept.cb
+ * @name: unique channel name/identifier
+ * @lcid: channel id, in local space
+ * @rcid: channel id, in remote space
+ * @intent_lock: lock for protection of @liids, @riids
+ * @liids: idr of all local intents
+ * @riids: idr of all remote intents
+ * @intent_work: worker responsible for transmitting rx_done packets
+ * @done_intents: list of intents that needs to be announced rx_done
+ * @buf: receive buffer, for gathering fragments
+ * @buf_offset: write offset in @buf
+ * @buf_size: size of current @buf
+ * @open_ack: completed once remote has acked the open-request
+ * @open_req: completed once open-request has been received
+ * @intent_req_lock: Synchronises multiple intent requests
+ * @intent_req_result: Result of intent request
+ * @intent_req_comp: Completion for intent_req signalling
+ */
+struct glink_channel {
+ struct rpmsg_endpoint ept;
+
+ struct rpmsg_device *rpdev;
+ struct qcom_glink *glink;
+
+ struct kref refcount;
+
+ spinlock_t recv_lock;
+
+ char *name;
+ unsigned int lcid;
+ unsigned int rcid;
+
+ spinlock_t intent_lock;
+ struct idr liids;
+ struct idr riids;
+ struct work_struct intent_work;
+ struct list_head done_intents;
+
+ struct glink_core_rx_intent *buf;
+ int buf_offset;
+ int buf_size;
+
+ struct completion open_ack;
+ struct completion open_req;
+
+ struct mutex intent_req_lock;
+ bool intent_req_result;
+ struct completion intent_req_comp;
+};
+
+#define to_glink_channel(_ept) container_of(_ept, struct glink_channel, ept)
+
+static const struct rpmsg_endpoint_ops glink_endpoint_ops;
+
+#define RPM_CMD_VERSION 0
+#define RPM_CMD_VERSION_ACK 1
+#define RPM_CMD_OPEN 2
+#define RPM_CMD_CLOSE 3
+#define RPM_CMD_OPEN_ACK 4
+#define RPM_CMD_INTENT 5
+#define RPM_CMD_RX_DONE 6
+#define RPM_CMD_RX_INTENT_REQ 7
+#define RPM_CMD_RX_INTENT_REQ_ACK 8
+#define RPM_CMD_TX_DATA 9
+#define RPM_CMD_CLOSE_ACK 11
+#define RPM_CMD_TX_DATA_CONT 12
+#define RPM_CMD_READ_NOTIF 13
+#define RPM_CMD_RX_DONE_W_REUSE 14
+
+#define GLINK_FEATURE_INTENTLESS BIT(1)
+
+static void qcom_glink_rx_done_work(struct work_struct *work);
+
+static struct glink_channel *qcom_glink_alloc_channel(struct qcom_glink *glink,
+ const char *name)
+{
+ struct glink_channel *channel;
+
+ channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ if (!channel)
+ return ERR_PTR(-ENOMEM);
+
+ /* Setup glink internal glink_channel data */
+ spin_lock_init(&channel->recv_lock);
+ spin_lock_init(&channel->intent_lock);
+
+ channel->glink = glink;
+ channel->name = kstrdup(name, GFP_KERNEL);
+
+ init_completion(&channel->open_req);
+ init_completion(&channel->open_ack);
+
+ INIT_LIST_HEAD(&channel->done_intents);
+ INIT_WORK(&channel->intent_work, qcom_glink_rx_done_work);
+
+ idr_init(&channel->liids);
+ idr_init(&channel->riids);
+ kref_init(&channel->refcount);
+
+ return channel;
+}
+
+static void qcom_glink_channel_release(struct kref *ref)
+{
+ struct glink_channel *channel = container_of(ref, struct glink_channel,
+ refcount);
+ unsigned long flags;
+
+ spin_lock_irqsave(&channel->intent_lock, flags);
+ idr_destroy(&channel->liids);
+ idr_destroy(&channel->riids);
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+
+ kfree(channel->name);
+ kfree(channel);
+}
+
+static size_t qcom_glink_rx_avail(struct qcom_glink *glink)
+{
+ return glink->rx_pipe->avail(glink->rx_pipe);
+}
+
+static void qcom_glink_rx_peak(struct qcom_glink *glink,
+ void *data, unsigned int offset, size_t count)
+{
+ glink->rx_pipe->peak(glink->rx_pipe, data, offset, count);
+}
+
+static void qcom_glink_rx_advance(struct qcom_glink *glink, size_t count)
+{
+ glink->rx_pipe->advance(glink->rx_pipe, count);
+}
+
+static size_t qcom_glink_tx_avail(struct qcom_glink *glink)
+{
+ return glink->tx_pipe->avail(glink->tx_pipe);
+}
+
+static void qcom_glink_tx_write(struct qcom_glink *glink,
+ const void *hdr, size_t hlen,
+ const void *data, size_t dlen)
+{
+ glink->tx_pipe->write(glink->tx_pipe, hdr, hlen, data, dlen);
+}
+
+static int qcom_glink_tx(struct qcom_glink *glink,
+ const void *hdr, size_t hlen,
+ const void *data, size_t dlen, bool wait)
+{
+ unsigned int tlen = hlen + dlen;
+ int ret;
+
+ /* Reject packets that are too big */
+ if (tlen >= glink->tx_pipe->length)
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&glink->tx_lock);
+ if (ret)
+ return ret;
+
+ while (qcom_glink_tx_avail(glink) < tlen) {
+ if (!wait) {
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ usleep_range(10000, 15000);
+ }
+
+ qcom_glink_tx_write(glink, hdr, hlen, data, dlen);
+
+ mbox_send_message(glink->mbox_chan, NULL);
+ mbox_client_txdone(glink->mbox_chan, 0);
+
+out:
+ mutex_unlock(&glink->tx_lock);
+
+ return ret;
+}
+
+static int qcom_glink_send_version(struct qcom_glink *glink)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_VERSION);
+ msg.param1 = cpu_to_le16(GLINK_VERSION_1);
+ msg.param2 = cpu_to_le32(glink->features);
+
+ return qcom_glink_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+static void qcom_glink_send_version_ack(struct qcom_glink *glink)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_VERSION_ACK);
+ msg.param1 = cpu_to_le16(GLINK_VERSION_1);
+ msg.param2 = cpu_to_le32(glink->features);
+
+ qcom_glink_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+static void qcom_glink_send_open_ack(struct qcom_glink *glink,
+ struct glink_channel *channel)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_OPEN_ACK);
+ msg.param1 = cpu_to_le16(channel->rcid);
+ msg.param2 = cpu_to_le32(0);
+
+ qcom_glink_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+}
+
+static void qcom_glink_handle_intent_req_ack(struct qcom_glink *glink,
+ unsigned int cid, bool granted)
+{
+ struct glink_channel *channel;
+ unsigned long flags;
+
+ spin_lock_irqsave(&glink->idr_lock, flags);
+ channel = idr_find(&glink->rcids, cid);
+ spin_unlock_irqrestore(&glink->idr_lock, flags);
+ if (!channel) {
+ dev_err(glink->dev, "unable to find channel\n");
+ return;
+ }
+
+ channel->intent_req_result = granted;
+ complete(&channel->intent_req_comp);
+}
+
+/**
+ * qcom_glink_send_open_req() - send a RPM_CMD_OPEN request to the remote
+ * @glink: Ptr to the glink edge
+ * @channel: Ptr to the channel that the open req is sent
+ *
+ * Allocates a local channel id and sends a RPM_CMD_OPEN message to the remote.
+ * Will return with refcount held, regardless of outcome.
+ *
+ * Returns 0 on success, negative errno otherwise.
+ */
+static int qcom_glink_send_open_req(struct qcom_glink *glink,
+ struct glink_channel *channel)
+{
+ struct {
+ struct glink_msg msg;
+ u8 name[GLINK_NAME_SIZE];
+ } __packed req;
+ int name_len = strlen(channel->name) + 1;
+ int req_len = ALIGN(sizeof(req.msg) + name_len, 8);
+ int ret;
+ unsigned long flags;
+
+ kref_get(&channel->refcount);
+
+ spin_lock_irqsave(&glink->idr_lock, flags);
+ ret = idr_alloc_cyclic(&glink->lcids, channel,
+ RPM_GLINK_CID_MIN, RPM_GLINK_CID_MAX,
+ GFP_ATOMIC);
+ spin_unlock_irqrestore(&glink->idr_lock, flags);
+ if (ret < 0)
+ return ret;
+
+ channel->lcid = ret;
+
+ req.msg.cmd = cpu_to_le16(RPM_CMD_OPEN);
+ req.msg.param1 = cpu_to_le16(channel->lcid);
+ req.msg.param2 = cpu_to_le32(name_len);
+ strcpy(req.name, channel->name);
+
+ ret = qcom_glink_tx(glink, &req, req_len, NULL, 0, true);
+ if (ret)
+ goto remove_idr;
+
+ return 0;
+
+remove_idr:
+ spin_lock_irqsave(&glink->idr_lock, flags);
+ idr_remove(&glink->lcids, channel->lcid);
+ channel->lcid = 0;
+ spin_unlock_irqrestore(&glink->idr_lock, flags);
+
+ return ret;
+}
+
+static void qcom_glink_send_close_req(struct qcom_glink *glink,
+ struct glink_channel *channel)
+{
+ struct glink_msg req;
+
+ req.cmd = cpu_to_le16(RPM_CMD_CLOSE);
+ req.param1 = cpu_to_le16(channel->lcid);
+ req.param2 = 0;
+
+ qcom_glink_tx(glink, &req, sizeof(req), NULL, 0, true);
+}
+
+static void qcom_glink_send_close_ack(struct qcom_glink *glink,
+ unsigned int rcid)
+{
+ struct glink_msg req;
+
+ req.cmd = cpu_to_le16(RPM_CMD_CLOSE_ACK);
+ req.param1 = cpu_to_le16(rcid);
+ req.param2 = 0;
+
+ qcom_glink_tx(glink, &req, sizeof(req), NULL, 0, true);
+}
+
+static void qcom_glink_rx_done_work(struct work_struct *work)
+{
+ struct glink_channel *channel = container_of(work, struct glink_channel,
+ intent_work);
+ struct qcom_glink *glink = channel->glink;
+ struct glink_core_rx_intent *intent, *tmp;
+ struct {
+ u16 id;
+ u16 lcid;
+ u32 liid;
+ } __packed cmd;
+
+ unsigned int cid = channel->lcid;
+ unsigned int iid;
+ bool reuse;
+ unsigned long flags;
+
+ spin_lock_irqsave(&channel->intent_lock, flags);
+ list_for_each_entry_safe(intent, tmp, &channel->done_intents, node) {
+ list_del(&intent->node);
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+ iid = intent->id;
+ reuse = intent->reuse;
+
+ cmd.id = reuse ? RPM_CMD_RX_DONE_W_REUSE : RPM_CMD_RX_DONE;
+ cmd.lcid = cid;
+ cmd.liid = iid;
+
+ qcom_glink_tx(glink, &cmd, sizeof(cmd), NULL, 0, true);
+ if (!reuse) {
+ kfree(intent->data);
+ kfree(intent);
+ }
+ spin_lock_irqsave(&channel->intent_lock, flags);
+ }
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+}
+
+static void qcom_glink_rx_done(struct qcom_glink *glink,
+ struct glink_channel *channel,
+ struct glink_core_rx_intent *intent)
+{
+ /* We don't send RX_DONE to intentless systems */
+ if (glink->intentless) {
+ kfree(intent->data);
+ kfree(intent);
+ return;
+ }
+
+ /* Take it off the tree of receive intents */
+ if (!intent->reuse) {
+ spin_lock(&channel->intent_lock);
+ idr_remove(&channel->liids, intent->id);
+ spin_unlock(&channel->intent_lock);
+ }
+
+ /* Schedule the sending of a rx_done indication */
+ spin_lock(&channel->intent_lock);
+ list_add_tail(&intent->node, &channel->done_intents);
+ spin_unlock(&channel->intent_lock);
+
+ schedule_work(&channel->intent_work);
+}
+
+/**
+ * qcom_glink_receive_version() - receive version/features from remote system
+ *
+ * @glink: pointer to transport interface
+ * @r_version: remote version
+ * @r_features: remote features
+ *
+ * This function is called in response to a remote-initiated version/feature
+ * negotiation sequence.
+ */
+static void qcom_glink_receive_version(struct qcom_glink *glink,
+ u32 version,
+ u32 features)
+{
+ switch (version) {
+ case 0:
+ break;
+ case GLINK_VERSION_1:
+ glink->features &= features;
+ /* FALLTHROUGH */
+ default:
+ qcom_glink_send_version_ack(glink);
+ break;
+ }
+}
+
+/**
+ * qcom_glink_receive_version_ack() - receive negotiation ack from remote system
+ *
+ * @glink: pointer to transport interface
+ * @r_version: remote version response
+ * @r_features: remote features response
+ *
+ * This function is called in response to a local-initiated version/feature
+ * negotiation sequence and is the counter-offer from the remote side based
+ * upon the initial version and feature set requested.
+ */
+static void qcom_glink_receive_version_ack(struct qcom_glink *glink,
+ u32 version,
+ u32 features)
+{
+ switch (version) {
+ case 0:
+ /* Version negotiation failed */
+ break;
+ case GLINK_VERSION_1:
+ if (features == glink->features)
+ break;
+
+ glink->features &= features;
+ /* FALLTHROUGH */
+ default:
+ qcom_glink_send_version(glink);
+ break;
+ }
+}
+
+/**
+ * qcom_glink_send_intent_req_ack() - convert an rx intent request ack cmd to
+ wire format and transmit
+ * @glink: The transport to transmit on.
+ * @channel: The glink channel
+ * @granted: The request response to encode.
+ *
+ * Return: 0 on success or standard Linux error code.
+ */
+static int qcom_glink_send_intent_req_ack(struct qcom_glink *glink,
+ struct glink_channel *channel,
+ bool granted)
+{
+ struct glink_msg msg;
+
+ msg.cmd = cpu_to_le16(RPM_CMD_RX_INTENT_REQ_ACK);
+ msg.param1 = cpu_to_le16(channel->lcid);
+ msg.param2 = cpu_to_le32(granted);
+
+ qcom_glink_tx(glink, &msg, sizeof(msg), NULL, 0, true);
+
+ return 0;
+}
+
+/**
+ * qcom_glink_advertise_intent - convert an rx intent cmd to wire format and
+ * transmit
+ * @glink: The transport to transmit on.
+ * @channel: The local channel
+ * @size: The intent to pass on to remote.
+ *
+ * Return: 0 on success or standard Linux error code.
+ */
+static int qcom_glink_advertise_intent(struct qcom_glink *glink,
+ struct glink_channel *channel,
+ struct glink_core_rx_intent *intent)
+{
+ struct command {
+ u16 id;
+ u16 lcid;
+ u32 count;
+ u32 size;
+ u32 liid;
+ } __packed;
+ struct command cmd;
+
+ cmd.id = cpu_to_le16(RPM_CMD_INTENT);
+ cmd.lcid = cpu_to_le16(channel->lcid);
+ cmd.count = cpu_to_le32(1);
+ cmd.size = cpu_to_le32(intent->size);
+ cmd.liid = cpu_to_le32(intent->id);
+
+ qcom_glink_tx(glink, &cmd, sizeof(cmd), NULL, 0, true);
+
+ return 0;
+}
+
+static struct glink_core_rx_intent *
+qcom_glink_alloc_intent(struct qcom_glink *glink,
+ struct glink_channel *channel,
+ size_t size,
+ bool reuseable)
+{
+ struct glink_core_rx_intent *intent;
+ int ret;
+ unsigned long flags;
+
+ intent = kzalloc(sizeof(*intent), GFP_KERNEL);
+
+ if (!intent)
+ return NULL;
+
+ intent->data = kzalloc(size, GFP_KERNEL);
+ if (!intent->data)
+ return NULL;
+
+ spin_lock_irqsave(&channel->intent_lock, flags);
+ ret = idr_alloc_cyclic(&channel->liids, intent, 1, -1, GFP_ATOMIC);
+ if (ret < 0) {
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+ return NULL;
+ }
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+
+ intent->id = ret;
+ intent->size = size;
+ intent->reuse = reuseable;
+
+ return intent;
+}
+
+static void qcom_glink_handle_rx_done(struct qcom_glink *glink,
+ u32 cid, uint32_t iid,
+ bool reuse)
+{
+ struct glink_core_rx_intent *intent;
+ struct glink_channel *channel;
+ unsigned long flags;
+
+ spin_lock_irqsave(&glink->idr_lock, flags);
+ channel = idr_find(&glink->rcids, cid);
+ spin_unlock_irqrestore(&glink->idr_lock, flags);
+ if (!channel) {
+ dev_err(glink->dev, "invalid channel id received\n");
+ return;
+ }
+
+ spin_lock_irqsave(&channel->intent_lock, flags);
+ intent = idr_find(&channel->riids, iid);
+
+ if (!intent) {
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+ dev_err(glink->dev, "invalid intent id received\n");
+ return;
+ }
+
+ intent->in_use = false;
+
+ if (!reuse) {
+ idr_remove(&channel->riids, intent->id);
+ kfree(intent);
+ }
+ spin_unlock_irqrestore(&channel->intent_lock, flags);
+}
+
+/**
+ * qcom_glink_handle_intent_req() - Receive a request for rx_intent
+ * from remote side
+ * if_ptr: Pointer to the transport interface
+ * rcid: Remote channel ID
+ * size: size of the intent
+ *
+ * The function searches for the local channel to