mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
e5ed639913
The following patch renames __in_dev_get() to __in_dev_get_rtnl() and introduces __in_dev_get_rcu() to cover the second case. 1) RCU with refcnt should use in_dev_get(). 2) RCU without refcnt should use __in_dev_get_rcu(). 3) All others must hold RTNL and use __in_dev_get_rtnl(). There is one exception in net/ipv4/route.c which is in fact a pre-existing race condition. I've marked it as such so that we remember to fix it. This patch is based on suggestions and prior work by Suzanne Wood and Paul McKenney. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
388 lines
10 KiB
C
388 lines
10 KiB
C
/*********************************************************************
|
|
*
|
|
* Filename: irlan_eth.c
|
|
* Version:
|
|
* Description:
|
|
* Status: Experimental.
|
|
* Author: Dag Brattli <dagb@cs.uit.no>
|
|
* Created at: Thu Oct 15 08:37:58 1998
|
|
* Modified at: Tue Mar 21 09:06:41 2000
|
|
* Modified by: Dag Brattli <dagb@cs.uit.no>
|
|
* Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov>
|
|
* slip.c by Laurence Culhane, <loz@holmes.demon.co.uk>
|
|
* Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
|
|
*
|
|
* Copyright (c) 1998-2000 Dag Brattli, All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* Neither Dag Brattli nor University of Tromsø admit liability nor
|
|
* provide warranty for any of this software. This material is
|
|
* provided "AS-IS" and at no charge.
|
|
*
|
|
********************************************************************/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/inetdevice.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/module.h>
|
|
#include <net/arp.h>
|
|
|
|
#include <net/irda/irda.h>
|
|
#include <net/irda/irmod.h>
|
|
#include <net/irda/irlan_common.h>
|
|
#include <net/irda/irlan_client.h>
|
|
#include <net/irda/irlan_event.h>
|
|
#include <net/irda/irlan_eth.h>
|
|
|
|
static int irlan_eth_open(struct net_device *dev);
|
|
static int irlan_eth_close(struct net_device *dev);
|
|
static int irlan_eth_xmit(struct sk_buff *skb, struct net_device *dev);
|
|
static void irlan_eth_set_multicast_list( struct net_device *dev);
|
|
static struct net_device_stats *irlan_eth_get_stats(struct net_device *dev);
|
|
|
|
/*
|
|
* Function irlan_eth_setup (dev)
|
|
*
|
|
* The network device initialization function.
|
|
*
|
|
*/
|
|
static void irlan_eth_setup(struct net_device *dev)
|
|
{
|
|
dev->open = irlan_eth_open;
|
|
dev->stop = irlan_eth_close;
|
|
dev->hard_start_xmit = irlan_eth_xmit;
|
|
dev->get_stats = irlan_eth_get_stats;
|
|
dev->set_multicast_list = irlan_eth_set_multicast_list;
|
|
dev->destructor = free_netdev;
|
|
|
|
SET_MODULE_OWNER(dev);
|
|
|
|
ether_setup(dev);
|
|
|
|
/*
|
|
* Lets do all queueing in IrTTP instead of this device driver.
|
|
* Queueing here as well can introduce some strange latency
|
|
* problems, which we will avoid by setting the queue size to 0.
|
|
*/
|
|
/*
|
|
* The bugs in IrTTP and IrLAN that created this latency issue
|
|
* have now been fixed, and we can propagate flow control properly
|
|
* to the network layer. However, this requires a minimal queue of
|
|
* packets for the device.
|
|
* Without flow control, the Tx Queue is 14 (ttp) + 0 (dev) = 14
|
|
* With flow control, the Tx Queue is 7 (ttp) + 4 (dev) = 11
|
|
* See irlan_eth_flow_indication()...
|
|
* Note : this number was randomly selected and would need to
|
|
* be adjusted.
|
|
* Jean II */
|
|
dev->tx_queue_len = 4;
|
|
}
|
|
|
|
/*
|
|
* Function alloc_irlandev
|
|
*
|
|
* Allocate network device and control block
|
|
*
|
|
*/
|
|
struct net_device *alloc_irlandev(const char *name)
|
|
{
|
|
return alloc_netdev(sizeof(struct irlan_cb), name,
|
|
irlan_eth_setup);
|
|
}
|
|
|
|
/*
|
|
* Function irlan_eth_open (dev)
|
|
*
|
|
* Network device has been opened by user
|
|
*
|
|
*/
|
|
static int irlan_eth_open(struct net_device *dev)
|
|
{
|
|
struct irlan_cb *self = netdev_priv(dev);
|
|
|
|
IRDA_DEBUG(2, "%s()\n", __FUNCTION__ );
|
|
|
|
/* Ready to play! */
|
|
netif_stop_queue(dev); /* Wait until data link is ready */
|
|
|
|
/* We are now open, so time to do some work */
|
|
self->disconnect_reason = 0;
|
|
irlan_client_wakeup(self, self->saddr, self->daddr);
|
|
|
|
/* Make sure we have a hardware address before we return,
|
|
so DHCP clients gets happy */
|
|
return wait_event_interruptible(self->open_wait,
|
|
!self->tsap_data->connected);
|
|
}
|
|
|
|
/*
|
|
* Function irlan_eth_close (dev)
|
|
*
|
|
* Stop the ether network device, his function will usually be called by
|
|
* ifconfig down. We should now disconnect the link, We start the
|
|
* close timer, so that the instance will be removed if we are unable
|
|
* to discover the remote device after the disconnect.
|
|
*/
|
|
static int irlan_eth_close(struct net_device *dev)
|
|
{
|
|
struct irlan_cb *self = netdev_priv(dev);
|
|
|
|
IRDA_DEBUG(2, "%s()\n", __FUNCTION__ );
|
|
|
|
/* Stop device */
|
|
netif_stop_queue(dev);
|
|
|
|
irlan_close_data_channel(self);
|
|
irlan_close_tsaps(self);
|
|
|
|
irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL);
|
|
irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL);
|
|
|
|
/* Remove frames queued on the control channel */
|
|
skb_queue_purge(&self->client.txq);
|
|
|
|
self->client.tx_busy = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function irlan_eth_tx (skb)
|
|
*
|
|
* Transmits ethernet frames over IrDA link.
|
|
*
|
|
*/
|
|
static int irlan_eth_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
{
|
|
struct irlan_cb *self = netdev_priv(dev);
|
|
int ret;
|
|
|
|
/* skb headroom large enough to contain all IrDA-headers? */
|
|
if ((skb_headroom(skb) < self->max_header_size) || (skb_shared(skb))) {
|
|
struct sk_buff *new_skb =
|
|
skb_realloc_headroom(skb, self->max_header_size);
|
|
|
|
/* We have to free the original skb anyway */
|
|
dev_kfree_skb(skb);
|
|
|
|
/* Did the realloc succeed? */
|
|
if (new_skb == NULL)
|
|
return 0;
|
|
|
|
/* Use the new skb instead */
|
|
skb = new_skb;
|
|
}
|
|
|
|
dev->trans_start = jiffies;
|
|
|
|
/* Now queue the packet in the transport layer */
|
|
if (self->use_udata)
|
|
ret = irttp_udata_request(self->tsap_data, skb);
|
|
else
|
|
ret = irttp_data_request(self->tsap_data, skb);
|
|
|
|
if (ret < 0) {
|
|
/*
|
|
* IrTTPs tx queue is full, so we just have to
|
|
* drop the frame! You might think that we should
|
|
* just return -1 and don't deallocate the frame,
|
|
* but that is dangerous since it's possible that
|
|
* we have replaced the original skb with a new
|
|
* one with larger headroom, and that would really
|
|
* confuse do_dev_queue_xmit() in dev.c! I have
|
|
* tried :-) DB
|
|
*/
|
|
/* irttp_data_request already free the packet */
|
|
self->stats.tx_dropped++;
|
|
} else {
|
|
self->stats.tx_packets++;
|
|
self->stats.tx_bytes += skb->len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function irlan_eth_receive (handle, skb)
|
|
*
|
|
* This function gets the data that is received on the data channel
|
|
*
|
|
*/
|
|
int irlan_eth_receive(void *instance, void *sap, struct sk_buff *skb)
|
|
{
|
|
struct irlan_cb *self = instance;
|
|
|
|
if (skb == NULL) {
|
|
++self->stats.rx_dropped;
|
|
return 0;
|
|
}
|
|
if (skb->len < ETH_HLEN) {
|
|
IRDA_DEBUG(0, "%s() : IrLAN frame too short (%d)\n",
|
|
__FUNCTION__, skb->len);
|
|
++self->stats.rx_dropped;
|
|
dev_kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Adopt this frame! Important to set all these fields since they
|
|
* might have been previously set by the low level IrDA network
|
|
* device driver
|
|
*/
|
|
skb->dev = self->dev;
|
|
skb->protocol=eth_type_trans(skb, skb->dev); /* Remove eth header */
|
|
|
|
self->stats.rx_packets++;
|
|
self->stats.rx_bytes += skb->len;
|
|
|
|
netif_rx(skb); /* Eat it! */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function irlan_eth_flow (status)
|
|
*
|
|
* Do flow control between IP/Ethernet and IrLAN/IrTTP. This is done by
|
|
* controlling the queue stop/start.
|
|
*
|
|
* The IrDA link layer has the advantage to have flow control, and
|
|
* IrTTP now properly handles that. Flow controlling the higher layers
|
|
* prevent us to drop Tx packets in here (up to 15% for a TCP socket,
|
|
* more for UDP socket).
|
|
* Also, this allow us to reduce the overall transmit queue, which means
|
|
* less latency in case of mixed traffic.
|
|
* Jean II
|
|
*/
|
|
void irlan_eth_flow_indication(void *instance, void *sap, LOCAL_FLOW flow)
|
|
{
|
|
struct irlan_cb *self;
|
|
struct net_device *dev;
|
|
|
|
self = (struct irlan_cb *) instance;
|
|
|
|
IRDA_ASSERT(self != NULL, return;);
|
|
IRDA_ASSERT(self->magic == IRLAN_MAGIC, return;);
|
|
|
|
dev = self->dev;
|
|
|
|
IRDA_ASSERT(dev != NULL, return;);
|
|
|
|
IRDA_DEBUG(0, "%s() : flow %s ; running %d\n", __FUNCTION__,
|
|
flow == FLOW_STOP ? "FLOW_STOP" : "FLOW_START",
|
|
netif_running(dev));
|
|
|
|
switch (flow) {
|
|
case FLOW_STOP:
|
|
/* IrTTP is full, stop higher layers */
|
|
netif_stop_queue(dev);
|
|
break;
|
|
case FLOW_START:
|
|
default:
|
|
/* Tell upper layers that its time to transmit frames again */
|
|
/* Schedule network layer */
|
|
netif_wake_queue(dev);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function irlan_etc_send_gratuitous_arp (dev)
|
|
*
|
|
* Send gratuitous ARP to announce that we have changed
|
|
* hardware address, so that all peers updates their ARP tables
|
|
*/
|
|
void irlan_eth_send_gratuitous_arp(struct net_device *dev)
|
|
{
|
|
struct in_device *in_dev;
|
|
|
|
/*
|
|
* When we get a new MAC address do a gratuitous ARP. This
|
|
* is useful if we have changed access points on the same
|
|
* subnet.
|
|
*/
|
|
#ifdef CONFIG_INET
|
|
IRDA_DEBUG(4, "IrLAN: Sending gratuitous ARP\n");
|
|
rcu_read_lock();
|
|
in_dev = __in_dev_get_rcu(dev);
|
|
if (in_dev == NULL)
|
|
goto out;
|
|
if (in_dev->ifa_list)
|
|
|
|
arp_send(ARPOP_REQUEST, ETH_P_ARP,
|
|
in_dev->ifa_list->ifa_address,
|
|
dev,
|
|
in_dev->ifa_list->ifa_address,
|
|
NULL, dev->dev_addr, NULL);
|
|
out:
|
|
rcu_read_unlock();
|
|
#endif /* CONFIG_INET */
|
|
}
|
|
|
|
/*
|
|
* Function set_multicast_list (dev)
|
|
*
|
|
* Configure the filtering of the device
|
|
*
|
|
*/
|
|
#define HW_MAX_ADDRS 4 /* Must query to get it! */
|
|
static void irlan_eth_set_multicast_list(struct net_device *dev)
|
|
{
|
|
struct irlan_cb *self = netdev_priv(dev);
|
|
|
|
IRDA_DEBUG(2, "%s()\n", __FUNCTION__ );
|
|
|
|
/* Check if data channel has been connected yet */
|
|
if (self->client.state != IRLAN_DATA) {
|
|
IRDA_DEBUG(1, "%s(), delaying!\n", __FUNCTION__ );
|
|
return;
|
|
}
|
|
|
|
if (dev->flags & IFF_PROMISC) {
|
|
/* Enable promiscuous mode */
|
|
IRDA_WARNING("Promiscous mode not implemented by IrLAN!\n");
|
|
}
|
|
else if ((dev->flags & IFF_ALLMULTI) || dev->mc_count > HW_MAX_ADDRS) {
|
|
/* Disable promiscuous mode, use normal mode. */
|
|
IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__ );
|
|
/* hardware_set_filter(NULL); */
|
|
|
|
irlan_set_multicast_filter(self, TRUE);
|
|
}
|
|
else if (dev->mc_count) {
|
|
IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__ );
|
|
/* Walk the address list, and load the filter */
|
|
/* hardware_set_filter(dev->mc_list); */
|
|
|
|
irlan_set_multicast_filter(self, TRUE);
|
|
}
|
|
else {
|
|
IRDA_DEBUG(4, "%s(), Clearing multicast filter\n", __FUNCTION__ );
|
|
irlan_set_multicast_filter(self, FALSE);
|
|
}
|
|
|
|
if (dev->flags & IFF_BROADCAST)
|
|
irlan_set_broadcast_filter(self, TRUE);
|
|
else
|
|
irlan_set_broadcast_filter(self, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Function irlan_get_stats (dev)
|
|
*
|
|
* Get the current statistics for this device
|
|
*
|
|
*/
|
|
static struct net_device_stats *irlan_eth_get_stats(struct net_device *dev)
|
|
{
|
|
struct irlan_cb *self = netdev_priv(dev);
|
|
|
|
return &self->stats;
|
|
}
|