diff --git a/net/qrtr/Kconfig b/net/qrtr/Kconfig index 78c078284532..0fde52dbc565 100644 --- a/net/qrtr/Kconfig +++ b/net/qrtr/Kconfig @@ -68,4 +68,13 @@ config QRTR_HAVEN Router communication between two virtual machines. The transport uses dynamically shared memory and haven doorbells. +config QRTR_MHI_DEV + tristate "MHI Device IPC Router channels" + depends on MSM_MHI_DEV || (COMPILE_TEST && MSM_MHI_DEV=n) + help + Say Y here to support MHI base ipcrouter channels for device + endpoint mode. MHI is the transport used for external modem + connections. This driver enables QRTR to run on the modem device + side. + endif # QRTR diff --git a/net/qrtr/Makefile b/net/qrtr/Makefile index dfca913847d4..ecb9b5a29c58 100644 --- a/net/qrtr/Makefile +++ b/net/qrtr/Makefile @@ -7,5 +7,7 @@ obj-$(CONFIG_QRTR_TUN) += qrtr-tun.o qrtr-tun-y := tun.o obj-$(CONFIG_QRTR_MHI) += qrtr-mhi.o qrtr-mhi-y := mhi.o +obj-$(CONFIG_QRTR_MHI_DEV) += qrtr-mhi-dev.o +qrtr-mhi-dev-y := mhi_dev.o obj-$(CONFIG_QRTR_HAVEN) += qrtr-haven.o qrtr-haven-y := haven.o diff --git a/net/qrtr/mhi_dev.c b/net/qrtr/mhi_dev.c new file mode 100644 index 000000000000..3f6091c1656b --- /dev/null +++ b/net/qrtr/mhi_dev.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2019-2021 The Linux Foundation. All rights reserved. + * + * 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 +#include +#include +#include +#include +#include "qrtr.h" + +#define QRTR_MAX_PKT_SIZE SZ_32K + +/* MHI DEV Enums are defined from Host perspective */ +#define QRTR_MHI_DEV_OUT MHI_CLIENT_IPCR_IN +#define QRTR_MHI_DEV_IN MHI_CLIENT_IPCR_OUT + +/** + * struct qrtr_mhi_dev_ep - qrtr mhi device endpoint + * @ep: endpoint + * @dev: device from platform bus + * @out: channel handle from mhi dev + * @out_tre: complete when channel is ready to send + * @out_lock: hold when resetting completion variable + * @in: channel handle from mhi dev + * @buf_in: buffer to hold incoming data + * @net_id: subnet id used by qrtr core + * @rt: realtime option used by qrtr core + */ +struct qrtr_mhi_dev_ep { + struct qrtr_endpoint ep; + struct device *dev; + struct mhi_dev_client *out; + struct completion out_tre; + struct mutex out_lock; /* for out critical sections */ + struct mhi_dev_client *in; + void *buf_in; + + u32 net_id; + bool rt; +}; + +static struct qrtr_mhi_dev_ep *qrtr_mhi_device_endpoint; + +static int qrtr_mhi_dev_send(struct qrtr_endpoint *ep, struct sk_buff *skb) +{ + struct qrtr_mhi_dev_ep *qep; + struct mhi_req req = { 0 }; + int rc; + + qep = container_of(ep, struct qrtr_mhi_dev_ep, ep); + rc = skb_linearize(skb); + if (rc) { + kfree_skb(skb); + return rc; + } + + req.chan = QRTR_MHI_DEV_OUT; + req.client = qep->out; + req.mode = DMA_SYNC; + req.buf = skb->data; + req.len = skb->len; + + do { + wait_for_completion(&qep->out_tre); + + mutex_lock(&qep->out_lock); + rc = mhi_dev_write_channel(&req); + if (rc == 0) + reinit_completion(&qep->out_tre); + mutex_unlock(&qep->out_lock); + } while (!rc); + + if (rc != skb->len) { + dev_err(qep->dev, "send failed rc:%d len:%d\n", rc, skb->len); + kfree_skb(skb); + return rc; + } + + consume_skb(skb); + return 0; +} + +static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep) +{ + struct mhi_req req = { 0 }; + int rc; + int bytes_read; + + req.chan = QRTR_MHI_DEV_IN; + req.client = qep->in; + req.mode = DMA_SYNC; + req.buf = qep->buf_in; + req.len = QRTR_MAX_PKT_SIZE; + + do { + bytes_read = mhi_dev_read_channel(&req); + if (bytes_read < 0) { + dev_err(qep->dev, "failed to read channel %d\n", + bytes_read); + return; + } + if (bytes_read == 0) + return; + + rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len); + if (rc == -EINVAL) + dev_err(qep->dev, "invalid ipcrouter packet\n"); + } while (bytes_read > 0); +} + +static void qrtr_mhi_dev_event_cb(struct mhi_dev_client_cb_reason *reason) +{ + struct qrtr_mhi_dev_ep *qep; + + qep = qrtr_mhi_device_endpoint; + if (!qep) + return; + + if (reason->reason == MHI_DEV_TRE_AVAILABLE) { + pr_debug("TRE available event for chan %d\n", reason->ch_id); + if (reason->ch_id == QRTR_MHI_DEV_IN) { + qrtr_mhi_dev_read(qep); + } else { + mutex_lock(&qep->out_lock); + complete_all(&qep->out_tre); + mutex_unlock(&qep->out_lock); + } + } +} + +static int qrtr_mhi_dev_open_channels(struct qrtr_mhi_dev_ep *qep) +{ + int rc; + + /* write channel */ + rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in, + qrtr_mhi_dev_event_cb); + if (rc < 0) + return rc; + + /* read channel */ + rc = mhi_dev_open_channel(QRTR_MHI_DEV_OUT, &qep->out, + qrtr_mhi_dev_event_cb); + if (rc < 0) { + mhi_dev_close_channel(qep->in); + return rc; + } + return 0; +} + +static void qrtr_mhi_dev_close_channels(struct qrtr_mhi_dev_ep *qep) +{ + + mhi_dev_close_channel(qep->in); + mhi_dev_close_channel(qep->out); +} + +static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data) +{ + struct qrtr_mhi_dev_ep *qep; + int rc; + + if (!cb_data || !cb_data->user_data) + return; + qep = cb_data->user_data; + + switch (cb_data->ctrl_info) { + case MHI_STATE_CONNECTED: + rc = qrtr_mhi_dev_open_channels(qep); + if (rc) { + dev_err(qep->dev, "open failed %d\n", rc); + return; + } + + rc = qrtr_endpoint_register(&qep->ep, qep->net_id, qep->rt); + if (rc) { + dev_err(qep->dev, "register failed %d\n", rc); + qrtr_mhi_dev_close_channels(qep); + } + break; + case MHI_STATE_DISCONNECTED: + qrtr_endpoint_unregister(&qep->ep); + qrtr_mhi_dev_close_channels(qep); + break; + default: + break; + } +} + +static int qrtr_mhi_dev_probe(struct platform_device *pdev) +{ + struct qrtr_mhi_dev_ep *qep; + struct device_node *node; + int rc; + struct mhi_dev_client_cb_data cb_data; + + qep = devm_kzalloc(&pdev->dev, sizeof(*qep), GFP_KERNEL); + if (!qep) + return -ENOMEM; + qep->dev = &pdev->dev; + + node = pdev->dev.of_node; + rc = of_property_read_u32(node, "qcom,net-id", &qep->net_id); + if (rc < 0) + qep->net_id = QRTR_EP_NET_ID_AUTO; + qep->rt = of_property_read_bool(node, "qcom,low-latency"); + + qep->buf_in = devm_kzalloc(&pdev->dev, QRTR_MAX_PKT_SIZE, GFP_KERNEL); + if (!qep->buf_in) + return -ENOMEM; + + qrtr_mhi_device_endpoint = qep; + + mutex_init(&qep->out_lock); + init_completion(&qep->out_tre); + qep->ep.xmit = qrtr_mhi_dev_send; + /* HOST init TX first followed by RX, so register for endpoint TX + * which makes both channel ready by checking one channel state. + */ + rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep, + QRTR_MHI_DEV_OUT); + if (rc == -EEXIST) { + /** + * MHI stack will return -EEXIST if mhi channel is already + * opend by the host and will not invoke reqistered callback. + * But future state change notification will inform through + * registered callback. + */ + complete_all(&qep->out_tre); + cb_data.user_data = (void *)qep; + cb_data.channel = QRTR_MHI_DEV_OUT; + cb_data.ctrl_info = MHI_STATE_CONNECTED; + qrtr_mhi_dev_state_cb(&cb_data); + } else if (rc) { + return rc; + } + + return 0; +} + +static const struct of_device_id qrtr_mhi_dev_match_table[] = { + { .compatible = "qcom,qrtr-mhi-dev"}, + {}, +}; + +static struct platform_driver qrtr_mhi_dev_driver = { + .probe = qrtr_mhi_dev_probe, + .driver = { + .name = "qrtr_mhi_dev", + .of_match_table = qrtr_mhi_dev_match_table, + }, +}; +module_platform_driver(qrtr_mhi_dev_driver); + +MODULE_DESCRIPTION("QTI IPC-Router MHI device interface driver"); +MODULE_LICENSE("GPL v2");