mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![David S. Miller](/assets/img/avatar_default.png)
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>
319 lines
6.6 KiB
C
319 lines
6.6 KiB
C
/*
|
|
* File: pep-gprs.c
|
|
*
|
|
* GPRS over Phonet pipe end point socket
|
|
*
|
|
* Copyright (C) 2008 Nokia Corporation.
|
|
*
|
|
* Author: Rémi Denis-Courmont
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#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>
|
|
|
|
#define GPRS_DEFAULT_MTU 1400
|
|
|
|
struct gprs_dev {
|
|
struct sock *sk;
|
|
void (*old_state_change)(struct sock *);
|
|
void (*old_data_ready)(struct sock *);
|
|
void (*old_write_space)(struct sock *);
|
|
|
|
struct net_device *dev;
|
|
};
|
|
|
|
static __be16 gprs_type_trans(struct sk_buff *skb)
|
|
{
|
|
const u8 *pvfc;
|
|
u8 buf;
|
|
|
|
pvfc = skb_header_pointer(skb, 0, 1, &buf);
|
|
if (!pvfc)
|
|
return htons(0);
|
|
/* Look at IP version field */
|
|
switch (*pvfc >> 4) {
|
|
case 4:
|
|
return htons(ETH_P_IP);
|
|
case 6:
|
|
return htons(ETH_P_IPV6);
|
|
}
|
|
return htons(0);
|
|
}
|
|
|
|
static void gprs_writeable(struct gprs_dev *gp)
|
|
{
|
|
struct net_device *dev = gp->dev;
|
|
|
|
if (pep_writeable(gp->sk))
|
|
netif_wake_queue(dev);
|
|
}
|
|
|
|
/*
|
|
* Socket callbacks
|
|
*/
|
|
|
|
static void gprs_state_change(struct sock *sk)
|
|
{
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
|
|
|
if (sk->sk_state == TCP_CLOSE_WAIT) {
|
|
struct net_device *dev = gp->dev;
|
|
|
|
netif_stop_queue(dev);
|
|
netif_carrier_off(dev);
|
|
}
|
|
}
|
|
|
|
static int gprs_recv(struct gprs_dev *gp, struct sk_buff *skb)
|
|
{
|
|
struct net_device *dev = gp->dev;
|
|
int err = 0;
|
|
__be16 protocol = gprs_type_trans(skb);
|
|
|
|
if (!protocol) {
|
|
err = -EINVAL;
|
|
goto drop;
|
|
}
|
|
|
|
if (skb_headroom(skb) & 3) {
|
|
struct sk_buff *rskb, *fs;
|
|
int flen = 0;
|
|
|
|
/* Phonet Pipe data header may be misaligned (3 bytes),
|
|
* 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. */
|
|
rskb = netdev_alloc_skb(dev, 0);
|
|
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 */
|
|
skb_walk_frags(skb, fs)
|
|
flen += fs->len;
|
|
skb->next = skb_shinfo(skb)->frag_list;
|
|
skb_frag_list_init(skb);
|
|
skb->len -= flen;
|
|
skb->data_len -= flen;
|
|
skb->truesize -= flen;
|
|
|
|
skb = rskb;
|
|
}
|
|
|
|
skb->protocol = protocol;
|
|
skb_reset_mac_header(skb);
|
|
skb->dev = dev;
|
|
|
|
if (likely(dev->flags & IFF_UP)) {
|
|
dev->stats.rx_packets++;
|
|
dev->stats.rx_bytes += skb->len;
|
|
netif_rx(skb);
|
|
skb = NULL;
|
|
} else
|
|
err = -ENODEV;
|
|
|
|
drop:
|
|
if (skb) {
|
|
dev_kfree_skb(skb);
|
|
dev->stats.rx_dropped++;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void gprs_data_ready(struct sock *sk)
|
|
{
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
|
struct sk_buff *skb;
|
|
|
|
while ((skb = pep_read(sk)) != NULL) {
|
|
skb_orphan(skb);
|
|
gprs_recv(gp, skb);
|
|
}
|
|
}
|
|
|
|
static void gprs_write_space(struct sock *sk)
|
|
{
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
|
|
|
if (netif_running(gp->dev))
|
|
gprs_writeable(gp);
|
|
}
|
|
|
|
/*
|
|
* Network device callbacks
|
|
*/
|
|
|
|
static int gprs_open(struct net_device *dev)
|
|
{
|
|
struct gprs_dev *gp = netdev_priv(dev);
|
|
|
|
gprs_writeable(gp);
|
|
return 0;
|
|
}
|
|
|
|
static int gprs_close(struct net_device *dev)
|
|
{
|
|
netif_stop_queue(dev);
|
|
return 0;
|
|
}
|
|
|
|
static netdev_tx_t gprs_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct gprs_dev *gp = netdev_priv(dev);
|
|
struct sock *sk = gp->sk;
|
|
int len, err;
|
|
|
|
switch (skb->protocol) {
|
|
case htons(ETH_P_IP):
|
|
case htons(ETH_P_IPV6):
|
|
break;
|
|
default:
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
skb_orphan(skb);
|
|
skb_set_owner_w(skb, sk);
|
|
len = skb->len;
|
|
err = pep_write(sk, skb);
|
|
if (err) {
|
|
net_dbg_ratelimited("%s: TX error (%d)\n", dev->name, err);
|
|
dev->stats.tx_aborted_errors++;
|
|
dev->stats.tx_errors++;
|
|
} else {
|
|
dev->stats.tx_packets++;
|
|
dev->stats.tx_bytes += len;
|
|
}
|
|
|
|
netif_stop_queue(dev);
|
|
if (pep_writeable(sk))
|
|
netif_wake_queue(dev);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
static const struct net_device_ops gprs_netdev_ops = {
|
|
.ndo_open = gprs_open,
|
|
.ndo_stop = gprs_close,
|
|
.ndo_start_xmit = gprs_xmit,
|
|
};
|
|
|
|
static void gprs_setup(struct net_device *dev)
|
|
{
|
|
dev->features = NETIF_F_FRAGLIST;
|
|
dev->type = ARPHRD_PHONET_PIPE;
|
|
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
|
dev->mtu = GPRS_DEFAULT_MTU;
|
|
dev->min_mtu = 576;
|
|
dev->max_mtu = (PHONET_MAX_MTU - 11);
|
|
dev->hard_header_len = 0;
|
|
dev->addr_len = 0;
|
|
dev->tx_queue_len = 10;
|
|
|
|
dev->netdev_ops = &gprs_netdev_ops;
|
|
dev->needs_free_netdev = true;
|
|
}
|
|
|
|
/*
|
|
* 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";
|
|
struct gprs_dev *gp;
|
|
struct net_device *dev;
|
|
int err;
|
|
|
|
if (unlikely(sk->sk_type == SOCK_STREAM))
|
|
return -EINVAL; /* need packet boundaries */
|
|
|
|
/* Create net device */
|
|
dev = alloc_netdev(sizeof(*gp), ifname, NET_NAME_UNKNOWN, gprs_setup);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
gp = netdev_priv(dev);
|
|
gp->sk = sk;
|
|
gp->dev = dev;
|
|
|
|
netif_stop_queue(dev);
|
|
err = register_netdev(dev);
|
|
if (err) {
|
|
free_netdev(dev);
|
|
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;
|
|
}
|
|
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;
|
|
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);
|
|
|
|
printk(KERN_DEBUG"%s: attached\n", dev->name);
|
|
return dev->ifindex;
|
|
|
|
out_rel:
|
|
release_sock(sk);
|
|
unregister_netdev(dev);
|
|
return err;
|
|
}
|
|
|
|
void gprs_detach(struct sock *sk)
|
|
{
|
|
struct gprs_dev *gp = sk->sk_user_data;
|
|
struct net_device *dev = gp->dev;
|
|
|
|
lock_sock(sk);
|
|
sk->sk_user_data = NULL;
|
|
sk->sk_state_change = gp->old_state_change;
|
|
sk->sk_data_ready = gp->old_data_ready;
|
|
sk->sk_write_space = gp->old_write_space;
|
|
release_sock(sk);
|
|
|
|
printk(KERN_DEBUG"%s: detached\n", dev->name);
|
|
unregister_netdev(dev);
|
|
sock_put(sk);
|
|
}
|