diff options
Diffstat (limited to 'drivers/tty/ipwireless/network.c')
-rw-r--r-- | drivers/tty/ipwireless/network.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/drivers/tty/ipwireless/network.c b/drivers/tty/ipwireless/network.c new file mode 100644 index 00000000000..f7daeea598e --- /dev/null +++ b/drivers/tty/ipwireless/network.c | |||
@@ -0,0 +1,508 @@ | |||
1 | /* | ||
2 | * IPWireless 3G PCMCIA Network Driver | ||
3 | * | ||
4 | * Original code | ||
5 | * by Stephen Blackheath <stephen@blacksapphire.com>, | ||
6 | * Ben Martel <benm@symmetric.co.nz> | ||
7 | * | ||
8 | * Copyrighted as follows: | ||
9 | * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) | ||
10 | * | ||
11 | * Various driver changes and rewrites, port to new kernels | ||
12 | * Copyright (C) 2006-2007 Jiri Kosina | ||
13 | * | ||
14 | * Misc code cleanups and updates | ||
15 | * Copyright (C) 2007 David Sterba | ||
16 | */ | ||
17 | |||
18 | #include <linux/interrupt.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/mutex.h> | ||
21 | #include <linux/netdevice.h> | ||
22 | #include <linux/ppp_channel.h> | ||
23 | #include <linux/ppp_defs.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/if_ppp.h> | ||
26 | #include <linux/skbuff.h> | ||
27 | |||
28 | #include "network.h" | ||
29 | #include "hardware.h" | ||
30 | #include "main.h" | ||
31 | #include "tty.h" | ||
32 | |||
33 | #define MAX_ASSOCIATED_TTYS 2 | ||
34 | |||
35 | #define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP) | ||
36 | |||
37 | struct ipw_network { | ||
38 | /* Hardware context, used for calls to hardware layer. */ | ||
39 | struct ipw_hardware *hardware; | ||
40 | /* Context for kernel 'generic_ppp' functionality */ | ||
41 | struct ppp_channel *ppp_channel; | ||
42 | /* tty context connected with IPW console */ | ||
43 | struct ipw_tty *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS]; | ||
44 | /* True if ppp needs waking up once we're ready to xmit */ | ||
45 | int ppp_blocked; | ||
46 | /* Number of packets queued up in hardware module. */ | ||
47 | int outgoing_packets_queued; | ||
48 | /* Spinlock to avoid interrupts during shutdown */ | ||
49 | spinlock_t lock; | ||
50 | struct mutex close_lock; | ||
51 | |||
52 | /* PPP ioctl data, not actually used anywere */ | ||
53 | unsigned int flags; | ||
54 | unsigned int rbits; | ||
55 | u32 xaccm[8]; | ||
56 | u32 raccm; | ||
57 | int mru; | ||
58 | |||
59 | int shutting_down; | ||
60 | unsigned int ras_control_lines; | ||
61 | |||
62 | struct work_struct work_go_online; | ||
63 | struct work_struct work_go_offline; | ||
64 | }; | ||
65 | |||
66 | static void notify_packet_sent(void *callback_data, unsigned int packet_length) | ||
67 | { | ||
68 | struct ipw_network *network = callback_data; | ||
69 | unsigned long flags; | ||
70 | |||
71 | spin_lock_irqsave(&network->lock, flags); | ||
72 | network->outgoing_packets_queued--; | ||
73 | if (network->ppp_channel != NULL) { | ||
74 | if (network->ppp_blocked) { | ||
75 | network->ppp_blocked = 0; | ||
76 | spin_unlock_irqrestore(&network->lock, flags); | ||
77 | ppp_output_wakeup(network->ppp_channel); | ||
78 | if (ipwireless_debug) | ||
79 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME | ||
80 | ": ppp unblocked\n"); | ||
81 | } else | ||
82 | spin_unlock_irqrestore(&network->lock, flags); | ||
83 | } else | ||
84 | spin_unlock_irqrestore(&network->lock, flags); | ||
85 | } | ||
86 | |||
87 | /* | ||
88 | * Called by the ppp system when it has a packet to send to the hardware. | ||
89 | */ | ||
90 | static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel, | ||
91 | struct sk_buff *skb) | ||
92 | { | ||
93 | struct ipw_network *network = ppp_channel->private; | ||
94 | unsigned long flags; | ||
95 | |||
96 | spin_lock_irqsave(&network->lock, flags); | ||
97 | if (network->outgoing_packets_queued < ipwireless_out_queue) { | ||
98 | unsigned char *buf; | ||
99 | static unsigned char header[] = { | ||
100 | PPP_ALLSTATIONS, /* 0xff */ | ||
101 | PPP_UI, /* 0x03 */ | ||
102 | }; | ||
103 | int ret; | ||
104 | |||
105 | network->outgoing_packets_queued++; | ||
106 | spin_unlock_irqrestore(&network->lock, flags); | ||
107 | |||
108 | /* | ||
109 | * If we have the requested amount of headroom in the skb we | ||
110 | * were handed, then we can add the header efficiently. | ||
111 | */ | ||
112 | if (skb_headroom(skb) >= 2) { | ||
113 | memcpy(skb_push(skb, 2), header, 2); | ||
114 | ret = ipwireless_send_packet(network->hardware, | ||
115 | IPW_CHANNEL_RAS, skb->data, | ||
116 | skb->len, | ||
117 | notify_packet_sent, | ||
118 | network); | ||
119 | if (ret == -1) { | ||
120 | skb_pull(skb, 2); | ||
121 | return 0; | ||
122 | } | ||
123 | } else { | ||
124 | /* Otherwise (rarely) we do it inefficiently. */ | ||
125 | buf = kmalloc(skb->len + 2, GFP_ATOMIC); | ||
126 | if (!buf) | ||
127 | return 0; | ||
128 | memcpy(buf + 2, skb->data, skb->len); | ||
129 | memcpy(buf, header, 2); | ||
130 | ret = ipwireless_send_packet(network->hardware, | ||
131 | IPW_CHANNEL_RAS, buf, | ||
132 | skb->len + 2, | ||
133 | notify_packet_sent, | ||
134 | network); | ||
135 | kfree(buf); | ||
136 | if (ret == -1) | ||
137 | return 0; | ||
138 | } | ||
139 | kfree_skb(skb); | ||
140 | return 1; | ||
141 | } else { | ||
142 | /* | ||
143 | * Otherwise reject the packet, and flag that the ppp system | ||
144 | * needs to be unblocked once we are ready to send. | ||
145 | */ | ||
146 | network->ppp_blocked = 1; | ||
147 | spin_unlock_irqrestore(&network->lock, flags); | ||
148 | if (ipwireless_debug) | ||
149 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": ppp blocked\n"); | ||
150 | return 0; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */ | ||
155 | static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel, | ||
156 | unsigned int cmd, unsigned long arg) | ||
157 | { | ||
158 | struct ipw_network *network = ppp_channel->private; | ||
159 | int err, val; | ||
160 | u32 accm[8]; | ||
161 | int __user *user_arg = (int __user *) arg; | ||
162 | |||
163 | err = -EFAULT; | ||
164 | switch (cmd) { | ||
165 | case PPPIOCGFLAGS: | ||
166 | val = network->flags | network->rbits; | ||
167 | if (put_user(val, user_arg)) | ||
168 | break; | ||
169 | err = 0; | ||
170 | break; | ||
171 | |||
172 | case PPPIOCSFLAGS: | ||
173 | if (get_user(val, user_arg)) | ||
174 | break; | ||
175 | network->flags = val & ~SC_RCV_BITS; | ||
176 | network->rbits = val & SC_RCV_BITS; | ||
177 | err = 0; | ||
178 | break; | ||
179 | |||
180 | case PPPIOCGASYNCMAP: | ||
181 | if (put_user(network->xaccm[0], user_arg)) | ||
182 | break; | ||
183 | err = 0; | ||
184 | break; | ||
185 | |||
186 | case PPPIOCSASYNCMAP: | ||
187 | if (get_user(network->xaccm[0], user_arg)) | ||
188 | break; | ||
189 | err = 0; | ||
190 | break; | ||
191 | |||
192 | case PPPIOCGRASYNCMAP: | ||
193 | if (put_user(network->raccm, user_arg)) | ||
194 | break; | ||
195 | err = 0; | ||
196 | break; | ||
197 | |||
198 | case PPPIOCSRASYNCMAP: | ||
199 | if (get_user(network->raccm, user_arg)) | ||
200 | break; | ||
201 | err = 0; | ||
202 | break; | ||
203 | |||
204 | case PPPIOCGXASYNCMAP: | ||
205 | if (copy_to_user((void __user *) arg, network->xaccm, | ||
206 | sizeof(network->xaccm))) | ||
207 | break; | ||
208 | err = 0; | ||
209 | break; | ||
210 | |||
211 | case PPPIOCSXASYNCMAP: | ||
212 | if (copy_from_user(accm, (void __user *) arg, sizeof(accm))) | ||
213 | break; | ||
214 | accm[2] &= ~0x40000000U; /* can't escape 0x5e */ | ||
215 | accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ | ||
216 | memcpy(network->xaccm, accm, sizeof(network->xaccm)); | ||
217 | err = 0; | ||
218 | break; | ||
219 | |||
220 | case PPPIOCGMRU: | ||
221 | if (put_user(network->mru, user_arg)) | ||
222 | break; | ||
223 | err = 0; | ||
224 | break; | ||
225 | |||
226 | case PPPIOCSMRU: | ||
227 | if (get_user(val, user_arg)) | ||
228 | break; | ||
229 | if (val < PPP_MRU) | ||
230 | val = PPP_MRU; | ||
231 | network->mru = val; | ||
232 | err = 0; | ||
233 | break; | ||
234 | |||
235 | default: | ||
236 | err = -ENOTTY; | ||
237 | } | ||
238 | |||
239 | return err; | ||
240 | } | ||
241 | |||
242 | static const struct ppp_channel_ops ipwireless_ppp_channel_ops = { | ||
243 | .start_xmit = ipwireless_ppp_start_xmit, | ||
244 | .ioctl = ipwireless_ppp_ioctl | ||
245 | }; | ||
246 | |||
247 | static void do_go_online(struct work_struct *work_go_online) | ||
248 | { | ||
249 | struct ipw_network *network = | ||
250 | container_of(work_go_online, struct ipw_network, | ||
251 | work_go_online); | ||
252 | unsigned long flags; | ||
253 | |||
254 | spin_lock_irqsave(&network->lock, flags); | ||
255 | if (!network->ppp_channel) { | ||
256 | struct ppp_channel *channel; | ||
257 | |||
258 | spin_unlock_irqrestore(&network->lock, flags); | ||
259 | channel = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL); | ||
260 | if (!channel) { | ||
261 | printk(KERN_ERR IPWIRELESS_PCCARD_NAME | ||
262 | ": unable to allocate PPP channel\n"); | ||
263 | return; | ||
264 | } | ||
265 | channel->private = network; | ||
266 | channel->mtu = 16384; /* Wild guess */ | ||
267 | channel->hdrlen = 2; | ||
268 | channel->ops = &ipwireless_ppp_channel_ops; | ||
269 | |||
270 | network->flags = 0; | ||
271 | network->rbits = 0; | ||
272 | network->mru = PPP_MRU; | ||
273 | memset(network->xaccm, 0, sizeof(network->xaccm)); | ||
274 | network->xaccm[0] = ~0U; | ||
275 | network->xaccm[3] = 0x60000000U; | ||
276 | network->raccm = ~0U; | ||
277 | ppp_register_channel(channel); | ||
278 | spin_lock_irqsave(&network->lock, flags); | ||
279 | network->ppp_channel = channel; | ||
280 | } | ||
281 | spin_unlock_irqrestore(&network->lock, flags); | ||
282 | } | ||
283 | |||
284 | static void do_go_offline(struct work_struct *work_go_offline) | ||
285 | { | ||
286 | struct ipw_network *network = | ||
287 | container_of(work_go_offline, struct ipw_network, | ||
288 | work_go_offline); | ||
289 | unsigned long flags; | ||
290 | |||
291 | mutex_lock(&network->close_lock); | ||
292 | spin_lock_irqsave(&network->lock, flags); | ||
293 | if (network->ppp_channel != NULL) { | ||
294 | struct ppp_channel *channel = network->ppp_channel; | ||
295 | |||
296 | network->ppp_channel = NULL; | ||
297 | spin_unlock_irqrestore(&network->lock, flags); | ||
298 | mutex_unlock(&network->close_lock); | ||
299 | ppp_unregister_channel(channel); | ||
300 | } else { | ||
301 | spin_unlock_irqrestore(&network->lock, flags); | ||
302 | mutex_unlock(&network->close_lock); | ||
303 | } | ||
304 | } | ||
305 | |||
306 | void ipwireless_network_notify_control_line_change(struct ipw_network *network, | ||
307 | unsigned int channel_idx, | ||
308 | unsigned int control_lines, | ||
309 | unsigned int changed_mask) | ||
310 | { | ||
311 | int i; | ||
312 | |||
313 | if (channel_idx == IPW_CHANNEL_RAS) | ||
314 | network->ras_control_lines = control_lines; | ||
315 | |||
316 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { | ||
317 | struct ipw_tty *tty = | ||
318 | network->associated_ttys[channel_idx][i]; | ||
319 | |||
320 | /* | ||
321 | * If it's associated with a tty (other than the RAS channel | ||
322 | * when we're online), then send the data to that tty. The RAS | ||
323 | * channel's data is handled above - it always goes through | ||
324 | * ppp_generic. | ||
325 | */ | ||
326 | if (tty) | ||
327 | ipwireless_tty_notify_control_line_change(tty, | ||
328 | channel_idx, | ||
329 | control_lines, | ||
330 | changed_mask); | ||
331 | } | ||
332 | } | ||
333 | |||
334 | /* | ||
335 | * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI) | ||
336 | * bytes, which are required on sent packet, but not always present on received | ||
337 | * packets | ||
338 | */ | ||
339 | static struct sk_buff *ipw_packet_received_skb(unsigned char *data, | ||
340 | unsigned int length) | ||
341 | { | ||
342 | struct sk_buff *skb; | ||
343 | |||
344 | if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) { | ||
345 | length -= 2; | ||
346 | data += 2; | ||
347 | } | ||
348 | |||
349 | skb = dev_alloc_skb(length + 4); | ||
350 | skb_reserve(skb, 2); | ||
351 | memcpy(skb_put(skb, length), data, length); | ||
352 | |||
353 | return skb; | ||
354 | } | ||
355 | |||
356 | void ipwireless_network_packet_received(struct ipw_network *network, | ||
357 | unsigned int channel_idx, | ||
358 | unsigned char *data, | ||
359 | unsigned int length) | ||
360 | { | ||
361 | int i; | ||
362 | unsigned long flags; | ||
363 | |||
364 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { | ||
365 | struct ipw_tty *tty = network->associated_ttys[channel_idx][i]; | ||
366 | |||
367 | if (!tty) | ||
368 | continue; | ||
369 | |||
370 | /* | ||
371 | * If it's associated with a tty (other than the RAS channel | ||
372 | * when we're online), then send the data to that tty. The RAS | ||
373 | * channel's data is handled above - it always goes through | ||
374 | * ppp_generic. | ||
375 | */ | ||
376 | if (channel_idx == IPW_CHANNEL_RAS | ||
377 | && (network->ras_control_lines & | ||
378 | IPW_CONTROL_LINE_DCD) != 0 | ||
379 | && ipwireless_tty_is_modem(tty)) { | ||
380 | /* | ||
381 | * If data came in on the RAS channel and this tty is | ||
382 | * the modem tty, and we are online, then we send it to | ||
383 | * the PPP layer. | ||
384 | */ | ||
385 | mutex_lock(&network->close_lock); | ||
386 | spin_lock_irqsave(&network->lock, flags); | ||
387 | if (network->ppp_channel != NULL) { | ||
388 | struct sk_buff *skb; | ||
389 | |||
390 | spin_unlock_irqrestore(&network->lock, | ||
391 | flags); | ||
392 | |||
393 | /* Send the data to the ppp_generic module. */ | ||
394 | skb = ipw_packet_received_skb(data, length); | ||
395 | ppp_input(network->ppp_channel, skb); | ||
396 | } else | ||
397 | spin_unlock_irqrestore(&network->lock, | ||
398 | flags); | ||
399 | mutex_unlock(&network->close_lock); | ||
400 | } | ||
401 | /* Otherwise we send it out the tty. */ | ||
402 | else | ||
403 | ipwireless_tty_received(tty, data, length); | ||
404 | } | ||
405 | } | ||
406 | |||
407 | struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw) | ||
408 | { | ||
409 | struct ipw_network *network = | ||
410 | kzalloc(sizeof(struct ipw_network), GFP_ATOMIC); | ||
411 | |||
412 | if (!network) | ||
413 | return NULL; | ||
414 | |||
415 | spin_lock_init(&network->lock); | ||
416 | mutex_init(&network->close_lock); | ||
417 | |||
418 | network->hardware = hw; | ||
419 | |||
420 | INIT_WORK(&network->work_go_online, do_go_online); | ||
421 | INIT_WORK(&network->work_go_offline, do_go_offline); | ||
422 | |||
423 | ipwireless_associate_network(hw, network); | ||
424 | |||
425 | return network; | ||
426 | } | ||
427 | |||
428 | void ipwireless_network_free(struct ipw_network *network) | ||
429 | { | ||
430 | network->shutting_down = 1; | ||
431 | |||
432 | ipwireless_ppp_close(network); | ||
433 | flush_work_sync(&network->work_go_online); | ||
434 | flush_work_sync(&network->work_go_offline); | ||
435 | |||
436 | ipwireless_stop_interrupts(network->hardware); | ||
437 | ipwireless_associate_network(network->hardware, NULL); | ||
438 | |||
439 | kfree(network); | ||
440 | } | ||
441 | |||
442 | void ipwireless_associate_network_tty(struct ipw_network *network, | ||
443 | unsigned int channel_idx, | ||
444 | struct ipw_tty *tty) | ||
445 | { | ||
446 | int i; | ||
447 | |||
448 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) | ||
449 | if (network->associated_ttys[channel_idx][i] == NULL) { | ||
450 | network->associated_ttys[channel_idx][i] = tty; | ||
451 | break; | ||
452 | } | ||
453 | } | ||
454 | |||
455 | void ipwireless_disassociate_network_ttys(struct ipw_network *network, | ||
456 | unsigned int channel_idx) | ||
457 | { | ||
458 | int i; | ||
459 | |||
460 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) | ||
461 | network->associated_ttys[channel_idx][i] = NULL; | ||
462 | } | ||
463 | |||
464 | void ipwireless_ppp_open(struct ipw_network *network) | ||
465 | { | ||
466 | if (ipwireless_debug) | ||
467 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n"); | ||
468 | schedule_work(&network->work_go_online); | ||
469 | } | ||
470 | |||
471 | void ipwireless_ppp_close(struct ipw_network *network) | ||
472 | { | ||
473 | /* Disconnect from the wireless network. */ | ||
474 | if (ipwireless_debug) | ||
475 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n"); | ||
476 | schedule_work(&network->work_go_offline); | ||
477 | } | ||
478 | |||
479 | int ipwireless_ppp_channel_index(struct ipw_network *network) | ||
480 | { | ||
481 | int ret = -1; | ||
482 | unsigned long flags; | ||
483 | |||
484 | spin_lock_irqsave(&network->lock, flags); | ||
485 | if (network->ppp_channel != NULL) | ||
486 | ret = ppp_channel_index(network->ppp_channel); | ||
487 | spin_unlock_irqrestore(&network->lock, flags); | ||
488 | |||
489 | return ret; | ||
490 | } | ||
491 | |||
492 | int ipwireless_ppp_unit_number(struct ipw_network *network) | ||
493 | { | ||
494 | int ret = -1; | ||
495 | unsigned long flags; | ||
496 | |||
497 | spin_lock_irqsave(&network->lock, flags); | ||
498 | if (network->ppp_channel != NULL) | ||
499 | ret = ppp_unit_number(network->ppp_channel); | ||
500 | spin_unlock_irqrestore(&network->lock, flags); | ||
501 | |||
502 | return ret; | ||
503 | } | ||
504 | |||
505 | int ipwireless_ppp_mru(const struct ipw_network *network) | ||
506 | { | ||
507 | return network->mru; | ||
508 | } | ||