diff options
author | 2016-04-11 10:41:07 +0300 | |
---|---|---|
committer | 2016-04-13 08:17:18 +0300 | |
commit | e09b41010ba33a20a87472ee821fa407a5b8da36 (patch) | |
tree | d10dc367189862e7ca5c592f033dc3726e1df4e3 /kernel/drivers/net/ethernet/rocker/rocker.c | |
parent | f93b97fd65072de626c074dbe099a1fff05ce060 (diff) |
These changes are the raw update to linux-4.4.6-rt14. Kernel sources
are taken from kernel.org, and rt patch from the rt wiki download page.
During the rebasing, the following patch collided:
Force tick interrupt and get rid of softirq magic(I70131fb85).
Collisions have been removed because its logic was found on the
source already.
Change-Id: I7f57a4081d9deaa0d9ccfc41a6c8daccdee3b769
Signed-off-by: José Pekkarinen <jose.pekkarinen@nokia.com>
Diffstat (limited to 'kernel/drivers/net/ethernet/rocker/rocker.c')
-rw-r--r-- | kernel/drivers/net/ethernet/rocker/rocker.c | 1844 |
1 files changed, 1138 insertions, 706 deletions
diff --git a/kernel/drivers/net/ethernet/rocker/rocker.c b/kernel/drivers/net/ethernet/rocker/rocker.c index 73b6fc21e..52ec3d6e0 100644 --- a/kernel/drivers/net/ethernet/rocker/rocker.c +++ b/kernel/drivers/net/ethernet/rocker/rocker.c @@ -36,7 +36,7 @@ #include <net/ip_fib.h> #include <net/netevent.h> #include <net/arp.h> -#include <asm-generic/io-64-nonatomic-lo-hi.h> +#include <linux/io-64-nonatomic-lo-hi.h> #include <generated/utsrelease.h> #include "rocker.h" @@ -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; @@ -181,7 +182,7 @@ struct rocker_desc_info { size_t data_size; size_t tlv_size; struct rocker_desc *desc; - DEFINE_DMA_UNMAP_ADDR(mapaddr); + dma_addr_t mapaddr; }; struct rocker_dma_ring_info { @@ -202,6 +203,7 @@ enum { ROCKER_CTRL_IPV4_MCAST, ROCKER_CTRL_IPV6_MCAST, ROCKER_CTRL_DFLT_BRIDGING, + ROCKER_CTRL_DFLT_OVS, ROCKER_CTRL_MAX, }; @@ -219,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; @@ -236,21 +239,22 @@ struct rocker { struct { u64 id; } hw; - spinlock_t cmd_ring_lock; + spinlock_t cmd_ring_lock; /* for cmd ring accesses */ struct rocker_dma_ring_info cmd_ring; struct rocker_dma_ring_info event_ring; DECLARE_HASHTABLE(flow_tbl, 16); - spinlock_t flow_tbl_lock; + spinlock_t flow_tbl_lock; /* for flow tbl accesses */ u64 flow_tbl_next_cookie; DECLARE_HASHTABLE(group_tbl, 16); - spinlock_t group_tbl_lock; + 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; + spinlock_t fdb_tbl_lock; /* for fdb tbl accesses */ unsigned long internal_vlan_bitmap[ROCKER_INTERNAL_VLAN_BITMAP_LEN]; DECLARE_HASHTABLE(internal_vlan_tbl, 8); - spinlock_t internal_vlan_tbl_lock; + spinlock_t internal_vlan_tbl_lock; /* for vlan tbl accesses */ DECLARE_HASHTABLE(neigh_tbl, 16); - spinlock_t neigh_tbl_lock; + spinlock_t neigh_tbl_lock; /* for neigh tbl accesses */ u32 neigh_tbl_next_index; }; @@ -294,7 +298,7 @@ static bool rocker_vlan_id_is_internal(__be16 vlan_id) return (_vlan_id >= start && _vlan_id <= end); } -static __be16 rocker_port_vid_to_vlan(struct rocker_port *rocker_port, +static __be16 rocker_port_vid_to_vlan(const struct rocker_port *rocker_port, u16 vid, bool *pop_vlan) { __be16 vlan_id; @@ -311,7 +315,7 @@ static __be16 rocker_port_vid_to_vlan(struct rocker_port *rocker_port, return vlan_id; } -static u16 rocker_port_vlan_to_vid(struct rocker_port *rocker_port, +static u16 rocker_port_vlan_to_vid(const struct rocker_port *rocker_port, __be16 vlan_id) { if (rocker_vlan_id_is_internal(vlan_id)) @@ -320,9 +324,82 @@ static u16 rocker_port_vlan_to_vid(struct rocker_port *rocker_port, return ntohs(vlan_id); } -static bool rocker_port_is_bridged(struct rocker_port *rocker_port) +static bool rocker_port_is_bridged(const struct rocker_port *rocker_port) +{ + return rocker_port->bridge_dev && + netif_is_bridge_master(rocker_port->bridge_dev); +} + +static bool rocker_port_is_ovsed(const struct rocker_port *rocker_port) { - return !!rocker_port->bridge_dev; + return rocker_port->bridge_dev && + netif_is_ovs_master(rocker_port->bridge_dev); +} + +#define ROCKER_OP_FLAG_REMOVE BIT(0) +#define ROCKER_OP_FLAG_NOWAIT BIT(1) +#define ROCKER_OP_FLAG_LEARNED BIT(2) +#define ROCKER_OP_FLAG_REFRESH BIT(3) + +static void *__rocker_port_mem_alloc(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + size_t size) +{ + struct switchdev_trans_item *elem = NULL; + gfp_t gfp_flags = (flags & ROCKER_OP_FLAG_NOWAIT) ? + GFP_ATOMIC : GFP_KERNEL; + + /* If in transaction prepare phase, allocate the memory + * and enqueue it on a transaction. If in transaction + * commit phase, dequeue the memory from the transaction + * rather than re-allocating the memory. The idea is the + * driver code paths for prepare and commit are identical + * so the memory allocated in the prepare phase is the + * memory used in the commit phase. + */ + + if (!trans) { + elem = kzalloc(size + sizeof(*elem), gfp_flags); + } else if (switchdev_trans_ph_prepare(trans)) { + elem = kzalloc(size + sizeof(*elem), gfp_flags); + if (!elem) + return NULL; + switchdev_trans_item_enqueue(trans, elem, kfree, elem); + } else { + elem = switchdev_trans_item_dequeue(trans); + } + + return elem ? elem + 1 : NULL; +} + +static void *rocker_port_kzalloc(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + size_t size) +{ + return __rocker_port_mem_alloc(rocker_port, trans, flags, size); +} + +static void *rocker_port_kcalloc(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + size_t n, size_t size) +{ + return __rocker_port_mem_alloc(rocker_port, trans, flags, n * size); +} + +static void rocker_port_kfree(struct switchdev_trans *trans, const void *mem) +{ + struct switchdev_trans_item *elem; + + /* Frees are ignored if in transaction prepare phase. The + * memory remains on the per-port list until freed in the + * commit phase. + */ + + if (switchdev_trans_ph_prepare(trans)) + return; + + elem = (struct switchdev_trans_item *) mem - 1; + kfree(elem); } struct rocker_wait { @@ -343,20 +420,23 @@ static void rocker_wait_init(struct rocker_wait *wait) rocker_wait_reset(wait); } -static struct rocker_wait *rocker_wait_create(gfp_t gfp) +static struct rocker_wait *rocker_wait_create(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + int flags) { struct rocker_wait *wait; - wait = kmalloc(sizeof(*wait), gfp); + wait = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*wait)); if (!wait) return NULL; rocker_wait_init(wait); return wait; } -static void rocker_wait_destroy(struct rocker_wait *work) +static void rocker_wait_destroy(struct switchdev_trans *trans, + struct rocker_wait *wait) { - kfree(work); + rocker_port_kfree(trans, wait); } static bool rocker_wait_event_timeout(struct rocker_wait *wait, @@ -374,18 +454,18 @@ static void rocker_wait_wake_up(struct rocker_wait *wait) wake_up(&wait->wait); } -static u32 rocker_msix_vector(struct rocker *rocker, unsigned int vector) +static u32 rocker_msix_vector(const struct rocker *rocker, unsigned int vector) { return rocker->msix_entries[vector].vector; } -static u32 rocker_msix_tx_vector(struct rocker_port *rocker_port) +static u32 rocker_msix_tx_vector(const struct rocker_port *rocker_port) { return rocker_msix_vector(rocker_port->rocker, ROCKER_MSIX_VEC_TX(rocker_port->port_number)); } -static u32 rocker_msix_rx_vector(struct rocker_port *rocker_port) +static u32 rocker_msix_rx_vector(const struct rocker_port *rocker_port) { return rocker_msix_vector(rocker_port->rocker, ROCKER_MSIX_VEC_RX(rocker_port->port_number)); @@ -404,9 +484,9 @@ static u32 rocker_msix_rx_vector(struct rocker_port *rocker_port) * HW basic testing functions *****************************/ -static int rocker_reg_test(struct rocker *rocker) +static int rocker_reg_test(const struct rocker *rocker) { - struct pci_dev *pdev = rocker->pdev; + const struct pci_dev *pdev = rocker->pdev; u64 test_reg; u64 rnd; @@ -434,12 +514,12 @@ static int rocker_reg_test(struct rocker *rocker) return 0; } -static int rocker_dma_test_one(struct rocker *rocker, struct rocker_wait *wait, - u32 test_type, dma_addr_t dma_handle, - unsigned char *buf, unsigned char *expect, - size_t size) +static int rocker_dma_test_one(const struct rocker *rocker, + struct rocker_wait *wait, u32 test_type, + dma_addr_t dma_handle, const unsigned char *buf, + const unsigned char *expect, size_t size) { - struct pci_dev *pdev = rocker->pdev; + const struct pci_dev *pdev = rocker->pdev; int i; rocker_wait_reset(wait); @@ -463,7 +543,7 @@ static int rocker_dma_test_one(struct rocker *rocker, struct rocker_wait *wait, #define ROCKER_TEST_DMA_BUF_SIZE (PAGE_SIZE * 4) #define ROCKER_TEST_DMA_FILL_PATTERN 0x96 -static int rocker_dma_test_offset(struct rocker *rocker, +static int rocker_dma_test_offset(const struct rocker *rocker, struct rocker_wait *wait, int offset) { struct pci_dev *pdev = rocker->pdev; @@ -523,7 +603,8 @@ free_alloc: return err; } -static int rocker_dma_test(struct rocker *rocker, struct rocker_wait *wait) +static int rocker_dma_test(const struct rocker *rocker, + struct rocker_wait *wait) { int i; int err; @@ -545,9 +626,9 @@ static irqreturn_t rocker_test_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } -static int rocker_basic_hw_test(struct rocker *rocker) +static int rocker_basic_hw_test(const struct rocker *rocker) { - struct pci_dev *pdev = rocker->pdev; + const struct pci_dev *pdev = rocker->pdev; struct rocker_wait wait; int err; @@ -680,7 +761,7 @@ static u64 rocker_tlv_get_u64(const struct rocker_tlv *tlv) return *(u64 *) rocker_tlv_data(tlv); } -static void rocker_tlv_parse(struct rocker_tlv **tb, int maxtype, +static void rocker_tlv_parse(const struct rocker_tlv **tb, int maxtype, const char *buf, int buf_len) { const struct rocker_tlv *tlv; @@ -693,19 +774,19 @@ static void rocker_tlv_parse(struct rocker_tlv **tb, int maxtype, u32 type = rocker_tlv_type(tlv); if (type > 0 && type <= maxtype) - tb[type] = (struct rocker_tlv *) tlv; + tb[type] = tlv; } } -static void rocker_tlv_parse_nested(struct rocker_tlv **tb, int maxtype, +static void rocker_tlv_parse_nested(const struct rocker_tlv **tb, int maxtype, const struct rocker_tlv *tlv) { rocker_tlv_parse(tb, maxtype, rocker_tlv_data(tlv), rocker_tlv_len(tlv)); } -static void rocker_tlv_parse_desc(struct rocker_tlv **tb, int maxtype, - struct rocker_desc_info *desc_info) +static void rocker_tlv_parse_desc(const struct rocker_tlv **tb, int maxtype, + const struct rocker_desc_info *desc_info) { rocker_tlv_parse(tb, maxtype, desc_info->data, desc_info->desc->tlv_size); @@ -790,9 +871,9 @@ static void rocker_tlv_nest_end(struct rocker_desc_info *desc_info, } static void rocker_tlv_nest_cancel(struct rocker_desc_info *desc_info, - struct rocker_tlv *start) + const struct rocker_tlv *start) { - desc_info->tlv_size = (char *) start - desc_info->data; + desc_info->tlv_size = (const char *) start - desc_info->data; } /****************************************** @@ -804,7 +885,7 @@ static u32 __pos_inc(u32 pos, size_t limit) return ++pos == limit ? 0 : pos; } -static int rocker_desc_err(struct rocker_desc_info *desc_info) +static int rocker_desc_err(const struct rocker_desc_info *desc_info) { int err = desc_info->desc->comp_err & ~ROCKER_DMA_DESC_COMP_ERR_GEN; @@ -832,31 +913,31 @@ static int rocker_desc_err(struct rocker_desc_info *desc_info) return -EINVAL; } -static void rocker_desc_gen_clear(struct rocker_desc_info *desc_info) +static void rocker_desc_gen_clear(const struct rocker_desc_info *desc_info) { desc_info->desc->comp_err &= ~ROCKER_DMA_DESC_COMP_ERR_GEN; } -static bool rocker_desc_gen(struct rocker_desc_info *desc_info) +static bool rocker_desc_gen(const struct rocker_desc_info *desc_info) { u32 comp_err = desc_info->desc->comp_err; return comp_err & ROCKER_DMA_DESC_COMP_ERR_GEN ? true : false; } -static void *rocker_desc_cookie_ptr_get(struct rocker_desc_info *desc_info) +static void *rocker_desc_cookie_ptr_get(const struct rocker_desc_info *desc_info) { return (void *)(uintptr_t)desc_info->desc->cookie; } -static void rocker_desc_cookie_ptr_set(struct rocker_desc_info *desc_info, +static void rocker_desc_cookie_ptr_set(const struct rocker_desc_info *desc_info, void *ptr) { desc_info->desc->cookie = (uintptr_t) ptr; } static struct rocker_desc_info * -rocker_desc_head_get(struct rocker_dma_ring_info *info) +rocker_desc_head_get(const struct rocker_dma_ring_info *info) { static struct rocker_desc_info *desc_info; u32 head = __pos_inc(info->head, info->size); @@ -868,15 +949,15 @@ rocker_desc_head_get(struct rocker_dma_ring_info *info) return desc_info; } -static void rocker_desc_commit(struct rocker_desc_info *desc_info) +static void rocker_desc_commit(const struct rocker_desc_info *desc_info) { desc_info->desc->buf_size = desc_info->data_size; desc_info->desc->tlv_size = desc_info->tlv_size; } -static void rocker_desc_head_set(struct rocker *rocker, +static void rocker_desc_head_set(const struct rocker *rocker, struct rocker_dma_ring_info *info, - struct rocker_desc_info *desc_info) + const struct rocker_desc_info *desc_info) { u32 head = __pos_inc(info->head, info->size); @@ -901,8 +982,8 @@ rocker_desc_tail_get(struct rocker_dma_ring_info *info) return desc_info; } -static void rocker_dma_ring_credits_set(struct rocker *rocker, - struct rocker_dma_ring_info *info, +static void rocker_dma_ring_credits_set(const struct rocker *rocker, + const struct rocker_dma_ring_info *info, u32 credits) { if (credits) @@ -915,7 +996,7 @@ static unsigned long rocker_dma_ring_size_fix(size_t size) min(roundup_pow_of_two(size), ROCKER_DMA_SIZE_MAX)); } -static int rocker_dma_ring_create(struct rocker *rocker, +static int rocker_dma_ring_create(const struct rocker *rocker, unsigned int type, size_t size, struct rocker_dma_ring_info *info) @@ -951,8 +1032,8 @@ static int rocker_dma_ring_create(struct rocker *rocker, return 0; } -static void rocker_dma_ring_destroy(struct rocker *rocker, - struct rocker_dma_ring_info *info) +static void rocker_dma_ring_destroy(const struct rocker *rocker, + const struct rocker_dma_ring_info *info) { rocker_write64(rocker, DMA_DESC_ADDR(info->type), 0); @@ -962,7 +1043,7 @@ static void rocker_dma_ring_destroy(struct rocker *rocker, kfree(info->desc_info); } -static void rocker_dma_ring_pass_to_producer(struct rocker *rocker, +static void rocker_dma_ring_pass_to_producer(const struct rocker *rocker, struct rocker_dma_ring_info *info) { int i; @@ -977,8 +1058,8 @@ static void rocker_dma_ring_pass_to_producer(struct rocker *rocker, rocker_desc_commit(&info->desc_info[i]); } -static int rocker_dma_ring_bufs_alloc(struct rocker *rocker, - struct rocker_dma_ring_info *info, +static int rocker_dma_ring_bufs_alloc(const struct rocker *rocker, + const struct rocker_dma_ring_info *info, int direction, size_t buf_size) { struct pci_dev *pdev = rocker->pdev; @@ -1015,7 +1096,7 @@ static int rocker_dma_ring_bufs_alloc(struct rocker *rocker, rollback: for (i--; i >= 0; i--) { - struct rocker_desc_info *desc_info = &info->desc_info[i]; + const struct rocker_desc_info *desc_info = &info->desc_info[i]; pci_unmap_single(pdev, dma_unmap_addr(desc_info, mapaddr), desc_info->data_size, direction); @@ -1024,15 +1105,15 @@ rollback: return err; } -static void rocker_dma_ring_bufs_free(struct rocker *rocker, - struct rocker_dma_ring_info *info, +static void rocker_dma_ring_bufs_free(const struct rocker *rocker, + const struct rocker_dma_ring_info *info, int direction) { struct pci_dev *pdev = rocker->pdev; int i; for (i = 0; i < info->size; i++) { - struct rocker_desc_info *desc_info = &info->desc_info[i]; + const struct rocker_desc_info *desc_info = &info->desc_info[i]; struct rocker_desc *desc = &info->desc[i]; desc->buf_addr = 0; @@ -1045,7 +1126,7 @@ static void rocker_dma_ring_bufs_free(struct rocker *rocker, static int rocker_dma_rings_init(struct rocker *rocker) { - struct pci_dev *pdev = rocker->pdev; + const struct pci_dev *pdev = rocker->pdev; int err; err = rocker_dma_ring_create(rocker, ROCKER_DMA_CMD, @@ -1102,11 +1183,11 @@ static void rocker_dma_rings_fini(struct rocker *rocker) rocker_dma_ring_destroy(rocker, &rocker->cmd_ring); } -static int rocker_dma_rx_ring_skb_map(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_dma_rx_ring_skb_map(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, struct sk_buff *skb, size_t buf_len) { + const struct rocker *rocker = rocker_port->rocker; struct pci_dev *pdev = rocker->pdev; dma_addr_t dma_handle; @@ -1126,13 +1207,12 @@ tlv_put_failure: return -EMSGSIZE; } -static size_t rocker_port_rx_buf_len(struct rocker_port *rocker_port) +static size_t rocker_port_rx_buf_len(const struct rocker_port *rocker_port) { return rocker_port->dev->mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN; } -static int rocker_dma_rx_ring_skb_alloc(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_dma_rx_ring_skb_alloc(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info) { struct net_device *dev = rocker_port->dev; @@ -1149,8 +1229,7 @@ static int rocker_dma_rx_ring_skb_alloc(struct rocker *rocker, skb = netdev_alloc_skb_ip_align(dev, buf_len); if (!skb) return -ENOMEM; - err = rocker_dma_rx_ring_skb_map(rocker, rocker_port, desc_info, - skb, buf_len); + err = rocker_dma_rx_ring_skb_map(rocker_port, desc_info, skb, buf_len); if (err) { dev_kfree_skb_any(skb); return err; @@ -1159,8 +1238,8 @@ static int rocker_dma_rx_ring_skb_alloc(struct rocker *rocker, return 0; } -static void rocker_dma_rx_ring_skb_unmap(struct rocker *rocker, - struct rocker_tlv **attrs) +static void rocker_dma_rx_ring_skb_unmap(const struct rocker *rocker, + const struct rocker_tlv **attrs) { struct pci_dev *pdev = rocker->pdev; dma_addr_t dma_handle; @@ -1174,10 +1253,10 @@ static void rocker_dma_rx_ring_skb_unmap(struct rocker *rocker, pci_unmap_single(pdev, dma_handle, len, PCI_DMA_FROMDEVICE); } -static void rocker_dma_rx_ring_skb_free(struct rocker *rocker, - struct rocker_desc_info *desc_info) +static void rocker_dma_rx_ring_skb_free(const struct rocker *rocker, + const struct rocker_desc_info *desc_info) { - struct rocker_tlv *attrs[ROCKER_TLV_RX_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_RX_MAX + 1]; struct sk_buff *skb = rocker_desc_cookie_ptr_get(desc_info); if (!skb) @@ -1187,15 +1266,15 @@ static void rocker_dma_rx_ring_skb_free(struct rocker *rocker, dev_kfree_skb_any(skb); } -static int rocker_dma_rx_ring_skbs_alloc(struct rocker *rocker, - struct rocker_port *rocker_port) +static int rocker_dma_rx_ring_skbs_alloc(const struct rocker_port *rocker_port) { - struct rocker_dma_ring_info *rx_ring = &rocker_port->rx_ring; + const struct rocker_dma_ring_info *rx_ring = &rocker_port->rx_ring; + const struct rocker *rocker = rocker_port->rocker; int i; int err; for (i = 0; i < rx_ring->size; i++) { - err = rocker_dma_rx_ring_skb_alloc(rocker, rocker_port, + err = rocker_dma_rx_ring_skb_alloc(rocker_port, &rx_ring->desc_info[i]); if (err) goto rollback; @@ -1208,10 +1287,10 @@ rollback: return err; } -static void rocker_dma_rx_ring_skbs_free(struct rocker *rocker, - struct rocker_port *rocker_port) +static void rocker_dma_rx_ring_skbs_free(const struct rocker_port *rocker_port) { - struct rocker_dma_ring_info *rx_ring = &rocker_port->rx_ring; + const struct rocker_dma_ring_info *rx_ring = &rocker_port->rx_ring; + const struct rocker *rocker = rocker_port->rocker; int i; for (i = 0; i < rx_ring->size; i++) @@ -1257,7 +1336,7 @@ static int rocker_port_dma_rings_init(struct rocker_port *rocker_port) goto err_dma_rx_ring_bufs_alloc; } - err = rocker_dma_rx_ring_skbs_alloc(rocker, rocker_port); + err = rocker_dma_rx_ring_skbs_alloc(rocker_port); if (err) { netdev_err(rocker_port->dev, "failed to alloc rx dma ring skbs\n"); goto err_dma_rx_ring_skbs_alloc; @@ -1283,7 +1362,7 @@ static void rocker_port_dma_rings_fini(struct rocker_port *rocker_port) { struct rocker *rocker = rocker_port->rocker; - rocker_dma_rx_ring_skbs_free(rocker, rocker_port); + rocker_dma_rx_ring_skbs_free(rocker_port); rocker_dma_ring_bufs_free(rocker, &rocker_port->rx_ring, PCI_DMA_BIDIRECTIONAL); rocker_dma_ring_destroy(rocker, &rocker_port->rx_ring); @@ -1292,7 +1371,8 @@ static void rocker_port_dma_rings_fini(struct rocker_port *rocker_port) rocker_dma_ring_destroy(rocker, &rocker_port->tx_ring); } -static void rocker_port_set_enable(struct rocker_port *rocker_port, bool enable) +static void rocker_port_set_enable(const struct rocker_port *rocker_port, + bool enable) { u64 val = rocker_read64(rocker_port->rocker, PORT_PHYS_ENABLE); @@ -1310,7 +1390,7 @@ static void rocker_port_set_enable(struct rocker_port *rocker_port, bool enable) static irqreturn_t rocker_cmd_irq_handler(int irq, void *dev_id) { struct rocker *rocker = dev_id; - struct rocker_desc_info *desc_info; + const struct rocker_desc_info *desc_info; struct rocker_wait *wait; u32 credits = 0; @@ -1319,7 +1399,7 @@ static irqreturn_t rocker_cmd_irq_handler(int irq, void *dev_id) wait = rocker_desc_cookie_ptr_get(desc_info); if (wait->nowait) { rocker_desc_gen_clear(desc_info); - rocker_wait_destroy(wait); + rocker_wait_destroy(NULL, wait); } else { rocker_wait_wake_up(wait); } @@ -1331,22 +1411,22 @@ static irqreturn_t rocker_cmd_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } -static void rocker_port_link_up(struct rocker_port *rocker_port) +static void rocker_port_link_up(const struct rocker_port *rocker_port) { netif_carrier_on(rocker_port->dev); netdev_info(rocker_port->dev, "Link is up\n"); } -static void rocker_port_link_down(struct rocker_port *rocker_port) +static void rocker_port_link_down(const struct rocker_port *rocker_port) { netif_carrier_off(rocker_port->dev); netdev_info(rocker_port->dev, "Link is down\n"); } -static int rocker_event_link_change(struct rocker *rocker, +static int rocker_event_link_change(const struct rocker *rocker, const struct rocker_tlv *info) { - struct rocker_tlv *attrs[ROCKER_TLV_EVENT_LINK_CHANGED_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_EVENT_LINK_CHANGED_MAX + 1]; unsigned int port_number; bool link_up; struct rocker_port *rocker_port; @@ -1373,22 +1453,18 @@ static int rocker_event_link_change(struct rocker *rocker, return 0; } -#define ROCKER_OP_FLAG_REMOVE BIT(0) -#define ROCKER_OP_FLAG_NOWAIT BIT(1) -#define ROCKER_OP_FLAG_LEARNED BIT(2) -#define ROCKER_OP_FLAG_REFRESH BIT(3) - static int rocker_port_fdb(struct rocker_port *rocker_port, + struct switchdev_trans *trans, const unsigned char *addr, __be16 vlan_id, int flags); -static int rocker_event_mac_vlan_seen(struct rocker *rocker, +static int rocker_event_mac_vlan_seen(const struct rocker *rocker, const struct rocker_tlv *info) { - struct rocker_tlv *attrs[ROCKER_TLV_EVENT_MAC_VLAN_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_EVENT_MAC_VLAN_MAX + 1]; unsigned int port_number; struct rocker_port *rocker_port; - unsigned char *addr; + const unsigned char *addr; int flags = ROCKER_OP_FLAG_NOWAIT | ROCKER_OP_FLAG_LEARNED; __be16 vlan_id; @@ -1411,14 +1487,14 @@ static int rocker_event_mac_vlan_seen(struct rocker *rocker, rocker_port->stp_state != BR_STATE_FORWARDING) return 0; - return rocker_port_fdb(rocker_port, addr, vlan_id, flags); + return rocker_port_fdb(rocker_port, NULL, addr, vlan_id, flags); } -static int rocker_event_process(struct rocker *rocker, - struct rocker_desc_info *desc_info) +static int rocker_event_process(const struct rocker *rocker, + const struct rocker_desc_info *desc_info) { - struct rocker_tlv *attrs[ROCKER_TLV_EVENT_MAX + 1]; - struct rocker_tlv *info; + const struct rocker_tlv *attrs[ROCKER_TLV_EVENT_MAX + 1]; + const struct rocker_tlv *info; u16 type; rocker_tlv_parse_desc(attrs, ROCKER_TLV_EVENT_MAX, desc_info); @@ -1442,8 +1518,8 @@ static int rocker_event_process(struct rocker *rocker, static irqreturn_t rocker_event_irq_handler(int irq, void *dev_id) { struct rocker *rocker = dev_id; - struct pci_dev *pdev = rocker->pdev; - struct rocker_desc_info *desc_info; + const struct pci_dev *pdev = rocker->pdev; + const struct rocker_desc_info *desc_info; u32 credits = 0; int err; @@ -1487,65 +1563,75 @@ static irqreturn_t rocker_rx_irq_handler(int irq, void *dev_id) * Command interface ********************/ -typedef int (*rocker_cmd_cb_t)(struct rocker *rocker, - struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info, - void *priv); +typedef int (*rocker_cmd_prep_cb_t)(const struct rocker_port *rocker_port, + struct rocker_desc_info *desc_info, + void *priv); -static int rocker_cmd_exec(struct rocker *rocker, - struct rocker_port *rocker_port, - rocker_cmd_cb_t prepare, void *prepare_priv, - rocker_cmd_cb_t process, void *process_priv, - bool nowait) +typedef int (*rocker_cmd_proc_cb_t)(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info, + void *priv); + +static int rocker_cmd_exec(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + rocker_cmd_prep_cb_t prepare, void *prepare_priv, + rocker_cmd_proc_cb_t process, void *process_priv) { + struct rocker *rocker = rocker_port->rocker; struct rocker_desc_info *desc_info; struct rocker_wait *wait; - unsigned long flags; + bool nowait = !!(flags & ROCKER_OP_FLAG_NOWAIT); + unsigned long lock_flags; int err; - wait = rocker_wait_create(nowait ? GFP_ATOMIC : GFP_KERNEL); + wait = rocker_wait_create(rocker_port, trans, flags); if (!wait) return -ENOMEM; wait->nowait = nowait; - spin_lock_irqsave(&rocker->cmd_ring_lock, flags); + spin_lock_irqsave(&rocker->cmd_ring_lock, lock_flags); + desc_info = rocker_desc_head_get(&rocker->cmd_ring); if (!desc_info) { - spin_unlock_irqrestore(&rocker->cmd_ring_lock, flags); + spin_unlock_irqrestore(&rocker->cmd_ring_lock, lock_flags); err = -EAGAIN; goto out; } - err = prepare(rocker, rocker_port, desc_info, prepare_priv); + + err = prepare(rocker_port, desc_info, prepare_priv); if (err) { - spin_unlock_irqrestore(&rocker->cmd_ring_lock, flags); + spin_unlock_irqrestore(&rocker->cmd_ring_lock, lock_flags); goto out; } + rocker_desc_cookie_ptr_set(desc_info, wait); - rocker_desc_head_set(rocker, &rocker->cmd_ring, desc_info); - spin_unlock_irqrestore(&rocker->cmd_ring_lock, flags); + + if (!switchdev_trans_ph_prepare(trans)) + rocker_desc_head_set(rocker, &rocker->cmd_ring, desc_info); + + spin_unlock_irqrestore(&rocker->cmd_ring_lock, lock_flags); if (nowait) return 0; - if (!rocker_wait_event_timeout(wait, HZ / 10)) - return -EIO; + if (!switchdev_trans_ph_prepare(trans)) + if (!rocker_wait_event_timeout(wait, HZ / 10)) + return -EIO; err = rocker_desc_err(desc_info); if (err) return err; if (process) - err = process(rocker, rocker_port, desc_info, process_priv); + err = process(rocker_port, desc_info, process_priv); rocker_desc_gen_clear(desc_info); out: - rocker_wait_destroy(wait); + rocker_wait_destroy(trans, wait); return err; } static int -rocker_cmd_get_port_settings_prep(struct rocker *rocker, - struct rocker_port *rocker_port, +rocker_cmd_get_port_settings_prep(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -1565,14 +1651,13 @@ rocker_cmd_get_port_settings_prep(struct rocker *rocker, } static int -rocker_cmd_get_port_settings_ethtool_proc(struct rocker *rocker, - struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info, +rocker_cmd_get_port_settings_ethtool_proc(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info, void *priv) { struct ethtool_cmd *ecmd = priv; - struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; - struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; + const struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; u32 speed; u8 duplex; u8 autoneg; @@ -1604,15 +1689,14 @@ rocker_cmd_get_port_settings_ethtool_proc(struct rocker *rocker, } static int -rocker_cmd_get_port_settings_macaddr_proc(struct rocker *rocker, - struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info, +rocker_cmd_get_port_settings_macaddr_proc(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info, void *priv) { unsigned char *macaddr = priv; - struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; - struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; - struct rocker_tlv *attr; + const struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; + const struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; + const struct rocker_tlv *attr; rocker_tlv_parse_desc(attrs, ROCKER_TLV_CMD_MAX, desc_info); if (!attrs[ROCKER_TLV_CMD_INFO]) @@ -1637,17 +1721,16 @@ struct port_name { }; static int -rocker_cmd_get_port_settings_phys_name_proc(struct rocker *rocker, - struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info, +rocker_cmd_get_port_settings_phys_name_proc(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info, void *priv) { - struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; - struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; + const struct rocker_tlv *info_attrs[ROCKER_TLV_CMD_PORT_SETTINGS_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; struct port_name *name = priv; - struct rocker_tlv *attr; + const struct rocker_tlv *attr; size_t i, j, len; - char *str; + const char *str; rocker_tlv_parse_desc(attrs, ROCKER_TLV_CMD_MAX, desc_info); if (!attrs[ROCKER_TLV_CMD_INFO]) @@ -1679,8 +1762,7 @@ rocker_cmd_get_port_settings_phys_name_proc(struct rocker *rocker, } static int -rocker_cmd_set_port_settings_ethtool_prep(struct rocker *rocker, - struct rocker_port *rocker_port, +rocker_cmd_set_port_settings_ethtool_prep(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -1710,12 +1792,11 @@ rocker_cmd_set_port_settings_ethtool_prep(struct rocker *rocker, } static int -rocker_cmd_set_port_settings_macaddr_prep(struct rocker *rocker, - struct rocker_port *rocker_port, +rocker_cmd_set_port_settings_macaddr_prep(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { - unsigned char *macaddr = priv; + const unsigned char *macaddr = priv; struct rocker_tlv *cmd_info; if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, @@ -1735,8 +1816,31 @@ rocker_cmd_set_port_settings_macaddr_prep(struct rocker *rocker, } static int -rocker_cmd_set_port_learning_prep(struct rocker *rocker, - struct rocker_port *rocker_port, +rocker_cmd_set_port_settings_mtu_prep(const struct rocker_port *rocker_port, + struct rocker_desc_info *desc_info, + void *priv) +{ + int mtu = *(int *)priv; + struct rocker_tlv *cmd_info; + + if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_TYPE, + ROCKER_TLV_CMD_TYPE_SET_PORT_SETTINGS)) + return -EMSGSIZE; + cmd_info = rocker_tlv_nest_start(desc_info, ROCKER_TLV_CMD_INFO); + if (!cmd_info) + return -EMSGSIZE; + if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_CMD_PORT_SETTINGS_PPORT, + rocker_port->pport)) + return -EMSGSIZE; + if (rocker_tlv_put_u16(desc_info, ROCKER_TLV_CMD_PORT_SETTINGS_MTU, + mtu)) + return -EMSGSIZE; + rocker_tlv_nest_end(desc_info, cmd_info); + return 0; +} + +static int +rocker_cmd_set_port_learning_prep(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -1761,46 +1865,56 @@ rocker_cmd_set_port_learning_prep(struct rocker *rocker, static int rocker_cmd_get_port_settings_ethtool(struct rocker_port *rocker_port, struct ethtool_cmd *ecmd) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, rocker_cmd_get_port_settings_prep, NULL, rocker_cmd_get_port_settings_ethtool_proc, - ecmd, false); + ecmd); } static int rocker_cmd_get_port_settings_macaddr(struct rocker_port *rocker_port, unsigned char *macaddr) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, rocker_cmd_get_port_settings_prep, NULL, rocker_cmd_get_port_settings_macaddr_proc, - macaddr, false); + macaddr); } static int rocker_cmd_set_port_settings_ethtool(struct rocker_port *rocker_port, struct ethtool_cmd *ecmd) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, rocker_cmd_set_port_settings_ethtool_prep, - ecmd, NULL, NULL, false); + ecmd, NULL, NULL); } static int rocker_cmd_set_port_settings_macaddr(struct rocker_port *rocker_port, unsigned char *macaddr) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, rocker_cmd_set_port_settings_macaddr_prep, - macaddr, NULL, NULL, false); + macaddr, NULL, NULL); } -static int rocker_port_set_learning(struct rocker_port *rocker_port) +static int rocker_cmd_set_port_settings_mtu(struct rocker_port *rocker_port, + int mtu) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, + rocker_cmd_set_port_settings_mtu_prep, + &mtu, NULL, NULL); +} + +static int rocker_port_set_learning(struct rocker_port *rocker_port, + struct switchdev_trans *trans) +{ + return rocker_cmd_exec(rocker_port, trans, 0, rocker_cmd_set_port_learning_prep, - NULL, NULL, NULL, false); + NULL, NULL, NULL); } -static int rocker_cmd_flow_tbl_add_ig_port(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) +static int +rocker_cmd_flow_tbl_add_ig_port(struct rocker_desc_info *desc_info, + const struct rocker_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.ig_port.in_pport)) @@ -1815,8 +1929,9 @@ static int rocker_cmd_flow_tbl_add_ig_port(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_flow_tbl_add_vlan(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) +static int +rocker_cmd_flow_tbl_add_vlan(struct rocker_desc_info *desc_info, + const struct rocker_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.vlan.in_pport)) @@ -1838,8 +1953,9 @@ static int rocker_cmd_flow_tbl_add_vlan(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_flow_tbl_add_term_mac(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) +static int +rocker_cmd_flow_tbl_add_term_mac(struct rocker_desc_info *desc_info, + const struct rocker_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.term_mac.in_pport)) @@ -1875,7 +1991,7 @@ static int rocker_cmd_flow_tbl_add_term_mac(struct rocker_desc_info *desc_info, static int rocker_cmd_flow_tbl_add_ucast_routing(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) + const struct rocker_flow_tbl_entry *entry) { if (rocker_tlv_put_be16(desc_info, ROCKER_TLV_OF_DPA_ETHERTYPE, entry->key.ucast_routing.eth_type)) @@ -1896,8 +2012,9 @@ rocker_cmd_flow_tbl_add_ucast_routing(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_flow_tbl_add_bridge(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) +static int +rocker_cmd_flow_tbl_add_bridge(struct rocker_desc_info *desc_info, + const struct rocker_flow_tbl_entry *entry) { if (entry->key.bridge.has_eth_dst && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_DST_MAC, @@ -1929,8 +2046,9 @@ static int rocker_cmd_flow_tbl_add_bridge(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_flow_tbl_add_acl(struct rocker_desc_info *desc_info, - struct rocker_flow_tbl_entry *entry) +static int +rocker_cmd_flow_tbl_add_acl(struct rocker_desc_info *desc_info, + const struct rocker_flow_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_IN_PPORT, entry->key.acl.in_pport)) @@ -1995,12 +2113,11 @@ static int rocker_cmd_flow_tbl_add_acl(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_flow_tbl_add(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_cmd_flow_tbl_add(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { - struct rocker_flow_tbl_entry *entry = priv; + const struct rocker_flow_tbl_entry *entry = priv; struct rocker_tlv *cmd_info; int err = 0; @@ -2053,8 +2170,7 @@ static int rocker_cmd_flow_tbl_add(struct rocker *rocker, return 0; } -static int rocker_cmd_flow_tbl_del(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_cmd_flow_tbl_del(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -2090,7 +2206,7 @@ rocker_cmd_group_tbl_add_l2_interface(struct rocker_desc_info *desc_info, static int rocker_cmd_group_tbl_add_l2_rewrite(struct rocker_desc_info *desc_info, - struct rocker_group_tbl_entry *entry) + const struct rocker_group_tbl_entry *entry) { if (rocker_tlv_put_u32(desc_info, ROCKER_TLV_OF_DPA_GROUP_ID_LOWER, entry->l2_rewrite.group_id)) @@ -2113,7 +2229,7 @@ rocker_cmd_group_tbl_add_l2_rewrite(struct rocker_desc_info *desc_info, static int rocker_cmd_group_tbl_add_group_ids(struct rocker_desc_info *desc_info, - struct rocker_group_tbl_entry *entry) + const struct rocker_group_tbl_entry *entry) { int i; struct rocker_tlv *group_ids; @@ -2139,7 +2255,7 @@ rocker_cmd_group_tbl_add_group_ids(struct rocker_desc_info *desc_info, static int rocker_cmd_group_tbl_add_l3_unicast(struct rocker_desc_info *desc_info, - struct rocker_group_tbl_entry *entry) + const struct rocker_group_tbl_entry *entry) { if (!is_zero_ether_addr(entry->l3_unicast.eth_src) && rocker_tlv_put(desc_info, ROCKER_TLV_OF_DPA_SRC_MAC, @@ -2163,8 +2279,7 @@ rocker_cmd_group_tbl_add_l3_unicast(struct rocker_desc_info *desc_info, return 0; } -static int rocker_cmd_group_tbl_add(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_cmd_group_tbl_add(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -2209,8 +2324,7 @@ static int rocker_cmd_group_tbl_add(struct rocker *rocker, return 0; } -static int rocker_cmd_group_tbl_del(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_cmd_group_tbl_del(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -2293,7 +2407,8 @@ static void rocker_free_tbls(struct rocker *rocker) } static struct rocker_flow_tbl_entry * -rocker_flow_tbl_find(struct rocker *rocker, struct rocker_flow_tbl_entry *match) +rocker_flow_tbl_find(const struct rocker *rocker, + const struct rocker_flow_tbl_entry *match) { struct rocker_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); @@ -2308,24 +2423,25 @@ rocker_flow_tbl_find(struct rocker *rocker, struct rocker_flow_tbl_entry *match) } static int rocker_flow_tbl_add(struct rocker_port *rocker_port, - struct rocker_flow_tbl_entry *match, - bool nowait) + struct switchdev_trans *trans, int flags, + struct rocker_flow_tbl_entry *match) { struct rocker *rocker = rocker_port->rocker; struct rocker_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); - unsigned long flags; + unsigned long lock_flags; match->key_crc32 = crc32(~0, &match->key, key_len); - spin_lock_irqsave(&rocker->flow_tbl_lock, flags); + spin_lock_irqsave(&rocker->flow_tbl_lock, lock_flags); found = rocker_flow_tbl_find(rocker, match); if (found) { match->cookie = found->cookie; - hash_del(&found->entry); - kfree(found); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); + rocker_port_kfree(trans, found); found = match; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD; } else { @@ -2334,73 +2450,69 @@ static int rocker_flow_tbl_add(struct rocker_port *rocker_port, found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD; } - hash_add(rocker->flow_tbl, &found->entry, found->key_crc32); + if (!switchdev_trans_ph_prepare(trans)) + hash_add(rocker->flow_tbl, &found->entry, found->key_crc32); - spin_unlock_irqrestore(&rocker->flow_tbl_lock, flags); + spin_unlock_irqrestore(&rocker->flow_tbl_lock, lock_flags); - return rocker_cmd_exec(rocker, rocker_port, - rocker_cmd_flow_tbl_add, - found, NULL, NULL, nowait); + return rocker_cmd_exec(rocker_port, trans, flags, + rocker_cmd_flow_tbl_add, found, NULL, NULL); } static int rocker_flow_tbl_del(struct rocker_port *rocker_port, - struct rocker_flow_tbl_entry *match, - bool nowait) + struct switchdev_trans *trans, int flags, + struct rocker_flow_tbl_entry *match) { struct rocker *rocker = rocker_port->rocker; struct rocker_flow_tbl_entry *found; size_t key_len = match->key_len ? match->key_len : sizeof(found->key); - unsigned long flags; + unsigned long lock_flags; int err = 0; match->key_crc32 = crc32(~0, &match->key, key_len); - spin_lock_irqsave(&rocker->flow_tbl_lock, flags); + spin_lock_irqsave(&rocker->flow_tbl_lock, lock_flags); found = rocker_flow_tbl_find(rocker, match); if (found) { - hash_del(&found->entry); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL; } - spin_unlock_irqrestore(&rocker->flow_tbl_lock, flags); + spin_unlock_irqrestore(&rocker->flow_tbl_lock, lock_flags); - kfree(match); + rocker_port_kfree(trans, match); if (found) { - err = rocker_cmd_exec(rocker, rocker_port, + err = rocker_cmd_exec(rocker_port, trans, flags, rocker_cmd_flow_tbl_del, - found, NULL, NULL, nowait); - kfree(found); + found, NULL, NULL); + rocker_port_kfree(trans, found); } return err; } -static gfp_t rocker_op_flags_gfp(int flags) -{ - return flags & ROCKER_OP_FLAG_NOWAIT ? GFP_ATOMIC : GFP_KERNEL; -} - static int rocker_flow_tbl_do(struct rocker_port *rocker_port, - int flags, struct rocker_flow_tbl_entry *entry) + struct switchdev_trans *trans, int flags, + struct rocker_flow_tbl_entry *entry) { - bool nowait = flags & ROCKER_OP_FLAG_NOWAIT; - if (flags & ROCKER_OP_FLAG_REMOVE) - return rocker_flow_tbl_del(rocker_port, entry, nowait); + return rocker_flow_tbl_del(rocker_port, trans, flags, entry); else - return rocker_flow_tbl_add(rocker_port, entry, nowait); + return rocker_flow_tbl_add(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_ig_port(struct rocker_port *rocker_port, - int flags, u32 in_pport, u32 in_pport_mask, + struct switchdev_trans *trans, int flags, + u32 in_pport, u32 in_pport_mask, enum rocker_of_dpa_table_id goto_tbl) { struct rocker_flow_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2410,18 +2522,19 @@ static int rocker_flow_tbl_ig_port(struct rocker_port *rocker_port, entry->key.ig_port.in_pport_mask = in_pport_mask; entry->key.ig_port.goto_tbl = goto_tbl; - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_vlan(struct rocker_port *rocker_port, - int flags, u32 in_pport, - __be16 vlan_id, __be16 vlan_id_mask, + struct switchdev_trans *trans, int flags, + u32 in_pport, __be16 vlan_id, + __be16 vlan_id_mask, enum rocker_of_dpa_table_id goto_tbl, bool untagged, __be16 new_vlan_id) { struct rocker_flow_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2435,10 +2548,11 @@ static int rocker_flow_tbl_vlan(struct rocker_port *rocker_port, entry->key.vlan.untagged = untagged; entry->key.vlan.new_vlan_id = new_vlan_id; - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_term_mac(struct rocker_port *rocker_port, + struct switchdev_trans *trans, u32 in_pport, u32 in_pport_mask, __be16 eth_type, const u8 *eth_dst, const u8 *eth_dst_mask, __be16 vlan_id, @@ -2447,7 +2561,7 @@ static int rocker_flow_tbl_term_mac(struct rocker_port *rocker_port, { struct rocker_flow_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2471,11 +2585,11 @@ static int rocker_flow_tbl_term_mac(struct rocker_port *rocker_port, entry->key.term_mac.vlan_id_mask = vlan_id_mask; entry->key.term_mac.copy_to_cpu = copy_to_cpu; - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port, - int flags, + struct switchdev_trans *trans, int flags, const u8 *eth_dst, const u8 *eth_dst_mask, __be16 vlan_id, u32 tunnel_id, enum rocker_of_dpa_table_id goto_tbl, @@ -2487,7 +2601,7 @@ static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port, bool dflt = !eth_dst || (eth_dst && eth_dst_mask); bool wild = false; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2500,7 +2614,7 @@ static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port, if (eth_dst_mask) { entry->key.bridge.has_eth_dst_mask = 1; ether_addr_copy(entry->key.bridge.eth_dst_mask, eth_dst_mask); - if (memcmp(eth_dst_mask, ff_mac, ETH_ALEN)) + if (!ether_addr_equal(eth_dst_mask, ff_mac)) wild = true; } @@ -2525,10 +2639,11 @@ static int rocker_flow_tbl_bridge(struct rocker_port *rocker_port, entry->key.bridge.group_id = group_id; entry->key.bridge.copy_to_cpu = copy_to_cpu; - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_ucast4_routing(struct rocker_port *rocker_port, + struct switchdev_trans *trans, __be16 eth_type, __be32 dst, __be32 dst_mask, u32 priority, enum rocker_of_dpa_table_id goto_tbl, @@ -2536,7 +2651,7 @@ static int rocker_flow_tbl_ucast4_routing(struct rocker_port *rocker_port, { struct rocker_flow_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2550,30 +2665,29 @@ static int rocker_flow_tbl_ucast4_routing(struct rocker_port *rocker_port, entry->key_len = offsetof(struct rocker_flow_tbl_key, ucast_routing.group_id); - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static int rocker_flow_tbl_acl(struct rocker_port *rocker_port, - int flags, u32 in_pport, - u32 in_pport_mask, + struct switchdev_trans *trans, int flags, + u32 in_pport, u32 in_pport_mask, const u8 *eth_src, const u8 *eth_src_mask, const u8 *eth_dst, const u8 *eth_dst_mask, - __be16 eth_type, - __be16 vlan_id, __be16 vlan_id_mask, - u8 ip_proto, u8 ip_proto_mask, - u8 ip_tos, u8 ip_tos_mask, + __be16 eth_type, __be16 vlan_id, + __be16 vlan_id_mask, u8 ip_proto, + u8 ip_proto_mask, u8 ip_tos, u8 ip_tos_mask, u32 group_id) { u32 priority; struct rocker_flow_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; priority = ROCKER_PRIORITY_ACL_NORMAL; if (eth_dst && eth_dst_mask) { - if (memcmp(eth_dst_mask, mcast_mac, ETH_ALEN) == 0) + if (ether_addr_equal(eth_dst_mask, mcast_mac)) priority = ROCKER_PRIORITY_ACL_DFLT; else if (is_link_local_ether_addr(eth_dst)) priority = ROCKER_PRIORITY_ACL_CTRL; @@ -2602,12 +2716,12 @@ static int rocker_flow_tbl_acl(struct rocker_port *rocker_port, entry->key.acl.ip_tos_mask = ip_tos_mask; entry->key.acl.group_id = group_id; - return rocker_flow_tbl_do(rocker_port, flags, entry); + return rocker_flow_tbl_do(rocker_port, trans, flags, entry); } static struct rocker_group_tbl_entry * -rocker_group_tbl_find(struct rocker *rocker, - struct rocker_group_tbl_entry *match) +rocker_group_tbl_find(const struct rocker *rocker, + const struct rocker_group_tbl_entry *match) { struct rocker_group_tbl_entry *found; @@ -2620,34 +2734,36 @@ rocker_group_tbl_find(struct rocker *rocker, return NULL; } -static void rocker_group_tbl_entry_free(struct rocker_group_tbl_entry *entry) +static void rocker_group_tbl_entry_free(struct switchdev_trans *trans, + struct rocker_group_tbl_entry *entry) { switch (ROCKER_GROUP_TYPE_GET(entry->group_id)) { case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD: case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST: - kfree(entry->group_ids); + rocker_port_kfree(trans, entry->group_ids); break; default: break; } - kfree(entry); + rocker_port_kfree(trans, entry); } static int rocker_group_tbl_add(struct rocker_port *rocker_port, - struct rocker_group_tbl_entry *match, - bool nowait) + struct switchdev_trans *trans, int flags, + struct rocker_group_tbl_entry *match) { struct rocker *rocker = rocker_port->rocker; struct rocker_group_tbl_entry *found; - unsigned long flags; + unsigned long lock_flags; - spin_lock_irqsave(&rocker->group_tbl_lock, flags); + spin_lock_irqsave(&rocker->group_tbl_lock, lock_flags); found = rocker_group_tbl_find(rocker, match); if (found) { - hash_del(&found->entry); - rocker_group_tbl_entry_free(found); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); + rocker_group_tbl_entry_free(trans, found); found = match; found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD; } else { @@ -2655,116 +2771,118 @@ static int rocker_group_tbl_add(struct rocker_port *rocker_port, found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD; } - hash_add(rocker->group_tbl, &found->entry, found->group_id); + if (!switchdev_trans_ph_prepare(trans)) + hash_add(rocker->group_tbl, &found->entry, found->group_id); - spin_unlock_irqrestore(&rocker->group_tbl_lock, flags); + spin_unlock_irqrestore(&rocker->group_tbl_lock, lock_flags); - return rocker_cmd_exec(rocker, rocker_port, - rocker_cmd_group_tbl_add, - found, NULL, NULL, nowait); + return rocker_cmd_exec(rocker_port, trans, flags, + rocker_cmd_group_tbl_add, found, NULL, NULL); } static int rocker_group_tbl_del(struct rocker_port *rocker_port, - struct rocker_group_tbl_entry *match, - bool nowait) + struct switchdev_trans *trans, int flags, + struct rocker_group_tbl_entry *match) { struct rocker *rocker = rocker_port->rocker; struct rocker_group_tbl_entry *found; - unsigned long flags; + unsigned long lock_flags; int err = 0; - spin_lock_irqsave(&rocker->group_tbl_lock, flags); + spin_lock_irqsave(&rocker->group_tbl_lock, lock_flags); found = rocker_group_tbl_find(rocker, match); if (found) { - hash_del(&found->entry); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); found->cmd = ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL; } - spin_unlock_irqrestore(&rocker->group_tbl_lock, flags); + spin_unlock_irqrestore(&rocker->group_tbl_lock, lock_flags); - rocker_group_tbl_entry_free(match); + rocker_group_tbl_entry_free(trans, match); if (found) { - err = rocker_cmd_exec(rocker, rocker_port, + err = rocker_cmd_exec(rocker_port, trans, flags, rocker_cmd_group_tbl_del, - found, NULL, NULL, nowait); - rocker_group_tbl_entry_free(found); + found, NULL, NULL); + rocker_group_tbl_entry_free(trans, found); } return err; } static int rocker_group_tbl_do(struct rocker_port *rocker_port, - int flags, struct rocker_group_tbl_entry *entry) + struct switchdev_trans *trans, int flags, + struct rocker_group_tbl_entry *entry) { - bool nowait = flags & ROCKER_OP_FLAG_NOWAIT; - if (flags & ROCKER_OP_FLAG_REMOVE) - return rocker_group_tbl_del(rocker_port, entry, nowait); + return rocker_group_tbl_del(rocker_port, trans, flags, entry); else - return rocker_group_tbl_add(rocker_port, entry, nowait); + return rocker_group_tbl_add(rocker_port, trans, flags, entry); } static int rocker_group_l2_interface(struct rocker_port *rocker_port, - int flags, __be16 vlan_id, - u32 out_pport, int pop_vlan) + struct switchdev_trans *trans, int flags, + __be16 vlan_id, u32 out_pport, + int pop_vlan) { struct rocker_group_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; entry->group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); entry->l2_interface.pop_vlan = pop_vlan; - return rocker_group_tbl_do(rocker_port, flags, entry); + return rocker_group_tbl_do(rocker_port, trans, flags, entry); } static int rocker_group_l2_fan_out(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, u8 group_count, - u32 *group_ids, u32 group_id) + const u32 *group_ids, u32 group_id) { struct rocker_group_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; entry->group_id = group_id; entry->group_count = group_count; - entry->group_ids = kcalloc(group_count, sizeof(u32), - rocker_op_flags_gfp(flags)); + entry->group_ids = rocker_port_kcalloc(rocker_port, trans, flags, + group_count, sizeof(u32)); if (!entry->group_ids) { - kfree(entry); + rocker_port_kfree(trans, entry); return -ENOMEM; } memcpy(entry->group_ids, group_ids, group_count * sizeof(u32)); - return rocker_group_tbl_do(rocker_port, flags, entry); + return rocker_group_tbl_do(rocker_port, trans, flags, entry); } static int rocker_group_l2_flood(struct rocker_port *rocker_port, - int flags, __be16 vlan_id, - u8 group_count, u32 *group_ids, - u32 group_id) + struct switchdev_trans *trans, int flags, + __be16 vlan_id, u8 group_count, + const u32 *group_ids, u32 group_id) { - return rocker_group_l2_fan_out(rocker_port, flags, + return rocker_group_l2_fan_out(rocker_port, trans, flags, group_count, group_ids, group_id); } static int rocker_group_l3_unicast(struct rocker_port *rocker_port, - int flags, u32 index, u8 *src_mac, - u8 *dst_mac, __be16 vlan_id, - bool ttl_check, u32 pport) + struct switchdev_trans *trans, int flags, + u32 index, const u8 *src_mac, const u8 *dst_mac, + __be16 vlan_id, bool ttl_check, u32 pport) { struct rocker_group_tbl_entry *entry; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2777,11 +2895,11 @@ static int rocker_group_l3_unicast(struct rocker_port *rocker_port, entry->l3_unicast.ttl_check = ttl_check; entry->l3_unicast.group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, pport); - return rocker_group_tbl_do(rocker_port, flags, entry); + return rocker_group_tbl_do(rocker_port, trans, flags, entry); } static struct rocker_neigh_tbl_entry * - rocker_neigh_tbl_find(struct rocker *rocker, __be32 ip_addr) +rocker_neigh_tbl_find(const struct rocker *rocker, __be32 ip_addr) { struct rocker_neigh_tbl_entry *found; @@ -2794,37 +2912,44 @@ static struct rocker_neigh_tbl_entry * } static void _rocker_neigh_add(struct rocker *rocker, + struct switchdev_trans *trans, struct rocker_neigh_tbl_entry *entry) { - entry->index = rocker->neigh_tbl_next_index++; + if (!switchdev_trans_ph_commit(trans)) + entry->index = rocker->neigh_tbl_next_index++; + if (switchdev_trans_ph_prepare(trans)) + return; entry->ref_count++; hash_add(rocker->neigh_tbl, &entry->entry, be32_to_cpu(entry->ip_addr)); } -static void _rocker_neigh_del(struct rocker *rocker, +static void _rocker_neigh_del(struct switchdev_trans *trans, struct rocker_neigh_tbl_entry *entry) { + if (switchdev_trans_ph_prepare(trans)) + return; if (--entry->ref_count == 0) { hash_del(&entry->entry); - kfree(entry); + rocker_port_kfree(trans, entry); } } -static void _rocker_neigh_update(struct rocker *rocker, - struct rocker_neigh_tbl_entry *entry, - u8 *eth_dst, bool ttl_check) +static void _rocker_neigh_update(struct rocker_neigh_tbl_entry *entry, + struct switchdev_trans *trans, + const u8 *eth_dst, bool ttl_check) { if (eth_dst) { ether_addr_copy(entry->eth_dst, eth_dst); entry->ttl_check = ttl_check; - } else { + } else if (!switchdev_trans_ph_prepare(trans)) { entry->ref_count++; } } static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, - int flags, __be32 ip_addr, u8 *eth_dst) + struct switchdev_trans *trans, + int flags, __be32 ip_addr, const u8 *eth_dst) { struct rocker *rocker = rocker_port->rocker; struct rocker_neigh_tbl_entry *entry; @@ -2840,7 +2965,7 @@ static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, bool removing; int err = 0; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2857,12 +2982,12 @@ static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, entry->dev = rocker_port->dev; ether_addr_copy(entry->eth_dst, eth_dst); entry->ttl_check = true; - _rocker_neigh_add(rocker, entry); + _rocker_neigh_add(rocker, trans, entry); } else if (removing) { memcpy(entry, found, sizeof(*entry)); - _rocker_neigh_del(rocker, found); + _rocker_neigh_del(trans, found); } else if (updating) { - _rocker_neigh_update(rocker, found, eth_dst, true); + _rocker_neigh_update(found, trans, eth_dst, true); memcpy(entry, found, sizeof(*entry)); } else { err = -ENOENT; @@ -2879,7 +3004,7 @@ static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, * other routes' nexthops. */ - err = rocker_group_l3_unicast(rocker_port, flags, + err = rocker_group_l3_unicast(rocker_port, trans, flags, entry->index, rocker_port->dev->dev_addr, entry->eth_dst, @@ -2895,7 +3020,7 @@ static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, if (adding || removing) { group_id = ROCKER_GROUP_L3_UNICAST(entry->index); - err = rocker_flow_tbl_ucast4_routing(rocker_port, + err = rocker_flow_tbl_ucast4_routing(rocker_port, trans, eth_type, ip_addr, inet_make_mask(32), priority, goto_tbl, @@ -2909,12 +3034,13 @@ static int rocker_port_ipv4_neigh(struct rocker_port *rocker_port, err_out: if (!adding) - kfree(entry); + rocker_port_kfree(trans, entry); return err; } static int rocker_port_ipv4_resolve(struct rocker_port *rocker_port, + struct switchdev_trans *trans, __be32 ip_addr) { struct net_device *dev = rocker_port->dev; @@ -2933,7 +3059,8 @@ static int rocker_port_ipv4_resolve(struct rocker_port *rocker_port, */ if (n->nud_state & NUD_VALID) - err = rocker_port_ipv4_neigh(rocker_port, 0, ip_addr, n->ha); + err = rocker_port_ipv4_neigh(rocker_port, trans, 0, + ip_addr, n->ha); else neigh_event_send(n, NULL); @@ -2941,7 +3068,8 @@ static int rocker_port_ipv4_resolve(struct rocker_port *rocker_port, return err; } -static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, int flags, +static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, __be32 ip_addr, u32 *index) { struct rocker *rocker = rocker_port->rocker; @@ -2954,7 +3082,7 @@ static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, int flags, bool resolved = true; int err = 0; - entry = kzalloc(sizeof(*entry), rocker_op_flags_gfp(flags)); + entry = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*entry)); if (!entry) return -ENOMEM; @@ -2971,13 +3099,13 @@ static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, int flags, if (adding) { entry->ip_addr = ip_addr; entry->dev = rocker_port->dev; - _rocker_neigh_add(rocker, entry); + _rocker_neigh_add(rocker, trans, entry); *index = entry->index; resolved = false; } else if (removing) { - _rocker_neigh_del(rocker, found); + _rocker_neigh_del(trans, found); } else if (updating) { - _rocker_neigh_update(rocker, found, NULL, false); + _rocker_neigh_update(found, trans, NULL, false); resolved = !is_zero_ether_addr(found->eth_dst); } else { err = -ENOENT; @@ -2986,7 +3114,7 @@ static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, int flags, spin_unlock_irqrestore(&rocker->neigh_tbl_lock, lock_flags); if (!adding) - kfree(entry); + rocker_port_kfree(trans, entry); if (err) return err; @@ -2994,24 +3122,25 @@ static int rocker_port_ipv4_nh(struct rocker_port *rocker_port, int flags, /* Resolved means neigh ip_addr is resolved to neigh mac. */ if (!resolved) - err = rocker_port_ipv4_resolve(rocker_port, ip_addr); + err = rocker_port_ipv4_resolve(rocker_port, trans, ip_addr); return err; } static int rocker_port_vlan_flood_group(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, __be16 vlan_id) { struct rocker_port *p; - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; u32 group_id = ROCKER_GROUP_L2_FLOOD(vlan_id, 0); u32 *group_ids; u8 group_count = 0; int err = 0; int i; - group_ids = kcalloc(rocker->port_count, sizeof(u32), - rocker_op_flags_gfp(flags)); + group_ids = rocker_port_kcalloc(rocker_port, trans, flags, + rocker->port_count, sizeof(u32)); if (!group_ids) return -ENOMEM; @@ -3022,6 +3151,8 @@ static int rocker_port_vlan_flood_group(struct rocker_port *rocker_port, for (i = 0; i < rocker->port_count; i++) { p = rocker->ports[i]; + if (!p) + continue; if (!rocker_port_is_bridged(p)) continue; if (test_bit(ntohs(vlan_id), p->vlan_bitmap)) { @@ -3034,23 +3165,22 @@ static int rocker_port_vlan_flood_group(struct rocker_port *rocker_port, if (group_count == 0) goto no_ports_in_vlan; - err = rocker_group_l2_flood(rocker_port, flags, vlan_id, - group_count, group_ids, - group_id); + err = rocker_group_l2_flood(rocker_port, trans, flags, vlan_id, + group_count, group_ids, group_id); if (err) netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 flood group\n", err); no_ports_in_vlan: - kfree(group_ids); + rocker_port_kfree(trans, group_ids); return err; } static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port, - int flags, __be16 vlan_id, - bool pop_vlan) + struct switchdev_trans *trans, int flags, + __be16 vlan_id, bool pop_vlan) { - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; struct rocker_port *p; bool adding = !(flags & ROCKER_OP_FLAG_REMOVE); u32 out_pport; @@ -3065,9 +3195,8 @@ static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port, if (rocker_port->stp_state == BR_STATE_LEARNING || rocker_port->stp_state == BR_STATE_FORWARDING) { out_pport = rocker_port->pport; - err = rocker_group_l2_interface(rocker_port, flags, - vlan_id, out_pport, - pop_vlan); + err = rocker_group_l2_interface(rocker_port, trans, flags, + vlan_id, out_pport, pop_vlan); if (err) { netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 group for pport %d\n", @@ -3083,7 +3212,7 @@ static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port, for (i = 0; i < rocker->port_count; i++) { p = rocker->ports[i]; - if (test_bit(ntohs(vlan_id), p->vlan_bitmap)) + if (p && test_bit(ntohs(vlan_id), p->vlan_bitmap)) ref++; } @@ -3091,9 +3220,8 @@ static int rocker_port_vlan_l2_groups(struct rocker_port *rocker_port, return 0; out_pport = 0; - err = rocker_group_l2_interface(rocker_port, flags, - vlan_id, out_pport, - pop_vlan); + err = rocker_group_l2_interface(rocker_port, trans, flags, + vlan_id, out_pport, pop_vlan); if (err) { netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 group for CPU port\n", err); @@ -3146,17 +3274,23 @@ static struct rocker_ctrl { .bridge = true, .copy_to_cpu = true, }, + [ROCKER_CTRL_DFLT_OVS] = { + /* pass all pkts up to CPU */ + .eth_dst = zero_mac, + .eth_dst_mask = zero_mac, + .acl = true, + }, }; static int rocker_port_ctrl_vlan_acl(struct rocker_port *rocker_port, - int flags, struct rocker_ctrl *ctrl, - __be16 vlan_id) + struct switchdev_trans *trans, int flags, + const struct rocker_ctrl *ctrl, __be16 vlan_id) { u32 in_pport = rocker_port->pport; u32 in_pport_mask = 0xffffffff; u32 out_pport = 0; - u8 *eth_src = NULL; - u8 *eth_src_mask = NULL; + const u8 *eth_src = NULL; + const u8 *eth_src_mask = NULL; __be16 vlan_id_mask = htons(0xffff); u8 ip_proto = 0; u8 ip_proto_mask = 0; @@ -3165,7 +3299,7 @@ static int rocker_port_ctrl_vlan_acl(struct rocker_port *rocker_port, u32 group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); int err; - err = rocker_flow_tbl_acl(rocker_port, flags, + err = rocker_flow_tbl_acl(rocker_port, trans, flags, in_pport, in_pport_mask, eth_src, eth_src_mask, ctrl->eth_dst, ctrl->eth_dst_mask, @@ -3182,7 +3316,9 @@ static int rocker_port_ctrl_vlan_acl(struct rocker_port *rocker_port, } static int rocker_port_ctrl_vlan_bridge(struct rocker_port *rocker_port, - int flags, struct rocker_ctrl *ctrl, + struct switchdev_trans *trans, + int flags, + const struct rocker_ctrl *ctrl, __be16 vlan_id) { enum rocker_of_dpa_table_id goto_tbl = @@ -3194,7 +3330,7 @@ static int rocker_port_ctrl_vlan_bridge(struct rocker_port *rocker_port, if (!rocker_port_is_bridged(rocker_port)) return 0; - err = rocker_flow_tbl_bridge(rocker_port, flags, + err = rocker_flow_tbl_bridge(rocker_port, trans, flags, ctrl->eth_dst, ctrl->eth_dst_mask, vlan_id, tunnel_id, goto_tbl, group_id, ctrl->copy_to_cpu); @@ -3206,8 +3342,8 @@ static int rocker_port_ctrl_vlan_bridge(struct rocker_port *rocker_port, } static int rocker_port_ctrl_vlan_term(struct rocker_port *rocker_port, - int flags, struct rocker_ctrl *ctrl, - __be16 vlan_id) + struct switchdev_trans *trans, int flags, + const struct rocker_ctrl *ctrl, __be16 vlan_id) { u32 in_pport_mask = 0xffffffff; __be16 vlan_id_mask = htons(0xffff); @@ -3216,7 +3352,7 @@ static int rocker_port_ctrl_vlan_term(struct rocker_port *rocker_port, if (ntohs(vlan_id) == 0) vlan_id = rocker_port->internal_vlan_id; - err = rocker_flow_tbl_term_mac(rocker_port, + err = rocker_flow_tbl_term_mac(rocker_port, trans, rocker_port->pport, in_pport_mask, ctrl->eth_type, ctrl->eth_dst, ctrl->eth_dst_mask, vlan_id, @@ -3229,32 +3365,34 @@ static int rocker_port_ctrl_vlan_term(struct rocker_port *rocker_port, return err; } -static int rocker_port_ctrl_vlan(struct rocker_port *rocker_port, int flags, - struct rocker_ctrl *ctrl, __be16 vlan_id) +static int rocker_port_ctrl_vlan(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + const struct rocker_ctrl *ctrl, __be16 vlan_id) { if (ctrl->acl) - return rocker_port_ctrl_vlan_acl(rocker_port, flags, + return rocker_port_ctrl_vlan_acl(rocker_port, trans, flags, ctrl, vlan_id); if (ctrl->bridge) - return rocker_port_ctrl_vlan_bridge(rocker_port, flags, + return rocker_port_ctrl_vlan_bridge(rocker_port, trans, flags, ctrl, vlan_id); if (ctrl->term) - return rocker_port_ctrl_vlan_term(rocker_port, flags, + return rocker_port_ctrl_vlan_term(rocker_port, trans, flags, ctrl, vlan_id); return -EOPNOTSUPP; } static int rocker_port_ctrl_vlan_add(struct rocker_port *rocker_port, - int flags, __be16 vlan_id) + struct switchdev_trans *trans, int flags, + __be16 vlan_id) { int err = 0; int i; for (i = 0; i < ROCKER_CTRL_MAX; i++) { if (rocker_port->ctrls[i]) { - err = rocker_port_ctrl_vlan(rocker_port, flags, + err = rocker_port_ctrl_vlan(rocker_port, trans, flags, &rocker_ctrls[i], vlan_id); if (err) return err; @@ -3264,8 +3402,9 @@ static int rocker_port_ctrl_vlan_add(struct rocker_port *rocker_port, return err; } -static int rocker_port_ctrl(struct rocker_port *rocker_port, int flags, - struct rocker_ctrl *ctrl) +static int rocker_port_ctrl(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + const struct rocker_ctrl *ctrl) { u16 vid; int err = 0; @@ -3273,7 +3412,7 @@ static int rocker_port_ctrl(struct rocker_port *rocker_port, int flags, for (vid = 1; vid < VLAN_N_VID; vid++) { if (!test_bit(vid, rocker_port->vlan_bitmap)) continue; - err = rocker_port_ctrl_vlan(rocker_port, flags, + err = rocker_port_ctrl_vlan(rocker_port, trans, flags, ctrl, htons(vid)); if (err) break; @@ -3282,8 +3421,8 @@ static int rocker_port_ctrl(struct rocker_port *rocker_port, int flags, return err; } -static int rocker_port_vlan(struct rocker_port *rocker_port, int flags, - u16 vid) +static int rocker_port_vlan(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, u16 vid) { enum rocker_of_dpa_table_id goto_tbl = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC; @@ -3297,50 +3436,57 @@ static int rocker_port_vlan(struct rocker_port *rocker_port, int flags, internal_vlan_id = rocker_port_vid_to_vlan(rocker_port, vid, &untagged); - if (adding && test_and_set_bit(ntohs(internal_vlan_id), - rocker_port->vlan_bitmap)) + if (adding && test_bit(ntohs(internal_vlan_id), + rocker_port->vlan_bitmap)) return 0; /* already added */ - else if (!adding && !test_and_clear_bit(ntohs(internal_vlan_id), - rocker_port->vlan_bitmap)) + else if (!adding && !test_bit(ntohs(internal_vlan_id), + rocker_port->vlan_bitmap)) return 0; /* already removed */ + change_bit(ntohs(internal_vlan_id), rocker_port->vlan_bitmap); + if (adding) { - err = rocker_port_ctrl_vlan_add(rocker_port, flags, + err = rocker_port_ctrl_vlan_add(rocker_port, trans, flags, internal_vlan_id); if (err) { netdev_err(rocker_port->dev, "Error (%d) port ctrl vlan add\n", err); - return err; + goto err_out; } } - err = rocker_port_vlan_l2_groups(rocker_port, flags, + err = rocker_port_vlan_l2_groups(rocker_port, trans, flags, internal_vlan_id, untagged); if (err) { netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 groups\n", err); - return err; + goto err_out; } - err = rocker_port_vlan_flood_group(rocker_port, flags, + err = rocker_port_vlan_flood_group(rocker_port, trans, flags, internal_vlan_id); if (err) { netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 flood group\n", err); - return err; + goto err_out; } - err = rocker_flow_tbl_vlan(rocker_port, flags, + err = rocker_flow_tbl_vlan(rocker_port, trans, flags, in_pport, vlan_id, vlan_id_mask, goto_tbl, untagged, internal_vlan_id); if (err) netdev_err(rocker_port->dev, "Error (%d) port VLAN table\n", err); +err_out: + if (switchdev_trans_ph_prepare(trans)) + change_bit(ntohs(internal_vlan_id), rocker_port->vlan_bitmap); + return err; } -static int rocker_port_ig_tbl(struct rocker_port *rocker_port, int flags) +static int rocker_port_ig_tbl(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags) { enum rocker_of_dpa_table_id goto_tbl; u32 in_pport; @@ -3355,7 +3501,7 @@ static int rocker_port_ig_tbl(struct rocker_port *rocker_port, int flags) in_pport_mask = 0xffff0000; goto_tbl = ROCKER_OF_DPA_TABLE_ID_VLAN; - err = rocker_flow_tbl_ig_port(rocker_port, flags, + err = rocker_flow_tbl_ig_port(rocker_port, trans, flags, in_pport, in_pport_mask, goto_tbl); if (err) @@ -3367,7 +3513,8 @@ static int rocker_port_ig_tbl(struct rocker_port *rocker_port, int flags) struct rocker_fdb_learn_work { struct work_struct work; - struct net_device *dev; + struct rocker_port *rocker_port; + struct switchdev_trans *trans; int flags; u8 addr[ETH_ALEN]; u16 vid; @@ -3375,27 +3522,30 @@ struct rocker_fdb_learn_work { static void rocker_port_fdb_learn_work(struct work_struct *work) { - struct rocker_fdb_learn_work *lw = + const struct rocker_fdb_learn_work *lw = container_of(work, struct rocker_fdb_learn_work, work); bool removing = (lw->flags & ROCKER_OP_FLAG_REMOVE); bool learned = (lw->flags & ROCKER_OP_FLAG_LEARNED); - struct netdev_switch_notifier_fdb_info info; + struct switchdev_notifier_fdb_info info; info.addr = lw->addr; info.vid = lw->vid; + rtnl_lock(); if (learned && removing) - call_netdev_switch_notifiers(NETDEV_SWITCH_FDB_DEL, - lw->dev, &info.info); + call_switchdev_notifiers(SWITCHDEV_FDB_DEL, + lw->rocker_port->dev, &info.info); else if (learned && !removing) - call_netdev_switch_notifiers(NETDEV_SWITCH_FDB_ADD, - lw->dev, &info.info); + call_switchdev_notifiers(SWITCHDEV_FDB_ADD, + lw->rocker_port->dev, &info.info); + rtnl_unlock(); - kfree(work); + rocker_port_kfree(lw->trans, work); } static int rocker_port_fdb_learn(struct rocker_port *rocker_port, - int flags, const u8 *addr, __be16 vlan_id) + struct switchdev_trans *trans, int flags, + const u8 *addr, __be16 vlan_id) { struct rocker_fdb_learn_work *lw; enum rocker_of_dpa_table_id goto_tbl = @@ -3411,8 +3561,8 @@ static int rocker_port_fdb_learn(struct rocker_port *rocker_port, group_id = ROCKER_GROUP_L2_INTERFACE(vlan_id, out_pport); if (!(flags & ROCKER_OP_FLAG_REFRESH)) { - err = rocker_flow_tbl_bridge(rocker_port, flags, addr, NULL, - vlan_id, tunnel_id, goto_tbl, + err = rocker_flow_tbl_bridge(rocker_port, trans, flags, addr, + NULL, vlan_id, tunnel_id, goto_tbl, group_id, copy_to_cpu); if (err) return err; @@ -3424,24 +3574,29 @@ static int rocker_port_fdb_learn(struct rocker_port *rocker_port, if (!rocker_port_is_bridged(rocker_port)) return 0; - lw = kmalloc(sizeof(*lw), rocker_op_flags_gfp(flags)); + lw = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*lw)); if (!lw) return -ENOMEM; INIT_WORK(&lw->work, rocker_port_fdb_learn_work); - lw->dev = rocker_port->dev; + lw->rocker_port = rocker_port; + lw->trans = trans; lw->flags = flags; ether_addr_copy(lw->addr, addr); lw->vid = rocker_port_vlan_to_vid(rocker_port, vlan_id); - schedule_work(&lw->work); + if (switchdev_trans_ph_prepare(trans)) + rocker_port_kfree(trans, lw); + else + schedule_work(&lw->work); return 0; } static struct rocker_fdb_tbl_entry * -rocker_fdb_tbl_find(struct rocker *rocker, struct rocker_fdb_tbl_entry *match) +rocker_fdb_tbl_find(const struct rocker *rocker, + const struct rocker_fdb_tbl_entry *match) { struct rocker_fdb_tbl_entry *found; @@ -3453,6 +3608,7 @@ rocker_fdb_tbl_find(struct rocker *rocker, struct rocker_fdb_tbl_entry *match) } static int rocker_port_fdb(struct rocker_port *rocker_port, + struct switchdev_trans *trans, const unsigned char *addr, __be16 vlan_id, int flags) { @@ -3462,12 +3618,13 @@ static int rocker_port_fdb(struct rocker_port *rocker_port, bool removing = (flags & ROCKER_OP_FLAG_REMOVE); unsigned long lock_flags; - fdb = kzalloc(sizeof(*fdb), rocker_op_flags_gfp(flags)); + fdb = rocker_port_kzalloc(rocker_port, trans, flags, sizeof(*fdb)); if (!fdb) 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)); @@ -3476,33 +3633,39 @@ static int rocker_port_fdb(struct rocker_port *rocker_port, found = rocker_fdb_tbl_find(rocker, fdb); - if (removing && found) { - kfree(fdb); - hash_del(&found->entry); - } else if (!removing && !found) { - hash_add(rocker->fdb_tbl, &fdb->entry, fdb->key_crc32); + if (found) { + found->touched = jiffies; + if (removing) { + rocker_port_kfree(trans, fdb); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); + } + } else if (!removing) { + if (!switchdev_trans_ph_prepare(trans)) + hash_add(rocker->fdb_tbl, &fdb->entry, + fdb->key_crc32); } spin_unlock_irqrestore(&rocker->fdb_tbl_lock, lock_flags); /* Check if adding and already exists, or removing and can't find */ if (!found != !removing) { - kfree(fdb); + rocker_port_kfree(trans, fdb); if (!found && removing) return 0; /* Refreshing existing to update aging timers */ flags |= ROCKER_OP_FLAG_REFRESH; } - return rocker_port_fdb_learn(rocker_port, flags, addr, vlan_id); + return rocker_port_fdb_learn(rocker_port, trans, flags, addr, vlan_id); } -static int rocker_port_fdb_flush(struct rocker_port *rocker_port) +static int rocker_port_fdb_flush(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags) { struct rocker *rocker = rocker_port->rocker; struct rocker_fdb_tbl_entry *found; unsigned long lock_flags; - int flags = ROCKER_OP_FLAG_NOWAIT | ROCKER_OP_FLAG_REMOVE; struct hlist_node *tmp; int bkt; int err = 0; @@ -3511,19 +3674,22 @@ static int rocker_port_fdb_flush(struct rocker_port *rocker_port) rocker_port->stp_state == BR_STATE_FORWARDING) return 0; + flags |= ROCKER_OP_FLAG_NOWAIT | ROCKER_OP_FLAG_REMOVE; + 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; - err = rocker_port_fdb_learn(rocker_port, flags, + err = rocker_port_fdb_learn(rocker_port, trans, flags, found->key.addr, found->key.vlan_id); if (err) goto err_out; - hash_del(&found->entry); + if (!switchdev_trans_ph_prepare(trans)) + hash_del(&found->entry); } err_out: @@ -3532,8 +3698,44 @@ 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, NULL, + 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, - int flags, __be16 vlan_id) + struct switchdev_trans *trans, int flags, + __be16 vlan_id) { u32 in_pport_mask = 0xffffffff; __be16 eth_type; @@ -3546,7 +3748,7 @@ static int rocker_port_router_mac(struct rocker_port *rocker_port, vlan_id = rocker_port->internal_vlan_id; eth_type = htons(ETH_P_IP); - err = rocker_flow_tbl_term_mac(rocker_port, + err = rocker_flow_tbl_term_mac(rocker_port, trans, rocker_port->pport, in_pport_mask, eth_type, rocker_port->dev->dev_addr, dst_mac_mask, vlan_id, vlan_id_mask, @@ -3555,7 +3757,7 @@ static int rocker_port_router_mac(struct rocker_port *rocker_port, return err; eth_type = htons(ETH_P_IPV6); - err = rocker_flow_tbl_term_mac(rocker_port, + err = rocker_flow_tbl_term_mac(rocker_port, trans, rocker_port->pport, in_pport_mask, eth_type, rocker_port->dev->dev_addr, dst_mac_mask, vlan_id, vlan_id_mask, @@ -3564,13 +3766,13 @@ static int rocker_port_router_mac(struct rocker_port *rocker_port, return err; } -static int rocker_port_fwding(struct rocker_port *rocker_port) +static int rocker_port_fwding(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags) { bool pop_vlan; u32 out_pport; __be16 vlan_id; u16 vid; - int flags = ROCKER_OP_FLAG_NOWAIT; int err; /* Port will be forwarding-enabled if its STP state is LEARNING @@ -3590,9 +3792,8 @@ static int rocker_port_fwding(struct rocker_port *rocker_port) continue; vlan_id = htons(vid); pop_vlan = rocker_vlan_id_is_internal(vlan_id); - err = rocker_group_l2_interface(rocker_port, flags, - vlan_id, out_pport, - pop_vlan); + err = rocker_group_l2_interface(rocker_port, trans, flags, + vlan_id, out_pport, pop_vlan); if (err) { netdev_err(rocker_port->dev, "Error (%d) port VLAN l2 group for pport %d\n", @@ -3604,13 +3805,21 @@ static int rocker_port_fwding(struct rocker_port *rocker_port) return 0; } -static int rocker_port_stp_update(struct rocker_port *rocker_port, u8 state) +static int rocker_port_stp_update(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags, + u8 state) { bool want[ROCKER_CTRL_MAX] = { 0, }; - int flags; + bool prev_ctrls[ROCKER_CTRL_MAX]; + u8 uninitialized_var(prev_state); int err; int i; + if (switchdev_trans_ph_prepare(trans)) { + memcpy(prev_ctrls, rocker_port->ctrls, sizeof(prev_ctrls)); + prev_state = rocker_port->stp_state; + } + if (rocker_port->stp_state == state) return 0; @@ -3626,11 +3835,14 @@ static int rocker_port_stp_update(struct rocker_port *rocker_port, u8 state) break; case BR_STATE_LEARNING: case BR_STATE_FORWARDING: - want[ROCKER_CTRL_LINK_LOCAL_MCAST] = true; + if (!rocker_port_is_ovsed(rocker_port)) + want[ROCKER_CTRL_LINK_LOCAL_MCAST] = true; want[ROCKER_CTRL_IPV4_MCAST] = true; want[ROCKER_CTRL_IPV6_MCAST] = true; if (rocker_port_is_bridged(rocker_port)) want[ROCKER_CTRL_DFLT_BRIDGING] = true; + else if (rocker_port_is_ovsed(rocker_port)) + want[ROCKER_CTRL_DFLT_OVS] = true; else want[ROCKER_CTRL_LOCAL_ARP] = true; break; @@ -3638,45 +3850,57 @@ static int rocker_port_stp_update(struct rocker_port *rocker_port, u8 state) for (i = 0; i < ROCKER_CTRL_MAX; i++) { if (want[i] != rocker_port->ctrls[i]) { - flags = ROCKER_OP_FLAG_NOWAIT | - (want[i] ? 0 : ROCKER_OP_FLAG_REMOVE); - err = rocker_port_ctrl(rocker_port, flags, + int ctrl_flags = flags | + (want[i] ? 0 : ROCKER_OP_FLAG_REMOVE); + err = rocker_port_ctrl(rocker_port, trans, ctrl_flags, &rocker_ctrls[i]); if (err) - return err; + goto err_out; rocker_port->ctrls[i] = want[i]; } } - err = rocker_port_fdb_flush(rocker_port); + err = rocker_port_fdb_flush(rocker_port, trans, flags); if (err) - return err; + goto err_out; + + err = rocker_port_fwding(rocker_port, trans, flags); + +err_out: + if (switchdev_trans_ph_prepare(trans)) { + memcpy(rocker_port->ctrls, prev_ctrls, sizeof(prev_ctrls)); + rocker_port->stp_state = prev_state; + } - return rocker_port_fwding(rocker_port); + return err; } -static int rocker_port_fwd_enable(struct rocker_port *rocker_port) +static int rocker_port_fwd_enable(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags) { if (rocker_port_is_bridged(rocker_port)) /* bridge STP will enable port */ return 0; /* port is not bridged, so simulate going to FORWARDING state */ - return rocker_port_stp_update(rocker_port, BR_STATE_FORWARDING); + return rocker_port_stp_update(rocker_port, trans, flags, + BR_STATE_FORWARDING); } -static int rocker_port_fwd_disable(struct rocker_port *rocker_port) +static int rocker_port_fwd_disable(struct rocker_port *rocker_port, + struct switchdev_trans *trans, int flags) { if (rocker_port_is_bridged(rocker_port)) /* bridge STP will disable port */ return 0; /* port is not bridged, so simulate going to DISABLED state */ - return rocker_port_stp_update(rocker_port, BR_STATE_DISABLED); + return rocker_port_stp_update(rocker_port, trans, flags, + BR_STATE_DISABLED); } static struct rocker_internal_vlan_tbl_entry * -rocker_internal_vlan_tbl_find(struct rocker *rocker, int ifindex) +rocker_internal_vlan_tbl_find(const struct rocker *rocker, int ifindex) { struct rocker_internal_vlan_tbl_entry *found; @@ -3731,8 +3955,9 @@ found: return found->vlan_id; } -static void rocker_port_internal_vlan_id_put(struct rocker_port *rocker_port, - int ifindex) +static void +rocker_port_internal_vlan_id_put(const struct rocker_port *rocker_port, + int ifindex) { struct rocker *rocker = rocker_port->rocker; struct rocker_internal_vlan_tbl_entry *found; @@ -3760,11 +3985,12 @@ not_found: spin_unlock_irqrestore(&rocker->internal_vlan_tbl_lock, lock_flags); } -static int rocker_port_fib_ipv4(struct rocker_port *rocker_port, __be32 dst, - int dst_len, struct fib_info *fi, u32 tb_id, - int flags) +static int rocker_port_fib_ipv4(struct rocker_port *rocker_port, + struct switchdev_trans *trans, __be32 dst, + int dst_len, const struct fib_info *fi, + u32 tb_id, int flags) { - struct fib_nh *nh; + const struct fib_nh *nh; __be16 eth_type = htons(ETH_P_IP); __be32 dst_mask = inet_make_mask(dst_len); __be16 internal_vlan_id = rocker_port->internal_vlan_id; @@ -3784,7 +4010,7 @@ static int rocker_port_fib_ipv4(struct rocker_port *rocker_port, __be32 dst, has_gw = !!nh->nh_gw; if (has_gw && nh_on_port) { - err = rocker_port_ipv4_nh(rocker_port, flags, + err = rocker_port_ipv4_nh(rocker_port, trans, flags, nh->nh_gw, &index); if (err) return err; @@ -3795,7 +4021,7 @@ static int rocker_port_fib_ipv4(struct rocker_port *rocker_port, __be32 dst, group_id = ROCKER_GROUP_L2_INTERFACE(internal_vlan_id, 0); } - err = rocker_flow_tbl_ucast4_routing(rocker_port, eth_type, dst, + err = rocker_flow_tbl_ucast4_routing(rocker_port, trans, eth_type, dst, dst_mask, priority, goto_tbl, group_id, flags); if (err) @@ -3834,13 +4060,14 @@ static int rocker_port_open(struct net_device *dev) goto err_request_rx_irq; } - err = rocker_port_fwd_enable(rocker_port); + err = rocker_port_fwd_enable(rocker_port, NULL, 0); if (err) goto err_fwd_enable; napi_enable(&rocker_port->napi_tx); napi_enable(&rocker_port->napi_rx); - rocker_port_set_enable(rocker_port, true); + if (!dev->proto_down) + rocker_port_set_enable(rocker_port, true); netif_start_queue(dev); return 0; @@ -3861,7 +4088,8 @@ static int rocker_port_stop(struct net_device *dev) rocker_port_set_enable(rocker_port, false); napi_disable(&rocker_port->napi_rx); napi_disable(&rocker_port->napi_tx); - rocker_port_fwd_disable(rocker_port); + rocker_port_fwd_disable(rocker_port, NULL, + ROCKER_OP_FLAG_NOWAIT); free_irq(rocker_msix_rx_vector(rocker_port), rocker_port); free_irq(rocker_msix_tx_vector(rocker_port), rocker_port); rocker_port_dma_rings_fini(rocker_port); @@ -3869,12 +4097,12 @@ static int rocker_port_stop(struct net_device *dev) return 0; } -static void rocker_tx_desc_frags_unmap(struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info) +static void rocker_tx_desc_frags_unmap(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info) { - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; struct pci_dev *pdev = rocker->pdev; - struct rocker_tlv *attrs[ROCKER_TLV_TX_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_TX_MAX + 1]; struct rocker_tlv *attr; int rem; @@ -3882,7 +4110,7 @@ static void rocker_tx_desc_frags_unmap(struct rocker_port *rocker_port, if (!attrs[ROCKER_TLV_TX_FRAGS]) return; rocker_tlv_for_each_nested(attr, attrs[ROCKER_TLV_TX_FRAGS], rem) { - struct rocker_tlv *frag_attrs[ROCKER_TLV_TX_FRAG_ATTR_MAX + 1]; + const struct rocker_tlv *frag_attrs[ROCKER_TLV_TX_FRAG_ATTR_MAX + 1]; dma_addr_t dma_handle; size_t len; @@ -3899,11 +4127,11 @@ static void rocker_tx_desc_frags_unmap(struct rocker_port *rocker_port, } } -static int rocker_tx_desc_frag_map_put(struct rocker_port *rocker_port, +static int rocker_tx_desc_frag_map_put(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, char *buf, size_t buf_len) { - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; struct pci_dev *pdev = rocker->pdev; dma_addr_t dma_handle; struct rocker_tlv *frag; @@ -3958,8 +4186,11 @@ static netdev_tx_t rocker_port_xmit(struct sk_buff *skb, struct net_device *dev) skb->data, skb_headlen(skb)); if (err) goto nest_cancel; - if (skb_shinfo(skb)->nr_frags > ROCKER_TX_FRAGS_MAX) - goto nest_cancel; + if (skb_shinfo(skb)->nr_frags > ROCKER_TX_FRAGS_MAX) { + err = skb_linearize(skb); + if (err) + goto unmap_frags; + } for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; @@ -4008,269 +4239,404 @@ static int rocker_port_set_mac_address(struct net_device *dev, void *p) return 0; } -static int rocker_port_vlan_rx_add_vid(struct net_device *dev, - __be16 proto, u16 vid) +static int rocker_port_change_mtu(struct net_device *dev, int new_mtu) { struct rocker_port *rocker_port = netdev_priv(dev); + int running = netif_running(dev); int err; - err = rocker_port_vlan(rocker_port, 0, vid); +#define ROCKER_PORT_MIN_MTU 68 +#define ROCKER_PORT_MAX_MTU 9000 + + if (new_mtu < ROCKER_PORT_MIN_MTU || new_mtu > ROCKER_PORT_MAX_MTU) + return -EINVAL; + + if (running) + rocker_port_stop(dev); + + netdev_info(dev, "MTU change from %d to %d\n", dev->mtu, new_mtu); + dev->mtu = new_mtu; + + err = rocker_cmd_set_port_settings_mtu(rocker_port, new_mtu); if (err) return err; - return rocker_port_router_mac(rocker_port, 0, htons(vid)); + if (running) + err = rocker_port_open(dev); + + return err; } -static int rocker_port_vlan_rx_kill_vid(struct net_device *dev, - __be16 proto, u16 vid) +static int rocker_port_get_phys_port_name(struct net_device *dev, + char *buf, size_t len) { struct rocker_port *rocker_port = netdev_priv(dev); + struct port_name name = { .buf = buf, .len = len }; int err; - err = rocker_port_router_mac(rocker_port, ROCKER_OP_FLAG_REMOVE, - htons(vid)); - if (err) - return err; + err = rocker_cmd_exec(rocker_port, NULL, 0, + rocker_cmd_get_port_settings_prep, NULL, + rocker_cmd_get_port_settings_phys_name_proc, + &name); - return rocker_port_vlan(rocker_port, ROCKER_OP_FLAG_REMOVE, vid); + return err ? -EOPNOTSUPP : 0; } -static int rocker_port_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], - struct net_device *dev, - const unsigned char *addr, u16 vid, - u16 nlm_flags) +static int rocker_port_change_proto_down(struct net_device *dev, + bool proto_down) { struct rocker_port *rocker_port = netdev_priv(dev); - __be16 vlan_id = rocker_port_vid_to_vlan(rocker_port, vid, NULL); - int flags = 0; - if (!rocker_port_is_bridged(rocker_port)) - return -EINVAL; + if (rocker_port->dev->flags & IFF_UP) + rocker_port_set_enable(rocker_port, !proto_down); + rocker_port->dev->proto_down = proto_down; + return 0; +} + +static void rocker_port_neigh_destroy(struct neighbour *n) +{ + struct rocker_port *rocker_port = netdev_priv(n->dev); + int flags = ROCKER_OP_FLAG_REMOVE | ROCKER_OP_FLAG_NOWAIT; + __be32 ip_addr = *(__be32 *)n->primary_key; - return rocker_port_fdb(rocker_port, addr, vlan_id, flags); + rocker_port_ipv4_neigh(rocker_port, NULL, + flags, ip_addr, n->ha); } -static int rocker_port_fdb_del(struct ndmsg *ndm, struct nlattr *tb[], - struct net_device *dev, - const unsigned char *addr, u16 vid) +static const struct net_device_ops rocker_port_netdev_ops = { + .ndo_open = rocker_port_open, + .ndo_stop = rocker_port_stop, + .ndo_start_xmit = rocker_port_xmit, + .ndo_set_mac_address = rocker_port_set_mac_address, + .ndo_change_mtu = rocker_port_change_mtu, + .ndo_bridge_getlink = switchdev_port_bridge_getlink, + .ndo_bridge_setlink = switchdev_port_bridge_setlink, + .ndo_bridge_dellink = switchdev_port_bridge_dellink, + .ndo_fdb_add = switchdev_port_fdb_add, + .ndo_fdb_del = switchdev_port_fdb_del, + .ndo_fdb_dump = switchdev_port_fdb_dump, + .ndo_get_phys_port_name = rocker_port_get_phys_port_name, + .ndo_change_proto_down = rocker_port_change_proto_down, + .ndo_neigh_destroy = rocker_port_neigh_destroy, +}; + +/******************** + * swdev interface + ********************/ + +static int rocker_port_attr_get(struct net_device *dev, + struct switchdev_attr *attr) { - struct rocker_port *rocker_port = netdev_priv(dev); - __be16 vlan_id = rocker_port_vid_to_vlan(rocker_port, vid, NULL); - int flags = ROCKER_OP_FLAG_REMOVE; + const struct rocker_port *rocker_port = netdev_priv(dev); + const struct rocker *rocker = rocker_port->rocker; - if (!rocker_port_is_bridged(rocker_port)) - return -EINVAL; + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_PARENT_ID: + attr->u.ppid.id_len = sizeof(rocker->hw.id); + memcpy(&attr->u.ppid.id, &rocker->hw.id, attr->u.ppid.id_len); + break; + case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: + attr->u.brport_flags = rocker_port->brport_flags; + break; + default: + return -EOPNOTSUPP; + } - return rocker_port_fdb(rocker_port, addr, vlan_id, flags); + return 0; } -static int rocker_fdb_fill_info(struct sk_buff *skb, - struct rocker_port *rocker_port, - const unsigned char *addr, u16 vid, - u32 portid, u32 seq, int type, - unsigned int flags) +static int rocker_port_brport_flags_set(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + unsigned long brport_flags) { - struct nlmsghdr *nlh; - struct ndmsg *ndm; + unsigned long orig_flags; + int err = 0; - nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); - if (!nlh) - return -EMSGSIZE; + orig_flags = rocker_port->brport_flags; + rocker_port->brport_flags = brport_flags; + if ((orig_flags ^ rocker_port->brport_flags) & BR_LEARNING) + err = rocker_port_set_learning(rocker_port, trans); - ndm = nlmsg_data(nlh); - ndm->ndm_family = AF_BRIDGE; - ndm->ndm_pad1 = 0; - ndm->ndm_pad2 = 0; - ndm->ndm_flags = NTF_SELF; - ndm->ndm_type = 0; - ndm->ndm_ifindex = rocker_port->dev->ifindex; - ndm->ndm_state = NUD_REACHABLE; + if (switchdev_trans_ph_prepare(trans)) + rocker_port->brport_flags = orig_flags; - if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr)) - goto nla_put_failure; + return err; +} - if (vid && nla_put_u16(skb, NDA_VLAN, vid)) - goto nla_put_failure; +static int rocker_port_bridge_ageing_time(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + u32 ageing_time) +{ + if (!switchdev_trans_ph_prepare(trans)) { + rocker_port->ageing_time = clock_t_to_jiffies(ageing_time); + mod_timer(&rocker_port->rocker->fdb_cleanup_timer, jiffies); + } - nlmsg_end(skb, nlh); return 0; - -nla_put_failure: - nlmsg_cancel(skb, nlh); - return -EMSGSIZE; } -static int rocker_port_fdb_dump(struct sk_buff *skb, - struct netlink_callback *cb, - struct net_device *dev, - struct net_device *filter_dev, - int idx) +static int rocker_port_attr_set(struct net_device *dev, + const struct switchdev_attr *attr, + struct switchdev_trans *trans) { struct rocker_port *rocker_port = netdev_priv(dev); - struct rocker *rocker = rocker_port->rocker; - struct rocker_fdb_tbl_entry *found; - struct hlist_node *tmp; - int bkt; - unsigned long lock_flags; - const unsigned char *addr; - u16 vid; - int err; + int err = 0; - 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) - continue; - if (idx < cb->args[0]) - goto skip; - addr = found->key.addr; - vid = rocker_port_vlan_to_vid(rocker_port, found->key.vlan_id); - err = rocker_fdb_fill_info(skb, rocker_port, addr, vid, - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, - RTM_NEWNEIGH, NLM_F_MULTI); - if (err < 0) - break; -skip: - ++idx; + switch (attr->id) { + case SWITCHDEV_ATTR_ID_PORT_STP_STATE: + err = rocker_port_stp_update(rocker_port, trans, 0, + attr->u.stp_state); + break; + case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS: + err = rocker_port_brport_flags_set(rocker_port, trans, + attr->u.brport_flags); + break; + case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME: + err = rocker_port_bridge_ageing_time(rocker_port, trans, + attr->u.ageing_time); + break; + default: + err = -EOPNOTSUPP; + break; } - spin_unlock_irqrestore(&rocker->fdb_tbl_lock, lock_flags); - return idx; + + return err; } -static int rocker_port_bridge_setlink(struct net_device *dev, - struct nlmsghdr *nlh, u16 flags) +static int rocker_port_vlan_add(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + u16 vid, u16 flags) { - struct rocker_port *rocker_port = netdev_priv(dev); - struct nlattr *protinfo; - struct nlattr *attr; int err; - protinfo = nlmsg_find_attr(nlh, sizeof(struct ifinfomsg), - IFLA_PROTINFO); - if (protinfo) { - attr = nla_find_nested(protinfo, IFLA_BRPORT_LEARNING); - if (attr) { - if (nla_len(attr) < sizeof(u8)) - return -EINVAL; - - if (nla_get_u8(attr)) - rocker_port->brport_flags |= BR_LEARNING; - else - rocker_port->brport_flags &= ~BR_LEARNING; - err = rocker_port_set_learning(rocker_port); - if (err) - return err; - } - attr = nla_find_nested(protinfo, IFLA_BRPORT_LEARNING_SYNC); - if (attr) { - if (nla_len(attr) < sizeof(u8)) - return -EINVAL; - - if (nla_get_u8(attr)) - rocker_port->brport_flags |= BR_LEARNING_SYNC; - else - rocker_port->brport_flags &= ~BR_LEARNING_SYNC; - } + /* XXX deal with flags for PVID and untagged */ + + err = rocker_port_vlan(rocker_port, trans, 0, vid); + if (err) + return err; + + err = rocker_port_router_mac(rocker_port, trans, 0, htons(vid)); + if (err) + rocker_port_vlan(rocker_port, trans, + ROCKER_OP_FLAG_REMOVE, vid); + + return err; +} + +static int rocker_port_vlans_add(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + const struct switchdev_obj_port_vlan *vlan) +{ + u16 vid; + int err; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + err = rocker_port_vlan_add(rocker_port, trans, + vid, vlan->flags); + if (err) + return err; } return 0; } -static int rocker_port_bridge_getlink(struct sk_buff *skb, u32 pid, u32 seq, - struct net_device *dev, - u32 filter_mask, int nlflags) +static int rocker_port_fdb_add(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + const struct switchdev_obj_port_fdb *fdb) { - struct rocker_port *rocker_port = netdev_priv(dev); - u16 mode = BRIDGE_MODE_UNDEF; - u32 mask = BR_LEARNING | BR_LEARNING_SYNC; + __be16 vlan_id = rocker_port_vid_to_vlan(rocker_port, fdb->vid, NULL); + int flags = 0; - return ndo_dflt_bridge_getlink(skb, pid, seq, dev, mode, - rocker_port->brport_flags, mask, - nlflags); + if (!rocker_port_is_bridged(rocker_port)) + return -EINVAL; + + return rocker_port_fdb(rocker_port, trans, fdb->addr, vlan_id, flags); } -static int rocker_port_get_phys_port_name(struct net_device *dev, - char *buf, size_t len) +static int rocker_port_obj_add(struct net_device *dev, + const struct switchdev_obj *obj, + struct switchdev_trans *trans) { struct rocker_port *rocker_port = netdev_priv(dev); - struct port_name name = { .buf = buf, .len = len }; - int err; + const struct switchdev_obj_ipv4_fib *fib4; + int err = 0; - err = rocker_cmd_exec(rocker_port->rocker, rocker_port, - rocker_cmd_get_port_settings_prep, NULL, - rocker_cmd_get_port_settings_phys_name_proc, - &name, false); + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_VLAN: + err = rocker_port_vlans_add(rocker_port, trans, + SWITCHDEV_OBJ_PORT_VLAN(obj)); + break; + case SWITCHDEV_OBJ_ID_IPV4_FIB: + fib4 = SWITCHDEV_OBJ_IPV4_FIB(obj); + err = rocker_port_fib_ipv4(rocker_port, trans, + htonl(fib4->dst), fib4->dst_len, + &fib4->fi, fib4->tb_id, 0); + break; + case SWITCHDEV_OBJ_ID_PORT_FDB: + err = rocker_port_fdb_add(rocker_port, trans, + SWITCHDEV_OBJ_PORT_FDB(obj)); + break; + default: + err = -EOPNOTSUPP; + break; + } - return err ? -EOPNOTSUPP : 0; + return err; } -static const struct net_device_ops rocker_port_netdev_ops = { - .ndo_open = rocker_port_open, - .ndo_stop = rocker_port_stop, - .ndo_start_xmit = rocker_port_xmit, - .ndo_set_mac_address = rocker_port_set_mac_address, - .ndo_vlan_rx_add_vid = rocker_port_vlan_rx_add_vid, - .ndo_vlan_rx_kill_vid = rocker_port_vlan_rx_kill_vid, - .ndo_fdb_add = rocker_port_fdb_add, - .ndo_fdb_del = rocker_port_fdb_del, - .ndo_fdb_dump = rocker_port_fdb_dump, - .ndo_bridge_setlink = rocker_port_bridge_setlink, - .ndo_bridge_getlink = rocker_port_bridge_getlink, - .ndo_get_phys_port_name = rocker_port_get_phys_port_name, -}; +static int rocker_port_vlan_del(struct rocker_port *rocker_port, + u16 vid, u16 flags) +{ + int err; -/******************** - * swdev interface - ********************/ + err = rocker_port_router_mac(rocker_port, NULL, + ROCKER_OP_FLAG_REMOVE, htons(vid)); + if (err) + return err; + + return rocker_port_vlan(rocker_port, NULL, + ROCKER_OP_FLAG_REMOVE, vid); +} -static int rocker_port_swdev_parent_id_get(struct net_device *dev, - struct netdev_phys_item_id *psid) +static int rocker_port_vlans_del(struct rocker_port *rocker_port, + const struct switchdev_obj_port_vlan *vlan) { - struct rocker_port *rocker_port = netdev_priv(dev); - struct rocker *rocker = rocker_port->rocker; + u16 vid; + int err; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + err = rocker_port_vlan_del(rocker_port, vid, vlan->flags); + if (err) + return err; + } - psid->id_len = sizeof(rocker->hw.id); - memcpy(&psid->id, &rocker->hw.id, psid->id_len); return 0; } -static int rocker_port_swdev_port_stp_update(struct net_device *dev, u8 state) +static int rocker_port_fdb_del(struct rocker_port *rocker_port, + struct switchdev_trans *trans, + const struct switchdev_obj_port_fdb *fdb) { - struct rocker_port *rocker_port = netdev_priv(dev); + __be16 vlan_id = rocker_port_vid_to_vlan(rocker_port, fdb->vid, NULL); + int flags = ROCKER_OP_FLAG_REMOVE; - return rocker_port_stp_update(rocker_port, state); + if (!rocker_port_is_bridged(rocker_port)) + return -EINVAL; + + return rocker_port_fdb(rocker_port, trans, fdb->addr, vlan_id, flags); } -static int rocker_port_swdev_fib_ipv4_add(struct net_device *dev, - __be32 dst, int dst_len, - struct fib_info *fi, - u8 tos, u8 type, - u32 nlflags, u32 tb_id) +static int rocker_port_obj_del(struct net_device *dev, + const struct switchdev_obj *obj) { struct rocker_port *rocker_port = netdev_priv(dev); - int flags = 0; + const struct switchdev_obj_ipv4_fib *fib4; + int err = 0; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_VLAN: + err = rocker_port_vlans_del(rocker_port, + SWITCHDEV_OBJ_PORT_VLAN(obj)); + break; + case SWITCHDEV_OBJ_ID_IPV4_FIB: + fib4 = SWITCHDEV_OBJ_IPV4_FIB(obj); + err = rocker_port_fib_ipv4(rocker_port, NULL, + htonl(fib4->dst), fib4->dst_len, + &fib4->fi, fib4->tb_id, + ROCKER_OP_FLAG_REMOVE); + break; + case SWITCHDEV_OBJ_ID_PORT_FDB: + err = rocker_port_fdb_del(rocker_port, NULL, + SWITCHDEV_OBJ_PORT_FDB(obj)); + break; + default: + err = -EOPNOTSUPP; + break; + } - return rocker_port_fib_ipv4(rocker_port, dst, dst_len, - fi, tb_id, flags); + return err; } -static int rocker_port_swdev_fib_ipv4_del(struct net_device *dev, - __be32 dst, int dst_len, - struct fib_info *fi, - u8 tos, u8 type, u32 tb_id) +static int rocker_port_fdb_dump(const struct rocker_port *rocker_port, + struct switchdev_obj_port_fdb *fdb, + switchdev_obj_dump_cb_t *cb) { - struct rocker_port *rocker_port = netdev_priv(dev); - int flags = ROCKER_OP_FLAG_REMOVE; + struct rocker *rocker = rocker_port->rocker; + struct rocker_fdb_tbl_entry *found; + struct hlist_node *tmp; + unsigned long lock_flags; + int bkt; + int err = 0; + + spin_lock_irqsave(&rocker->fdb_tbl_lock, lock_flags); + hash_for_each_safe(rocker->fdb_tbl, bkt, tmp, found, entry) { + if (found->key.rocker_port != rocker_port) + continue; + ether_addr_copy(fdb->addr, found->key.addr); + fdb->ndm_state = NUD_REACHABLE; + fdb->vid = rocker_port_vlan_to_vid(rocker_port, + found->key.vlan_id); + err = cb(&fdb->obj); + if (err) + break; + } + spin_unlock_irqrestore(&rocker->fdb_tbl_lock, lock_flags); + + return err; +} + +static int rocker_port_vlan_dump(const struct rocker_port *rocker_port, + struct switchdev_obj_port_vlan *vlan, + switchdev_obj_dump_cb_t *cb) +{ + u16 vid; + int err = 0; + + for (vid = 1; vid < VLAN_N_VID; vid++) { + if (!test_bit(vid, rocker_port->vlan_bitmap)) + continue; + vlan->flags = 0; + if (rocker_vlan_id_is_internal(htons(vid))) + vlan->flags |= BRIDGE_VLAN_INFO_PVID; + vlan->vid_begin = vlan->vid_end = vid; + err = cb(&vlan->obj); + if (err) + break; + } + + return err; +} + +static int rocker_port_obj_dump(struct net_device *dev, + struct switchdev_obj *obj, + switchdev_obj_dump_cb_t *cb) +{ + const struct rocker_port *rocker_port = netdev_priv(dev); + int err = 0; + + switch (obj->id) { + case SWITCHDEV_OBJ_ID_PORT_FDB: + err = rocker_port_fdb_dump(rocker_port, + SWITCHDEV_OBJ_PORT_FDB(obj), cb); + break; + case SWITCHDEV_OBJ_ID_PORT_VLAN: + err = rocker_port_vlan_dump(rocker_port, + SWITCHDEV_OBJ_PORT_VLAN(obj), cb); + break; + default: + err = -EOPNOTSUPP; + break; + } - return rocker_port_fib_ipv4(rocker_port, dst, dst_len, - fi, tb_id, flags); + return err; } -static const struct swdev_ops rocker_port_swdev_ops = { - .swdev_parent_id_get = rocker_port_swdev_parent_id_get, - .swdev_port_stp_update = rocker_port_swdev_port_stp_update, - .swdev_fib_ipv4_add = rocker_port_swdev_fib_ipv4_add, - .swdev_fib_ipv4_del = rocker_port_swdev_fib_ipv4_del, +static const struct switchdev_ops rocker_port_switchdev_ops = { + .switchdev_port_attr_get = rocker_port_attr_get, + .switchdev_port_attr_set = rocker_port_attr_set, + .switchdev_port_obj_add = rocker_port_obj_add, + .switchdev_port_obj_del = rocker_port_obj_del, + .switchdev_port_obj_dump = rocker_port_obj_dump, }; /******************** @@ -4334,8 +4700,7 @@ static void rocker_port_get_strings(struct net_device *netdev, u32 stringset, } static int -rocker_cmd_get_port_stats_prep(struct rocker *rocker, - struct rocker_port *rocker_port, +rocker_cmd_get_port_stats_prep(const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info, void *priv) { @@ -4359,14 +4724,13 @@ rocker_cmd_get_port_stats_prep(struct rocker *rocker, } static int -rocker_cmd_get_port_stats_ethtool_proc(struct rocker *rocker, - struct rocker_port *rocker_port, - struct rocker_desc_info *desc_info, +rocker_cmd_get_port_stats_ethtool_proc(const struct rocker_port *rocker_port, + const struct rocker_desc_info *desc_info, void *priv) { - struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; - struct rocker_tlv *stats_attrs[ROCKER_TLV_CMD_PORT_STATS_MAX + 1]; - struct rocker_tlv *pattr; + const struct rocker_tlv *attrs[ROCKER_TLV_CMD_MAX + 1]; + const struct rocker_tlv *stats_attrs[ROCKER_TLV_CMD_PORT_STATS_MAX + 1]; + const struct rocker_tlv *pattr; u32 pport; u64 *data = priv; int i; @@ -4400,10 +4764,10 @@ rocker_cmd_get_port_stats_ethtool_proc(struct rocker *rocker, static int rocker_cmd_get_port_stats_ethtool(struct rocker_port *rocker_port, void *priv) { - return rocker_cmd_exec(rocker_port->rocker, rocker_port, + return rocker_cmd_exec(rocker_port, NULL, 0, rocker_cmd_get_port_stats_prep, NULL, rocker_cmd_get_port_stats_ethtool_proc, - priv, false); + priv); } static void rocker_port_get_stats(struct net_device *dev, @@ -4417,8 +4781,6 @@ static void rocker_port_get_stats(struct net_device *dev, for (i = 0; i < ARRAY_SIZE(rocker_port_stats); ++i) data[i] = 0; } - - return; } static int rocker_port_get_sset_count(struct net_device *netdev, int sset) @@ -4453,8 +4815,8 @@ static struct rocker_port *rocker_port_napi_tx_get(struct napi_struct *napi) static int rocker_port_poll_tx(struct napi_struct *napi, int budget) { struct rocker_port *rocker_port = rocker_port_napi_tx_get(napi); - struct rocker *rocker = rocker_port->rocker; - struct rocker_desc_info *desc_info; + const struct rocker *rocker = rocker_port->rocker; + const struct rocker_desc_info *desc_info; u32 credits = 0; int err; @@ -4472,8 +4834,9 @@ static int rocker_port_poll_tx(struct napi_struct *napi, int budget) if (err == 0) { rocker_port->dev->stats.tx_packets++; rocker_port->dev->stats.tx_bytes += skb->len; - } else + } else { rocker_port->dev->stats.tx_errors++; + } dev_kfree_skb_any(skb); credits++; @@ -4488,13 +4851,14 @@ static int rocker_port_poll_tx(struct napi_struct *napi, int budget) return 0; } -static int rocker_port_rx_proc(struct rocker *rocker, - struct rocker_port *rocker_port, +static int rocker_port_rx_proc(const struct rocker *rocker, + const struct rocker_port *rocker_port, struct rocker_desc_info *desc_info) { - struct rocker_tlv *attrs[ROCKER_TLV_RX_MAX + 1]; + const struct rocker_tlv *attrs[ROCKER_TLV_RX_MAX + 1]; struct sk_buff *skb = rocker_desc_cookie_ptr_get(desc_info); size_t rx_len; + u16 rx_flags = 0; if (!skb) return -ENOENT; @@ -4502,6 +4866,8 @@ static int rocker_port_rx_proc(struct rocker *rocker, rocker_tlv_parse_desc(attrs, ROCKER_TLV_RX_MAX, desc_info); if (!attrs[ROCKER_TLV_RX_FRAG_LEN]) return -EINVAL; + if (attrs[ROCKER_TLV_RX_FLAGS]) + rx_flags = rocker_tlv_get_u16(attrs[ROCKER_TLV_RX_FLAGS]); rocker_dma_rx_ring_skb_unmap(rocker, attrs); @@ -4509,12 +4875,15 @@ static int rocker_port_rx_proc(struct rocker *rocker, skb_put(skb, rx_len); skb->protocol = eth_type_trans(skb, rocker_port->dev); + if (rx_flags & ROCKER_RX_FLAGS_FWD_OFFLOAD) + skb->offload_fwd_mark = rocker_port->dev->offload_fwd_mark; + rocker_port->dev->stats.rx_packets++; rocker_port->dev->stats.rx_bytes += skb->len; netif_receive_skb(skb); - return rocker_dma_rx_ring_skb_alloc(rocker, rocker_port, desc_info); + return rocker_dma_rx_ring_skb_alloc(rocker_port, desc_info); } static struct rocker_port *rocker_port_napi_rx_get(struct napi_struct *napi) @@ -4525,7 +4894,7 @@ static struct rocker_port *rocker_port_napi_rx_get(struct napi_struct *napi) static int rocker_port_poll_rx(struct napi_struct *napi, int budget) { struct rocker_port *rocker_port = rocker_port_napi_rx_get(napi); - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; struct rocker_desc_info *desc_info; u32 credits = 0; int err; @@ -4565,9 +4934,9 @@ static int rocker_port_poll_rx(struct napi_struct *napi, int budget) * PCI driver ops *****************/ -static void rocker_carrier_init(struct rocker_port *rocker_port) +static void rocker_carrier_init(const struct rocker_port *rocker_port) { - struct rocker *rocker = rocker_port->rocker; + const struct rocker *rocker = rocker_port->rocker; u64 link_status = rocker_read64(rocker, PORT_PHYS_LINK_STATUS); bool link_up; @@ -4578,24 +4947,26 @@ static void rocker_carrier_init(struct rocker_port *rocker_port) netif_carrier_off(rocker_port->dev); } -static void rocker_remove_ports(struct rocker *rocker) +static void rocker_remove_ports(const struct rocker *rocker) { struct rocker_port *rocker_port; int i; for (i = 0; i < rocker->port_count; i++) { rocker_port = rocker->ports[i]; - rocker_port_ig_tbl(rocker_port, ROCKER_OP_FLAG_REMOVE); + if (!rocker_port) + continue; + rocker_port_ig_tbl(rocker_port, NULL, ROCKER_OP_FLAG_REMOVE); unregister_netdev(rocker_port->dev); free_netdev(rocker_port->dev); } kfree(rocker->ports); } -static void rocker_port_dev_addr_init(struct rocker *rocker, - struct rocker_port *rocker_port) +static void rocker_port_dev_addr_init(struct rocker_port *rocker_port) { - struct pci_dev *pdev = rocker->pdev; + const struct rocker *rocker = rocker_port->rocker; + const struct pci_dev *pdev = rocker->pdev; int err; err = rocker_cmd_get_port_settings_macaddr(rocker_port, @@ -4608,9 +4979,10 @@ static void rocker_port_dev_addr_init(struct rocker *rocker, static int rocker_probe_port(struct rocker *rocker, unsigned int port_number) { - struct pci_dev *pdev = rocker->pdev; + const struct pci_dev *pdev = rocker->pdev; struct rocker_port *rocker_port; struct net_device *dev; + u16 untagged_vid = 0; int err; dev = alloc_etherdev(sizeof(struct rocker_port)); @@ -4622,20 +4994,19 @@ 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; - rocker_port_dev_addr_init(rocker, rocker_port); + rocker_port_dev_addr_init(rocker_port); dev->netdev_ops = &rocker_port_netdev_ops; dev->ethtool_ops = &rocker_port_ethtool_ops; - dev->swdev_ops = &rocker_port_swdev_ops; + dev->switchdev_ops = &rocker_port_switchdev_ops; netif_napi_add(dev, &rocker_port->napi_tx, rocker_port_poll_tx, NAPI_POLL_WEIGHT); netif_napi_add(dev, &rocker_port->napi_rx, rocker_port_poll_rx, NAPI_POLL_WEIGHT); rocker_carrier_init(rocker_port); - dev->features |= NETIF_F_NETNS_LOCAL | - NETIF_F_HW_VLAN_CTAG_FILTER | - NETIF_F_HW_SWITCH_OFFLOAD; + dev->features |= NETIF_F_NETNS_LOCAL | NETIF_F_SG; err = register_netdev(dev); if (err) { @@ -4644,19 +5015,31 @@ static int rocker_probe_port(struct rocker *rocker, unsigned int port_number) } rocker->ports[port_number] = rocker_port; - rocker_port_set_learning(rocker_port); + switchdev_port_fwd_mark_set(rocker_port->dev, NULL, false); + + rocker_port_set_learning(rocker_port, NULL); + + err = rocker_port_ig_tbl(rocker_port, NULL, 0); + if (err) { + netdev_err(rocker_port->dev, "install ig port table failed\n"); + goto err_port_ig_tbl; + } rocker_port->internal_vlan_id = rocker_port_internal_vlan_id_get(rocker_port, dev->ifindex); - err = rocker_port_ig_tbl(rocker_port, 0); + + err = rocker_port_vlan_add(rocker_port, NULL, untagged_vid, 0); if (err) { - dev_err(&pdev->dev, "install ig port table failed\n"); - goto err_port_ig_tbl; + netdev_err(rocker_port->dev, "install untagged VLAN failed\n"); + goto err_untagged_vlan; } return 0; +err_untagged_vlan: + rocker_port_ig_tbl(rocker_port, NULL, ROCKER_OP_FLAG_REMOVE); err_port_ig_tbl: + rocker->ports[port_number] = NULL; unregister_netdev(dev); err_register_netdev: free_netdev(dev); @@ -4670,7 +5053,7 @@ static int rocker_probe_ports(struct rocker *rocker) int err; alloc_size = sizeof(struct rocker_port *) * rocker->port_count; - rocker->ports = kmalloc(alloc_size, GFP_KERNEL); + rocker->ports = kzalloc(alloc_size, GFP_KERNEL); if (!rocker->ports) return -ENOMEM; for (i = 0; i < rocker->port_count; i++) { @@ -4719,7 +5102,7 @@ err_enable_msix: return err; } -static void rocker_msix_fini(struct rocker *rocker) +static void rocker_msix_fini(const struct rocker *rocker) { pci_disable_msix(rocker->pdev); kfree(rocker->msix_entries); @@ -4823,17 +5206,23 @@ 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"); goto err_probe_ports; } - dev_info(&pdev->dev, "Rocker switch with id %016llx\n", rocker->hw.id); + dev_info(&pdev->dev, "Rocker switch with id %*phN\n", + (int)sizeof(rocker->hw.id), &rocker->hw.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); @@ -4861,6 +5250,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); @@ -4885,7 +5275,7 @@ static struct pci_driver rocker_pci_driver = { * Net device notifier event handler ************************************/ -static bool rocker_port_dev_check(struct net_device *dev) +static bool rocker_port_dev_check(const struct net_device *dev) { return dev->netdev_ops == &rocker_port_netdev_ops; } @@ -4893,89 +5283,130 @@ static bool rocker_port_dev_check(struct net_device *dev) static int rocker_port_bridge_join(struct rocker_port *rocker_port, struct net_device *bridge) { + u16 untagged_vid = 0; int err; + /* Port is joining bridge, so the internal VLAN for the + * port is going to change to the bridge internal VLAN. + * Let's remove untagged VLAN (vid=0) from port and + * re-add once internal VLAN has changed. + */ + + err = rocker_port_vlan_del(rocker_port, untagged_vid, 0); + if (err) + return err; + rocker_port_internal_vlan_id_put(rocker_port, rocker_port->dev->ifindex); + rocker_port->internal_vlan_id = + rocker_port_internal_vlan_id_get(rocker_port, bridge->ifindex); rocker_port->bridge_dev = bridge; + switchdev_port_fwd_mark_set(rocker_port->dev, bridge, true); - /* Use bridge internal VLAN ID for untagged pkts */ - err = rocker_port_vlan(rocker_port, ROCKER_OP_FLAG_REMOVE, 0); - if (err) - return err; - rocker_port->internal_vlan_id = - rocker_port_internal_vlan_id_get(rocker_port, - bridge->ifindex); - return rocker_port_vlan(rocker_port, 0, 0); + return rocker_port_vlan_add(rocker_port, NULL, untagged_vid, 0); } static int rocker_port_bridge_leave(struct rocker_port *rocker_port) { + u16 untagged_vid = 0; int err; - rocker_port_internal_vlan_id_put(rocker_port, - rocker_port->bridge_dev->ifindex); - - rocker_port->bridge_dev = NULL; - - /* Use port internal VLAN ID for untagged pkts */ - err = rocker_port_vlan(rocker_port, ROCKER_OP_FLAG_REMOVE, 0); + err = rocker_port_vlan_del(rocker_port, untagged_vid, 0); if (err) return err; + + rocker_port_internal_vlan_id_put(rocker_port, + rocker_port->bridge_dev->ifindex); rocker_port->internal_vlan_id = rocker_port_internal_vlan_id_get(rocker_port, rocker_port->dev->ifindex); - err = rocker_port_vlan(rocker_port, 0, 0); + + switchdev_port_fwd_mark_set(rocker_port->dev, rocker_port->bridge_dev, + false); + rocker_port->bridge_dev = NULL; + + err = rocker_port_vlan_add(rocker_port, NULL, untagged_vid, 0); if (err) return err; if (rocker_port->dev->flags & IFF_UP) - err = rocker_port_fwd_enable(rocker_port); + err = rocker_port_fwd_enable(rocker_port, NULL, 0); return err; } -static int rocker_port_master_changed(struct net_device *dev) + +static int rocker_port_ovs_changed(struct rocker_port *rocker_port, + struct net_device *master) +{ + int err; + + rocker_port->bridge_dev = master; + + err = rocker_port_fwd_disable(rocker_port, NULL, 0); + if (err) + return err; + err = rocker_port_fwd_enable(rocker_port, NULL, 0); + + return err; +} + +static int rocker_port_master_linked(struct rocker_port *rocker_port, + struct net_device *master) { - struct rocker_port *rocker_port = netdev_priv(dev); - struct net_device *master = netdev_master_upper_dev_get(dev); int err = 0; - /* There are currently three cases handled here: - * 1. Joining a bridge - * 2. Leaving a previously joined bridge - * 3. Other, e.g. being added to or removed from a bond or openvswitch, - * in which case nothing is done - */ - if (master && master->rtnl_link_ops && - !strcmp(master->rtnl_link_ops->kind, "bridge")) + if (netif_is_bridge_master(master)) err = rocker_port_bridge_join(rocker_port, master); - else if (rocker_port_is_bridged(rocker_port)) - err = rocker_port_bridge_leave(rocker_port); + else if (netif_is_ovs_master(master)) + err = rocker_port_ovs_changed(rocker_port, master); + return err; +} + +static int rocker_port_master_unlinked(struct rocker_port *rocker_port) +{ + int err = 0; + if (rocker_port_is_bridged(rocker_port)) + err = rocker_port_bridge_leave(rocker_port); + else if (rocker_port_is_ovsed(rocker_port)) + err = rocker_port_ovs_changed(rocker_port, NULL); return err; } static int rocker_netdevice_event(struct notifier_block *unused, unsigned long event, void *ptr) { - struct net_device *dev; + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct netdev_notifier_changeupper_info *info; + struct rocker_port *rocker_port; int err; + if (!rocker_port_dev_check(dev)) + return NOTIFY_DONE; + switch (event) { case NETDEV_CHANGEUPPER: - dev = netdev_notifier_info_to_dev(ptr); - if (!rocker_port_dev_check(dev)) - return NOTIFY_DONE; - err = rocker_port_master_changed(dev); - if (err) - netdev_warn(dev, - "failed to reflect master change (err %d)\n", - err); + info = ptr; + if (!info->master) + goto out; + rocker_port = netdev_priv(dev); + if (info->linking) { + err = rocker_port_master_linked(rocker_port, + info->upper_dev); + if (err) + netdev_warn(dev, "failed to reflect master linked (err %d)\n", + err); + } else { + err = rocker_port_master_unlinked(rocker_port); + if (err) + netdev_warn(dev, "failed to reflect master unlinked (err %d)\n", + err); + } break; } - +out: return NOTIFY_DONE; } @@ -4990,10 +5421,11 @@ static struct notifier_block rocker_netdevice_nb __read_mostly = { static int rocker_neigh_update(struct net_device *dev, struct neighbour *n) { struct rocker_port *rocker_port = netdev_priv(dev); - int flags = (n->nud_state & NUD_VALID) ? 0 : ROCKER_OP_FLAG_REMOVE; + int flags = (n->nud_state & NUD_VALID ? 0 : ROCKER_OP_FLAG_REMOVE) | + ROCKER_OP_FLAG_NOWAIT; __be32 ip_addr = *(__be32 *)n->primary_key; - return rocker_port_ipv4_neigh(rocker_port, flags, ip_addr, n->ha); + return rocker_port_ipv4_neigh(rocker_port, NULL, flags, ip_addr, n->ha); } static int rocker_netevent_event(struct notifier_block *unused, @@ -5041,7 +5473,7 @@ static int __init rocker_module_init(void) return 0; err_pci_register_driver: - unregister_netdevice_notifier(&rocker_netevent_nb); + unregister_netevent_notifier(&rocker_netevent_nb); unregister_netdevice_notifier(&rocker_netdevice_nb); return err; } |