blob: c78b45354c96c65b4ee161970998e072742918e6 [file] [log] [blame]
Eric Holmberg8ed30f22012-05-10 19:16:51 -06001/* drivers/tty/smux_loopback.c
2 *
Duy Truong790f06d2013-02-13 16:38:12 -08003 * Copyright (c) 2012, The Linux Foundation. All rights reserved.
Eric Holmberg8ed30f22012-05-10 19:16:51 -06004 *
5 * This software is licensed under the terms of the GNU General Public
6 * License version 2, as published by the Free Software Foundation, and
7 * may be copied, distributed, and modified under those terms.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15#include <linux/types.h>
16#include <linux/err.h>
17#include <linux/workqueue.h>
18#include <linux/kfifo.h>
19#include <linux/slab.h>
20#include <linux/smux.h>
21#include "smux_private.h"
22
23#define SMUX_LOOP_FIFO_SIZE 128
24
25static void smux_loopback_rx_worker(struct work_struct *work);
26static struct workqueue_struct *smux_loopback_wq;
27static DECLARE_WORK(smux_loopback_work, smux_loopback_rx_worker);
28static struct kfifo smux_loop_pkt_fifo;
29static DEFINE_SPINLOCK(hw_fn_lock);
30
31/**
32 * Initialize loopback framework (called by n_smux.c).
33 */
34int smux_loopback_init(void)
35{
36 int ret = 0;
37
38 spin_lock_init(&hw_fn_lock);
39 smux_loopback_wq = create_singlethread_workqueue("smux_loopback_wq");
40 if (IS_ERR(smux_loopback_wq)) {
41 pr_err("%s: failed to create workqueue\n", __func__);
42 return -ENOMEM;
43 }
44
45 ret |= kfifo_alloc(&smux_loop_pkt_fifo,
46 SMUX_LOOP_FIFO_SIZE * sizeof(struct smux_pkt_t *),
47 GFP_KERNEL);
48
49 return ret;
50}
51
52/**
53 * Simulate a write to the TTY hardware by duplicating
54 * the TX packet and putting it into the RX queue.
55 *
56 * @pkt Packet to write
57 *
58 * @returns 0 on success
59 */
60int smux_tx_loopback(struct smux_pkt_t *pkt_ptr)
61{
62 struct smux_pkt_t *send_pkt;
63 unsigned long flags;
64 int i;
65 int ret;
66
67 /* duplicate packet */
68 send_pkt = smux_alloc_pkt();
69 send_pkt->hdr = pkt_ptr->hdr;
70 if (pkt_ptr->hdr.payload_len) {
71 ret = smux_alloc_pkt_payload(send_pkt);
72 if (ret) {
73 ret = -ENOMEM;
74 goto out;
75 }
76 memcpy(send_pkt->payload, pkt_ptr->payload,
77 pkt_ptr->hdr.payload_len);
78 }
79
80 /* queue duplicate as pseudo-RX data */
81 spin_lock_irqsave(&hw_fn_lock, flags);
82 i = kfifo_avail(&smux_loop_pkt_fifo);
83 if (i < sizeof(struct smux_pkt_t *)) {
84 pr_err("%s: no space in fifo\n", __func__);
85 ret = -ENOMEM;
86 goto unlock;
87 }
88
89 i = kfifo_in(&smux_loop_pkt_fifo,
90 &send_pkt,
91 sizeof(struct smux_pkt_t *));
92 if (i < 0) {
93 pr_err("%s: fifo error\n", __func__);
94 ret = -ENOMEM;
95 goto unlock;
96 }
97 queue_work(smux_loopback_wq, &smux_loopback_work);
98 ret = 0;
99
100unlock:
101 spin_unlock_irqrestore(&hw_fn_lock, flags);
102out:
103 return ret;
104}
105
106/**
107 * Receive loopback byte processor.
108 *
109 * @pkt Incoming packet
110 */
111static void smux_loopback_rx_byte(struct smux_pkt_t *pkt)
112{
113 static int simulated_retry_cnt;
114 const char ack = SMUX_WAKEUP_ACK;
115
116 switch (pkt->hdr.flags) {
117 case SMUX_WAKEUP_REQ:
118 /* reply with ACK after appropriate delays */
119 ++simulated_retry_cnt;
120 if (simulated_retry_cnt >= smux_simulate_wakeup_delay) {
121 pr_err("%s: completed %d of %d\n",
122 __func__, simulated_retry_cnt,
123 smux_simulate_wakeup_delay);
124 pr_err("%s: simulated wakeup\n", __func__);
125 simulated_retry_cnt = 0;
126 smux_rx_state_machine(&ack, 1, 0);
127 } else {
128 /* force retry */
129 pr_err("%s: dropping wakeup request %d of %d\n",
130 __func__, simulated_retry_cnt,
131 smux_simulate_wakeup_delay);
132 }
133 break;
134 case SMUX_WAKEUP_ACK:
135 /* this shouldn't happen since we don't send requests */
136 pr_err("%s: wakeup ACK unexpected\n", __func__);
137 break;
138
139 default:
140 /* invalid character */
141 pr_err("%s: invalid character 0x%x\n",
142 __func__, (unsigned)pkt->hdr.flags);
143 break;
144 }
145}
146
147/**
148 * Simulated remote hardware used for local loopback testing.
149 *
150 * @work Not used
151 */
152static void smux_loopback_rx_worker(struct work_struct *work)
153{
154 struct smux_pkt_t *pkt;
155 struct smux_pkt_t reply_pkt;
156 char *data;
157 int len;
158 int lcid;
159 int i;
160 unsigned long flags;
161
162 data = kzalloc(SMUX_MAX_PKT_SIZE, GFP_ATOMIC);
163
164 spin_lock_irqsave(&hw_fn_lock, flags);
165 while (kfifo_len(&smux_loop_pkt_fifo) >= sizeof(struct smux_pkt_t *)) {
166 i = kfifo_out(&smux_loop_pkt_fifo, &pkt,
167 sizeof(struct smux_pkt_t *));
168 spin_unlock_irqrestore(&hw_fn_lock, flags);
169
170 if (pkt->hdr.magic != SMUX_MAGIC) {
171 pr_err("%s: invalid magic %x\n", __func__,
172 pkt->hdr.magic);
173 return;
174 }
175
176 lcid = pkt->hdr.lcid;
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600177
178 switch (pkt->hdr.cmd) {
179 case SMUX_CMD_OPEN_LCH:
Eric Holmbergf9622662012-06-13 15:55:45 -0600180 if (smux_assert_lch_id(lcid)) {
181 pr_err("%s: invalid channel id %d\n",
182 __func__, lcid);
183 break;
184 }
185
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600186 if (pkt->hdr.flags & SMUX_CMD_OPEN_ACK)
187 break;
188
189 /* Reply with Open ACK */
190 smux_init_pkt(&reply_pkt);
191 reply_pkt.hdr.lcid = lcid;
192 reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH;
193 reply_pkt.hdr.flags = SMUX_CMD_OPEN_ACK
194 | SMUX_CMD_OPEN_POWER_COLLAPSE;
195 reply_pkt.hdr.payload_len = 0;
196 reply_pkt.hdr.pad_len = 0;
197 smux_serialize(&reply_pkt, data, &len);
198 smux_rx_state_machine(data, len, 0);
199
200 /* Send Remote Open */
201 smux_init_pkt(&reply_pkt);
202 reply_pkt.hdr.lcid = lcid;
203 reply_pkt.hdr.cmd = SMUX_CMD_OPEN_LCH;
204 reply_pkt.hdr.flags = SMUX_CMD_OPEN_POWER_COLLAPSE;
205 reply_pkt.hdr.payload_len = 0;
206 reply_pkt.hdr.pad_len = 0;
207 smux_serialize(&reply_pkt, data, &len);
208 smux_rx_state_machine(data, len, 0);
209 break;
210
211 case SMUX_CMD_CLOSE_LCH:
Eric Holmbergf9622662012-06-13 15:55:45 -0600212 if (smux_assert_lch_id(lcid)) {
213 pr_err("%s: invalid channel id %d\n",
214 __func__, lcid);
215 break;
216 }
217
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600218 if (pkt->hdr.flags == SMUX_CMD_CLOSE_ACK)
219 break;
220
221 /* Reply with Close ACK */
222 smux_init_pkt(&reply_pkt);
223 reply_pkt.hdr.lcid = lcid;
224 reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH;
225 reply_pkt.hdr.flags = SMUX_CMD_CLOSE_ACK;
226 reply_pkt.hdr.payload_len = 0;
227 reply_pkt.hdr.pad_len = 0;
228 smux_serialize(&reply_pkt, data, &len);
229 smux_rx_state_machine(data, len, 0);
230
231 /* Send Remote Close */
232 smux_init_pkt(&reply_pkt);
233 reply_pkt.hdr.lcid = lcid;
234 reply_pkt.hdr.cmd = SMUX_CMD_CLOSE_LCH;
235 reply_pkt.hdr.flags = 0;
236 reply_pkt.hdr.payload_len = 0;
237 reply_pkt.hdr.pad_len = 0;
238 smux_serialize(&reply_pkt, data, &len);
239 smux_rx_state_machine(data, len, 0);
240 break;
241
242 case SMUX_CMD_DATA:
Eric Holmbergf9622662012-06-13 15:55:45 -0600243 if (smux_assert_lch_id(lcid)) {
244 pr_err("%s: invalid channel id %d\n",
245 __func__, lcid);
246 break;
247 }
248
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600249 /* Echo back received data */
250 smux_init_pkt(&reply_pkt);
251 reply_pkt.hdr.lcid = lcid;
252 reply_pkt.hdr.cmd = SMUX_CMD_DATA;
253 reply_pkt.hdr.flags = 0;
254 reply_pkt.hdr.payload_len = pkt->hdr.payload_len;
255 reply_pkt.payload = pkt->payload;
256 reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
257 smux_serialize(&reply_pkt, data, &len);
258 smux_rx_state_machine(data, len, 0);
259 break;
260
261 case SMUX_CMD_STATUS:
Eric Holmbergf9622662012-06-13 15:55:45 -0600262 if (smux_assert_lch_id(lcid)) {
263 pr_err("%s: invalid channel id %d\n",
264 __func__, lcid);
265 break;
266 }
267
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600268 /* Echo back received status */
269 smux_init_pkt(&reply_pkt);
270 reply_pkt.hdr.lcid = lcid;
271 reply_pkt.hdr.cmd = SMUX_CMD_STATUS;
272 reply_pkt.hdr.flags = pkt->hdr.flags;
273 reply_pkt.hdr.payload_len = 0;
274 reply_pkt.payload = NULL;
275 reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
276 smux_serialize(&reply_pkt, data, &len);
277 smux_rx_state_machine(data, len, 0);
278 break;
279
280 case SMUX_CMD_PWR_CTL:
281 /* reply with ack */
282 smux_init_pkt(&reply_pkt);
Eric Holmbergf9622662012-06-13 15:55:45 -0600283 reply_pkt.hdr.lcid = SMUX_BROADCAST_LCID;
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600284 reply_pkt.hdr.cmd = SMUX_CMD_PWR_CTL;
Eric Holmberg3371d372012-05-31 13:30:42 -0600285 reply_pkt.hdr.flags = SMUX_CMD_PWR_CTL_ACK;
Eric Holmberg8ed30f22012-05-10 19:16:51 -0600286 reply_pkt.hdr.payload_len = 0;
287 reply_pkt.payload = NULL;
288 reply_pkt.hdr.pad_len = pkt->hdr.pad_len;
289 smux_serialize(&reply_pkt, data, &len);
290 smux_rx_state_machine(data, len, 0);
291 break;
292
293 case SMUX_CMD_BYTE:
294 smux_loopback_rx_byte(pkt);
295 break;
296
297 default:
298 pr_err("%s: unknown command %d\n",
299 __func__, pkt->hdr.cmd);
300 break;
301 };
302
303 smux_free_pkt(pkt);
304 spin_lock_irqsave(&hw_fn_lock, flags);
305 }
306 spin_unlock_irqrestore(&hw_fn_lock, flags);
307 kfree(data);
308}