diff options
Diffstat (limited to 'net/x25/x25_link.c')
-rw-r--r-- | net/x25/x25_link.c | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/net/x25/x25_link.c b/net/x25/x25_link.c new file mode 100644 index 000000000000..0a760fe66843 --- /dev/null +++ b/net/x25/x25_link.c | |||
@@ -0,0 +1,401 @@ | |||
1 | /* | ||
2 | * X.25 Packet Layer release 002 | ||
3 | * | ||
4 | * This is ALPHA test software. This code may break your machine, | ||
5 | * randomly fail to work with new releases, misbehave and/or generally | ||
6 | * screw up. It might even work. | ||
7 | * | ||
8 | * This code REQUIRES 2.1.15 or higher | ||
9 | * | ||
10 | * This module: | ||
11 | * This module is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * as published by the Free Software Foundation; either version | ||
14 | * 2 of the License, or (at your option) any later version. | ||
15 | * | ||
16 | * History | ||
17 | * X.25 001 Jonathan Naylor Started coding. | ||
18 | * X.25 002 Jonathan Naylor New timer architecture. | ||
19 | * mar/20/00 Daniela Squassoni Disabling/enabling of facilities | ||
20 | * negotiation. | ||
21 | * 2000-09-04 Henner Eisen dev_hold() / dev_put() for x25_neigh. | ||
22 | */ | ||
23 | |||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/jiffies.h> | ||
26 | #include <linux/timer.h> | ||
27 | #include <linux/netdevice.h> | ||
28 | #include <linux/skbuff.h> | ||
29 | #include <asm/uaccess.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <net/x25.h> | ||
32 | |||
33 | static struct list_head x25_neigh_list = LIST_HEAD_INIT(x25_neigh_list); | ||
34 | static DEFINE_RWLOCK(x25_neigh_list_lock); | ||
35 | |||
36 | static void x25_t20timer_expiry(unsigned long); | ||
37 | |||
38 | static void x25_transmit_restart_confirmation(struct x25_neigh *nb); | ||
39 | static void x25_transmit_restart_request(struct x25_neigh *nb); | ||
40 | |||
41 | /* | ||
42 | * Linux set/reset timer routines | ||
43 | */ | ||
44 | static inline void x25_start_t20timer(struct x25_neigh *nb) | ||
45 | { | ||
46 | mod_timer(&nb->t20timer, jiffies + nb->t20); | ||
47 | } | ||
48 | |||
49 | static void x25_t20timer_expiry(unsigned long param) | ||
50 | { | ||
51 | struct x25_neigh *nb = (struct x25_neigh *)param; | ||
52 | |||
53 | x25_transmit_restart_request(nb); | ||
54 | |||
55 | x25_start_t20timer(nb); | ||
56 | } | ||
57 | |||
58 | static inline void x25_stop_t20timer(struct x25_neigh *nb) | ||
59 | { | ||
60 | del_timer(&nb->t20timer); | ||
61 | } | ||
62 | |||
63 | static inline int x25_t20timer_pending(struct x25_neigh *nb) | ||
64 | { | ||
65 | return timer_pending(&nb->t20timer); | ||
66 | } | ||
67 | |||
68 | /* | ||
69 | * This handles all restart and diagnostic frames. | ||
70 | */ | ||
71 | void x25_link_control(struct sk_buff *skb, struct x25_neigh *nb, | ||
72 | unsigned short frametype) | ||
73 | { | ||
74 | struct sk_buff *skbn; | ||
75 | int confirm; | ||
76 | |||
77 | switch (frametype) { | ||
78 | case X25_RESTART_REQUEST: | ||
79 | confirm = !x25_t20timer_pending(nb); | ||
80 | x25_stop_t20timer(nb); | ||
81 | nb->state = X25_LINK_STATE_3; | ||
82 | if (confirm) | ||
83 | x25_transmit_restart_confirmation(nb); | ||
84 | break; | ||
85 | |||
86 | case X25_RESTART_CONFIRMATION: | ||
87 | x25_stop_t20timer(nb); | ||
88 | nb->state = X25_LINK_STATE_3; | ||
89 | break; | ||
90 | |||
91 | case X25_DIAGNOSTIC: | ||
92 | printk(KERN_WARNING "x25: diagnostic #%d - " | ||
93 | "%02X %02X %02X\n", | ||
94 | skb->data[3], skb->data[4], | ||
95 | skb->data[5], skb->data[6]); | ||
96 | break; | ||
97 | |||
98 | default: | ||
99 | printk(KERN_WARNING "x25: received unknown %02X " | ||
100 | "with LCI 000\n", frametype); | ||
101 | break; | ||
102 | } | ||
103 | |||
104 | if (nb->state == X25_LINK_STATE_3) | ||
105 | while ((skbn = skb_dequeue(&nb->queue)) != NULL) | ||
106 | x25_send_frame(skbn, nb); | ||
107 | } | ||
108 | |||
109 | /* | ||
110 | * This routine is called when a Restart Request is needed | ||
111 | */ | ||
112 | static void x25_transmit_restart_request(struct x25_neigh *nb) | ||
113 | { | ||
114 | unsigned char *dptr; | ||
115 | int len = X25_MAX_L2_LEN + X25_STD_MIN_LEN + 2; | ||
116 | struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); | ||
117 | |||
118 | if (!skb) | ||
119 | return; | ||
120 | |||
121 | skb_reserve(skb, X25_MAX_L2_LEN); | ||
122 | |||
123 | dptr = skb_put(skb, X25_STD_MIN_LEN + 2); | ||
124 | |||
125 | *dptr++ = nb->extended ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ; | ||
126 | *dptr++ = 0x00; | ||
127 | *dptr++ = X25_RESTART_REQUEST; | ||
128 | *dptr++ = 0x00; | ||
129 | *dptr++ = 0; | ||
130 | |||
131 | skb->sk = NULL; | ||
132 | |||
133 | x25_send_frame(skb, nb); | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * This routine is called when a Restart Confirmation is needed | ||
138 | */ | ||
139 | static void x25_transmit_restart_confirmation(struct x25_neigh *nb) | ||
140 | { | ||
141 | unsigned char *dptr; | ||
142 | int len = X25_MAX_L2_LEN + X25_STD_MIN_LEN; | ||
143 | struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); | ||
144 | |||
145 | if (!skb) | ||
146 | return; | ||
147 | |||
148 | skb_reserve(skb, X25_MAX_L2_LEN); | ||
149 | |||
150 | dptr = skb_put(skb, X25_STD_MIN_LEN); | ||
151 | |||
152 | *dptr++ = nb->extended ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ; | ||
153 | *dptr++ = 0x00; | ||
154 | *dptr++ = X25_RESTART_CONFIRMATION; | ||
155 | |||
156 | skb->sk = NULL; | ||
157 | |||
158 | x25_send_frame(skb, nb); | ||
159 | } | ||
160 | |||
161 | /* | ||
162 | * This routine is called when a Clear Request is needed outside of the context | ||
163 | * of a connected socket. | ||
164 | */ | ||
165 | void x25_transmit_clear_request(struct x25_neigh *nb, unsigned int lci, | ||
166 | unsigned char cause) | ||
167 | { | ||
168 | unsigned char *dptr; | ||
169 | int len = X25_MAX_L2_LEN + X25_STD_MIN_LEN + 2; | ||
170 | struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); | ||
171 | |||
172 | if (!skb) | ||
173 | return; | ||
174 | |||
175 | skb_reserve(skb, X25_MAX_L2_LEN); | ||
176 | |||
177 | dptr = skb_put(skb, X25_STD_MIN_LEN + 2); | ||
178 | |||
179 | *dptr++ = ((lci >> 8) & 0x0F) | (nb->extended ? | ||
180 | X25_GFI_EXTSEQ : | ||
181 | X25_GFI_STDSEQ); | ||
182 | *dptr++ = (lci >> 0) & 0xFF; | ||
183 | *dptr++ = X25_CLEAR_REQUEST; | ||
184 | *dptr++ = cause; | ||
185 | *dptr++ = 0x00; | ||
186 | |||
187 | skb->sk = NULL; | ||
188 | |||
189 | x25_send_frame(skb, nb); | ||
190 | } | ||
191 | |||
192 | void x25_transmit_link(struct sk_buff *skb, struct x25_neigh *nb) | ||
193 | { | ||
194 | switch (nb->state) { | ||
195 | case X25_LINK_STATE_0: | ||
196 | skb_queue_tail(&nb->queue, skb); | ||
197 | nb->state = X25_LINK_STATE_1; | ||
198 | x25_establish_link(nb); | ||
199 | break; | ||
200 | case X25_LINK_STATE_1: | ||
201 | case X25_LINK_STATE_2: | ||
202 | skb_queue_tail(&nb->queue, skb); | ||
203 | break; | ||
204 | case X25_LINK_STATE_3: | ||
205 | x25_send_frame(skb, nb); | ||
206 | break; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /* | ||
211 | * Called when the link layer has become established. | ||
212 | */ | ||
213 | void x25_link_established(struct x25_neigh *nb) | ||
214 | { | ||
215 | switch (nb->state) { | ||
216 | case X25_LINK_STATE_0: | ||
217 | nb->state = X25_LINK_STATE_2; | ||
218 | break; | ||
219 | case X25_LINK_STATE_1: | ||
220 | x25_transmit_restart_request(nb); | ||
221 | nb->state = X25_LINK_STATE_2; | ||
222 | x25_start_t20timer(nb); | ||
223 | break; | ||
224 | } | ||
225 | } | ||
226 | |||
227 | /* | ||
228 | * Called when the link layer has terminated, or an establishment | ||
229 | * request has failed. | ||
230 | */ | ||
231 | |||
232 | void x25_link_terminated(struct x25_neigh *nb) | ||
233 | { | ||
234 | nb->state = X25_LINK_STATE_0; | ||
235 | /* Out of order: clear existing virtual calls (X.25 03/93 4.6.3) */ | ||
236 | x25_kill_by_neigh(nb); | ||
237 | } | ||
238 | |||
239 | /* | ||
240 | * Add a new device. | ||
241 | */ | ||
242 | void x25_link_device_up(struct net_device *dev) | ||
243 | { | ||
244 | struct x25_neigh *nb = kmalloc(sizeof(*nb), GFP_ATOMIC); | ||
245 | |||
246 | if (!nb) | ||
247 | return; | ||
248 | |||
249 | skb_queue_head_init(&nb->queue); | ||
250 | |||
251 | init_timer(&nb->t20timer); | ||
252 | nb->t20timer.data = (unsigned long)nb; | ||
253 | nb->t20timer.function = &x25_t20timer_expiry; | ||
254 | |||
255 | dev_hold(dev); | ||
256 | nb->dev = dev; | ||
257 | nb->state = X25_LINK_STATE_0; | ||
258 | nb->extended = 0; | ||
259 | /* | ||
260 | * Enables negotiation | ||
261 | */ | ||
262 | nb->global_facil_mask = X25_MASK_REVERSE | | ||
263 | X25_MASK_THROUGHPUT | | ||
264 | X25_MASK_PACKET_SIZE | | ||
265 | X25_MASK_WINDOW_SIZE; | ||
266 | nb->t20 = sysctl_x25_restart_request_timeout; | ||
267 | atomic_set(&nb->refcnt, 1); | ||
268 | |||
269 | write_lock_bh(&x25_neigh_list_lock); | ||
270 | list_add(&nb->node, &x25_neigh_list); | ||
271 | write_unlock_bh(&x25_neigh_list_lock); | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * __x25_remove_neigh - remove neighbour from x25_neigh_list | ||
276 | * @nb - neigh to remove | ||
277 | * | ||
278 | * Remove neighbour from x25_neigh_list. If it was there. | ||
279 | * Caller must hold x25_neigh_list_lock. | ||
280 | */ | ||
281 | static void __x25_remove_neigh(struct x25_neigh *nb) | ||
282 | { | ||
283 | skb_queue_purge(&nb->queue); | ||
284 | x25_stop_t20timer(nb); | ||
285 | |||
286 | if (nb->node.next) { | ||
287 | list_del(&nb->node); | ||
288 | x25_neigh_put(nb); | ||
289 | } | ||
290 | } | ||
291 | |||
292 | /* | ||
293 | * A device has been removed, remove its links. | ||
294 | */ | ||
295 | void x25_link_device_down(struct net_device *dev) | ||
296 | { | ||
297 | struct x25_neigh *nb; | ||
298 | struct list_head *entry, *tmp; | ||
299 | |||
300 | write_lock_bh(&x25_neigh_list_lock); | ||
301 | |||
302 | list_for_each_safe(entry, tmp, &x25_neigh_list) { | ||
303 | nb = list_entry(entry, struct x25_neigh, node); | ||
304 | |||
305 | if (nb->dev == dev) { | ||
306 | __x25_remove_neigh(nb); | ||
307 | dev_put(dev); | ||
308 | } | ||
309 | } | ||
310 | |||
311 | write_unlock_bh(&x25_neigh_list_lock); | ||
312 | } | ||
313 | |||
314 | /* | ||
315 | * Given a device, return the neighbour address. | ||
316 | */ | ||
317 | struct x25_neigh *x25_get_neigh(struct net_device *dev) | ||
318 | { | ||
319 | struct x25_neigh *nb, *use = NULL; | ||
320 | struct list_head *entry; | ||
321 | |||
322 | read_lock_bh(&x25_neigh_list_lock); | ||
323 | list_for_each(entry, &x25_neigh_list) { | ||
324 | nb = list_entry(entry, struct x25_neigh, node); | ||
325 | |||
326 | if (nb->dev == dev) { | ||
327 | use = nb; | ||
328 | break; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | if (use) | ||
333 | x25_neigh_hold(use); | ||
334 | read_unlock_bh(&x25_neigh_list_lock); | ||
335 | return use; | ||
336 | } | ||
337 | |||
338 | /* | ||
339 | * Handle the ioctls that control the subscription functions. | ||
340 | */ | ||
341 | int x25_subscr_ioctl(unsigned int cmd, void __user *arg) | ||
342 | { | ||
343 | struct x25_subscrip_struct x25_subscr; | ||
344 | struct x25_neigh *nb; | ||
345 | struct net_device *dev; | ||
346 | int rc = -EINVAL; | ||
347 | |||
348 | if (cmd != SIOCX25GSUBSCRIP && cmd != SIOCX25SSUBSCRIP) | ||
349 | goto out; | ||
350 | |||
351 | rc = -EFAULT; | ||
352 | if (copy_from_user(&x25_subscr, arg, sizeof(x25_subscr))) | ||
353 | goto out; | ||
354 | |||
355 | rc = -EINVAL; | ||
356 | if ((dev = x25_dev_get(x25_subscr.device)) == NULL) | ||
357 | goto out; | ||
358 | |||
359 | if ((nb = x25_get_neigh(dev)) == NULL) | ||
360 | goto out_dev_put; | ||
361 | |||
362 | dev_put(dev); | ||
363 | |||
364 | if (cmd == SIOCX25GSUBSCRIP) { | ||
365 | x25_subscr.extended = nb->extended; | ||
366 | x25_subscr.global_facil_mask = nb->global_facil_mask; | ||
367 | rc = copy_to_user(arg, &x25_subscr, | ||
368 | sizeof(x25_subscr)) ? -EFAULT : 0; | ||
369 | } else { | ||
370 | rc = -EINVAL; | ||
371 | if (!(x25_subscr.extended && x25_subscr.extended != 1)) { | ||
372 | rc = 0; | ||
373 | nb->extended = x25_subscr.extended; | ||
374 | nb->global_facil_mask = x25_subscr.global_facil_mask; | ||
375 | } | ||
376 | } | ||
377 | x25_neigh_put(nb); | ||
378 | out: | ||
379 | return rc; | ||
380 | out_dev_put: | ||
381 | dev_put(dev); | ||
382 | goto out; | ||
383 | } | ||
384 | |||
385 | |||
386 | /* | ||
387 | * Release all memory associated with X.25 neighbour structures. | ||
388 | */ | ||
389 | void __exit x25_link_free(void) | ||
390 | { | ||
391 | struct x25_neigh *nb; | ||
392 | struct list_head *entry, *tmp; | ||
393 | |||
394 | write_lock_bh(&x25_neigh_list_lock); | ||
395 | |||
396 | list_for_each_safe(entry, tmp, &x25_neigh_list) { | ||
397 | nb = list_entry(entry, struct x25_neigh, node); | ||
398 | __x25_remove_neigh(nb); | ||
399 | } | ||
400 | write_unlock_bh(&x25_neigh_list_lock); | ||
401 | } | ||