Merge branch 'bridge_external_fdb_aging'

Scott Feldman says:

====================
bridge: don't age out externally added FDB entries

v3: Per davem review: add del_timer_sync on rocker port remove.

v2: Per Jiri review comment: add BR_DEFAULT_AGEING_TIME to defines

Siva originally proposed skipping externally added FDB entries in the bridge's
FDB garbage collection func, and moving the ageing of externally added entries
to the port driver/device.  This broke rocker, since rocker didn't have a
hardware (or software) mechanism for ageing out its learned FDB entries.

This patchset reintroduces Siva's bridge driver patch to skip externally added
entries and adds support in rocker so rocker can age out its own entries.
Rocker does this using a software timer similar to the bridge's FDB garbage
collection timer.  Other switchdev devices/drivers can use this software timer
method or program the device to nofity aged-out entries to the driver.

Updated switchdev.txt documentation to reflect current state-of-the-art.  This
removes one more XXX todo comment in switchdev.txt.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2015-09-23 14:35:59 -07:00
Родитель 34ea90ef72 45ffda75e1
Коммит 16cfbae160
5 изменённых файлов: 80 добавлений и 22 удалений

Просмотреть файл

@ -239,20 +239,20 @@ The driver should initialize the attributes to the hardware defaults.
FDB Ageing
^^^^^^^^^^
There are two FDB ageing models supported: 1) ageing by the device, and 2)
ageing by the kernel. Ageing by the device is preferred if many FDB entries
are supported. The driver calls call_switchdev_notifiers(SWITCHDEV_FDB_DEL,
...) to age out the FDB entry. In this model, ageing by the kernel should be
turned off. XXX: how to turn off ageing in kernel on a per-port basis or
otherwise prevent the kernel from ageing out the FDB entry?
The bridge will skip ageing FDB entries marked with NTF_EXT_LEARNED and it is
the responsibility of the port driver/device to age out these entries. If the
port device supports ageing, when the FDB entry expires, it will notify the
driver which in turn will notify the bridge with SWITCHDEV_FDB_DEL. If the
device does not support ageing, the driver can simulate ageing using a
garbage collection timer to monitor FBD entries. Expired entries will be
notified to the bridge using SWITCHDEV_FDB_DEL. See rocker driver for
example of driver running ageing timer.
In the kernel ageing model, the standard bridge ageing mechanism is used to age
out stale FDB entries. To keep an FDB entry "alive", the driver should refresh
the FDB entry by calling call_switchdev_notifiers(SWITCHDEV_FDB_ADD, ...). The
To keep an NTF_EXT_LEARNED entry "alive", the driver should refresh the FDB
entry by calling call_switchdev_notifiers(SWITCHDEV_FDB_ADD, ...). The
notification will reset the FDB entry's last-used time to now. The driver
should rate limit refresh notifications, for example, no more than once a
second. If the FDB entry expires, fdb_delete is called to remove entry from
the device.
second. (The last-used time is visible using the bridge -s fdb option).
STP State Change on Port
^^^^^^^^^^^^^^^^^^^^^^^^

Просмотреть файл

@ -152,8 +152,9 @@ struct rocker_fdb_tbl_entry {
struct hlist_node entry;
u32 key_crc32; /* key */
bool learned;
unsigned long touched;
struct rocker_fdb_tbl_key {
u32 pport;
struct rocker_port *rocker_port;
u8 addr[ETH_ALEN];
__be16 vlan_id;
} key;
@ -220,6 +221,7 @@ struct rocker_port {
__be16 internal_vlan_id;
int stp_state;
u32 brport_flags;
unsigned long ageing_time;
bool ctrls[ROCKER_CTRL_MAX];
unsigned long vlan_bitmap[ROCKER_VLAN_BITMAP_LEN];
struct napi_struct napi_tx;
@ -246,6 +248,7 @@ struct rocker {
u64 flow_tbl_next_cookie;
DECLARE_HASHTABLE(group_tbl, 16);
spinlock_t group_tbl_lock; /* for group tbl accesses */
struct timer_list fdb_cleanup_timer;
DECLARE_HASHTABLE(fdb_tbl, 16);
spinlock_t fdb_tbl_lock; /* for fdb tbl accesses */
unsigned long internal_vlan_bitmap[ROCKER_INTERNAL_VLAN_BITMAP_LEN];
@ -3629,7 +3632,8 @@ static int rocker_port_fdb(struct rocker_port *rocker_port,
return -ENOMEM;
fdb->learned = (flags & ROCKER_OP_FLAG_LEARNED);
fdb->key.pport = rocker_port->pport;
fdb->touched = jiffies;
fdb->key.rocker_port = rocker_port;
ether_addr_copy(fdb->key.addr, addr);
fdb->key.vlan_id = vlan_id;
fdb->key_crc32 = crc32(~0, &fdb->key, sizeof(fdb->key));
@ -3638,13 +3642,17 @@ static int rocker_port_fdb(struct rocker_port *rocker_port,
found = rocker_fdb_tbl_find(rocker, fdb);
if (removing && found) {
rocker_port_kfree(trans, fdb);
if (found) {
found->touched = jiffies;
if (removing) {
rocker_port_kfree(trans, fdb);
if (trans != SWITCHDEV_TRANS_PREPARE)
hash_del(&found->entry);
}
} else if (!removing) {
if (trans != SWITCHDEV_TRANS_PREPARE)
hash_del(&found->entry);
} else if (!removing && !found) {
if (trans != SWITCHDEV_TRANS_PREPARE)
hash_add(rocker->fdb_tbl, &fdb->entry, fdb->key_crc32);
hash_add(rocker->fdb_tbl, &fdb->entry,
fdb->key_crc32);
}
spin_unlock_irqrestore(&rocker->fdb_tbl_lock, lock_flags);
@ -3680,7 +3688,7 @@ static int rocker_port_fdb_flush(struct rocker_port *rocker_port,
spin_lock_irqsave(&rocker->fdb_tbl_lock, lock_flags);
hash_for_each_safe(rocker->fdb_tbl, bkt, tmp, found, entry) {
if (found->key.pport != rocker_port->pport)
if (found->key.rocker_port != rocker_port)
continue;
if (!found->learned)
continue;
@ -3699,6 +3707,41 @@ err_out:
return err;
}
static void rocker_fdb_cleanup(unsigned long data)
{
struct rocker *rocker = (struct rocker *)data;
struct rocker_port *rocker_port;
struct rocker_fdb_tbl_entry *entry;
struct hlist_node *tmp;
unsigned long next_timer = jiffies + BR_MIN_AGEING_TIME;
unsigned long expires;
unsigned long lock_flags;
int flags = ROCKER_OP_FLAG_NOWAIT | ROCKER_OP_FLAG_REMOVE |
ROCKER_OP_FLAG_LEARNED;
int bkt;
spin_lock_irqsave(&rocker->fdb_tbl_lock, lock_flags);
hash_for_each_safe(rocker->fdb_tbl, bkt, tmp, entry, entry) {
if (!entry->learned)
continue;
rocker_port = entry->key.rocker_port;
expires = entry->touched + rocker_port->ageing_time;
if (time_before_eq(expires, jiffies)) {
rocker_port_fdb_learn(rocker_port, SWITCHDEV_TRANS_NONE,
flags, entry->key.addr,
entry->key.vlan_id);
hash_del(&entry->entry);
} else if (time_before(expires, next_timer)) {
next_timer = expires;
}
}
spin_unlock_irqrestore(&rocker->fdb_tbl_lock, lock_flags);
mod_timer(&rocker->fdb_cleanup_timer, round_jiffies_up(next_timer));
}
static int rocker_port_router_mac(struct rocker_port *rocker_port,
enum switchdev_trans trans, int flags,
__be16 vlan_id)
@ -4547,7 +4590,7 @@ static int rocker_port_fdb_dump(const struct rocker_port *rocker_port,
spin_lock_irqsave(&rocker->fdb_tbl_lock, lock_flags);
hash_for_each_safe(rocker->fdb_tbl, bkt, tmp, found, entry) {
if (found->key.pport != rocker_port->pport)
if (found->key.rocker_port != rocker_port)
continue;
fdb->addr = found->key.addr;
fdb->ndm_state = NUD_REACHABLE;
@ -4969,6 +5012,7 @@ static int rocker_probe_port(struct rocker *rocker, unsigned int port_number)
rocker_port->port_number = port_number;
rocker_port->pport = port_number + 1;
rocker_port->brport_flags = BR_LEARNING | BR_LEARNING_SYNC;
rocker_port->ageing_time = BR_DEFAULT_AGEING_TIME;
INIT_LIST_HEAD(&rocker_port->trans_mem);
rocker_port_dev_addr_init(rocker_port);
@ -5183,6 +5227,10 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_init_tbls;
}
setup_timer(&rocker->fdb_cleanup_timer, rocker_fdb_cleanup,
(unsigned long) rocker);
mod_timer(&rocker->fdb_cleanup_timer, jiffies);
err = rocker_probe_ports(rocker);
if (err) {
dev_err(&pdev->dev, "failed to probe ports\n");
@ -5195,6 +5243,7 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return 0;
err_probe_ports:
del_timer_sync(&rocker->fdb_cleanup_timer);
rocker_free_tbls(rocker);
err_init_tbls:
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_EVENT), rocker);
@ -5222,6 +5271,7 @@ static void rocker_remove(struct pci_dev *pdev)
{
struct rocker *rocker = pci_get_drvdata(pdev);
del_timer_sync(&rocker->fdb_cleanup_timer);
rocker_free_tbls(rocker);
rocker_write32(rocker, CONTROL, ROCKER_CONTROL_RESET);
rocker_remove_ports(rocker);

Просмотреть файл

@ -46,6 +46,12 @@ struct br_ip_list {
#define BR_LEARNING_SYNC BIT(9)
#define BR_PROXYARP_WIFI BIT(10)
/* values as per ieee8021QBridgeFdbAgingTime */
#define BR_MIN_AGEING_TIME (10 * HZ)
#define BR_MAX_AGEING_TIME (1000000 * HZ)
#define BR_DEFAULT_AGEING_TIME (300 * HZ)
extern void brioctl_set(int (*ioctl_hook)(struct net *, unsigned int, void __user *));
typedef int br_should_route_hook_t(struct sk_buff *skb);

Просмотреть файл

@ -391,7 +391,7 @@ void br_dev_setup(struct net_device *dev)
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
br->ageing_time = 300 * HZ;
br->ageing_time = BR_DEFAULT_AGEING_TIME;
br_netfilter_rtable_init(br);
br_stp_timer_init(br);

Просмотреть файл

@ -299,6 +299,8 @@ void br_fdb_cleanup(unsigned long _data)
unsigned long this_timer;
if (f->is_static)
continue;
if (f->added_by_external_learn)
continue;
this_timer = f->updated + delay;
if (time_before_eq(this_timer, jiffies))
fdb_delete(br, f);