// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2017 - 2019 Pensando Systems, Inc */ #include #include #include #include #include "ionic.h" #include "ionic_lif.h" #include "ionic_rx_filter.h" void ionic_rx_filter_free(struct ionic_lif *lif, struct ionic_rx_filter *f) { struct device *dev = lif->ionic->dev; hlist_del(&f->by_id); hlist_del(&f->by_hash); devm_kfree(dev, f); } void ionic_rx_filter_replay(struct ionic_lif *lif) { struct ionic_rx_filter_add_cmd *ac; struct hlist_head new_id_list; struct ionic_admin_ctx ctx; struct ionic_rx_filter *f; struct hlist_head *head; struct hlist_node *tmp; unsigned int key; unsigned int i; int err; INIT_HLIST_HEAD(&new_id_list); ac = &ctx.cmd.rx_filter_add; for (i = 0; i < IONIC_RX_FILTER_HLISTS; i++) { head = &lif->rx_filters.by_id[i]; hlist_for_each_entry_safe(f, tmp, head, by_id) { ctx.work = COMPLETION_INITIALIZER_ONSTACK(ctx.work); memcpy(ac, &f->cmd, sizeof(f->cmd)); dev_dbg(&lif->netdev->dev, "replay filter command:\n"); dynamic_hex_dump("cmd ", DUMP_PREFIX_OFFSET, 16, 1, &ctx.cmd, sizeof(ctx.cmd), true); err = ionic_adminq_post_wait(lif, &ctx); if (err) { switch (le16_to_cpu(ac->match)) { case IONIC_RX_FILTER_MATCH_VLAN: netdev_info(lif->netdev, "Replay failed - %d: vlan %d\n", err, le16_to_cpu(ac->vlan.vlan)); break; case IONIC_RX_FILTER_MATCH_MAC: netdev_info(lif->netdev, "Replay failed - %d: mac %pM\n", err, ac->mac.addr); break; case IONIC_RX_FILTER_MATCH_MAC_VLAN: netdev_info(lif->netdev, "Replay failed - %d: vlan %d mac %pM\n", err, le16_to_cpu(ac->vlan.vlan), ac->mac.addr); break; } spin_lock_bh(&lif->rx_filters.lock); ionic_rx_filter_free(lif, f); spin_unlock_bh(&lif->rx_filters.lock); continue; } /* remove from old id list, save new id in tmp list */ spin_lock_bh(&lif->rx_filters.lock); hlist_del(&f->by_id); spin_unlock_bh(&lif->rx_filters.lock); f->filter_id = le32_to_cpu(ctx.comp.rx_filter_add.filter_id); hlist_add_head(&f->by_id, &new_id_list); } } /* rebuild the by_id hash lists with the new filter ids */ spin_lock_bh(&lif->rx_filters.lock); hlist_for_each_entry_safe(f, tmp, &new_id_list, by_id) { key = f->filter_id & IONIC_RX_FILTER_HLISTS_MASK; head = &lif->rx_filters.by_id[key]; hlist_add_head(&f->by_id, head); } spin_unlock_bh(&lif->rx_filters.lock); } int ionic_rx_filters_init(struct ionic_lif *lif) { unsigned int i; spin_lock_init(&lif->rx_filters.lock); spin_lock_bh(&lif->rx_filters.lock); for (i = 0; i < IONIC_RX_FILTER_HLISTS; i++) { INIT_HLIST_HEAD(&lif->rx_filters.by_hash[i]); INIT_HLIST_HEAD(&lif->rx_filters.by_id[i]); } spin_unlock_bh(&lif->rx_filters.lock); return 0; } void ionic_rx_filters_deinit(struct ionic_lif *lif) { struct ionic_rx_filter *f; struct hlist_head *head; struct hlist_node *tmp; unsigned int i; spin_lock_bh(&lif->rx_filters.lock); for (i = 0; i < IONIC_RX_FILTER_HLISTS; i++) { head = &lif->rx_filters.by_id[i]; hlist_for_each_entry_safe(f, tmp, head, by_id) ionic_rx_filter_free(lif, f); } spin_unlock_bh(&lif->rx_filters.lock); } int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index, u32 hash, struct ionic_admin_ctx *ctx, enum ionic_filter_state state) { struct device *dev = lif->ionic->dev; struct ionic_rx_filter_add_cmd *ac; struct ionic_rx_filter *f = NULL; struct hlist_head *head; unsigned int key; ac = &ctx->cmd.rx_filter_add; switch (le16_to_cpu(ac->match)) { case IONIC_RX_FILTER_MATCH_VLAN: key = le16_to_cpu(ac->vlan.vlan); f = ionic_rx_filter_by_vlan(lif, le16_to_cpu(ac->vlan.vlan)); break; case IONIC_RX_FILTER_MATCH_MAC: key = *(u32 *)ac->mac.addr; f = ionic_rx_filter_by_addr(lif, ac->mac.addr); break; case IONIC_RX_FILTER_MATCH_MAC_VLAN: key = le16_to_cpu(ac->mac_vlan.vlan); break; case IONIC_RX_FILTER_STEER_PKTCLASS: key = 0; break; default: return -EINVAL; } if (f) { /* remove from current linking so we can refresh it */ hlist_del(&f->by_id); hlist_del(&f->by_hash); } else { f = devm_kzalloc(dev, sizeof(*f), GFP_ATOMIC); if (!f) return -ENOMEM; } f->flow_id = flow_id; f->filter_id = le32_to_cpu(ctx->comp.rx_filter_add.filter_id); f->state = state; f->rxq_index = rxq_index; memcpy(&f->cmd, ac, sizeof(f->cmd)); netdev_dbg(lif->netdev, "rx_filter add filter_id %d\n", f->filter_id); INIT_HLIST_NODE(&f->by_hash); INIT_HLIST_NODE(&f->by_id); key = hash_32(key, IONIC_RX_FILTER_HASH_BITS); head = &lif->rx_filters.by_hash[key]; hlist_add_head(&f->by_hash, head); key = f->filter_id & IONIC_RX_FILTER_HLISTS_MASK; head = &lif->rx_filters.by_id[key]; hlist_add_head(&f->by_id, head); return 0; } struct ionic_rx_filter *ionic_rx_filter_by_vlan(struct ionic_lif *lif, u16 vid) { struct ionic_rx_filter *f; struct hlist_head *head; unsigned int key; key = hash_32(vid, IONIC_RX_FILTER_HASH_BITS); head = &lif->rx_filters.by_hash[key]; hlist_for_each_entry(f, head, by_hash) { if (le16_to_cpu(f->cmd.match) != IONIC_RX_FILTER_MATCH_VLAN) continue; if (le16_to_cpu(f->cmd.vlan.vlan) == vid) return f; } return NULL; } struct ionic_rx_filter *ionic_rx_filter_by_addr(struct ionic_lif *lif, const u8 *addr) { struct ionic_rx_filter *f; struct hlist_head *head; unsigned int key; key = hash_32(*(u32 *)addr, IONIC_RX_FILTER_HASH_BITS); head = &lif->rx_filters.by_hash[key]; hlist_for_each_entry(f, head, by_hash) { if (le16_to_cpu(f->cmd.match) != IONIC_RX_FILTER_MATCH_MAC) continue; if (memcmp(addr, f->cmd.mac.addr, ETH_ALEN) == 0) return f; } return NULL; } struct ionic_rx_filter *ionic_rx_filter_rxsteer(struct ionic_lif *lif) { struct ionic_rx_filter *f; struct hlist_head *head; unsigned int key; key = hash_32(0, IONIC_RX_FILTER_HASH_BITS); head = &lif->rx_filters.by_hash[key]; hlist_for_each_entry(f, head, by_hash) { if (le16_to_cpu(f->cmd.match) != IONIC_RX_FILTER_STEER_PKTCLASS) continue; return f; } return NULL; } int ionic_lif_list_addr(struct ionic_lif *lif, const u8 *addr, bool mode) { struct ionic_rx_filter *f; int err; spin_lock_bh(&lif->rx_filters.lock); f = ionic_rx_filter_by_addr(lif, addr); if (mode == ADD_ADDR && !f) { struct ionic_admin_ctx ctx = { .work = COMPLETION_INITIALIZER_ONSTACK(ctx.work), .cmd.rx_filter_add = { .opcode = IONIC_CMD_RX_FILTER_ADD, .lif_index = cpu_to_le16(lif->index), .match = cpu_to_le16(IONIC_RX_FILTER_MATCH_MAC), }, }; memcpy(ctx.cmd.rx_filter_add.mac.addr, addr, ETH_ALEN); err = ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx, IONIC_FILTER_STATE_NEW); if (err) { spin_unlock_bh(&lif->rx_filters.lock); return err; } } else if (mode == ADD_ADDR && f) { if (f->state == IONIC_FILTER_STATE_OLD) f->state = IONIC_FILTER_STATE_SYNCED; } else if (mode == DEL_ADDR && f) { if (f->state == IONIC_FILTER_STATE_NEW) ionic_rx_filter_free(lif, f); else if (f->state == IONIC_FILTER_STATE_SYNCED) f->state = IONIC_FILTER_STATE_OLD; } else if (mode == DEL_ADDR && !f) { spin_unlock_bh(&lif->rx_filters.lock); return -ENOENT; } spin_unlock_bh(&lif->rx_filters.lock); set_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); return 0; } struct sync_item { struct list_head list; struct ionic_rx_filter f; }; void ionic_rx_filter_sync(struct ionic_lif *lif) { struct device *dev = lif->ionic->dev; struct list_head sync_add_list; struct list_head sync_del_list; struct sync_item *sync_item; struct ionic_rx_filter *f; struct hlist_head *head; struct hlist_node *tmp; struct sync_item *spos; unsigned int i; INIT_LIST_HEAD(&sync_add_list); INIT_LIST_HEAD(&sync_del_list); clear_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); /* Copy the filters to be added and deleted * into a separate local list that needs no locking. */ spin_lock_bh(&lif->rx_filters.lock); for (i = 0; i < IONIC_RX_FILTER_HLISTS; i++) { head = &lif->rx_filters.by_id[i]; hlist_for_each_entry_safe(f, tmp, head, by_id) { if (f->state == IONIC_FILTER_STATE_NEW || f->state == IONIC_FILTER_STATE_OLD) { sync_item = devm_kzalloc(dev, sizeof(*sync_item), GFP_ATOMIC); if (!sync_item) goto loop_out; sync_item->f = *f; if (f->state == IONIC_FILTER_STATE_NEW) list_add(&sync_item->list, &sync_add_list); else list_add(&sync_item->list, &sync_del_list); } } } loop_out: spin_unlock_bh(&lif->rx_filters.lock); /* If the add or delete fails, it won't get marked as sync'd * and will be tried again in the next sync action. * Do the deletes first in case we're in an overflow state and * they can clear room for some new filters */ list_for_each_entry_safe(sync_item, spos, &sync_del_list, list) { (void)ionic_lif_addr_del(lif, sync_item->f.cmd.mac.addr); list_del(&sync_item->list); devm_kfree(dev, sync_item); } list_for_each_entry_safe(sync_item, spos, &sync_add_list, list) { (void)ionic_lif_addr_add(lif, sync_item->f.cmd.mac.addr); if (sync_item->f.state != IONIC_FILTER_STATE_SYNCED) set_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state); list_del(&sync_item->list); devm_kfree(dev, sync_item); } }