diff --git a/MAINTAINERS b/MAINTAINERS
index c774bbcdb348..444891e02546 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8745,6 +8745,7 @@ F:	Documentation/virt/gunyah/
 F:	arch/arm64/gunyah/
 F:	drivers/virt/gunyah/
 F:	include/asm-generic/gunyah.h
+F:	include/linux/gunyah.h
 
 HABANALABS PCI DRIVER
 M:	Oded Gabbay <ogabbay@kernel.org>
diff --git a/arch/arm64/gunyah/hypercall.c b/arch/arm64/gunyah/hypercall.c
index 5b08c9d80de0..042cca31879e 100644
--- a/arch/arm64/gunyah/hypercall.c
+++ b/arch/arm64/gunyah/hypercall.c
@@ -26,6 +26,8 @@
 							| ((fn) & GH_CALL_FUNCTION_NUM_MASK))
 
 #define GH_HYPERCALL_HYP_IDENTIFY		GH_HYPERCALL(0x0000)
+#define GH_HYPERCALL_MSGQ_SEND			GH_HYPERCALL(0x001B)
+#define GH_HYPERCALL_MSGQ_RECV			GH_HYPERCALL(0x001C)
 
 /**
  * gh_hypercall_get_uid() - Returns a UID when running under a Gunyah hypervisor.
@@ -67,5 +69,36 @@ void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identi
 }
 EXPORT_SYMBOL_GPL(gh_hypercall_hyp_identify);
 
+int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_hvc(GH_HYPERCALL_MSGQ_SEND, capid, size, buff, tx_flags, 0, &res);
+
+	if (res.a0)
+		return res.a0;
+
+	*ready = res.a1;
+
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_msgq_send);
+
+int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_size, bool *ready)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_hvc(GH_HYPERCALL_MSGQ_RECV, capid, buff, size, 0, &res);
+
+	if (res.a0)
+		return res.a0;
+
+	*recv_size = res.a1;
+	*ready = res.a2;
+
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(gh_hypercall_msgq_recv);
+
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Gunyah Hypervisor Hypercalls");
diff --git a/drivers/virt/gunyah/Makefile b/drivers/virt/gunyah/Makefile
index e15f16c17142..3c7efd2220c1 100644
--- a/drivers/virt/gunyah/Makefile
+++ b/drivers/virt/gunyah/Makefile
@@ -1,2 +1,2 @@
-gunyah-y += sysfs.o
+gunyah-y += sysfs.o msgq.o
 obj-$(CONFIG_GUNYAH) += gunyah.o
diff --git a/drivers/virt/gunyah/msgq.c b/drivers/virt/gunyah/msgq.c
new file mode 100644
index 000000000000..05c3d90ac1ed
--- /dev/null
+++ b/drivers/virt/gunyah/msgq.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/gunyah.h>
+#include <linux/printk.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+static irqreturn_t gh_msgq_irq_handler(int irq, void *data)
+{
+	struct gunyah_msgq *msgq = data;
+	void (*on_ready)(struct gunyah_msgq *msgq);
+
+	spin_lock(&msgq->lock);
+	msgq->ready = true;
+	on_ready = msgq->on_ready;
+	spin_unlock(&msgq->lock);
+
+	if (on_ready)
+		on_ready(msgq);
+
+	wake_up_interruptible_all(&msgq->wq);
+
+	return IRQ_HANDLED;
+}
+
+static int __gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, u64 tx_flags)
+{
+	unsigned long flags, gh_err;
+	ssize_t ret;
+	bool ready;
+
+	spin_lock_irqsave(&msgq->lock, flags);
+	gh_err = gh_hypercall_msgq_send(msgq->ghdev.capid, size, (uintptr_t)buff, tx_flags, &ready);
+	switch (gh_err) {
+	case GH_ERROR_OK:
+		ret = 0;
+		msgq->ready = ready;
+		break;
+	case GH_ERROR_MSGQUEUE_FULL:
+		ret = -EAGAIN;
+		msgq->ready = false;
+		break;
+	default:
+		ret = gh_remap_error(gh_err);
+		break;
+	}
+	spin_unlock_irqrestore(&msgq->lock, flags);
+
+	return ret;
+}
+
+/**
+ * gh_msgq_send() - Send a message to the client running on a different VM
+ * @client: The client descriptor that was obtained via gh_msgq_register()
+ * @buff: Pointer to the buffer where the received data must be placed
+ * @buff_size: The size of the buffer space available
+ * @flags: Optional flags to pass to receive the data. For the list of flags,
+ *         see linux/gunyah/gh_msgq.h
+ *
+ * Returns: The number of bytes copied to buff. <0 if there was an error.
+ *
+ * Note: this function may sleep and should not be called from interrupt context
+ */
+ssize_t gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags)
+{
+	ssize_t ret;
+	u64 tx_flags = 0;
+
+	if (unlikely(msgq->ghdev.type != GUNYAH_DEVICE_TYPE_MSGQ_TX))
+		return -EINVAL;
+
+	if (flags & GH_MSGQ_TX_PUSH)
+		tx_flags |= GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH;
+
+	do {
+		ret = __gh_msgq_send(msgq, buff, size, tx_flags);
+
+		if (ret == -EAGAIN) {
+			if (flags & GH_MSGQ_NONBLOCK)
+				goto out;
+			if (wait_event_interruptible(msgq->wq, msgq->ready))
+				ret = -ERESTARTSYS;
+		}
+	} while (ret == -EAGAIN);
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gh_msgq_send);
+
+static ssize_t __gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size)
+{
+	unsigned long flags, gh_err;
+	size_t recv_size;
+	ssize_t ret;
+	bool ready;
+
+	spin_lock_irqsave(&msgq->lock, flags);
+
+	gh_err = gh_hypercall_msgq_recv(msgq->ghdev.capid, (uintptr_t)buff, size,
+					&recv_size, &ready);
+	switch (gh_err) {
+	case GH_ERROR_OK:
+		ret = recv_size;
+		msgq->ready = ready;
+		break;
+	case GH_ERROR_MSGQUEUE_EMPTY:
+		ret = -EAGAIN;
+		msgq->ready = false;
+		break;
+	default:
+		ret = gh_remap_error(gh_err);
+		break;
+	}
+	spin_unlock_irqrestore(&msgq->lock, flags);
+
+	return ret;
+}
+
+/**
+ * gh_msgq_recv() - Receive a message from the client running on a different VM
+ * @client: The client descriptor that was obtained via gh_msgq_register()
+ * @buff: Pointer to the buffer where the received data must be placed
+ * @buff_size: The size of the buffer space available
+ * @flags: Optional flags to pass to receive the data. For the list of flags,
+ *         see linux/gunyah/gh_msgq.h
+ *
+ * Returns: The number of bytes copied to buff. <0 if there was an error.
+ *
+ * Note: this function may sleep and should not be called from interrupt context
+ */
+ssize_t gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags)
+{
+	ssize_t ret;
+
+	if (unlikely(msgq->ghdev.type != GUNYAH_DEVICE_TYPE_MSGQ_RX))
+		return -EINVAL;
+
+	do {
+		ret = __gh_msgq_recv(msgq, buff, size);
+
+		if (ret == -EAGAIN) {
+			if (flags & GH_MSGQ_NONBLOCK)
+				goto out;
+			if (wait_event_interruptible(msgq->wq, msgq->ready))
+				ret = -ERESTARTSYS;
+		}
+	} while (ret == -EAGAIN);
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gh_msgq_recv);
+
+struct gunyah_msgq *__gunyah_msgq_alloc(enum gunyah_device_type type, u64 capid, int irq)
+{
+	struct gunyah_msgq *msgq;
+	int ret;
+
+	msgq = kzalloc(sizeof(*msgq), GFP_KERNEL);
+	if (!msgq)
+		return NULL;
+
+	msgq->ghdev.type = type;
+	msgq->ghdev.capid = capid;
+	msgq->ghdev.irq = irq;
+
+	msgq->ready = true; /* Assume we can use the message queue right away */
+	init_waitqueue_head(&msgq->wq);
+	spin_lock_init(&msgq->lock);
+
+	ret = request_irq(msgq->ghdev.irq, gh_msgq_irq_handler, 0, "gh_msgq", msgq);
+	if (WARN(ret, "Failed to request message queue irq %d: %d\n", irq, ret)) {
+		kfree(msgq);
+		return NULL;
+	}
+
+	return msgq;
+}
+EXPORT_SYMBOL_GPL(__gunyah_msgq_alloc);
+
+void gunyah_msgq_free(struct gunyah_msgq *msgq)
+{
+	free_irq(msgq->ghdev.irq, msgq);
+	kfree(msgq);
+}
+EXPORT_SYMBOL_GPL(gunyah_msgq_free);
diff --git a/include/asm-generic/gunyah.h b/include/asm-generic/gunyah.h
index 86eb59e203ef..43915faea704 100644
--- a/include/asm-generic/gunyah.h
+++ b/include/asm-generic/gunyah.h
@@ -107,4 +107,9 @@ struct gh_hypercall_hyp_identify_resp {
 void gh_hypercall_get_uid(u32 *uid);
 void gh_hypercall_hyp_identify(struct gh_hypercall_hyp_identify_resp *hyp_identity);
 
+#define GH_HYPERCALL_MSGQ_TX_FLAGS_PUSH		BIT(0)
+
+int gh_hypercall_msgq_send(u64 capid, size_t size, uintptr_t buff, int tx_flags, bool *ready);
+int gh_hypercall_msgq_recv(u64 capid, uintptr_t buff, size_t size, size_t *recv_size, bool *ready);
+
 #endif
diff --git a/include/linux/gunyah.h b/include/linux/gunyah.h
new file mode 100644
index 000000000000..57e83b0d78cf
--- /dev/null
+++ b/include/linux/gunyah.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _GUNYAH_H
+#define _GUNYAH_H
+
+#include <asm-generic/gunyah.h>
+
+/* Follows resource manager's resource types for VM_GET_HYP_RESOURCES */
+enum gunyah_device_type {
+	GUNYAH_DEVICE_TYPE_BELL_TX	= 0,
+	GUNYAH_DEVICE_TYPE_BELL_RX	= 1,
+	GUNYAH_DEVICE_TYPE_MSGQ_TX	= 2,
+	GUNYAH_DEVICE_TYPE_MSGQ_RX	= 3,
+	GUNYAH_DEVICE_TYPE_VCPU		= 4,
+};
+
+struct gunyah_device {
+	enum gunyah_device_type type;
+	u64 capid;
+	int irq;
+};
+
+/**
+ * Gunyah Message Queues
+ */
+
+struct gunyah_msgq {
+	struct gunyah_device ghdev;
+
+	/* Set by the message queue client */
+	void (*on_ready)(struct gunyah_msgq *msgq);
+	void *data;
+
+	/* msgq private */
+	bool ready;
+	wait_queue_head_t wq;
+	spinlock_t lock;
+};
+
+#define GH_MSGQ_MAX_MSG_SIZE	1024
+
+/* Possible flags to pass for Tx or Rx */
+#define GH_MSGQ_TX_PUSH		BIT(0)
+#define GH_MSGQ_NONBLOCK	BIT(32)
+
+static inline struct gunyah_msgq * __must_check to_gunyah_msgq(struct gunyah_device *ghdev)
+{
+	if (ghdev->type != GUNYAH_DEVICE_TYPE_BELL_TX && ghdev->type != GUNYAH_DEVICE_TYPE_BELL_RX)
+		return NULL;
+	return container_of(ghdev, struct gunyah_msgq, ghdev);
+}
+
+ssize_t gh_msgq_send(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags);
+ssize_t gh_msgq_recv(struct gunyah_msgq *msgq, void *buff, size_t size, const unsigned long flags);
+struct gunyah_msgq *__gunyah_msgq_alloc(enum gunyah_device_type type, u64 capid, int irq);
+void gunyah_msgq_free(struct gunyah_msgq *msgq);
+
+static inline struct gunyah_msgq *gunyah_msgq_tx_alloc(u64 capid, int irq)
+{
+	return __gunyah_msgq_alloc(GUNYAH_DEVICE_TYPE_BELL_TX, capid, irq);
+}
+
+static inline struct gunyah_msgq *gunyah_msgq_rx_alloc(u64 capid, int irq)
+{
+	return __gunyah_msgq_alloc(GUNYAH_DEVICE_TYPE_BELL_RX, capid, irq);
+}
+
+#endif
