xref: /openbmc/linux/net/qrtr/mhi.c (revision ac4dfccb)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
4  */
5 
6 #include <linux/mhi.h>
7 #include <linux/mod_devicetable.h>
8 #include <linux/module.h>
9 #include <linux/skbuff.h>
10 #include <net/sock.h>
11 
12 #include "qrtr.h"
13 
14 struct qrtr_mhi_dev {
15 	struct qrtr_endpoint ep;
16 	struct mhi_device *mhi_dev;
17 	struct device *dev;
18 	struct completion ready;
19 };
20 
21 /* From MHI to QRTR */
22 static void qcom_mhi_qrtr_dl_callback(struct mhi_device *mhi_dev,
23 				      struct mhi_result *mhi_res)
24 {
25 	struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
26 	int rc;
27 
28 	if (!qdev || mhi_res->transaction_status)
29 		return;
30 
31 	rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr,
32 				mhi_res->bytes_xferd);
33 	if (rc == -EINVAL)
34 		dev_err(qdev->dev, "invalid ipcrouter packet\n");
35 }
36 
37 /* From QRTR to MHI */
38 static void qcom_mhi_qrtr_ul_callback(struct mhi_device *mhi_dev,
39 				      struct mhi_result *mhi_res)
40 {
41 	struct sk_buff *skb = mhi_res->buf_addr;
42 
43 	if (skb->sk)
44 		sock_put(skb->sk);
45 	consume_skb(skb);
46 }
47 
48 /* Send data over MHI */
49 static int qcom_mhi_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
50 {
51 	struct qrtr_mhi_dev *qdev = container_of(ep, struct qrtr_mhi_dev, ep);
52 	int rc;
53 
54 	rc = wait_for_completion_interruptible(&qdev->ready);
55 	if (rc)
56 		goto free_skb;
57 
58 	if (skb->sk)
59 		sock_hold(skb->sk);
60 
61 	rc = skb_linearize(skb);
62 	if (rc)
63 		goto free_skb;
64 
65 	rc = mhi_queue_skb(qdev->mhi_dev, DMA_TO_DEVICE, skb, skb->len,
66 			   MHI_EOT);
67 	if (rc)
68 		goto free_skb;
69 
70 	return rc;
71 
72 free_skb:
73 	if (skb->sk)
74 		sock_put(skb->sk);
75 	kfree_skb(skb);
76 
77 	return rc;
78 }
79 
80 static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev,
81 			       const struct mhi_device_id *id)
82 {
83 	struct qrtr_mhi_dev *qdev;
84 	int rc;
85 
86 	/* start channels */
87 	rc = mhi_prepare_for_transfer(mhi_dev, 0);
88 	if (rc)
89 		return rc;
90 
91 	qdev = devm_kzalloc(&mhi_dev->dev, sizeof(*qdev), GFP_KERNEL);
92 	if (!qdev)
93 		return -ENOMEM;
94 
95 	qdev->mhi_dev = mhi_dev;
96 	qdev->dev = &mhi_dev->dev;
97 	qdev->ep.xmit = qcom_mhi_qrtr_send;
98 
99 	dev_set_drvdata(&mhi_dev->dev, qdev);
100 	rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
101 	if (rc)
102 		return rc;
103 
104 	/* start channels */
105 	rc = mhi_prepare_for_transfer(mhi_dev, MHI_CH_INBOUND_ALLOC_BUFS);
106 	if (rc) {
107 		qrtr_endpoint_unregister(&qdev->ep);
108 		dev_set_drvdata(&mhi_dev->dev, NULL);
109 		return rc;
110 	}
111 
112 	complete_all(&qdev->ready);
113 	dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n");
114 
115 	return 0;
116 }
117 
118 static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev)
119 {
120 	struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
121 
122 	qrtr_endpoint_unregister(&qdev->ep);
123 	mhi_unprepare_from_transfer(mhi_dev);
124 	dev_set_drvdata(&mhi_dev->dev, NULL);
125 }
126 
127 static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
128 	{ .chan = "IPCR" },
129 	{}
130 };
131 MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
132 
133 static struct mhi_driver qcom_mhi_qrtr_driver = {
134 	.probe = qcom_mhi_qrtr_probe,
135 	.remove = qcom_mhi_qrtr_remove,
136 	.dl_xfer_cb = qcom_mhi_qrtr_dl_callback,
137 	.ul_xfer_cb = qcom_mhi_qrtr_ul_callback,
138 	.id_table = qcom_mhi_qrtr_id_table,
139 	.driver = {
140 		.name = "qcom_mhi_qrtr",
141 	},
142 };
143 
144 module_mhi_driver(qcom_mhi_qrtr_driver);
145 
146 MODULE_AUTHOR("Chris Lew <clew@codeaurora.org>");
147 MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
148 MODULE_DESCRIPTION("Qualcomm IPC-Router MHI interface driver");
149 MODULE_LICENSE("GPL v2");
150