blob: c90534dac92397883bca86b3138605bf88d2ede3 [file] [log] [blame]
// 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>
#include <microvisor/microvisor.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)
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");