From 478add0ea718dee38702c066907a510525964b48 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Tue, 7 Mar 2000 16:02:18 +0000 Subject: Rest of the merge. --- drivers/net/bonding.c | 350 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 drivers/net/bonding.c (limited to 'drivers/net') diff --git a/drivers/net/bonding.c b/drivers/net/bonding.c new file mode 100644 index 000000000..f1a4526d4 --- /dev/null +++ b/drivers/net/bonding.c @@ -0,0 +1,350 @@ +/* + * originally based on the dummy device. + * + * Copyright 1999, Thomas Davis, tadavis@lbl.gov. + * Licensed under the GPL. Based on dummy.c, and eql.c devices. + * + * bond.c: a bonding/etherchannel/sun trunking net driver + * + * This is useful to talk to a Cisco 5500, running Etherchannel, aka: + * Linux Channel Bonding + * Sun Trunking (Solaris) + * + * How it works: + * ifconfig bond0 ipaddress netmask up + * will setup a network device, with an ip address. No mac address + * will be assigned at this time. The hw mac address will come from + * the first slave bonded to the channel. All slaves will then use + * this hw mac address. + * + * ifconfig bond0 down + * will release all slaves, marking them as down. + * + * ifenslave bond0 eth0 + * will attache eth0 to bond0 as a slave. eth0 hw mac address will either + * a: be used as initial mac address + * b: if a hw mac address already is there, eth0's hw mac address + * will then be set from bond0. + * + * v0.1 - first working version. + * v0.2 - changed stats to be calculated by summing slaves stats. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +typedef struct slave +{ + struct slave *next; + struct slave *prev; + struct net_device *dev; +} slave_t; + +typedef struct bonding +{ + slave_t *next; + slave_t *prev; + struct net_device *master; + + slave_t *current_slave; + struct net_device_stats stats; +} bonding_t; + + +static int bond_xmit(struct sk_buff *skb, struct net_device *dev); +static struct net_device_stats *bond_get_stats(struct net_device *dev); + +static struct net_device *this_bond; + +static int bond_open(struct net_device *dev) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static int bond_close(struct net_device *master) +{ + bonding_t *bond = master->priv; + slave_t *slave; + + while ((slave = bond->next) != (slave_t*)bond) { + + spin_lock_bh(&master->xmit_lock); + slave->next->prev = slave->prev; + slave->prev->next = slave->next; + bond->current_slave = (slave_t*)bond; + spin_unlock_bh(&master->xmit_lock); + + netdev_set_master(slave->dev, NULL); + + kfree(slave); + } + + MOD_DEC_USE_COUNT; + return 0; +} + +/* Fake multicast ability. + + NB. It is possible and necessary to make it true one, otherwise + the device is not functional. + */ +static void bond_set_multicast_list(struct net_device *dev) +{ +} + +static int bond_enslave(struct net_device *master, struct net_device *dev) +{ + int err; + bonding_t *bond = master->priv; + slave_t *slave; + + if (dev->type != master->type) + return -ENODEV; + + if ((slave = kmalloc(sizeof(slave_t), GFP_KERNEL)) == NULL) + return -ENOMEM; + + memset(slave, 0, sizeof(slave_t)); + + err = netdev_set_master(dev, master); + if (err) { + kfree(slave); + return err; + } + + slave->dev = dev; + + spin_lock_bh(&master->xmit_lock); + + dev_hold(dev); + + slave->prev = bond->prev; + slave->next = (slave_t*)bond; + slave->prev->next = slave; + slave->next->prev = slave; + + spin_unlock_bh(&master->xmit_lock); + + MOD_INC_USE_COUNT; + return 0; +} + +static int bond_release(struct net_device *master, struct net_device *dev) +{ + bonding_t *bond = master->priv; + slave_t *slave; + + if (dev->master != master) + return -EINVAL; + + netdev_set_master(dev, NULL); + + for (slave = bond->next; slave != (slave_t*)bond; slave = slave->next) { + if (slave->dev == dev) { + spin_lock_bh(&master->xmit_lock); + if (bond->current_slave == slave) + bond->current_slave = slave->next; + slave->next->prev = slave->prev; + slave->prev->next = slave->next; + spin_unlock_bh(&master->xmit_lock); + + kfree(slave); + dev_put(dev); + MOD_DEC_USE_COUNT; + break; + } + } + + return 0; +} + +/* It is pretty silly, SIOCSIFHWADDR exists to make this. */ + +static int bond_sethwaddr(struct net_device *master, struct net_device *slave) +{ + memcpy(master->dev_addr, slave->dev_addr, slave->addr_len); + return 0; +} + +static int bond_ioctl(struct net_device *master, struct ifreq *ifr, int cmd) +{ + struct net_device *slave = __dev_get_by_name(ifr->ifr_slave); + + if (slave == NULL) + return -ENODEV; + + switch (cmd) { + case BOND_ENSLAVE: + return bond_enslave(master, slave); + case BOND_RELEASE: + return bond_release(master, slave); + case BOND_SETHWADDR: + return bond_sethwaddr(master, slave); + default: + return -EOPNOTSUPP; + } +} + +static int bond_event(struct notifier_block *this, unsigned long event, void *ptr) +{ + struct net_device *slave = ptr; + + if (this_bond == NULL || + this_bond == slave || + this_bond != slave->master) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UNREGISTER: + bond_release(this_bond, slave); + break; + } + + return NOTIFY_DONE; +} + +struct notifier_block bond_netdev_notifier={ + bond_event, + NULL, + 0 +}; + +int __init bond_init(struct net_device *dev) +{ + bonding_t *bond; + + bond = kmalloc(sizeof(struct bonding), GFP_KERNEL); + if (bond == NULL) + return -ENOMEM; + + memset(bond, 0, sizeof(struct bonding)); + bond->next = (slave_t*)bond; + bond->prev = (slave_t*)bond; + bond->master = dev; + bond->current_slave = (slave_t*)bond; + dev->priv = bond; + + /* Initialize the device structure. */ + dev->hard_start_xmit = bond_xmit; + dev->get_stats = bond_get_stats; + dev->open = bond_open; + dev->stop = bond_close; + dev->set_multicast_list = bond_set_multicast_list; + dev->do_ioctl = bond_ioctl; + + /* Fill in the fields of the device structure with ethernet-generic + values. */ + ether_setup(dev); + dev->tx_queue_len = 0; + dev->flags |= IFF_MASTER; + + this_bond = dev; + + register_netdevice_notifier(&bond_netdev_notifier); + + return 0; +} + +static int bond_xmit(struct sk_buff *skb, struct net_device *dev) +{ + bonding_t *bond = dev->priv; + slave_t *slave, *start_at; + int pkt_len = skb->len; + + slave = start_at = bond->current_slave; + + do { + if (slave == (slave_t*)bond) + continue; + + if (netif_running(slave->dev) && netif_carrier_ok(dev)) { + bond->current_slave = slave->next; + skb->dev = slave->dev; + + if (dev_queue_xmit(skb)) { + bond->stats.tx_dropped++; + } else { + bond->stats.tx_packets++; + bond->stats.tx_bytes += pkt_len; + } + return 0; + } + } while ((slave = slave->next) != start_at); + + bond->stats.tx_dropped++; + kfree_skb(skb); + return 0; +} + +static struct net_device_stats *bond_get_stats(struct net_device *dev) +{ + bonding_t *bond = dev->priv; + + return &bond->stats; +} + +#ifdef MODULE + +static char bond_name[16]; + +static struct net_device dev_bond = { + bond_name, /* Needs to be writeable */ + 0, 0, 0, 0, + 0x0, 0, + 0, 0, 0, NULL, bond_init }; + +int init_module(void) +{ + /* Find a name for this unit */ + int err=dev_alloc_name(&dev_bond,"bond%d"); + + if (err<0) + return err; + + if (register_netdev(&dev_bond) != 0) + return -EIO; + + return 0; +} + +void cleanup_module(void) +{ + unregister_netdevice_notifier(&bond_netdev_notifier); + + unregister_netdev(&dev_bond); + + kfree(dev_bond.priv); +} +#endif /* MODULE */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ -- cgit v1.2.3