Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 68fc555c authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "net: ipc_router: Add IPC Router FIFO Transport"

parents b1459410 ec352816
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. IPC Router FIFO Transport

Required properties:
- compatible:	should be "qcom,ipcr_fifo_xprt"
- reg:		the irq register to raise an interrupt
- interrupts:	the receiving interrupt line
- qcom,ipc-shm:	Reference to shared memory phandle

Example:

	fifo_vipc_irq@176 {
		compatible = "qcom,ipcr-fifo-xprt";
		reg = <0x176>;
		interrupts = <0x0 0x142 0x1>;
		qcom,ipc-shm = <&ipc-shm>;
	};

	ipc-shm: shared-buffer@85af7000 {
		compatible = "qcom,hypervisor-shared-memory";
		phandle = <0x1e4>;
		reg = <0x0 0x85af7000 0x0 0x9000>;
		label = "ipc_shm";
		qcom,tx-is-first;
	};
+9 −0
Original line number Diff line number Diff line
@@ -33,3 +33,12 @@ config IPC_ROUTER_NODE_ID
	  The NODE defined here is used as the local NODE ID by IPC Router
	  core and publish the same NODE ID to other NODES present in the
	  network.

config IPC_ROUTER_FIFO_XPRT
	depends on IPC_ROUTER
	bool "IPC Router FIFO Transport"
	help
	  FIFO Transport Layer that enables IPC Router communication between
	  two virtual machines. When the Shared FIFO becomes available, this
	  layer registers the transport with IPC Router and enable message
	  exchange.
+1 −0
Original line number Diff line number Diff line
@@ -5,3 +5,4 @@
obj-$(CONFIG_IPC_ROUTER) := ipc_router_core.o
obj-$(CONFIG_IPC_ROUTER) += ipc_router_socket.o
obj-$(CONFIG_IPC_ROUTER_SECURITY) += ipc_router_security.o
obj-$(CONFIG_IPC_ROUTER_FIFO_XPRT) += ipc_router_fifo_xprt.o
+499 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
 */

#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/ipc_router_xprt.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>

#define MODULE_NAME "ipc_router_fifo_xprt"
#define XPRT_NAME_LEN 32

#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 msm_ipc_pipe {
	__le32 *tail;
	__le32 *head;

	void *fifo;
	size_t length;
};

/**
 * ipcr_fifo_xprt - IPC Router's FIFO XPRT structure
 * @xprt: IPC Router XPRT structure to contain XPRT specific info.
 * @tx_pipe: TX FIFO specific info.
 * @rx_pipe: RX FIFO specific info.
 * @fifo_xprt_wq: Workqueue to queue read & other XPRT related works.
 * @in_pkt: Pointer to any partially read packet.
 * @read_work: Read Work to perform read operation from SMD.
 * @sft_close_complete: Variable to indicate completion of SSR handling
 *                      by IPC Router.
 * @xprt_version: IPC Router header version supported by this XPRT.
 * @driver: Platform drivers register by this XPRT.
 * @xprt_name: Name of the XPRT to be registered with IPC Router.
 */
struct ipcr_fifo_xprt {
	struct msm_ipc_router_xprt xprt;
	struct msm_ipc_pipe tx_pipe;
	struct msm_ipc_pipe rx_pipe;
	struct workqueue_struct *xprt_wq;
	struct rr_packet *in_pkt;
	struct delayed_work read_work;
	struct completion sft_close_complete;
	unsigned int xprt_version;
	struct platform_driver driver;
	char xprt_name[XPRT_NAME_LEN];
	void *fifo_base;
	size_t fifo_size;
	int tx_fifo_idx;
	okl4_kcap_t kcap;
};

static void xprt_read_data(struct work_struct *work);
static void ipcr_fifo_raise_virq(struct ipcr_fifo_xprt *xprtp);

static size_t fifo_rx_avail(struct msm_ipc_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 msm_ipc_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 msm_ipc_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 msm_ipc_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 msm_ipc_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);
}

/**
 * set_xprt_version() - Set IPC Router header version in the transport
 * @xprt: Reference to the transport structure.
 * @version: The version to be set in transport.
 */
static void set_xprt_version(struct msm_ipc_router_xprt *xprt,
			     unsigned int version)
{
	struct ipcr_fifo_xprt *xprtp;

	if (!xprt)
		return;
	xprtp = container_of(xprt, struct ipcr_fifo_xprt, xprt);
	xprtp->xprt_version = version;
}

static int get_xprt_version(struct msm_ipc_router_xprt *xprt)
{
	struct ipcr_fifo_xprt *xprtp;

	if (!xprt)
		return -EINVAL;
	xprtp = container_of(xprt, struct ipcr_fifo_xprt, xprt);
	return (int)xprtp->xprt_version;
}

static int get_xprt_option(struct msm_ipc_router_xprt *xprt)
{
	/* fragmented data is NOT supported */
	return 0;
}

static int xprt_close(struct msm_ipc_router_xprt *xprt)
{
	return 0;
}

static void xprt_sft_close_done(struct msm_ipc_router_xprt *xprt)
{
	struct ipcr_fifo_xprt *xprtp;

	if (!xprt)
		return;

	xprtp = container_of(xprt, struct ipcr_fifo_xprt, xprt);
	complete_all(&xprtp->sft_close_complete);
}

static int xprt_write(void *data, uint32_t len,
		      struct msm_ipc_router_xprt *xprt)
{
	struct rr_packet *pkt = (struct rr_packet *)data;
	struct sk_buff *skb;
	struct ipcr_fifo_xprt *xprtp;

	xprtp = container_of(xprt, struct ipcr_fifo_xprt, xprt);

	if (!pkt)
		return -EINVAL;

	if (!len || pkt->length != len)
		return -EINVAL;

	/* TODO: FIFO write : check if we can write full packet at one shot */
	if (skb_queue_len(pkt->pkt_fragment_q) != 1) {
		pr_err("IPC router core is given fragmented data\n");
		return -EINVAL;
	}
	if (fifo_tx_avail(&xprtp->tx_pipe) < len) {
		pr_err("No Space in FIFO\n");
		return -EAGAIN;
	}

	skb_queue_walk(pkt->pkt_fragment_q, skb) {
		fifo_tx_write(&xprtp->tx_pipe, skb->data, skb->len);
	}

	ipcr_fifo_raise_virq(xprtp);

	return len;
}

static void xprt_read_data(struct work_struct *work)
{
	void *data;
	size_t hdr_len;
	size_t rx_avail;
	size_t pkt_len;
	struct rr_header_v1 hdr;
	struct sk_buff *ipc_rtr_pkt;
	struct ipcr_fifo_xprt *xprtp;
	struct delayed_work *rwork = to_delayed_work(work);

	xprtp = container_of(rwork, struct ipcr_fifo_xprt, read_work);

	hdr_len = sizeof(struct rr_header_v1);
	while (1) {
		rx_avail = fifo_rx_avail(&xprtp->rx_pipe);
		if (!rx_avail || (rx_avail < hdr_len))
			break;

		fifo_rx_peak(&xprtp->rx_pipe, &hdr, 0, hdr_len);
		pkt_len = ipc_router_peek_pkt_size((char *)&hdr);

		if (pkt_len < 0) {
			pr_err("%s invalid pkt_len %zu\n", __func__, pkt_len);
			break;
		}
		if (!xprtp->in_pkt) {
			xprtp->in_pkt = create_pkt(NULL);
			if (!xprtp->in_pkt)
				break;
		}
		ipc_rtr_pkt = alloc_skb(pkt_len, GFP_KERNEL);
		if (!ipc_rtr_pkt) {
			release_pkt(xprtp->in_pkt);
			xprtp->in_pkt = NULL;
			break;
		}
		data = skb_put(ipc_rtr_pkt, pkt_len);
		do {
			rx_avail = fifo_rx_avail(&xprtp->rx_pipe);
			if (rx_avail >= pkt_len) {
				fifo_rx_peak(&xprtp->rx_pipe, data, 0, pkt_len);
				fifo_rx_advance(&xprtp->rx_pipe, pkt_len);
				break;
			}
			pr_debug("%s wait for FULL PKT [avail: len][%zu:%zu]\n",
				 __func__, rx_avail, pkt_len);
			/* wait for complete packet written into FIFO */
			msleep(20);
		} while (1);

		skb_queue_tail(xprtp->in_pkt->pkt_fragment_q, ipc_rtr_pkt);
		xprtp->in_pkt->length = pkt_len;
		msm_ipc_router_xprt_notify(&xprtp->xprt,
					   IPC_ROUTER_XPRT_EVENT_DATA,
					   (void *)xprtp->in_pkt);
		release_pkt(xprtp->in_pkt);
		xprtp->in_pkt = NULL;
	}
}

static void ipcr_fifo_raise_virq(struct ipcr_fifo_xprt *xprtp)
{
	okl4_error_t err;
	unsigned long payload = 0xffff;

	err = _okl4_sys_vinterrupt_raise(xprtp->kcap, payload);
}

static irqreturn_t ipcr_fifo_virq_handler(int irq, void *dev_id)
{
	struct ipcr_fifo_xprt *xprtp = dev_id;

	queue_delayed_work(xprtp->xprt_wq, &xprtp->read_work, 0);
	return IRQ_HANDLED;
}

/**
 * ipcr_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 int ipcr_fifo_config_init(struct ipcr_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;

	xprtp->xprt.link_id = 1;
	xprtp->xprt_version = 1;

	strlcpy(xprtp->xprt_name, "IPCR_FIFO_XPRT", XPRT_NAME_LEN);
	xprtp->xprt.name = xprtp->xprt_name;

	xprtp->xprt.set_version = set_xprt_version;
	xprtp->xprt.get_version = get_xprt_version;
	xprtp->xprt.get_option = get_xprt_option;
	xprtp->xprt.read_avail = NULL;
	xprtp->xprt.read = NULL;
	xprtp->xprt.write_avail = NULL;
	xprtp->xprt.write = xprt_write;
	xprtp->xprt.close = xprt_close;
	xprtp->xprt.sft_close_done = xprt_sft_close_done;
	xprtp->xprt.priv = NULL;

	xprtp->in_pkt = NULL;
	xprtp->xprt_wq = create_singlethread_workqueue(xprtp->xprt_name);
	if (!xprtp->xprt_wq)
		return -EFAULT;

	INIT_DELAYED_WORK(&xprtp->read_work, xprt_read_data);

	msm_ipc_router_xprt_notify(&xprtp->xprt,
				   IPC_ROUTER_XPRT_EVENT_OPEN,
				   NULL);

	if (fifo_rx_avail(&xprtp->rx_pipe))
		queue_delayed_work(xprtp->xprt_wq, &xprtp->read_work, 0);

	return 0;
}

/**
 * ipcr_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 ipcr_fifo_xprt_probe(struct platform_device *pdev)
{
	int irq;
	int ret;
	struct resource *r;
	struct device *parent;
	struct ipcr_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 (IS_ERR_OR_NULL(xprtp))
		return -ENOMEM;

	parent = &pdev->dev;
	ipc_irq_np = parent->of_node;

	irq = platform_get_irq(pdev, 0);

	if (irq >= 0) {
		ret = devm_request_irq(parent, irq, ipcr_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)
		return -ENODEV;

	r = platform_get_resource(ipc_shm_dev, IORESOURCE_MEM, 0);
	if (!r) {
		pr_err("%s failed to get shared FIFO\n", __func__);
		return -ENODEV;
	}

	xprtp->tx_fifo_idx = of_property_read_bool(ipc_shm_np,
						   "qcom,tx-is-first");

	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("%s ioreamp_nocache() failed\n", __func__);
		return -ENOMEM;
	}

	ret = ipcr_fifo_config_init(xprtp);
	if (ret) {
		IPC_RTR_ERR("%s init failed ret[%d]\n", __func__, ret);
		return ret;
	}

	return 0;
}

static const struct of_device_id ipcr_fifo_xprt_match_table[] = {
	{ .compatible = "qcom,ipcr-fifo-xprt" },
	{},
};

static struct platform_driver ipcr_fifo_xprt_driver = {
	.probe = ipcr_fifo_xprt_probe,
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = ipcr_fifo_xprt_match_table,
	 },
};

static int __init ipcr_fifo_xprt_init(void)
{
	int rc;

	rc = platform_driver_register(&ipcr_fifo_xprt_driver);
	if (rc) {
		IPC_RTR_ERR("%s: driver register failed %d\n", __func__, rc);
		return rc;
	}

	return 0;
}

module_init(ipcr_fifo_xprt_init);
MODULE_DESCRIPTION("IPC Router FIFO XPRT");
MODULE_LICENSE("GPL v2");