diff options
Diffstat (limited to 'net/qrtr/mhi-ep.c')
-rw-r--r-- | net/qrtr/mhi-ep.c | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/net/qrtr/mhi-ep.c b/net/qrtr/mhi-ep.c new file mode 100644 index 000000000000..3837179d5025 --- /dev/null +++ b/net/qrtr/mhi-ep.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + */ + +#include <linux/mhi_ep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <net/sock.h> + +#include "qrtr.h" + +struct qrtr_mhi_dev { + struct qrtr_endpoint ep; + struct mhi_ep_device *mhi_dev; + struct device *dev; + struct completion out_tre; + struct mutex out_lock; +}; + +/* Callback from host to notify available buffers to queue */ +static void qcom_mhi_qrtr_dl_callback(struct mhi_ep_device *mhi_dev, + struct mhi_result *mhi_res) +{ + struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev); + struct sk_buff *skb = mhi_res->buf_addr; + + mutex_lock(&qdev->out_lock); + complete_all(&qdev->out_tre); + mutex_unlock(&qdev->out_lock); +} + +static void qcom_mhi_qrtr_ul_callback(struct mhi_ep_device *mhi_dev, + struct mhi_result *mhi_res) +{ + struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev); + int rc; + + /* TODO */ + if (!qdev) + return; + + rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr, + mhi_res->bytes_xferd); + if (rc == -EINVAL) + dev_err(qdev->dev, "invalid ipcrouter packet\n"); +} + +/* Send data over MHI */ +static int qcom_mhi_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb) +{ + struct qrtr_mhi_dev *qdev = container_of(ep, struct qrtr_mhi_dev, ep); + int rc; + + if (skb->sk) + sock_hold(skb->sk); + + rc = skb_linearize(skb); + if (rc) + goto free_skb; + + wait_for_completion(&qdev->out_tre); + + rc = mhi_ep_queue_skb(qdev->mhi_dev, DMA_FROM_DEVICE, skb, skb->len, + MHI_EOT); + if (rc) + goto free_skb; + + reinit_completion(&qdev->out_tre); + consume_skb(skb); + + return rc; + +free_skb: + if (skb->sk) + sock_put(skb->sk); + kfree_skb(skb); + + return rc; +} + +static int qcom_mhi_qrtr_probe(struct mhi_ep_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct qrtr_mhi_dev *qdev; + int rc; + + /* start channels */ +// rc = mhi_ep_prepare_for_transfer(mhi_dev); +// if (rc) +// return rc; + + qdev = devm_kzalloc(&mhi_dev->dev, sizeof(*qdev), GFP_KERNEL); + if (!qdev) + return -ENOMEM; + + qdev->mhi_dev = mhi_dev; + qdev->dev = &mhi_dev->dev; + init_completion(&qdev->out_tre); + mutex_init(&qdev->out_lock); + qdev->ep.xmit = qcom_mhi_qrtr_send; + + dev_set_drvdata(&mhi_dev->dev, qdev); + rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO); + if (rc) + return rc; + + dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n"); + + return 0; +} + +static void qcom_mhi_qrtr_remove(struct mhi_ep_device *mhi_dev) +{ + struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev); + + qrtr_endpoint_unregister(&qdev->ep); +// mhi_unprepare_from_transfer(mhi_dev); + dev_set_drvdata(&mhi_dev->dev, NULL); +} + +static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = { + { .chan = "IPCR" }, + {} +}; +MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table); + +static struct mhi_ep_driver qcom_mhi_qrtr_driver = { + .probe = qcom_mhi_qrtr_probe, + .remove = qcom_mhi_qrtr_remove, + .dl_xfer_cb = qcom_mhi_qrtr_dl_callback, + .ul_xfer_cb = qcom_mhi_qrtr_ul_callback, + .id_table = qcom_mhi_qrtr_id_table, + .driver = { + .name = "qcom_mhi_qrtr", + }, +}; + +module_mhi_ep_driver(qcom_mhi_qrtr_driver); + +MODULE_AUTHOR("Chris Lew <clew@codeaurora.org>"); +MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm IPC-Router MHI interface driver"); +MODULE_LICENSE("GPL v2"); |