1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/mhi_ep.h>
#include <linux/module.h>
#include <linux/string.h>
struct mhi_ep_loopback {
struct workqueue_struct *wq;
struct mhi_ep_device *mdev;
};
struct mhi_ep_loopback_work {
struct mhi_ep_device *mdev;
struct work_struct work;
void *buf;
size_t len;
};
static void mhi_ep_loopback_work_handler(struct work_struct *work)
{
struct mhi_ep_loopback_work *mhi_ep_lb_work = container_of(work,
struct mhi_ep_loopback_work, work);
int ret;
ret = mhi_ep_queue_buf(mhi_ep_lb_work->mdev, mhi_ep_lb_work->buf,
mhi_ep_lb_work->len);
if (ret) {
dev_err(&mhi_ep_lb_work->mdev->dev, "Failed to queue buffer: %d\n", ret);
kfree(mhi_ep_lb_work->buf);
}
kfree(mhi_ep_lb_work);
}
static void mhi_ep_loopback_ul_callback(struct mhi_ep_device *mhi_dev,
struct mhi_result *mhi_res)
{
struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev);
struct mhi_ep_loopback_work *mhi_ep_lb_work;
void *buf;
if (!mhi_ep_lb)
return;
if (!mhi_res->transaction_status) {
if (!mhi_res->bytes_xferd)
return;
buf = kmemdup(mhi_res->buf_addr, mhi_res->bytes_xferd, GFP_KERNEL);
if (!buf)
return;
mhi_ep_lb_work = kmalloc_obj(*mhi_ep_lb_work, GFP_KERNEL);
if (!mhi_ep_lb_work) {
kfree(buf);
return;
}
INIT_WORK(&mhi_ep_lb_work->work, mhi_ep_loopback_work_handler);
mhi_ep_lb_work->mdev = mhi_dev;
mhi_ep_lb_work->buf = buf;
mhi_ep_lb_work->len = mhi_res->bytes_xferd;
queue_work(mhi_ep_lb->wq, &mhi_ep_lb_work->work);
}
}
static void mhi_ep_loopback_dl_callback(struct mhi_ep_device *mhi_dev,
struct mhi_result *mhi_res)
{
kfree(mhi_res->buf_addr);
}
static int mhi_ep_loopback_probe(struct mhi_ep_device *mhi_dev, const struct mhi_device_id *id)
{
struct mhi_ep_loopback *mhi_ep_lb;
mhi_ep_lb = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_ep_lb), GFP_KERNEL);
if (!mhi_ep_lb)
return -ENOMEM;
mhi_ep_lb->wq = alloc_ordered_workqueue("mhi_ep_loopback", WQ_MEM_RECLAIM);
if (!mhi_ep_lb->wq) {
dev_err(&mhi_dev->dev, "Failed to create workqueue\n");
return -ENOMEM;
}
mhi_ep_lb->mdev = mhi_dev;
dev_set_drvdata(&mhi_dev->dev, mhi_ep_lb);
return 0;
}
static void mhi_ep_loopback_remove(struct mhi_ep_device *mhi_dev)
{
struct mhi_ep_loopback *mhi_ep_lb = dev_get_drvdata(&mhi_dev->dev);
destroy_workqueue(mhi_ep_lb->wq);
}
static const struct mhi_device_id mhi_ep_loopback_id_table[] = {
{ .chan = "LOOPBACK" },
{ }
};
MODULE_DEVICE_TABLE(mhi, mhi_ep_loopback_id_table);
static struct mhi_ep_driver mhi_ep_loopback_driver = {
.probe = mhi_ep_loopback_probe,
.remove = mhi_ep_loopback_remove,
.dl_xfer_cb = mhi_ep_loopback_dl_callback,
.ul_xfer_cb = mhi_ep_loopback_ul_callback,
.id_table = mhi_ep_loopback_id_table,
.driver = {
.name = "mhi_ep_loopback",
},
};
module_mhi_ep_driver(mhi_ep_loopback_driver);
MODULE_AUTHOR("Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>");
MODULE_AUTHOR("Sumit Kumar <sumit.kumar@oss.qualcomm.com>");
MODULE_DESCRIPTION("MHI Endpoint Loopback driver");
MODULE_LICENSE("GPL");
|