From a471be41f2bfe4ee3a4b38b0e5af17d0abfd3aa6 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:14 -0700 Subject: [PATCH 1/7] rocker: track when FDB entry is touched. The entry is touched once when created, and touched again for each update. The touched time is used to calculate FDB entry age. Signed-off-by: Scott Feldman Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- drivers/net/ethernet/rocker/rocker.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c index 34ac41ac9e61..e517e9c355fa 100644 --- a/drivers/net/ethernet/rocker/rocker.c +++ b/drivers/net/ethernet/rocker/rocker.c @@ -152,6 +152,7 @@ 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; u8 addr[ETH_ALEN]; @@ -3629,6 +3630,7 @@ static int rocker_port_fdb(struct rocker_port *rocker_port, return -ENOMEM; fdb->learned = (flags & ROCKER_OP_FLAG_LEARNED); + fdb->touched = jiffies; fdb->key.pport = rocker_port->pport; ether_addr_copy(fdb->key.addr, addr); fdb->key.vlan_id = vlan_id; @@ -3638,13 +3640,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); From 4c660496c95af93f3461c06d46fd33c0719625db Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:15 -0700 Subject: [PATCH 2/7] rocker: store rocker_port in fdb key rather than pport We'll need more info from rocker_port than just pport when we age out fdb entries, so store rocker_port rather than pport in each fdb entry. Signed-off-by: Scott Feldman Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- drivers/net/ethernet/rocker/rocker.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c index e517e9c355fa..f55ed2c2d20f 100644 --- a/drivers/net/ethernet/rocker/rocker.c +++ b/drivers/net/ethernet/rocker/rocker.c @@ -154,7 +154,7 @@ struct rocker_fdb_tbl_entry { 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; @@ -3631,7 +3631,7 @@ static int rocker_port_fdb(struct rocker_port *rocker_port, fdb->learned = (flags & ROCKER_OP_FLAG_LEARNED); fdb->touched = jiffies; - fdb->key.pport = rocker_port->pport; + 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)); @@ -3686,7 +3686,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; @@ -4553,7 +4553,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; From a79e88d9fbbe2e3ecb9d883fb59dca7468d42d79 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:16 -0700 Subject: [PATCH 3/7] bridge: define some min/max/default ageing time constants Signed-off-by: Scott Feldman Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- include/linux/if_bridge.h | 6 ++++++ net/bridge/br_device.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index dad8b00beed2..a338a688ee4a 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -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); diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index 6ed2feb51e3c..2f81624a8257 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c @@ -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); From e7335703e33e7787333872a31e0f4491f9d76cac Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:17 -0700 Subject: [PATCH 4/7] rocker: adding port ageing_time for ageing out FDB entries Follow-up patcheset will allow user to change ageing_time, but for now just hard-code it to a fixed value (the same value used as the default for the bridge driver). Signed-off-by: Scott Feldman Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- drivers/net/ethernet/rocker/rocker.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c index f55ed2c2d20f..be8bb04b7355 100644 --- a/drivers/net/ethernet/rocker/rocker.c +++ b/drivers/net/ethernet/rocker/rocker.c @@ -221,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; @@ -4975,6 +4976,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); From 52fe3e2d51d27c0e68b2bea29dea0b64af2c665e Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:18 -0700 Subject: [PATCH 5/7] rocker: add FDB cleanup timer Add a timer to each rocker switch to do FDB entry cleanup by ageing out expired entries. The timer scheduling algo is copied from the bridge driver, for the most part, to keep the firing of the timer to a minimum. Signed-off-by: Scott Feldman Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- drivers/net/ethernet/rocker/rocker.c | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/net/ethernet/rocker/rocker.c b/drivers/net/ethernet/rocker/rocker.c index be8bb04b7355..32c5429ea5fe 100644 --- a/drivers/net/ethernet/rocker/rocker.c +++ b/drivers/net/ethernet/rocker/rocker.c @@ -248,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]; @@ -3706,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) @@ -5191,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"); @@ -5203,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); @@ -5230,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); From dcd45e06496ca586db35949d1d92d48aab8a0a00 Mon Sep 17 00:00:00 2001 From: Siva Mannem Date: Wed, 23 Sep 2015 08:39:19 -0700 Subject: [PATCH 6/7] bridge: don't age externally added FDB entries Signed-off-by: Siva Mannem Signed-off-by: Scott Feldman Acked-by: Vivien Didelot Acked-by: Jiri Pirko Acked-by: Premkumar Jonnala Signed-off-by: David S. Miller --- net/bridge/br_fdb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 9e9875da0a4f..6663cc0789a6 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -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); From 45ffda75e145ed7a2b40f6a5de35431d7e62d1f0 Mon Sep 17 00:00:00 2001 From: Scott Feldman Date: Wed, 23 Sep 2015 08:39:20 -0700 Subject: [PATCH 7/7] switchdev: update documentation on FDB ageing_time Signed-off-by: Scott Feldman Reviewed-by: Vivien Didelot Acked-by: Jiri Pirko Signed-off-by: David S. Miller --- Documentation/networking/switchdev.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/networking/switchdev.txt b/Documentation/networking/switchdev.txt index 476df0496686..67e43ee7840a 100644 --- a/Documentation/networking/switchdev.txt +++ b/Documentation/networking/switchdev.txt @@ -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 ^^^^^^^^^^^^^^^^^^^^^^^^