diff options
Diffstat (limited to 'drivers/net/wan/lapbether.c')
| -rw-r--r-- | drivers/net/wan/lapbether.c | 208 |
1 files changed, 146 insertions, 62 deletions
diff --git a/drivers/net/wan/lapbether.c b/drivers/net/wan/lapbether.c index e30d91a38cfb..f357a7ac70ac 100644 --- a/drivers/net/wan/lapbether.c +++ b/drivers/net/wan/lapbether.c @@ -6,7 +6,7 @@ * * This is a "pseudo" network driver to allow LAPB over Ethernet. * - * This driver can use any ethernet destination address, and can be + * This driver can use any ethernet destination address, and can be * limited to accept frames from one dedicated ethernet card only. * * History @@ -39,33 +39,41 @@ #include <linux/lapb.h> #include <linux/init.h> +#include <net/netdev_lock.h> #include <net/x25device.h> static const u8 bcast_addr[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* If this number is made larger, check that the temporary string buffer - * in lapbeth_new_device is large enough to store the probe device name.*/ + * in lapbeth_new_device is large enough to store the probe device name. + */ #define MAXLAPBDEV 100 struct lapbethdev { struct list_head node; struct net_device *ethdev; /* link to ethernet device */ struct net_device *axdev; /* lapbeth device (lapb#) */ + bool up; + spinlock_t up_lock; /* Protects "up" */ + struct sk_buff_head rx_queue; + struct napi_struct napi; }; static LIST_HEAD(lapbeth_devices); +static void lapbeth_connected(struct net_device *dev, int reason); +static void lapbeth_disconnected(struct net_device *dev, int reason); + /* ------------------------------------------------------------------------ */ -/* - * Get the LAPB device for the ethernet device +/* Get the LAPB device for the ethernet device */ static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev) { struct lapbethdev *lapbeth; list_for_each_entry_rcu(lapbeth, &lapbeth_devices, node, lockdep_rtnl_is_held()) { - if (lapbeth->ethdev == dev) + if (lapbeth->ethdev == dev) return lapbeth; } return NULL; @@ -73,15 +81,35 @@ static struct lapbethdev *lapbeth_get_x25_dev(struct net_device *dev) static __inline__ int dev_is_ethdev(struct net_device *dev) { - return dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5); + return dev->type == ARPHRD_ETHER && !netdev_need_ops_lock(dev); } /* ------------------------------------------------------------------------ */ -/* - * Receive a LAPB frame via an ethernet interface. +static int lapbeth_napi_poll(struct napi_struct *napi, int budget) +{ + struct lapbethdev *lapbeth = container_of(napi, struct lapbethdev, + napi); + struct sk_buff *skb; + int processed = 0; + + for (; processed < budget; ++processed) { + skb = skb_dequeue(&lapbeth->rx_queue); + if (!skb) + break; + netif_receive_skb_core(skb); + } + + if (processed < budget) + napi_complete(napi); + + return processed; +} + +/* Receive a LAPB frame via an ethernet interface. */ -static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype, struct net_device *orig_dev) +static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, struct net_device *orig_dev) { int len, err; struct lapbethdev *lapbeth; @@ -89,7 +117,8 @@ static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packe if (dev_net(dev) != &init_net) goto drop; - if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) + skb = skb_share_check(skb, GFP_ATOMIC); + if (!skb) return NET_RX_DROP; if (!pskb_may_pull(skb, 2)) @@ -98,8 +127,9 @@ static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packe rcu_read_lock(); lapbeth = lapbeth_get_x25_dev(dev); if (!lapbeth) - goto drop_unlock; - if (!netif_running(lapbeth->axdev)) + goto drop_unlock_rcu; + spin_lock_bh(&lapbeth->up_lock); + if (!lapbeth->up) goto drop_unlock; len = skb->data[0] + skb->data[1] * 256; @@ -109,16 +139,20 @@ static int lapbeth_rcv(struct sk_buff *skb, struct net_device *dev, struct packe skb_pull(skb, 2); /* Remove the length bytes */ skb_trim(skb, len); /* Set the length of the data */ - if ((err = lapb_data_received(lapbeth->axdev, skb)) != LAPB_OK) { + err = lapb_data_received(lapbeth->axdev, skb); + if (err != LAPB_OK) { printk(KERN_DEBUG "lapbether: lapb_data_received err - %d\n", err); goto drop_unlock; } out: + spin_unlock_bh(&lapbeth->up_lock); rcu_read_unlock(); return 0; drop_unlock: kfree_skb(skb); goto out; +drop_unlock_rcu: + rcu_read_unlock(); drop: kfree_skb(skb); return 0; @@ -126,57 +160,74 @@ drop: static int lapbeth_data_indication(struct net_device *dev, struct sk_buff *skb) { + struct lapbethdev *lapbeth = netdev_priv(dev); unsigned char *ptr; - skb_push(skb, 1); - - if (skb_cow(skb, 1)) + if (skb_cow(skb, 1)) { + kfree_skb(skb); return NET_RX_DROP; + } + + skb_push(skb, 1); ptr = skb->data; *ptr = X25_IFACE_DATA; skb->protocol = x25_type_trans(skb, dev); - return netif_rx(skb); + + skb_queue_tail(&lapbeth->rx_queue, skb); + napi_schedule(&lapbeth->napi); + return NET_RX_SUCCESS; } -/* - * Send a LAPB frame via an ethernet interface +/* Send a LAPB frame via an ethernet interface */ static netdev_tx_t lapbeth_xmit(struct sk_buff *skb, - struct net_device *dev) + struct net_device *dev) { + struct lapbethdev *lapbeth = netdev_priv(dev); int err; - /* - * Just to be *really* sure not to send anything if the interface - * is down, the ethernet device may have gone. + spin_lock_bh(&lapbeth->up_lock); + if (!lapbeth->up) + goto drop; + + /* There should be a pseudo header of 1 byte added by upper layers. + * Check to make sure it is there before reading it. */ - if (!netif_running(dev)) + if (skb->len < 1) goto drop; switch (skb->data[0]) { case X25_IFACE_DATA: break; case X25_IFACE_CONNECT: - if ((err = lapb_connect_request(dev)) != LAPB_OK) + err = lapb_connect_request(dev); + if (err == LAPB_CONNECTED) + lapbeth_connected(dev, LAPB_OK); + else if (err != LAPB_OK) pr_err("lapb_connect_request error: %d\n", err); goto drop; case X25_IFACE_DISCONNECT: - if ((err = lapb_disconnect_request(dev)) != LAPB_OK) + err = lapb_disconnect_request(dev); + if (err == LAPB_NOTCONNECTED) + lapbeth_disconnected(dev, LAPB_OK); + else if (err != LAPB_OK) pr_err("lapb_disconnect_request err: %d\n", err); - /* Fall thru */ + fallthrough; default: goto drop; } skb_pull(skb, 1); - if ((err = lapb_data_request(dev, skb)) != LAPB_OK) { + err = lapb_data_request(dev, skb); + if (err != LAPB_OK) { pr_err("lapb_data_request error - %d\n", err); goto drop; } out: + spin_unlock_bh(&lapbeth->up_lock); return NETDEV_TX_OK; drop: kfree_skb(skb); @@ -190,8 +241,6 @@ static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb) struct net_device *dev; int size = skb->len; - skb->protocol = htons(ETH_P_X25); - ptr = skb_push(skb, 2); *ptr++ = size % 256; @@ -202,6 +251,10 @@ static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb) skb->dev = dev = lapbeth->ethdev; + skb->protocol = htons(ETH_P_DEC); + + skb_reset_network_header(skb); + dev_hard_header(skb, dev, ETH_P_DEC, bcast_addr, NULL, 0); dev_queue_xmit(skb); @@ -209,49 +262,50 @@ static void lapbeth_data_transmit(struct net_device *ndev, struct sk_buff *skb) static void lapbeth_connected(struct net_device *dev, int reason) { + struct lapbethdev *lapbeth = netdev_priv(dev); unsigned char *ptr; - struct sk_buff *skb = dev_alloc_skb(1); + struct sk_buff *skb = __dev_alloc_skb(1, GFP_ATOMIC | __GFP_NOMEMALLOC); - if (!skb) { - pr_err("out of memory\n"); + if (!skb) return; - } ptr = skb_put(skb, 1); *ptr = X25_IFACE_CONNECT; skb->protocol = x25_type_trans(skb, dev); - netif_rx(skb); + + skb_queue_tail(&lapbeth->rx_queue, skb); + napi_schedule(&lapbeth->napi); } static void lapbeth_disconnected(struct net_device *dev, int reason) { + struct lapbethdev *lapbeth = netdev_priv(dev); unsigned char *ptr; - struct sk_buff *skb = dev_alloc_skb(1); + struct sk_buff *skb = __dev_alloc_skb(1, GFP_ATOMIC | __GFP_NOMEMALLOC); - if (!skb) { - pr_err("out of memory\n"); + if (!skb) return; - } ptr = skb_put(skb, 1); *ptr = X25_IFACE_DISCONNECT; skb->protocol = x25_type_trans(skb, dev); - netif_rx(skb); + + skb_queue_tail(&lapbeth->rx_queue, skb); + napi_schedule(&lapbeth->napi); } -/* - * Set AX.25 callsign +/* Set AX.25 callsign */ static int lapbeth_set_mac_address(struct net_device *dev, void *addr) { struct sockaddr *sa = addr; - memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); + + dev_addr_set(dev, sa->sa_data); return 0; } - static const struct lapb_register_struct lapbeth_callbacks = { .connect_confirmation = lapbeth_connected, .connect_indication = lapbeth_connected, @@ -261,31 +315,44 @@ static const struct lapb_register_struct lapbeth_callbacks = { .data_transmit = lapbeth_data_transmit, }; -/* - * open/close a device +/* open/close a device */ static int lapbeth_open(struct net_device *dev) { + struct lapbethdev *lapbeth = netdev_priv(dev); int err; - if ((err = lapb_register(dev, &lapbeth_callbacks)) != LAPB_OK) { + napi_enable(&lapbeth->napi); + + err = lapb_register(dev, &lapbeth_callbacks); + if (err != LAPB_OK) { + napi_disable(&lapbeth->napi); pr_err("lapb_register error: %d\n", err); return -ENODEV; } - netif_start_queue(dev); + spin_lock_bh(&lapbeth->up_lock); + lapbeth->up = true; + spin_unlock_bh(&lapbeth->up_lock); + return 0; } static int lapbeth_close(struct net_device *dev) { + struct lapbethdev *lapbeth = netdev_priv(dev); int err; - netif_stop_queue(dev); + spin_lock_bh(&lapbeth->up_lock); + lapbeth->up = false; + spin_unlock_bh(&lapbeth->up_lock); - if ((err = lapb_unregister(dev)) != LAPB_OK) + err = lapb_unregister(dev); + if (err != LAPB_OK) pr_err("lapb_unregister error: %d\n", err); + napi_disable(&lapbeth->napi); + return 0; } @@ -300,16 +367,16 @@ static const struct net_device_ops lapbeth_netdev_ops = { static void lapbeth_setup(struct net_device *dev) { + netdev_lockdep_set_classes(dev); dev->netdev_ops = &lapbeth_netdev_ops; dev->needs_free_netdev = true; dev->type = ARPHRD_X25; - dev->hard_header_len = 3; + dev->hard_header_len = 0; dev->mtu = 1000; dev->addr_len = 0; } -/* - * Setup a new device. +/* Setup a new device. */ static int lapbeth_new_device(struct net_device *dev) { @@ -319,17 +386,36 @@ static int lapbeth_new_device(struct net_device *dev) ASSERT_RTNL(); + if (dev->type != ARPHRD_ETHER) + return -EINVAL; + ndev = alloc_netdev(sizeof(*lapbeth), "lapb%d", NET_NAME_UNKNOWN, lapbeth_setup); if (!ndev) goto out; + /* When transmitting data: + * first this driver removes a pseudo header of 1 byte, + * then the lapb module prepends an LAPB header of at most 3 bytes, + * then this driver prepends a length field of 2 bytes, + * then the underlying Ethernet device prepends its own header. + */ + ndev->needed_headroom = -1 + 3 + 2 + dev->hard_header_len + + dev->needed_headroom; + ndev->needed_tailroom = dev->needed_tailroom; + lapbeth = netdev_priv(ndev); lapbeth->axdev = ndev; dev_hold(dev); lapbeth->ethdev = dev; + lapbeth->up = false; + spin_lock_init(&lapbeth->up_lock); + + skb_queue_head_init(&lapbeth->rx_queue); + netif_napi_add_weight(ndev, &lapbeth->napi, lapbeth_napi_poll, 16); + rc = -EIO; if (register_netdevice(ndev)) goto fail; @@ -344,8 +430,7 @@ fail: goto out; } -/* - * Free a lapb network device. +/* Free a lapb network device. */ static void lapbeth_free_device(struct lapbethdev *lapbeth) { @@ -354,8 +439,7 @@ static void lapbeth_free_device(struct lapbethdev *lapbeth) unregister_netdevice(lapbeth->axdev); } -/* - * Handle device status changes. +/* Handle device status changes. * * Called from notifier with RTNL held. */ @@ -368,19 +452,19 @@ static int lapbeth_device_event(struct notifier_block *this, if (dev_net(dev) != &init_net) return NOTIFY_DONE; - if (!dev_is_ethdev(dev)) + if (!dev_is_ethdev(dev) && !lapbeth_get_x25_dev(dev)) return NOTIFY_DONE; switch (event) { case NETDEV_UP: /* New ethernet device -> new LAPB interface */ - if (lapbeth_get_x25_dev(dev) == NULL) + if (!lapbeth_get_x25_dev(dev)) lapbeth_new_device(dev); break; - case NETDEV_DOWN: - /* ethernet device closed -> close LAPB interface */ + case NETDEV_GOING_DOWN: + /* ethernet device closes -> close LAPB interface */ lapbeth = lapbeth_get_x25_dev(dev); - if (lapbeth) + if (lapbeth) dev_close(lapbeth->axdev); break; case NETDEV_UNREGISTER: |
