Commit | Line | Data |
---|---|---|
28fb4e59 BA |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2018, Linaro Ltd */ | |
3 | ||
4 | #include <linux/miscdevice.h> | |
5 | #include <linux/module.h> | |
6 | #include <linux/poll.h> | |
7 | #include <linux/skbuff.h> | |
8 | #include <linux/uaccess.h> | |
9 | ||
10 | #include "qrtr.h" | |
11 | ||
12 | struct qrtr_tun { | |
13 | struct qrtr_endpoint ep; | |
14 | ||
15 | struct sk_buff_head queue; | |
16 | wait_queue_head_t readq; | |
17 | }; | |
18 | ||
19 | static int qrtr_tun_send(struct qrtr_endpoint *ep, struct sk_buff *skb) | |
20 | { | |
21 | struct qrtr_tun *tun = container_of(ep, struct qrtr_tun, ep); | |
22 | ||
23 | skb_queue_tail(&tun->queue, skb); | |
24 | ||
25 | /* wake up any blocking processes, waiting for new data */ | |
26 | wake_up_interruptible(&tun->readq); | |
27 | ||
28 | return 0; | |
29 | } | |
30 | ||
31 | static int qrtr_tun_open(struct inode *inode, struct file *filp) | |
32 | { | |
33 | struct qrtr_tun *tun; | |
fc0494ea | 34 | int ret; |
28fb4e59 BA |
35 | |
36 | tun = kzalloc(sizeof(*tun), GFP_KERNEL); | |
37 | if (!tun) | |
38 | return -ENOMEM; | |
39 | ||
40 | skb_queue_head_init(&tun->queue); | |
41 | init_waitqueue_head(&tun->readq); | |
42 | ||
43 | tun->ep.xmit = qrtr_tun_send; | |
44 | ||
45 | filp->private_data = tun; | |
46 | ||
fc0494ea TM |
47 | ret = qrtr_endpoint_register(&tun->ep, QRTR_EP_NID_AUTO); |
48 | if (ret) | |
49 | goto out; | |
50 | ||
51 | return 0; | |
52 | ||
53 | out: | |
54 | filp->private_data = NULL; | |
55 | kfree(tun); | |
56 | return ret; | |
28fb4e59 BA |
57 | } |
58 | ||
59 | static ssize_t qrtr_tun_read_iter(struct kiocb *iocb, struct iov_iter *to) | |
60 | { | |
61 | struct file *filp = iocb->ki_filp; | |
62 | struct qrtr_tun *tun = filp->private_data; | |
63 | struct sk_buff *skb; | |
64 | int count; | |
65 | ||
66 | while (!(skb = skb_dequeue(&tun->queue))) { | |
67 | if (filp->f_flags & O_NONBLOCK) | |
68 | return -EAGAIN; | |
69 | ||
70 | /* Wait until we get data or the endpoint goes away */ | |
71 | if (wait_event_interruptible(tun->readq, | |
72 | !skb_queue_empty(&tun->queue))) | |
73 | return -ERESTARTSYS; | |
74 | } | |
75 | ||
76 | count = min_t(size_t, iov_iter_count(to), skb->len); | |
77 | if (copy_to_iter(skb->data, count, to) != count) | |
78 | count = -EFAULT; | |
79 | ||
80 | kfree_skb(skb); | |
81 | ||
82 | return count; | |
83 | } | |
84 | ||
85 | static ssize_t qrtr_tun_write_iter(struct kiocb *iocb, struct iov_iter *from) | |
86 | { | |
87 | struct file *filp = iocb->ki_filp; | |
88 | struct qrtr_tun *tun = filp->private_data; | |
89 | size_t len = iov_iter_count(from); | |
90 | ssize_t ret; | |
91 | void *kbuf; | |
92 | ||
2a80c158 ST |
93 | if (!len) |
94 | return -EINVAL; | |
95 | ||
96 | if (len > KMALLOC_MAX_SIZE) | |
97 | return -ENOMEM; | |
98 | ||
28fb4e59 BA |
99 | kbuf = kzalloc(len, GFP_KERNEL); |
100 | if (!kbuf) | |
101 | return -ENOMEM; | |
102 | ||
a21b7f0c NE |
103 | if (!copy_from_iter_full(kbuf, len, from)) { |
104 | kfree(kbuf); | |
28fb4e59 | 105 | return -EFAULT; |
a21b7f0c | 106 | } |
28fb4e59 BA |
107 | |
108 | ret = qrtr_endpoint_post(&tun->ep, kbuf, len); | |
109 | ||
a21b7f0c | 110 | kfree(kbuf); |
28fb4e59 BA |
111 | return ret < 0 ? ret : len; |
112 | } | |
113 | ||
114 | static __poll_t qrtr_tun_poll(struct file *filp, poll_table *wait) | |
115 | { | |
116 | struct qrtr_tun *tun = filp->private_data; | |
117 | __poll_t mask = 0; | |
118 | ||
119 | poll_wait(filp, &tun->readq, wait); | |
120 | ||
121 | if (!skb_queue_empty(&tun->queue)) | |
122 | mask |= EPOLLIN | EPOLLRDNORM; | |
123 | ||
124 | return mask; | |
125 | } | |
126 | ||
127 | static int qrtr_tun_release(struct inode *inode, struct file *filp) | |
128 | { | |
129 | struct qrtr_tun *tun = filp->private_data; | |
28fb4e59 BA |
130 | |
131 | qrtr_endpoint_unregister(&tun->ep); | |
132 | ||
133 | /* Discard all SKBs */ | |
21d8bd12 | 134 | skb_queue_purge(&tun->queue); |
28fb4e59 BA |
135 | |
136 | kfree(tun); | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
141 | static const struct file_operations qrtr_tun_ops = { | |
142 | .owner = THIS_MODULE, | |
143 | .open = qrtr_tun_open, | |
144 | .poll = qrtr_tun_poll, | |
145 | .read_iter = qrtr_tun_read_iter, | |
146 | .write_iter = qrtr_tun_write_iter, | |
147 | .release = qrtr_tun_release, | |
148 | }; | |
149 | ||
150 | static struct miscdevice qrtr_tun_miscdev = { | |
151 | MISC_DYNAMIC_MINOR, | |
152 | "qrtr-tun", | |
153 | &qrtr_tun_ops, | |
154 | }; | |
155 | ||
156 | static int __init qrtr_tun_init(void) | |
157 | { | |
158 | int ret; | |
159 | ||
160 | ret = misc_register(&qrtr_tun_miscdev); | |
161 | if (ret) | |
162 | pr_err("failed to register Qualcomm IPC Router tun device\n"); | |
163 | ||
164 | return ret; | |
165 | } | |
166 | ||
167 | static void __exit qrtr_tun_exit(void) | |
168 | { | |
169 | misc_deregister(&qrtr_tun_miscdev); | |
170 | } | |
171 | ||
172 | module_init(qrtr_tun_init); | |
173 | module_exit(qrtr_tun_exit); | |
174 | ||
175 | MODULE_DESCRIPTION("Qualcomm IPC Router TUN device"); | |
176 | MODULE_LICENSE("GPL v2"); |