diff options
author | Rémi Denis-Courmont <remi.denis-courmont@nokia.com> | 2008-10-05 14:16:16 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-10-05 14:16:16 -0400 |
commit | 02a47617cdce440f60c71a51f3a93f9f5fcc5a7a (patch) | |
tree | 2f65d9978345b8eafdaf926a3342424a21c6e57a /net/phonet/pep-gprs.c | |
parent | c41bd97f815720f9404f97da0c4f4400b52c243d (diff) |
Phonet: implement GPRS virtual interface over PEP socket
Signed-off-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/phonet/pep-gprs.c')
-rw-r--r-- | net/phonet/pep-gprs.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/net/phonet/pep-gprs.c b/net/phonet/pep-gprs.c new file mode 100644 index 000000000000..9978afbd9f2a --- /dev/null +++ b/net/phonet/pep-gprs.c | |||
@@ -0,0 +1,347 @@ | |||
1 | /* | ||
2 | * File: pep-gprs.c | ||
3 | * | ||
4 | * GPRS over Phonet pipe end point socket | ||
5 | * | ||
6 | * Copyright (C) 2008 Nokia Corporation. | ||
7 | * | ||
8 | * Author: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or | ||
11 | * modify it under the terms of the GNU General Public License | ||
12 | * version 2 as published by the Free Software Foundation. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, but | ||
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
17 | * General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
22 | * 02110-1301 USA | ||
23 | */ | ||
24 | |||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/netdevice.h> | ||
27 | #include <linux/if_ether.h> | ||
28 | #include <linux/if_arp.h> | ||
29 | #include <net/sock.h> | ||
30 | |||
31 | #include <linux/if_phonet.h> | ||
32 | #include <net/tcp_states.h> | ||
33 | #include <net/phonet/gprs.h> | ||
34 | |||
35 | #define GPRS_DEFAULT_MTU 1400 | ||
36 | |||
37 | struct gprs_dev { | ||
38 | struct sock *sk; | ||
39 | void (*old_state_change)(struct sock *); | ||
40 | void (*old_data_ready)(struct sock *, int); | ||
41 | void (*old_write_space)(struct sock *); | ||
42 | |||
43 | struct net_device *net; | ||
44 | struct net_device_stats stats; | ||
45 | |||
46 | struct sk_buff_head tx_queue; | ||
47 | struct work_struct tx_work; | ||
48 | spinlock_t tx_lock; | ||
49 | unsigned tx_max; | ||
50 | }; | ||
51 | |||
52 | static int gprs_type_trans(struct sk_buff *skb) | ||
53 | { | ||
54 | const u8 *pvfc; | ||
55 | u8 buf; | ||
56 | |||
57 | pvfc = skb_header_pointer(skb, 0, 1, &buf); | ||
58 | if (!pvfc) | ||
59 | return 0; | ||
60 | /* Look at IP version field */ | ||
61 | switch (*pvfc >> 4) { | ||
62 | case 4: | ||
63 | return htons(ETH_P_IP); | ||
64 | case 6: | ||
65 | return htons(ETH_P_IPV6); | ||
66 | } | ||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | /* | ||
71 | * Socket callbacks | ||
72 | */ | ||
73 | |||
74 | static void gprs_state_change(struct sock *sk) | ||
75 | { | ||
76 | struct gprs_dev *dev = sk->sk_user_data; | ||
77 | |||
78 | if (sk->sk_state == TCP_CLOSE_WAIT) { | ||
79 | netif_stop_queue(dev->net); | ||
80 | netif_carrier_off(dev->net); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | static int gprs_recv(struct gprs_dev *dev, struct sk_buff *skb) | ||
85 | { | ||
86 | int err = 0; | ||
87 | u16 protocol = gprs_type_trans(skb); | ||
88 | |||
89 | if (!protocol) { | ||
90 | err = -EINVAL; | ||
91 | goto drop; | ||
92 | } | ||
93 | |||
94 | if (likely(skb_headroom(skb) & 3)) { | ||
95 | struct sk_buff *rskb, *fs; | ||
96 | int flen = 0; | ||
97 | |||
98 | /* Phonet Pipe data header is misaligned (3 bytes), | ||
99 | * so wrap the IP packet as a single fragment of an head-less | ||
100 | * socket buffer. The network stack will pull what it needs, | ||
101 | * but at least, the whole IP payload is not memcpy'd. */ | ||
102 | rskb = netdev_alloc_skb(dev->net, 0); | ||
103 | if (!rskb) { | ||
104 | err = -ENOBUFS; | ||
105 | goto drop; | ||
106 | } | ||
107 | skb_shinfo(rskb)->frag_list = skb; | ||
108 | rskb->len += skb->len; | ||
109 | rskb->data_len += rskb->len; | ||
110 | rskb->truesize += rskb->len; | ||
111 | |||
112 | /* Avoid nested fragments */ | ||
113 | for (fs = skb_shinfo(skb)->frag_list; fs; fs = fs->next) | ||
114 | flen += fs->len; | ||
115 | skb->next = skb_shinfo(skb)->frag_list; | ||
116 | skb_shinfo(skb)->frag_list = NULL; | ||
117 | skb->len -= flen; | ||
118 | skb->data_len -= flen; | ||
119 | skb->truesize -= flen; | ||
120 | |||
121 | skb = rskb; | ||
122 | } | ||
123 | |||
124 | skb->protocol = protocol; | ||
125 | skb_reset_mac_header(skb); | ||
126 | skb->dev = dev->net; | ||
127 | |||
128 | if (likely(dev->net->flags & IFF_UP)) { | ||
129 | dev->stats.rx_packets++; | ||
130 | dev->stats.rx_bytes += skb->len; | ||
131 | netif_rx(skb); | ||
132 | skb = NULL; | ||
133 | } else | ||
134 | err = -ENODEV; | ||
135 | |||
136 | drop: | ||
137 | if (skb) { | ||
138 | dev_kfree_skb(skb); | ||
139 | dev->stats.rx_dropped++; | ||
140 | } | ||
141 | return err; | ||
142 | } | ||
143 | |||
144 | static void gprs_data_ready(struct sock *sk, int len) | ||
145 | { | ||
146 | struct gprs_dev *dev = sk->sk_user_data; | ||
147 | struct sk_buff *skb; | ||
148 | |||
149 | while ((skb = pep_read(sk)) != NULL) { | ||
150 | skb_orphan(skb); | ||
151 | gprs_recv(dev, skb); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | static void gprs_write_space(struct sock *sk) | ||
156 | { | ||
157 | struct gprs_dev *dev = sk->sk_user_data; | ||
158 | unsigned credits = pep_writeable(sk); | ||
159 | |||
160 | spin_lock_bh(&dev->tx_lock); | ||
161 | dev->tx_max = credits; | ||
162 | if (credits > skb_queue_len(&dev->tx_queue)) | ||
163 | netif_wake_queue(dev->net); | ||
164 | spin_unlock_bh(&dev->tx_lock); | ||
165 | } | ||
166 | |||
167 | /* | ||
168 | * Network device callbacks | ||
169 | */ | ||
170 | |||
171 | static int gprs_xmit(struct sk_buff *skb, struct net_device *net) | ||
172 | { | ||
173 | struct gprs_dev *dev = netdev_priv(net); | ||
174 | |||
175 | switch (skb->protocol) { | ||
176 | case htons(ETH_P_IP): | ||
177 | case htons(ETH_P_IPV6): | ||
178 | break; | ||
179 | default: | ||
180 | dev_kfree_skb(skb); | ||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | spin_lock(&dev->tx_lock); | ||
185 | if (likely(skb_queue_len(&dev->tx_queue) < dev->tx_max)) { | ||
186 | skb_queue_tail(&dev->tx_queue, skb); | ||
187 | skb = NULL; | ||
188 | } | ||
189 | if (skb_queue_len(&dev->tx_queue) >= dev->tx_max) | ||
190 | netif_stop_queue(net); | ||
191 | spin_unlock(&dev->tx_lock); | ||
192 | |||
193 | schedule_work(&dev->tx_work); | ||
194 | if (unlikely(skb)) | ||
195 | dev_kfree_skb(skb); | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static void gprs_tx(struct work_struct *work) | ||
200 | { | ||
201 | struct gprs_dev *dev = container_of(work, struct gprs_dev, tx_work); | ||
202 | struct sock *sk = dev->sk; | ||
203 | struct sk_buff *skb; | ||
204 | |||
205 | while ((skb = skb_dequeue(&dev->tx_queue)) != NULL) { | ||
206 | int err; | ||
207 | |||
208 | dev->stats.tx_bytes += skb->len; | ||
209 | dev->stats.tx_packets++; | ||
210 | |||
211 | skb_orphan(skb); | ||
212 | skb_set_owner_w(skb, sk); | ||
213 | |||
214 | lock_sock(sk); | ||
215 | err = pep_write(sk, skb); | ||
216 | if (err) { | ||
217 | LIMIT_NETDEBUG(KERN_WARNING"%s: TX error (%d)\n", | ||
218 | dev->net->name, err); | ||
219 | dev->stats.tx_aborted_errors++; | ||
220 | dev->stats.tx_errors++; | ||
221 | } | ||
222 | release_sock(sk); | ||
223 | } | ||
224 | |||
225 | lock_sock(sk); | ||
226 | gprs_write_space(sk); | ||
227 | release_sock(sk); | ||
228 | } | ||
229 | |||
230 | static int gprs_set_mtu(struct net_device *net, int new_mtu) | ||
231 | { | ||
232 | if ((new_mtu < 576) || (new_mtu > (PHONET_MAX_MTU - 11))) | ||
233 | return -EINVAL; | ||
234 | |||
235 | net->mtu = new_mtu; | ||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static struct net_device_stats *gprs_get_stats(struct net_device *net) | ||
240 | { | ||
241 | struct gprs_dev *dev = netdev_priv(net); | ||
242 | |||
243 | return &dev->stats; | ||
244 | } | ||
245 | |||
246 | static void gprs_setup(struct net_device *net) | ||
247 | { | ||
248 | net->features = NETIF_F_FRAGLIST; | ||
249 | net->type = ARPHRD_NONE; | ||
250 | net->flags = IFF_POINTOPOINT | IFF_NOARP; | ||
251 | net->mtu = GPRS_DEFAULT_MTU; | ||
252 | net->hard_header_len = 0; | ||
253 | net->addr_len = 0; | ||
254 | net->tx_queue_len = 10; | ||
255 | |||
256 | net->destructor = free_netdev; | ||
257 | net->hard_start_xmit = gprs_xmit; /* mandatory */ | ||
258 | net->change_mtu = gprs_set_mtu; | ||
259 | net->get_stats = gprs_get_stats; | ||
260 | } | ||
261 | |||
262 | /* | ||
263 | * External interface | ||
264 | */ | ||
265 | |||
266 | /* | ||
267 | * Attach a GPRS interface to a datagram socket. | ||
268 | * Returns the interface index on success, negative error code on error. | ||
269 | */ | ||
270 | int gprs_attach(struct sock *sk) | ||
271 | { | ||
272 | static const char ifname[] = "gprs%d"; | ||
273 | struct gprs_dev *dev; | ||
274 | struct net_device *net; | ||
275 | int err; | ||
276 | |||
277 | if (unlikely(sk->sk_type == SOCK_STREAM)) | ||
278 | return -EINVAL; /* need packet boundaries */ | ||
279 | |||
280 | /* Create net device */ | ||
281 | net = alloc_netdev(sizeof(*dev), ifname, gprs_setup); | ||
282 | if (!net) | ||
283 | return -ENOMEM; | ||
284 | dev = netdev_priv(net); | ||
285 | dev->net = net; | ||
286 | dev->tx_max = 0; | ||
287 | spin_lock_init(&dev->tx_lock); | ||
288 | skb_queue_head_init(&dev->tx_queue); | ||
289 | INIT_WORK(&dev->tx_work, gprs_tx); | ||
290 | |||
291 | netif_stop_queue(net); | ||
292 | err = register_netdev(net); | ||
293 | if (err) { | ||
294 | free_netdev(net); | ||
295 | return err; | ||
296 | } | ||
297 | |||
298 | lock_sock(sk); | ||
299 | if (unlikely(sk->sk_user_data)) { | ||
300 | err = -EBUSY; | ||
301 | goto out_rel; | ||
302 | } | ||
303 | if (unlikely((1 << sk->sk_state & (TCPF_CLOSE|TCPF_LISTEN)) || | ||
304 | sock_flag(sk, SOCK_DEAD))) { | ||
305 | err = -EINVAL; | ||
306 | goto out_rel; | ||
307 | } | ||
308 | sk->sk_user_data = dev; | ||
309 | dev->old_state_change = sk->sk_state_change; | ||
310 | dev->old_data_ready = sk->sk_data_ready; | ||
311 | dev->old_write_space = sk->sk_write_space; | ||
312 | sk->sk_state_change = gprs_state_change; | ||
313 | sk->sk_data_ready = gprs_data_ready; | ||
314 | sk->sk_write_space = gprs_write_space; | ||
315 | release_sock(sk); | ||
316 | |||
317 | sock_hold(sk); | ||
318 | dev->sk = sk; | ||
319 | |||
320 | printk(KERN_DEBUG"%s: attached\n", net->name); | ||
321 | gprs_write_space(sk); /* kick off TX */ | ||
322 | return net->ifindex; | ||
323 | |||
324 | out_rel: | ||
325 | release_sock(sk); | ||
326 | unregister_netdev(net); | ||
327 | return err; | ||
328 | } | ||
329 | |||
330 | void gprs_detach(struct sock *sk) | ||
331 | { | ||
332 | struct gprs_dev *dev = sk->sk_user_data; | ||
333 | struct net_device *net = dev->net; | ||
334 | |||
335 | lock_sock(sk); | ||
336 | sk->sk_user_data = NULL; | ||
337 | sk->sk_state_change = dev->old_state_change; | ||
338 | sk->sk_data_ready = dev->old_data_ready; | ||
339 | sk->sk_write_space = dev->old_write_space; | ||
340 | release_sock(sk); | ||
341 | |||
342 | printk(KERN_DEBUG"%s: detached\n", net->name); | ||
343 | unregister_netdev(net); | ||
344 | flush_scheduled_work(); | ||
345 | sock_put(sk); | ||
346 | skb_queue_purge(&dev->tx_queue); | ||
347 | } | ||