blob: 4d38d0bce09380d12df3c339ccb51539eeba6653 [file] [log] [blame]
/*
* Copyright (C) 2003-2009 Chelsio Communications. All rights reserved.
*
* Written by Divy Le Ray (divy@chelsio.com)
*
* 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 LICENSE file included in this
* release for licensing terms and conditions.
*/
#include "defs.h"
#include <linux/toedev.h>
#include "tom.h"
#include "cpl_io_state.h"
#include "t3_cpl.h"
#include "cxgb3_ctl_defs.h"
#include "firmware_exports.h"
#include <drivers/net/bonding/bonding.h>
/* Adapted from drivers/net/bonding/bond_3ad.c:__get_bond_by_port() */
static inline struct bonding *toe_bond_get_bond_by_port(struct port *port)
{
if (port->slave == NULL)
return NULL;
return bond_get_bond_by_slave(port->slave);
}
/* Adapted from drivers/net/bonding/bond_3ad.c:__get_first_port() */
static inline struct port *toe_bond_get_first_port(struct bonding *bond)
{
if (bond->slave_cnt == 0)
return NULL;
return &(SLAVE_AD_INFO(bond->first_slave).port);
}
/* Adapted from drivers/net/bonding/bond_3ad.c:__get_next_port() */
static inline struct port *toe_bond_get_next_port(struct port *port)
{
struct bonding *bond = toe_bond_get_bond_by_port(port);
struct slave *slave = port->slave;
/* If there's no bond for this port, or this is the last slave */
if ((bond == NULL) || (slave->next == bond->first_slave))
return NULL;
return &(SLAVE_AD_INFO(slave->next).port);
}
static inline int total_ports(struct toedev *tdev)
{
struct adap_ports *port_info = TOM_DATA(tdev)->ports;
return port_info->nports;
}
static inline int lookup_port(struct net_device *slave_dev)
{
int i, port = -1;
struct toedev *tdev = TOEDEV(slave_dev);
struct adap_ports *port_info = TOM_DATA(tdev)->ports;
for (i = 0; i < port_info->nports; i++) {
if (slave_dev != port_info->lldevs[i])
continue;
port = i;
break;
}
return port;
}
static inline int lld_evt(int event)
{
return event + FAILOVER_ACTIVE_SLAVE;
}
static void four_ports_failover(struct toedev *tdev,
struct net_device *bond_dev,
struct net_device *slave_dev,
int event)
{
struct bond_ports bond_ports;
struct bonding *bond = (struct bonding *)netdev_priv(bond_dev);
struct slave *slave = NULL;
int port_idx, idx = 0, i;
struct port *port;
if (!slave_dev) /* bond release all */
return;
switch (bond->params.mode) {
case BOND_MODE_ACTIVEBACKUP:
/*
* Ignore release events.
* A new active slave might be picked up
* soon after, we just care about this.
*/
if (event != TOE_ACTIVE_SLAVE)
return;
port_idx = lookup_port(slave_dev);
bond_ports.port = port_idx;
bond_ports.nports = 0;
bond_for_each_slave(bond, slave, i) {
if (slave->dev != slave_dev) {
bond_ports.ports[idx++] =
lookup_port(slave->dev);
bond_ports.nports++;
}
}
tdev->ctl(tdev, lld_evt(event), &bond_ports);
break;
case BOND_MODE_8023AD:
if (event == TOE_ACTIVE_SLAVE)
return;
port_idx = lookup_port(slave_dev);
bond_ports.port = port_idx;
bond_ports.nports = 0;
for (port = toe_bond_get_first_port(bond); port;
port = toe_bond_get_next_port(port)) {
if (port->slave->dev != slave_dev &&
port->slave->state == BOND_STATE_ACTIVE) {
bond_ports.ports[idx++] =
lookup_port(port->slave->dev);
bond_ports.nports++;
}
}
tdev->ctl(tdev, lld_evt(event), &bond_ports);
break;
case BOND_MODE_XOR:
port_idx = lookup_port(slave_dev);
bond_ports.port = port_idx;
bond_ports.nports = 0;
bond_for_each_slave(bond, slave, i) {
if (slave->dev != slave_dev &&
slave->state == BOND_STATE_ACTIVE) {
bond_ports.ports[idx++] =
lookup_port(slave->dev);
bond_ports.nports++;
}
}
tdev->ctl(tdev, lld_evt(event), &bond_ports);
break;
}
return;
}
static void failover(struct toedev *tdev,
struct net_device *bond_dev,
struct net_device *slave_dev,
int event)
{
int failed_port, if_port = 0;
if (event == TOE_LINK_DOWN) {
failed_port = lookup_port(slave_dev);
if_port = !failed_port;
tdev->ctl(tdev, FAILOVER, &if_port);
} else if (event == TOE_LINK_UP) {
if_port = lookup_port(slave_dev);
tdev->ctl(tdev, FAILOVER, &if_port);
}
return;
}
/* Called under bonding locks (bond_mii_monitor) */
int t3_failover(struct toedev *tdev, struct net_device *bond_dev,
struct net_device *slave_dev, int event, struct net_device *last_dev)
{
struct bonding *bond = (struct bonding *)netdev_priv(bond_dev);
int active_ports = 0;
struct port *port;
int if_port;
/* differentiate 4 ports and 2 ports adapters */
if (tdev->nlldev > 2) {
four_ports_failover(tdev, bond_dev, slave_dev, event);
return 0;
}
/* Last slave removed. Map the event to a complete release */
if (event == TOE_RELEASE && bond->slave_cnt == 1)
event = TOE_RELEASE_ALL;
switch (bond->params.mode) {
case BOND_MODE_ACTIVEBACKUP:
if (event == TOE_ACTIVE_SLAVE) {
if (!slave_dev || bond->slave_cnt == 1)
tdev->ctl(tdev, FAILOVER_CLEAR, NULL);
else {
if_port = lookup_port(slave_dev);
tdev->ctl(tdev, FAILOVER, &if_port);
}
} else if (event == TOE_RELEASE_ALL)
tdev->ctl(tdev, FAILOVER_CLEAR, NULL);
break;
case BOND_MODE_8023AD:
if (event == TOE_ACTIVE_SLAVE)
return 0;
for (port = toe_bond_get_first_port(bond); port;
port = toe_bond_get_next_port(port))
active_ports +=
(port->slave->state == BOND_STATE_ACTIVE);
/* One port enslaved only. Ignore failover events */
if (bond->slave_cnt == 1)
return 0;
/* No more active port */
if ((event == TOE_LINK_DOWN || event == TOE_RELEASE) &&
!active_ports) {
tdev->ctl(tdev, FAILOVER_CLEAR, NULL);
return 0;
}
/* Dead port back alive in a already active bond device */
if (event == TOE_LINK_UP && active_ports > 1) {
if_port = lookup_port(slave_dev);
tdev->ctl(tdev, FAILOVER_DONE, &if_port);
return 0;
}
/* fall through */
case BOND_MODE_XOR:
/* One port enslaved only. Ignore failover events */
if (bond->slave_cnt == 1)
return 0;
failover(tdev, bond_dev, slave_dev, event);
}
return 0;
}
void t3_update_master_devs(struct toedev *tdev)
{
int i;
for (i = 0; i < tdev->nlldev; i++) {
struct net_device *dev = tdev->lldev[i];
if (dev->flags & IFF_SLAVE) {
struct net_device *bond_dev = dev->master;
struct bonding *bond = (struct bonding *)netdev_priv(bond_dev);
struct toedev *slave_tdev = NULL;
struct slave *slave;
int i, ofld_cnt = 0;
if (netdev_is_offload(bond_dev))
continue;
read_lock_bh(&bond->lock);
bond_for_each_slave(bond, slave, i) {
ofld_cnt += !!netdev_is_offload(slave->dev);
if (!slave_tdev)
slave_tdev = TOEDEV(slave->dev);
else if (slave_tdev != TOEDEV(slave->dev)) {
slave_tdev = NULL;
break;
}
}
read_unlock_bh(&bond->lock);
if (ofld_cnt == bond->slave_cnt && slave_tdev)
netdev_set_offload(bond_dev);
}
}
}