qrtr: Add FIFO based communication between VMs

Add QRTR communication support between two virtual machines
by using shared FIFO and virtual IRQs.

Change-Id: I8fd2115a6ad5acd9a6dac2fa511860a8a68c16ee
Signed-off-by: Arun Kumar Neelakantam <aneela@codeaurora.org>
This commit is contained in:
Arun Kumar Neelakantam 2018-12-12 16:17:06 +05:30 committed by Chris Lew
parent 31de7f836d
commit 7772a9041c
3 changed files with 393 additions and 0 deletions

View file

@ -47,4 +47,12 @@ config QRTR_USB
transport provides bulk endpoints to facilitate sending and receiving
IPC Router data packets.
config QRTR_FIFO
tristate "FIFO IPC Router channels"
help
Say Y here to support FIFO based ipcrouter channels. FIFO Transport
Layer enables IPC Router communication between two virtual machines.
The shared memory between virtual machines will be allocated by the
hypervisor and signal other VMs through virtualized interrupts.
endif # QRTR

View file

@ -9,3 +9,6 @@ qrtr-mhi-y := mhi.o
obj-$(CONFIG_QRTR_USB) += qrtr-usb.o
qrtr-usb-y := usb.o
obj-$(CONFIG_QRTR_FIFO) += qrtr-fifo.o
qrtr-fifo-y := fifo.o

382
net/qrtr/fifo.c Normal file
View file

@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/sched.h>
#include <microvisor/microvisor.h>
#include "qrtr.h"
#define FIFO_MAGIC_KEY 0x24495043 /* "$IPC" */
#define FIFO_SIZE 0x4000
#define FIFO_0_START 0x1000
#define FIFO_1_START (FIFO_0_START + FIFO_SIZE)
#define FIFO_MAGIC_IDX 0x0
#define TAIL_0_IDX 0x1
#define HEAD_0_IDX 0x2
#define TAIL_1_IDX 0x3
#define HEAD_1_IDX 0x4
struct fifo_pipe {
__le32 *tail;
__le32 *head;
void *fifo;
size_t length;
};
/**
* qrtr_fifo_xprt - qrtr FIFO transport structure
* @ep: qrtr endpoint specific info.
* @tx_pipe: TX FIFO specific info.
* @rx_pipe: RX FIFO specific info.
* @fifo_base: Base of the shared FIFO.
* @fifo_size: FIFO Size.
* @tx_fifo_idx: TX FIFO index.
* @kcap: Register info to raise irq to other VM.
*/
struct qrtr_fifo_xprt {
struct qrtr_endpoint ep;
struct fifo_pipe tx_pipe;
struct fifo_pipe rx_pipe;
void *fifo_base;
size_t fifo_size;
int tx_fifo_idx;
okl4_kcap_t kcap;
};
static void qrtr_fifo_raise_virq(struct qrtr_fifo_xprt *xprtp);
static size_t fifo_rx_avail(struct fifo_pipe *pipe)
{
u32 head;
u32 tail;
head = le32_to_cpu(*pipe->head);
tail = le32_to_cpu(*pipe->tail);
if (head < tail)
return pipe->length - tail + head;
return head - tail;
}
static void fifo_rx_peak(struct fifo_pipe *pipe,
void *data, unsigned int offset, size_t count)
{
size_t len;
u32 tail;
tail = le32_to_cpu(*pipe->tail);
tail += offset;
if (tail >= pipe->length)
tail -= pipe->length;
len = min_t(size_t, count, pipe->length - tail);
if (len)
memcpy_fromio(data, pipe->fifo + tail, len);
if (len != count)
memcpy_fromio(data + len, pipe->fifo, (count - len));
}
static void fifo_rx_advance(struct fifo_pipe *pipe, size_t count)
{
u32 tail;
tail = le32_to_cpu(*pipe->tail);
tail += count;
if (tail > pipe->length)
tail -= pipe->length;
*pipe->tail = cpu_to_le32(tail);
}
static size_t fifo_tx_avail(struct fifo_pipe *pipe)
{
u32 head;
u32 tail;
u32 avail;
head = le32_to_cpu(*pipe->head);
tail = le32_to_cpu(*pipe->tail);
if (tail <= head)
avail = pipe->length - head + tail;
else
avail = tail - head;
return avail;
}
static void fifo_tx_write(struct fifo_pipe *pipe,
const void *data, size_t count)
{
size_t len;
u32 head;
head = le32_to_cpu(*pipe->head);
len = min_t(size_t, count, pipe->length - head);
if (len)
memcpy_toio(pipe->fifo + head, data, len);
if (len != count)
memcpy_toio(pipe->fifo, data + len, count - len);
head += count;
if (head >= pipe->length)
head -= pipe->length;
/* Ensure ordering of fifo and head update */
wmb();
*pipe->head = cpu_to_le32(head);
}
/* from qrtr to FIFO */
static int xprt_write(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
struct qrtr_fifo_xprt *xprtp;
int rc;
xprtp = container_of(ep, struct qrtr_fifo_xprt, ep);
rc = skb_linearize(skb);
if (rc) {
kfree_skb(skb);
return rc;
}
if (fifo_tx_avail(&xprtp->tx_pipe) < skb->len) {
pr_err("No Space in FIFO\n");
return -EAGAIN;
}
fifo_tx_write(&xprtp->tx_pipe, skb->data, skb->len);
kfree_skb(skb);
qrtr_fifo_raise_virq(xprtp);
return 0;
}
static void xprt_read_data(struct qrtr_fifo_xprt *xprtp)
{
int rc;
u32 hdr[8];
void *data;
size_t pkt_len;
size_t rx_avail;
size_t hdr_len = sizeof(hdr);
while (fifo_rx_avail(&xprtp->rx_pipe)) {
fifo_rx_peak(&xprtp->rx_pipe, &hdr, 0, hdr_len);
pkt_len = qrtr_peek_pkt_size((void *)&hdr);
if ((int)pkt_len < 0) {
pr_err("invalid pkt_len %zu\n", pkt_len);
break;
}
data = kzalloc(pkt_len, GFP_ATOMIC);
if (!data)
break;
rx_avail = fifo_rx_avail(&xprtp->rx_pipe);
if (rx_avail < pkt_len) {
pr_err_ratelimited("Not FULL pkt in FIFO %zu %zu\n",
rx_avail, pkt_len);
break;
}
fifo_rx_peak(&xprtp->rx_pipe, data, 0, pkt_len);
fifo_rx_advance(&xprtp->rx_pipe, pkt_len);
rc = qrtr_endpoint_post(&xprtp->ep, data, pkt_len);
if (rc == -EINVAL)
pr_err("invalid ipcrouter packet\n");
kfree(data);
data = NULL;
}
}
static void qrtr_fifo_raise_virq(struct qrtr_fifo_xprt *xprtp)
{
okl4_error_t err;
unsigned long payload = 0xffff;
err = _okl4_sys_vinterrupt_raise(xprtp->kcap, payload);
}
static irqreturn_t qrtr_fifo_virq_handler(int irq, void *dev_id)
{
xprt_read_data((struct qrtr_fifo_xprt *)dev_id);
return IRQ_HANDLED;
}
/**
* qrtr_fifo_config_init() - init FIFO xprt configs
*
* @return: 0 on success, standard Linux error codes on error.
*
* This function is called to initialize the FIFO XPRT pointer with
* the FIFO XPRT configurations either from device tree or static arrays.
*/
static void qrtr_fifo_config_init(struct qrtr_fifo_xprt *xprtp)
{
__le32 *descs;
descs = xprtp->fifo_base;
descs[FIFO_MAGIC_IDX] = FIFO_MAGIC_KEY;
if (xprtp->tx_fifo_idx) {
xprtp->tx_pipe.tail = &descs[TAIL_0_IDX];
xprtp->tx_pipe.head = &descs[HEAD_0_IDX];
xprtp->tx_pipe.fifo = xprtp->fifo_base + FIFO_0_START;
xprtp->tx_pipe.length = FIFO_SIZE;
xprtp->rx_pipe.tail = &descs[TAIL_1_IDX];
xprtp->rx_pipe.head = &descs[HEAD_1_IDX];
xprtp->rx_pipe.fifo = xprtp->fifo_base + FIFO_1_START;
xprtp->rx_pipe.length = FIFO_SIZE;
} else {
xprtp->tx_pipe.tail = &descs[TAIL_1_IDX];
xprtp->tx_pipe.head = &descs[HEAD_1_IDX];
xprtp->tx_pipe.fifo = xprtp->fifo_base + FIFO_1_START;
xprtp->tx_pipe.length = FIFO_SIZE;
xprtp->rx_pipe.tail = &descs[TAIL_0_IDX];
xprtp->rx_pipe.head = &descs[HEAD_0_IDX];
xprtp->rx_pipe.fifo = xprtp->fifo_base + FIFO_0_START;
xprtp->rx_pipe.length = FIFO_SIZE;
}
/* Reset respective index */
*xprtp->tx_pipe.head = 0;
*xprtp->rx_pipe.tail = 0;
}
/**
* qrtr_fifo_xprt_probe() - Probe an FIFO xprt
*
* @pdev: Platform device corresponding to FIFO 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 FIFO transport.
*/
static int qrtr_fifo_xprt_probe(struct platform_device *pdev)
{
int irq;
int ret;
struct resource *r;
struct device *parent;
struct qrtr_fifo_xprt *xprtp;
struct device_node *ipc_irq_np;
struct device_node *ipc_shm_np;
struct platform_device *ipc_shm_dev;
xprtp = devm_kzalloc(&pdev->dev, sizeof(*xprtp), GFP_KERNEL);
if (!xprtp)
return -ENOMEM;
parent = &pdev->dev;
ipc_irq_np = parent->of_node;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return -ENODEV;
ret = devm_request_irq(parent, irq, qrtr_fifo_virq_handler,
IRQF_TRIGGER_RISING, dev_name(parent),
xprtp);
if (ret < 0)
return -ENODEV;
/* this kcap is required to raise VIRQ */
ret = of_property_read_u32(ipc_irq_np, "reg", &xprtp->kcap);
if (ret < 0)
return -ENODEV;
ipc_shm_np = of_parse_phandle(ipc_irq_np, "qcom,ipc-shm", 0);
if (!ipc_shm_np)
return -ENODEV;
ipc_shm_dev = of_find_device_by_node(ipc_shm_np);
if (!ipc_shm_dev) {
of_node_put(ipc_shm_np);
return -ENODEV;
}
r = platform_get_resource(ipc_shm_dev, IORESOURCE_MEM, 0);
if (!r) {
pr_err("failed to get shared FIFO\n");
of_node_put(ipc_shm_np);
return -ENODEV;
}
xprtp->tx_fifo_idx = of_property_read_bool(ipc_shm_np,
"qcom,tx-is-first");
of_node_put(ipc_shm_np);
xprtp->fifo_size = resource_size(r);
xprtp->fifo_base = devm_ioremap_nocache(&pdev->dev, r->start,
resource_size(r));
if (!xprtp->fifo_base) {
pr_err("ioreamp_nocache() failed\n");
return -ENOMEM;
}
qrtr_fifo_config_init(xprtp);
xprtp->ep.xmit = xprt_write;
ret = qrtr_endpoint_register(&xprtp->ep, QRTR_EP_NID_AUTO);
if (ret)
return ret;
if (fifo_rx_avail(&xprtp->rx_pipe))
xprt_read_data(xprtp);
return 0;
}
static const struct of_device_id qrtr_fifo_xprt_match_table[] = {
{ .compatible = "qcom,ipcr-fifo-xprt" },
{},
};
static struct platform_driver qrtr_fifo_xprt_driver = {
.probe = qrtr_fifo_xprt_probe,
.driver = {
.name = "qcom_fifo_qrtr",
.of_match_table = qrtr_fifo_xprt_match_table,
},
};
static int __init qrtr_fifo_xprt_init(void)
{
int rc;
rc = platform_driver_register(&qrtr_fifo_xprt_driver);
if (rc) {
pr_err("driver register failed %d\n", rc);
return rc;
}
return 0;
}
module_init(qrtr_fifo_xprt_init);
MODULE_DESCRIPTION("QTI IPC-router FIFO XPRT");
MODULE_LICENSE("GPL v2");