// SPDX-License-Identifier: GPL-2.0 
 | 
/* Copyright(c) 2017 - 2019 Pensando Systems, Inc */ 
 | 
  
 | 
#include <linux/netdevice.h> 
 | 
#include <linux/dynamic_debug.h> 
 | 
#include <linux/etherdevice.h> 
 | 
  
 | 
#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) 
 | 
{ 
 | 
    struct device *dev = lif->ionic->dev; 
 | 
    struct ionic_rx_filter_add_cmd *ac; 
 | 
    struct ionic_rx_filter *f; 
 | 
    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); 
 | 
        break; 
 | 
    case IONIC_RX_FILTER_MATCH_MAC: 
 | 
        key = *(u32 *)ac->mac.addr; 
 | 
        break; 
 | 
    case IONIC_RX_FILTER_MATCH_MAC_VLAN: 
 | 
        key = le16_to_cpu(ac->mac_vlan.vlan); 
 | 
        break; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    f = devm_kzalloc(dev, sizeof(*f), GFP_KERNEL); 
 | 
    if (!f) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    f->flow_id = flow_id; 
 | 
    f->filter_id = le32_to_cpu(ctx->comp.rx_filter_add.filter_id); 
 | 
    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); 
 | 
  
 | 
    spin_lock_bh(&lif->rx_filters.lock); 
 | 
  
 | 
    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); 
 | 
  
 | 
    spin_unlock_bh(&lif->rx_filters.lock); 
 | 
  
 | 
    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; 
 | 
} 
 |