diff options
author | David Sterba <dsterba@suse.cz> | 2008-02-07 04:57:12 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-07 20:19:49 -0500 |
commit | 099dc4fb62653f6019d78db55fba7a18ef02d65b (patch) | |
tree | ce488fb0777f722eb83e3b0fb2b5cde0a4a8fc7d /drivers/char/pcmcia/ipwireless/network.c | |
parent | 151db1fc23800875c7ac353b106b7dab77061275 (diff) |
ipwireless: driver for PC Card 3G/UMTS modem
The device is manufactured by IPWireless. In some countries (for
example Czech Republic, T-Mobile ISP) this card is shipped for service
called UMTS 4G.
It's a piece of PCMCIA "4G" UMTS PPP networking hardware that presents
itself as a serial character device (i.e. looks like usual modem to
userspace, accepts AT commands, etc).
Rewieved-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Ben Martel <benm@symmetric.co.nz>
Signed-off-by: Stephen Blackheath <stephen@symmetric.co.nz>
Signed-off-by: David Sterba <dsterba@suse.cz>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/char/pcmcia/ipwireless/network.c')
-rw-r--r-- | drivers/char/pcmcia/ipwireless/network.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/drivers/char/pcmcia/ipwireless/network.c b/drivers/char/pcmcia/ipwireless/network.c new file mode 100644 index 000000000000..ff35230058d3 --- /dev/null +++ b/drivers/char/pcmcia/ipwireless/network.c | |||
@@ -0,0 +1,512 @@ | |||
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/if_ppp.h> | ||
25 | #include <linux/skbuff.h> | ||
26 | |||
27 | #include "network.h" | ||
28 | #include "hardware.h" | ||
29 | #include "main.h" | ||
30 | #include "tty.h" | ||
31 | |||
32 | #define MAX_OUTGOING_PACKETS_QUEUED ipwireless_out_queue | ||
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 spinlock; | ||
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 | |||
67 | #ifdef IPWIRELESS_STATE_DEBUG | ||
68 | int ipwireless_dump_network_state(char *p, size_t limit, | ||
69 | struct ipw_network *network) | ||
70 | { | ||
71 | return snprintf(p, limit, | ||
72 | "debug: ppp_blocked=%d\n" | ||
73 | "debug: outgoing_packets_queued=%d\n" | ||
74 | "debug: network.shutting_down=%d\n", | ||
75 | network->ppp_blocked, | ||
76 | network->outgoing_packets_queued, | ||
77 | network->shutting_down); | ||
78 | } | ||
79 | #endif | ||
80 | |||
81 | static void notify_packet_sent(void *callback_data, unsigned int packet_length) | ||
82 | { | ||
83 | struct ipw_network *network = callback_data; | ||
84 | unsigned long flags; | ||
85 | |||
86 | spin_lock_irqsave(&network->spinlock, flags); | ||
87 | network->outgoing_packets_queued--; | ||
88 | if (network->ppp_channel != NULL) { | ||
89 | if (network->ppp_blocked) { | ||
90 | network->ppp_blocked = 0; | ||
91 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
92 | ppp_output_wakeup(network->ppp_channel); | ||
93 | if (ipwireless_debug) | ||
94 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME | ||
95 | ": ppp unblocked\n"); | ||
96 | } else | ||
97 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
98 | } else | ||
99 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
100 | } | ||
101 | |||
102 | /* | ||
103 | * Called by the ppp system when it has a packet to send to the hardware. | ||
104 | */ | ||
105 | static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel, | ||
106 | struct sk_buff *skb) | ||
107 | { | ||
108 | struct ipw_network *network = ppp_channel->private; | ||
109 | unsigned long flags; | ||
110 | |||
111 | spin_lock_irqsave(&network->spinlock, flags); | ||
112 | if (network->outgoing_packets_queued < MAX_OUTGOING_PACKETS_QUEUED) { | ||
113 | unsigned char *buf; | ||
114 | static unsigned char header[] = { | ||
115 | PPP_ALLSTATIONS, /* 0xff */ | ||
116 | PPP_UI, /* 0x03 */ | ||
117 | }; | ||
118 | int ret; | ||
119 | |||
120 | network->outgoing_packets_queued++; | ||
121 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
122 | |||
123 | /* | ||
124 | * If we have the requested amount of headroom in the skb we | ||
125 | * were handed, then we can add the header efficiently. | ||
126 | */ | ||
127 | if (skb_headroom(skb) >= 2) { | ||
128 | memcpy(skb_push(skb, 2), header, 2); | ||
129 | ret = ipwireless_send_packet(network->hardware, | ||
130 | IPW_CHANNEL_RAS, skb->data, | ||
131 | skb->len, | ||
132 | notify_packet_sent, | ||
133 | network); | ||
134 | if (ret == -1) { | ||
135 | skb_pull(skb, 2); | ||
136 | return 0; | ||
137 | } | ||
138 | } else { | ||
139 | /* Otherwise (rarely) we do it inefficiently. */ | ||
140 | buf = kmalloc(skb->len + 2, GFP_ATOMIC); | ||
141 | if (!buf) | ||
142 | return 0; | ||
143 | memcpy(buf + 2, skb->data, skb->len); | ||
144 | memcpy(buf, header, 2); | ||
145 | ret = ipwireless_send_packet(network->hardware, | ||
146 | IPW_CHANNEL_RAS, buf, | ||
147 | skb->len + 2, | ||
148 | notify_packet_sent, | ||
149 | network); | ||
150 | kfree(buf); | ||
151 | if (ret == -1) | ||
152 | return 0; | ||
153 | } | ||
154 | kfree_skb(skb); | ||
155 | return 1; | ||
156 | } else { | ||
157 | /* | ||
158 | * Otherwise reject the packet, and flag that the ppp system | ||
159 | * needs to be unblocked once we are ready to send. | ||
160 | */ | ||
161 | network->ppp_blocked = 1; | ||
162 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
163 | return 0; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */ | ||
168 | static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel, | ||
169 | unsigned int cmd, unsigned long arg) | ||
170 | { | ||
171 | struct ipw_network *network = ppp_channel->private; | ||
172 | int err, val; | ||
173 | u32 accm[8]; | ||
174 | int __user *user_arg = (int __user *) arg; | ||
175 | |||
176 | err = -EFAULT; | ||
177 | switch (cmd) { | ||
178 | case PPPIOCGFLAGS: | ||
179 | val = network->flags | network->rbits; | ||
180 | if (put_user(val, user_arg)) | ||
181 | break; | ||
182 | err = 0; | ||
183 | break; | ||
184 | |||
185 | case PPPIOCSFLAGS: | ||
186 | if (get_user(val, user_arg)) | ||
187 | break; | ||
188 | network->flags = val & ~SC_RCV_BITS; | ||
189 | network->rbits = val & SC_RCV_BITS; | ||
190 | err = 0; | ||
191 | break; | ||
192 | |||
193 | case PPPIOCGASYNCMAP: | ||
194 | if (put_user(network->xaccm[0], user_arg)) | ||
195 | break; | ||
196 | err = 0; | ||
197 | break; | ||
198 | |||
199 | case PPPIOCSASYNCMAP: | ||
200 | if (get_user(network->xaccm[0], user_arg)) | ||
201 | break; | ||
202 | err = 0; | ||
203 | break; | ||
204 | |||
205 | case PPPIOCGRASYNCMAP: | ||
206 | if (put_user(network->raccm, user_arg)) | ||
207 | break; | ||
208 | err = 0; | ||
209 | break; | ||
210 | |||
211 | case PPPIOCSRASYNCMAP: | ||
212 | if (get_user(network->raccm, user_arg)) | ||
213 | break; | ||
214 | err = 0; | ||
215 | break; | ||
216 | |||
217 | case PPPIOCGXASYNCMAP: | ||
218 | if (copy_to_user((void __user *) arg, network->xaccm, | ||
219 | sizeof(network->xaccm))) | ||
220 | break; | ||
221 | err = 0; | ||
222 | break; | ||
223 | |||
224 | case PPPIOCSXASYNCMAP: | ||
225 | if (copy_from_user(accm, (void __user *) arg, sizeof(accm))) | ||
226 | break; | ||
227 | accm[2] &= ~0x40000000U; /* can't escape 0x5e */ | ||
228 | accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ | ||
229 | memcpy(network->xaccm, accm, sizeof(network->xaccm)); | ||
230 | err = 0; | ||
231 | break; | ||
232 | |||
233 | case PPPIOCGMRU: | ||
234 | if (put_user(network->mru, user_arg)) | ||
235 | break; | ||
236 | err = 0; | ||
237 | break; | ||
238 | |||
239 | case PPPIOCSMRU: | ||
240 | if (get_user(val, user_arg)) | ||
241 | break; | ||
242 | if (val < PPP_MRU) | ||
243 | val = PPP_MRU; | ||
244 | network->mru = val; | ||
245 | err = 0; | ||
246 | break; | ||
247 | |||
248 | default: | ||
249 | err = -ENOTTY; | ||
250 | } | ||
251 | |||
252 | return err; | ||
253 | } | ||
254 | |||
255 | static struct ppp_channel_ops ipwireless_ppp_channel_ops = { | ||
256 | .start_xmit = ipwireless_ppp_start_xmit, | ||
257 | .ioctl = ipwireless_ppp_ioctl | ||
258 | }; | ||
259 | |||
260 | static void do_go_online(struct work_struct *work_go_online) | ||
261 | { | ||
262 | struct ipw_network *network = | ||
263 | container_of(work_go_online, struct ipw_network, | ||
264 | work_go_online); | ||
265 | unsigned long flags; | ||
266 | |||
267 | spin_lock_irqsave(&network->spinlock, flags); | ||
268 | if (!network->ppp_channel) { | ||
269 | struct ppp_channel *channel; | ||
270 | |||
271 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
272 | channel = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL); | ||
273 | if (!channel) { | ||
274 | printk(KERN_ERR IPWIRELESS_PCCARD_NAME | ||
275 | ": unable to allocate PPP channel\n"); | ||
276 | return; | ||
277 | } | ||
278 | channel->private = network; | ||
279 | channel->mtu = 16384; /* Wild guess */ | ||
280 | channel->hdrlen = 2; | ||
281 | channel->ops = &ipwireless_ppp_channel_ops; | ||
282 | |||
283 | network->flags = 0; | ||
284 | network->rbits = 0; | ||
285 | network->mru = PPP_MRU; | ||
286 | memset(network->xaccm, 0, sizeof(network->xaccm)); | ||
287 | network->xaccm[0] = ~0U; | ||
288 | network->xaccm[3] = 0x60000000U; | ||
289 | network->raccm = ~0U; | ||
290 | ppp_register_channel(channel); | ||
291 | spin_lock_irqsave(&network->spinlock, flags); | ||
292 | network->ppp_channel = channel; | ||
293 | } | ||
294 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
295 | } | ||
296 | |||
297 | static void do_go_offline(struct work_struct *work_go_offline) | ||
298 | { | ||
299 | struct ipw_network *network = | ||
300 | container_of(work_go_offline, struct ipw_network, | ||
301 | work_go_offline); | ||
302 | unsigned long flags; | ||
303 | |||
304 | mutex_lock(&network->close_lock); | ||
305 | spin_lock_irqsave(&network->spinlock, flags); | ||
306 | if (network->ppp_channel != NULL) { | ||
307 | struct ppp_channel *channel = network->ppp_channel; | ||
308 | |||
309 | network->ppp_channel = NULL; | ||
310 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
311 | mutex_unlock(&network->close_lock); | ||
312 | ppp_unregister_channel(channel); | ||
313 | } else { | ||
314 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
315 | mutex_unlock(&network->close_lock); | ||
316 | } | ||
317 | } | ||
318 | |||
319 | void ipwireless_network_notify_control_line_change(struct ipw_network *network, | ||
320 | unsigned int channel_idx, | ||
321 | unsigned int control_lines, | ||
322 | unsigned int changed_mask) | ||
323 | { | ||
324 | int i; | ||
325 | |||
326 | if (channel_idx == IPW_CHANNEL_RAS) | ||
327 | network->ras_control_lines = control_lines; | ||
328 | |||
329 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { | ||
330 | struct ipw_tty *tty = | ||
331 | network->associated_ttys[channel_idx][i]; | ||
332 | |||
333 | /* | ||
334 | * If it's associated with a tty (other than the RAS channel | ||
335 | * when we're online), then send the data to that tty. The RAS | ||
336 | * channel's data is handled above - it always goes through | ||
337 | * ppp_generic. | ||
338 | */ | ||
339 | if (tty) | ||
340 | ipwireless_tty_notify_control_line_change(tty, | ||
341 | channel_idx, | ||
342 | control_lines, | ||
343 | changed_mask); | ||
344 | } | ||
345 | } | ||
346 | |||
347 | /* | ||
348 | * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI) | ||
349 | * bytes, which are required on sent packet, but not always present on received | ||
350 | * packets | ||
351 | */ | ||
352 | static struct sk_buff *ipw_packet_received_skb(unsigned char *data, | ||
353 | unsigned int length) | ||
354 | { | ||
355 | struct sk_buff *skb; | ||
356 | |||
357 | if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) { | ||
358 | length -= 2; | ||
359 | data += 2; | ||
360 | } | ||
361 | |||
362 | skb = dev_alloc_skb(length + 4); | ||
363 | skb_reserve(skb, 2); | ||
364 | memcpy(skb_put(skb, length), data, length); | ||
365 | |||
366 | return skb; | ||
367 | } | ||
368 | |||
369 | void ipwireless_network_packet_received(struct ipw_network *network, | ||
370 | unsigned int channel_idx, | ||
371 | unsigned char *data, | ||
372 | unsigned int length) | ||
373 | { | ||
374 | int i; | ||
375 | unsigned long flags; | ||
376 | |||
377 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { | ||
378 | struct ipw_tty *tty = network->associated_ttys[channel_idx][i]; | ||
379 | |||
380 | /* | ||
381 | * If it's associated with a tty (other than the RAS channel | ||
382 | * when we're online), then send the data to that tty. The RAS | ||
383 | * channel's data is handled above - it always goes through | ||
384 | * ppp_generic. | ||
385 | */ | ||
386 | if (tty && channel_idx == IPW_CHANNEL_RAS | ||
387 | && (network->ras_control_lines & | ||
388 | IPW_CONTROL_LINE_DCD) != 0 | ||
389 | && ipwireless_tty_is_modem(tty)) { | ||
390 | /* | ||
391 | * If data came in on the RAS channel and this tty is | ||
392 | * the modem tty, and we are online, then we send it to | ||
393 | * the PPP layer. | ||
394 | */ | ||
395 | mutex_lock(&network->close_lock); | ||
396 | spin_lock_irqsave(&network->spinlock, flags); | ||
397 | if (network->ppp_channel != NULL) { | ||
398 | struct sk_buff *skb; | ||
399 | |||
400 | spin_unlock_irqrestore(&network->spinlock, | ||
401 | flags); | ||
402 | |||
403 | /* Send the data to the ppp_generic module. */ | ||
404 | skb = ipw_packet_received_skb(data, length); | ||
405 | ppp_input(network->ppp_channel, skb); | ||
406 | } else | ||
407 | spin_unlock_irqrestore(&network->spinlock, | ||
408 | flags); | ||
409 | mutex_unlock(&network->close_lock); | ||
410 | } | ||
411 | /* Otherwise we send it out the tty. */ | ||
412 | else | ||
413 | ipwireless_tty_received(tty, data, length); | ||
414 | } | ||
415 | } | ||
416 | |||
417 | struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw) | ||
418 | { | ||
419 | struct ipw_network *network = | ||
420 | kzalloc(sizeof(struct ipw_network), GFP_ATOMIC); | ||
421 | |||
422 | if (!network) | ||
423 | return NULL; | ||
424 | |||
425 | spin_lock_init(&network->spinlock); | ||
426 | mutex_init(&network->close_lock); | ||
427 | |||
428 | network->hardware = hw; | ||
429 | |||
430 | INIT_WORK(&network->work_go_online, do_go_online); | ||
431 | INIT_WORK(&network->work_go_offline, do_go_offline); | ||
432 | |||
433 | ipwireless_associate_network(hw, network); | ||
434 | |||
435 | return network; | ||
436 | } | ||
437 | |||
438 | void ipwireless_network_free(struct ipw_network *network) | ||
439 | { | ||
440 | network->shutting_down = 1; | ||
441 | |||
442 | ipwireless_ppp_close(network); | ||
443 | flush_scheduled_work(); | ||
444 | |||
445 | ipwireless_stop_interrupts(network->hardware); | ||
446 | ipwireless_associate_network(network->hardware, NULL); | ||
447 | |||
448 | kfree(network); | ||
449 | } | ||
450 | |||
451 | void ipwireless_associate_network_tty(struct ipw_network *network, | ||
452 | unsigned int channel_idx, | ||
453 | struct ipw_tty *tty) | ||
454 | { | ||
455 | int i; | ||
456 | |||
457 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) | ||
458 | if (network->associated_ttys[channel_idx][i] == NULL) { | ||
459 | network->associated_ttys[channel_idx][i] = tty; | ||
460 | break; | ||
461 | } | ||
462 | } | ||
463 | |||
464 | void ipwireless_disassociate_network_ttys(struct ipw_network *network, | ||
465 | unsigned int channel_idx) | ||
466 | { | ||
467 | int i; | ||
468 | |||
469 | for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) | ||
470 | network->associated_ttys[channel_idx][i] = NULL; | ||
471 | } | ||
472 | |||
473 | void ipwireless_ppp_open(struct ipw_network *network) | ||
474 | { | ||
475 | if (ipwireless_debug) | ||
476 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n"); | ||
477 | schedule_work(&network->work_go_online); | ||
478 | } | ||
479 | |||
480 | void ipwireless_ppp_close(struct ipw_network *network) | ||
481 | { | ||
482 | /* Disconnect from the wireless network. */ | ||
483 | if (ipwireless_debug) | ||
484 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n"); | ||
485 | schedule_work(&network->work_go_offline); | ||
486 | } | ||
487 | |||
488 | int ipwireless_ppp_channel_index(struct ipw_network *network) | ||
489 | { | ||
490 | int ret = -1; | ||
491 | unsigned long flags; | ||
492 | |||
493 | spin_lock_irqsave(&network->spinlock, flags); | ||
494 | if (network->ppp_channel != NULL) | ||
495 | ret = ppp_channel_index(network->ppp_channel); | ||
496 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
497 | |||
498 | return ret; | ||
499 | } | ||
500 | |||
501 | int ipwireless_ppp_unit_number(struct ipw_network *network) | ||
502 | { | ||
503 | int ret = -1; | ||
504 | unsigned long flags; | ||
505 | |||
506 | spin_lock_irqsave(&network->spinlock, flags); | ||
507 | if (network->ppp_channel != NULL) | ||
508 | ret = ppp_unit_number(network->ppp_channel); | ||
509 | spin_unlock_irqrestore(&network->spinlock, flags); | ||
510 | |||
511 | return ret; | ||
512 | } | ||