2019-05-29 23:57:50 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2008-10-05 18:16:16 +00:00
|
|
|
/*
|
|
|
|
* File: pep-gprs.c
|
|
|
|
*
|
|
|
|
* GPRS over Phonet pipe end point socket
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Nokia Corporation.
|
|
|
|
*
|
2012-06-13 22:29:03 +00:00
|
|
|
* Author: Rémi Denis-Courmont
|
2008-10-05 18:16:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/if_ether.h>
|
|
|
|
#include <linux/if_arp.h>
|
|
|
|
#include <net/sock.h>
|
|
|
|
|
|
|
|
#include <linux/if_phonet.h>
|
|
|
|
#include <net/tcp_states.h>
|
|
|
|
#include <net/phonet/gprs.h>
|
|
|
|
|
2023-01-20 00:45:16 +00:00
|
|
|
#include <trace/events/sock.h>
|
|
|
|
|
2008-10-05 18:16:16 +00:00
|
|
|
#define GPRS_DEFAULT_MTU 1400
|
|
|
|
|
|
|
|
struct gprs_dev {
|
|
|
|
struct sock *sk;
|
|
|
|
void (*old_state_change)(struct sock *);
|
2014-04-11 20:15:36 +00:00
|
|
|
void (*old_data_ready)(struct sock *);
|
2008-10-05 18:16:16 +00:00
|
|
|
void (*old_write_space)(struct sock *);
|
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
struct net_device *dev;
|
2008-10-05 18:16:16 +00:00
|
|
|
};
|
|
|
|
|
2008-11-07 07:10:50 +00:00
|
|
|
static __be16 gprs_type_trans(struct sk_buff *skb)
|
2008-10-05 18:16:16 +00:00
|
|
|
{
|
|
|
|
const u8 *pvfc;
|
|
|
|
u8 buf;
|
|
|
|
|
|
|
|
pvfc = skb_header_pointer(skb, 0, 1, &buf);
|
|
|
|
if (!pvfc)
|
2008-11-07 07:10:50 +00:00
|
|
|
return htons(0);
|
2008-10-05 18:16:16 +00:00
|
|
|
/* Look at IP version field */
|
|
|
|
switch (*pvfc >> 4) {
|
|
|
|
case 4:
|
|
|
|
return htons(ETH_P_IP);
|
|
|
|
case 6:
|
|
|
|
return htons(ETH_P_IPV6);
|
|
|
|
}
|
2008-11-07 07:10:50 +00:00
|
|
|
return htons(0);
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
2008-12-17 23:48:50 +00:00
|
|
|
static void gprs_writeable(struct gprs_dev *gp)
|
|
|
|
{
|
|
|
|
struct net_device *dev = gp->dev;
|
|
|
|
|
|
|
|
if (pep_writeable(gp->sk))
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
|
2008-10-05 18:16:16 +00:00
|
|
|
/*
|
|
|
|
* Socket callbacks
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void gprs_state_change(struct sock *sk)
|
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
|
|
|
if (sk->sk_state == TCP_CLOSE_WAIT) {
|
2008-12-16 09:18:31 +00:00
|
|
|
struct net_device *dev = gp->dev;
|
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
netif_carrier_off(dev);
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
static int gprs_recv(struct gprs_dev *gp, struct sk_buff *skb)
|
2008-10-05 18:16:16 +00:00
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct net_device *dev = gp->dev;
|
2008-10-05 18:16:16 +00:00
|
|
|
int err = 0;
|
2008-11-07 07:10:50 +00:00
|
|
|
__be16 protocol = gprs_type_trans(skb);
|
2008-10-05 18:16:16 +00:00
|
|
|
|
|
|
|
if (!protocol) {
|
|
|
|
err = -EINVAL;
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2010-01-04 02:02:47 +00:00
|
|
|
if (skb_headroom(skb) & 3) {
|
2008-10-05 18:16:16 +00:00
|
|
|
struct sk_buff *rskb, *fs;
|
|
|
|
int flen = 0;
|
|
|
|
|
2010-01-04 02:02:47 +00:00
|
|
|
/* Phonet Pipe data header may be misaligned (3 bytes),
|
2008-10-05 18:16:16 +00:00
|
|
|
* so wrap the IP packet as a single fragment of an head-less
|
|
|
|
* socket buffer. The network stack will pull what it needs,
|
|
|
|
* but at least, the whole IP payload is not memcpy'd. */
|
2008-12-16 09:18:31 +00:00
|
|
|
rskb = netdev_alloc_skb(dev, 0);
|
2008-10-05 18:16:16 +00:00
|
|
|
if (!rskb) {
|
|
|
|
err = -ENOBUFS;
|
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
skb_shinfo(rskb)->frag_list = skb;
|
|
|
|
rskb->len += skb->len;
|
|
|
|
rskb->data_len += rskb->len;
|
|
|
|
rskb->truesize += rskb->len;
|
|
|
|
|
|
|
|
/* Avoid nested fragments */
|
2009-06-09 07:21:58 +00:00
|
|
|
skb_walk_frags(skb, fs)
|
2008-10-05 18:16:16 +00:00
|
|
|
flen += fs->len;
|
|
|
|
skb->next = skb_shinfo(skb)->frag_list;
|
2009-06-09 07:21:58 +00:00
|
|
|
skb_frag_list_init(skb);
|
2008-10-05 18:16:16 +00:00
|
|
|
skb->len -= flen;
|
|
|
|
skb->data_len -= flen;
|
|
|
|
skb->truesize -= flen;
|
|
|
|
|
|
|
|
skb = rskb;
|
|
|
|
}
|
|
|
|
|
|
|
|
skb->protocol = protocol;
|
|
|
|
skb_reset_mac_header(skb);
|
2008-12-16 09:18:31 +00:00
|
|
|
skb->dev = dev;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
if (likely(dev->flags & IFF_UP)) {
|
|
|
|
dev->stats.rx_packets++;
|
|
|
|
dev->stats.rx_bytes += skb->len;
|
2008-10-05 18:16:16 +00:00
|
|
|
netif_rx(skb);
|
|
|
|
skb = NULL;
|
|
|
|
} else
|
|
|
|
err = -ENODEV;
|
|
|
|
|
|
|
|
drop:
|
|
|
|
if (skb) {
|
|
|
|
dev_kfree_skb(skb);
|
2008-12-16 09:18:31 +00:00
|
|
|
dev->stats.rx_dropped++;
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2014-04-11 20:15:36 +00:00
|
|
|
static void gprs_data_ready(struct sock *sk)
|
2008-10-05 18:16:16 +00:00
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
2008-10-05 18:16:16 +00:00
|
|
|
struct sk_buff *skb;
|
|
|
|
|
2023-01-20 00:45:16 +00:00
|
|
|
trace_sk_data_ready(sk);
|
|
|
|
|
2008-10-05 18:16:16 +00:00
|
|
|
while ((skb = pep_read(sk)) != NULL) {
|
|
|
|
skb_orphan(skb);
|
2008-12-16 09:18:31 +00:00
|
|
|
gprs_recv(gp, skb);
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gprs_write_space(struct sock *sk)
|
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
2008-12-17 23:48:50 +00:00
|
|
|
if (netif_running(gp->dev))
|
|
|
|
gprs_writeable(gp);
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Network device callbacks
|
|
|
|
*/
|
|
|
|
|
2008-12-15 08:53:57 +00:00
|
|
|
static int gprs_open(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct gprs_dev *gp = netdev_priv(dev);
|
|
|
|
|
2008-12-17 23:48:50 +00:00
|
|
|
gprs_writeable(gp);
|
2008-12-15 08:53:57 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gprs_close(struct net_device *dev)
|
|
|
|
{
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-08-31 19:50:51 +00:00
|
|
|
static netdev_tx_t gprs_xmit(struct sk_buff *skb, struct net_device *dev)
|
2008-10-05 18:16:16 +00:00
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp = netdev_priv(dev);
|
2008-12-17 23:48:50 +00:00
|
|
|
struct sock *sk = gp->sk;
|
|
|
|
int len, err;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
|
|
|
switch (skb->protocol) {
|
|
|
|
case htons(ETH_P_IP):
|
|
|
|
case htons(ETH_P_IPV6):
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_kfree_skb(skb);
|
2009-06-23 06:03:08 +00:00
|
|
|
return NETDEV_TX_OK;
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
2008-12-17 23:48:50 +00:00
|
|
|
skb_orphan(skb);
|
|
|
|
skb_set_owner_w(skb, sk);
|
|
|
|
len = skb->len;
|
|
|
|
err = pep_write(sk, skb);
|
|
|
|
if (err) {
|
2014-11-11 18:59:17 +00:00
|
|
|
net_dbg_ratelimited("%s: TX error (%d)\n", dev->name, err);
|
2008-12-17 23:48:50 +00:00
|
|
|
dev->stats.tx_aborted_errors++;
|
|
|
|
dev->stats.tx_errors++;
|
|
|
|
} else {
|
2008-12-16 09:18:31 +00:00
|
|
|
dev->stats.tx_packets++;
|
2008-12-17 23:48:50 +00:00
|
|
|
dev->stats.tx_bytes += len;
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
2009-06-01 00:35:16 +00:00
|
|
|
netif_stop_queue(dev);
|
|
|
|
if (pep_writeable(sk))
|
|
|
|
netif_wake_queue(dev);
|
2009-06-23 06:03:08 +00:00
|
|
|
return NETDEV_TX_OK;
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
2009-01-08 01:24:34 +00:00
|
|
|
static const struct net_device_ops gprs_netdev_ops = {
|
|
|
|
.ndo_open = gprs_open,
|
|
|
|
.ndo_stop = gprs_close,
|
|
|
|
.ndo_start_xmit = gprs_xmit,
|
|
|
|
};
|
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
static void gprs_setup(struct net_device *dev)
|
2008-10-05 18:16:16 +00:00
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
dev->features = NETIF_F_FRAGLIST;
|
2008-12-17 23:47:48 +00:00
|
|
|
dev->type = ARPHRD_PHONET_PIPE;
|
2008-12-16 09:18:31 +00:00
|
|
|
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
|
|
|
dev->mtu = GPRS_DEFAULT_MTU;
|
net: use core MTU range checking in misc drivers
firewire-net:
- set min/max_mtu
- remove fwnet_change_mtu
nes:
- set max_mtu
- clean up nes_netdev_change_mtu
xpnet:
- set min/max_mtu
- remove xpnet_dev_change_mtu
hippi:
- set min/max_mtu
- remove hippi_change_mtu
batman-adv:
- set max_mtu
- remove batadv_interface_change_mtu
- initialization is a little async, not 100% certain that max_mtu is set
in the optimal place, don't have hardware to test with
rionet:
- set min/max_mtu
- remove rionet_change_mtu
slip:
- set min/max_mtu
- streamline sl_change_mtu
um/net_kern:
- remove pointless ndo_change_mtu
hsi/clients/ssi_protocol:
- use core MTU range checking
- remove now redundant ssip_pn_set_mtu
ipoib:
- set a default max MTU value
- Note: ipoib's actual max MTU can vary, depending on if the device is in
connected mode or not, so we'll just set the max_mtu value to the max
possible, and let the ndo_change_mtu function continue to validate any new
MTU change requests with checks for CM or not. Note that ipoib has no
min_mtu set, and thus, the network core's mtu > 0 check is the only lower
bounds here.
mptlan:
- use net core MTU range checking
- remove now redundant mpt_lan_change_mtu
fddi:
- min_mtu = 21, max_mtu = 4470
- remove now redundant fddi_change_mtu (including export)
fjes:
- min_mtu = 8192, max_mtu = 65536
- The max_mtu value is actually one over IP_MAX_MTU here, but the idea is to
get past the core net MTU range checks so fjes_change_mtu can validate a
new MTU against what it supports (see fjes_support_mtu in fjes_hw.c)
hsr:
- min_mtu = 0 (calls ether_setup, max_mtu is 1500)
f_phonet:
- min_mtu = 6, max_mtu = 65541
u_ether:
- min_mtu = 14, max_mtu = 15412
phonet/pep-gprs:
- min_mtu = 576, max_mtu = 65530
- remove redundant gprs_set_mtu
CC: netdev@vger.kernel.org
CC: linux-rdma@vger.kernel.org
CC: Stefan Richter <stefanr@s5r6.in-berlin.de>
CC: Faisal Latif <faisal.latif@intel.com>
CC: linux-rdma@vger.kernel.org
CC: Cliff Whickman <cpw@sgi.com>
CC: Robin Holt <robinmholt@gmail.com>
CC: Jes Sorensen <jes@trained-monkey.org>
CC: Marek Lindner <mareklindner@neomailbox.ch>
CC: Simon Wunderlich <sw@simonwunderlich.de>
CC: Antonio Quartulli <a@unstable.cc>
CC: Sathya Prakash <sathya.prakash@broadcom.com>
CC: Chaitra P B <chaitra.basappa@broadcom.com>
CC: Suganath Prabu Subramani <suganath-prabu.subramani@broadcom.com>
CC: MPT-FusionLinux.pdl@broadcom.com
CC: Sebastian Reichel <sre@kernel.org>
CC: Felipe Balbi <balbi@kernel.org>
CC: Arvid Brodin <arvid.brodin@alten.se>
CC: Remi Denis-Courmont <courmisch@gmail.com>
Signed-off-by: Jarod Wilson <jarod@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-10-20 17:55:22 +00:00
|
|
|
dev->min_mtu = 576;
|
|
|
|
dev->max_mtu = (PHONET_MAX_MTU - 11);
|
2008-12-16 09:18:31 +00:00
|
|
|
dev->hard_header_len = 0;
|
|
|
|
dev->addr_len = 0;
|
|
|
|
dev->tx_queue_len = 10;
|
|
|
|
|
2009-01-08 01:24:34 +00:00
|
|
|
dev->netdev_ops = &gprs_netdev_ops;
|
net: Fix inconsistent teardown and release of private netdev state.
Network devices can allocate reasources and private memory using
netdev_ops->ndo_init(). However, the release of these resources
can occur in one of two different places.
Either netdev_ops->ndo_uninit() or netdev->destructor().
The decision of which operation frees the resources depends upon
whether it is necessary for all netdev refs to be released before it
is safe to perform the freeing.
netdev_ops->ndo_uninit() presumably can occur right after the
NETDEV_UNREGISTER notifier completes and the unicast and multicast
address lists are flushed.
netdev->destructor(), on the other hand, does not run until the
netdev references all go away.
Further complicating the situation is that netdev->destructor()
almost universally does also a free_netdev().
This creates a problem for the logic in register_netdevice().
Because all callers of register_netdevice() manage the freeing
of the netdev, and invoke free_netdev(dev) if register_netdevice()
fails.
If netdev_ops->ndo_init() succeeds, but something else fails inside
of register_netdevice(), it does call ndo_ops->ndo_uninit(). But
it is not able to invoke netdev->destructor().
This is because netdev->destructor() will do a free_netdev() and
then the caller of register_netdevice() will do the same.
However, this means that the resources that would normally be released
by netdev->destructor() will not be.
Over the years drivers have added local hacks to deal with this, by
invoking their destructor parts by hand when register_netdevice()
fails.
Many drivers do not try to deal with this, and instead we have leaks.
Let's close this hole by formalizing the distinction between what
private things need to be freed up by netdev->destructor() and whether
the driver needs unregister_netdevice() to perform the free_netdev().
netdev->priv_destructor() performs all actions to free up the private
resources that used to be freed by netdev->destructor(), except for
free_netdev().
netdev->needs_free_netdev is a boolean that indicates whether
free_netdev() should be done at the end of unregister_netdevice().
Now, register_netdevice() can sanely release all resources after
ndo_ops->ndo_init() succeeds, by invoking both ndo_ops->ndo_uninit()
and netdev->priv_destructor().
And at the end of unregister_netdevice(), we invoke
netdev->priv_destructor() and optionally call free_netdev().
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-05-08 16:52:56 +00:00
|
|
|
dev->needs_free_netdev = true;
|
2008-10-05 18:16:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* External interface
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Attach a GPRS interface to a datagram socket.
|
|
|
|
* Returns the interface index on success, negative error code on error.
|
|
|
|
*/
|
|
|
|
int gprs_attach(struct sock *sk)
|
|
|
|
{
|
|
|
|
static const char ifname[] = "gprs%d";
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp;
|
|
|
|
struct net_device *dev;
|
2008-10-05 18:16:16 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
if (unlikely(sk->sk_type == SOCK_STREAM))
|
|
|
|
return -EINVAL; /* need packet boundaries */
|
|
|
|
|
|
|
|
/* Create net device */
|
net: set name_assign_type in alloc_netdev()
Extend alloc_netdev{,_mq{,s}}() to take name_assign_type as argument, and convert
all users to pass NET_NAME_UNKNOWN.
Coccinelle patch:
@@
expression sizeof_priv, name, setup, txqs, rxqs, count;
@@
(
-alloc_netdev_mqs(sizeof_priv, name, setup, txqs, rxqs)
+alloc_netdev_mqs(sizeof_priv, name, NET_NAME_UNKNOWN, setup, txqs, rxqs)
|
-alloc_netdev_mq(sizeof_priv, name, setup, count)
+alloc_netdev_mq(sizeof_priv, name, NET_NAME_UNKNOWN, setup, count)
|
-alloc_netdev(sizeof_priv, name, setup)
+alloc_netdev(sizeof_priv, name, NET_NAME_UNKNOWN, setup)
)
v9: move comments here from the wrong commit
Signed-off-by: Tom Gundersen <teg@jklm.no>
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2014-07-14 14:37:24 +00:00
|
|
|
dev = alloc_netdev(sizeof(*gp), ifname, NET_NAME_UNKNOWN, gprs_setup);
|
2008-12-16 09:18:31 +00:00
|
|
|
if (!dev)
|
2008-10-05 18:16:16 +00:00
|
|
|
return -ENOMEM;
|
2008-12-16 09:18:31 +00:00
|
|
|
gp = netdev_priv(dev);
|
2008-12-17 23:48:50 +00:00
|
|
|
gp->sk = sk;
|
2008-12-16 09:18:31 +00:00
|
|
|
gp->dev = dev;
|
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
err = register_netdev(dev);
|
2008-10-05 18:16:16 +00:00
|
|
|
if (err) {
|
2008-12-16 09:18:31 +00:00
|
|
|
free_netdev(dev);
|
2008-10-05 18:16:16 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
lock_sock(sk);
|
|
|
|
if (unlikely(sk->sk_user_data)) {
|
|
|
|
err = -EBUSY;
|
|
|
|
goto out_rel;
|
|
|
|
}
|
|
|
|
if (unlikely((1 << sk->sk_state & (TCPF_CLOSE|TCPF_LISTEN)) ||
|
|
|
|
sock_flag(sk, SOCK_DEAD))) {
|
|
|
|
err = -EINVAL;
|
|
|
|
goto out_rel;
|
|
|
|
}
|
2008-12-16 09:18:31 +00:00
|
|
|
sk->sk_user_data = gp;
|
|
|
|
gp->old_state_change = sk->sk_state_change;
|
|
|
|
gp->old_data_ready = sk->sk_data_ready;
|
|
|
|
gp->old_write_space = sk->sk_write_space;
|
2008-10-05 18:16:16 +00:00
|
|
|
sk->sk_state_change = gprs_state_change;
|
|
|
|
sk->sk_data_ready = gprs_data_ready;
|
|
|
|
sk->sk_write_space = gprs_write_space;
|
|
|
|
release_sock(sk);
|
|
|
|
sock_hold(sk);
|
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
printk(KERN_DEBUG"%s: attached\n", dev->name);
|
|
|
|
return dev->ifindex;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
|
|
|
out_rel:
|
|
|
|
release_sock(sk);
|
2008-12-16 09:18:31 +00:00
|
|
|
unregister_netdev(dev);
|
2008-10-05 18:16:16 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
void gprs_detach(struct sock *sk)
|
|
|
|
{
|
2008-12-16 09:18:31 +00:00
|
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
|
|
|
struct net_device *dev = gp->dev;
|
2008-10-05 18:16:16 +00:00
|
|
|
|
|
|
|
lock_sock(sk);
|
|
|
|
sk->sk_user_data = NULL;
|
2008-12-16 09:18:31 +00:00
|
|
|
sk->sk_state_change = gp->old_state_change;
|
|
|
|
sk->sk_data_ready = gp->old_data_ready;
|
|
|
|
sk->sk_write_space = gp->old_write_space;
|
2008-10-05 18:16:16 +00:00
|
|
|
release_sock(sk);
|
|
|
|
|
2008-12-16 09:18:31 +00:00
|
|
|
printk(KERN_DEBUG"%s: detached\n", dev->name);
|
|
|
|
unregister_netdev(dev);
|
2008-10-05 18:16:16 +00:00
|
|
|
sock_put(sk);
|
|
|
|
}
|