blob: e070278f4ecc236e4a3ffb63a7257632f6923cb8 [file] [log] [blame]
Chris Lew8fd658e2018-02-28 17:13:09 -08001// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */
3
4#include <linux/module.h>
5#include <linux/skbuff.h>
6#include <linux/mod_devicetable.h>
7#include <linux/mhi.h>
8#include <net/sock.h>
9
10#include "qrtr.h"
11
12struct qrtr_mhi_dev {
13 struct qrtr_endpoint ep;
14 struct mhi_device *mhi_dev;
15 struct device *dev;
16 spinlock_t ul_lock; /* lock to protect ul_pkts */
17 struct list_head ul_pkts;
18};
19
20struct qrtr_mhi_pkt {
21 struct list_head node;
22 struct sk_buff *skb;
23 struct kref refcount;
24 struct completion done;
25};
26
27static void qrtr_mhi_pkt_release(struct kref *ref)
28{
29 struct qrtr_mhi_pkt *pkt = container_of(ref, struct qrtr_mhi_pkt,
30 refcount);
31 struct sock *sk = pkt->skb->sk;
32
33 consume_skb(pkt->skb);
34 if (sk)
35 sock_put(sk);
36 kfree(pkt);
37}
38
39/* from mhi to qrtr */
40static void qcom_mhi_qrtr_dl_callback(struct mhi_device *mhi_dev,
41 struct mhi_result *mhi_res)
42{
43 struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
44 int rc;
45
46 if (!qdev || mhi_res->transaction_status)
47 return;
48
49 rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr,
50 mhi_res->bytes_xferd);
51 if (rc == -EINVAL)
52 dev_err(qdev->dev, "invalid ipcrouter packet\n");
53}
54
55/* from mhi to qrtr */
56static void qcom_mhi_qrtr_ul_callback(struct mhi_device *mhi_dev,
57 struct mhi_result *mhi_res)
58{
59 struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
60 struct qrtr_mhi_pkt *pkt;
61
62 spin_lock_bh(&qdev->ul_lock);
63 pkt = list_first_entry(&qdev->ul_pkts, struct qrtr_mhi_pkt, node);
64 list_del(&pkt->node);
65 complete_all(&pkt->done);
66
67 kref_put(&pkt->refcount, qrtr_mhi_pkt_release);
68 spin_unlock_bh(&qdev->ul_lock);
69}
70
71/* from qrtr to mhi */
72static int qcom_mhi_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
73{
74 struct qrtr_mhi_dev *qdev = container_of(ep, struct qrtr_mhi_dev, ep);
75 struct qrtr_mhi_pkt *pkt;
76 int rc;
77
78 rc = skb_linearize(skb);
79 if (rc) {
80 kfree_skb(skb);
81 return rc;
82 }
83
84 pkt = kzalloc(sizeof(*pkt), GFP_KERNEL);
85 if (!pkt) {
86 kfree_skb(skb);
87 return -ENOMEM;
88 }
89
90 init_completion(&pkt->done);
91 kref_init(&pkt->refcount);
92 kref_get(&pkt->refcount);
93 pkt->skb = skb;
94
95 spin_lock_bh(&qdev->ul_lock);
96 list_add_tail(&pkt->node, &qdev->ul_pkts);
97 rc = mhi_queue_transfer(qdev->mhi_dev, DMA_TO_DEVICE, skb, skb->len,
98 MHI_EOT);
99 if (rc) {
100 list_del(&pkt->node);
101 kfree_skb(skb);
102 kfree(pkt);
103 spin_unlock_bh(&qdev->ul_lock);
104 return rc;
105 }
106 spin_unlock_bh(&qdev->ul_lock);
107 if (skb->sk)
108 sock_hold(skb->sk);
109
110 rc = wait_for_completion_interruptible_timeout(&pkt->done, HZ * 5);
111 if (rc > 0)
112 rc = 0;
113 else if (rc == 0)
114 rc = -ETIMEDOUT;
115
116 kref_put(&pkt->refcount, qrtr_mhi_pkt_release);
117 return rc;
118}
119
120static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev,
121 const struct mhi_device_id *id)
122{
123 struct qrtr_mhi_dev *qdev;
124 int rc;
125
126 qdev = devm_kzalloc(&mhi_dev->dev, sizeof(*qdev), GFP_KERNEL);
127 if (!qdev)
128 return -ENOMEM;
129
130 qdev->mhi_dev = mhi_dev;
131 qdev->dev = &mhi_dev->dev;
132 qdev->ep.xmit = qcom_mhi_qrtr_send;
133
134 INIT_LIST_HEAD(&qdev->ul_pkts);
135 spin_lock_init(&qdev->ul_lock);
136
137 rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
138 if (rc)
139 return rc;
140
141 dev_set_drvdata(&mhi_dev->dev, qdev);
142
143 dev_dbg(qdev->dev, "QTI MHI QRTR driver probed\n");
144
145 return 0;
146}
147
148static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev)
149{
150 struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
151
152 qrtr_endpoint_unregister(&qdev->ep);
153 dev_set_drvdata(&mhi_dev->dev, NULL);
154}
155
156static const struct mhi_device_id qcom_mhi_qrtr_mhi_match[] = {
157 { .chan = "IPCR" },
158 {}
159};
160
161static struct mhi_driver qcom_mhi_qrtr_driver = {
162 .probe = qcom_mhi_qrtr_probe,
163 .remove = qcom_mhi_qrtr_remove,
164 .dl_xfer_cb = qcom_mhi_qrtr_dl_callback,
165 .ul_xfer_cb = qcom_mhi_qrtr_ul_callback,
166 .id_table = qcom_mhi_qrtr_mhi_match,
167 .driver = {
168 .name = "qcom_mhi_qrtr",
169 .owner = THIS_MODULE,
170 },
171};
172
173module_driver(qcom_mhi_qrtr_driver, mhi_driver_register,
174 mhi_driver_unregister);
175
176MODULE_DESCRIPTION("QTI IPC-Router MHI interface driver");
177MODULE_LICENSE("GPL v2");