net/octeontx2: support multicast filter
authorVamsi Attunuru <vattunuru@marvell.com>
Tue, 15 Oct 2019 10:44:21 +0000 (16:14 +0530)
committerFerruh Yigit <ferruh.yigit@intel.com>
Wed, 23 Oct 2019 14:43:10 +0000 (16:43 +0200)
Patch adds mc filter support for otx2 eth devices.

Signed-off-by: Vamsi Attunuru <vattunuru@marvell.com>
Acked-by: Jerin Jacob <jerinj@marvell.com>
doc/guides/nics/features/octeontx2.ini
doc/guides/nics/features/octeontx2_vec.ini
doc/guides/nics/octeontx2.rst
drivers/net/octeontx2/Makefile
drivers/net/octeontx2/meson.build
drivers/net/octeontx2/otx2_ethdev.c
drivers/net/octeontx2/otx2_ethdev.h
drivers/net/octeontx2/otx2_mcast.c [new file with mode: 0644]

index 02073be..7c59b43 100644 (file)
@@ -21,6 +21,7 @@ TSO                  = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
+Multicast MAC filter = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
index 733bb67..2553105 100644 (file)
@@ -19,6 +19,7 @@ MTU update           = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
 Promiscuous mode     = Y
 Allmulticast mode    = Y
 Unicast MAC filter   = Y
+Multicast MAC filter = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
 RSS hash             = Y
 RSS key update       = Y
 RSS reta update      = Y
index 9a76567..3b567a5 100644 (file)
@@ -24,6 +24,7 @@ Features of the OCTEON TX2 Ethdev PMD are:
 - Multiple queues for TX and RX
 - Receiver Side Scaling (RSS)
 - MAC/VLAN filtering
 - Multiple queues for TX and RX
 - Receiver Side Scaling (RSS)
 - MAC/VLAN filtering
+- Multicast MAC filtering
 - Generic flow API
 - Inner and Outer Checksum offload
 - VLAN/QinQ stripping and insertion
 - Generic flow API
 - Inner and Outer Checksum offload
 - VLAN/QinQ stripping and insertion
@@ -193,6 +194,11 @@ CRC striping
 The OCTEON TX2 SoC family NICs strip the CRC for every packet being received by
 the host interface irrespective of the offload configuration.
 
 The OCTEON TX2 SoC family NICs strip the CRC for every packet being received by
 the host interface irrespective of the offload configuration.
 
+Multicast MAC filtering
+~~~~~~~~~~~~~~~~~~~~~~~
+
+``net_octeontx2`` pmd supports multicast mac filtering feature only on physical
+function devices.
 
 Debugging Options
 -----------------
 
 Debugging Options
 -----------------
index 66cb5f2..0bfceca 100644 (file)
@@ -44,6 +44,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_OCTEONTX2_PMD) += \
        otx2_link.c     \
        otx2_vlan.c     \
        otx2_stats.c    \
        otx2_link.c     \
        otx2_vlan.c     \
        otx2_stats.c    \
+       otx2_mcast.c    \
        otx2_lookup.c   \
        otx2_ethdev.c   \
        otx2_flow_ctrl.c \
        otx2_lookup.c   \
        otx2_ethdev.c   \
        otx2_flow_ctrl.c \
index 94bf09a..fad3076 100644 (file)
@@ -12,6 +12,7 @@ sources = files('otx2_rx.c',
                'otx2_link.c',
                'otx2_vlan.c',
                'otx2_stats.c',
                'otx2_link.c',
                'otx2_vlan.c',
                'otx2_stats.c',
+               'otx2_mcast.c',
                'otx2_lookup.c',
                'otx2_ethdev.c',
                'otx2_flow_ctrl.c',
                'otx2_lookup.c',
                'otx2_ethdev.c',
                'otx2_flow_ctrl.c',
index 4a60f9f..62291c6 100644 (file)
@@ -1583,6 +1583,7 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
        if (dev->configured == 1) {
                otx2_nix_rxchan_bpid_cfg(eth_dev, false);
                otx2_nix_vlan_fini(eth_dev);
        if (dev->configured == 1) {
                otx2_nix_rxchan_bpid_cfg(eth_dev, false);
                otx2_nix_vlan_fini(eth_dev);
+               otx2_nix_mc_addr_list_uninstall(eth_dev);
                otx2_flow_free_all_resources(dev);
                oxt2_nix_unregister_queue_irqs(eth_dev);
                if (eth_dev->data->dev_conf.intr_conf.rxq)
                otx2_flow_free_all_resources(dev);
                oxt2_nix_unregister_queue_irqs(eth_dev);
                if (eth_dev->data->dev_conf.intr_conf.rxq)
@@ -1678,6 +1679,12 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
                goto q_irq_fini;
        }
 
                goto q_irq_fini;
        }
 
+       rc = otx2_nix_mc_addr_list_install(eth_dev);
+       if (rc < 0) {
+               otx2_err("Failed to install mc address list rc=%d", rc);
+               goto cq_fini;
+       }
+
        /*
         * Restore queue config when reconfigure followed by
         * reconfigure and no queue configure invoked from application case.
        /*
         * Restore queue config when reconfigure followed by
         * reconfigure and no queue configure invoked from application case.
@@ -1685,7 +1692,7 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
        if (dev->configured == 1) {
                rc = nix_restore_queue_cfg(eth_dev);
                if (rc)
        if (dev->configured == 1) {
                rc = nix_restore_queue_cfg(eth_dev);
                if (rc)
-                       goto cq_fini;
+                       goto uninstall_mc_list;
        }
 
        /* Update the mac address */
        }
 
        /* Update the mac address */
@@ -1709,6 +1716,8 @@ otx2_nix_configure(struct rte_eth_dev *eth_dev)
        dev->configured_nb_tx_qs = data->nb_tx_queues;
        return 0;
 
        dev->configured_nb_tx_qs = data->nb_tx_queues;
        return 0;
 
+uninstall_mc_list:
+       otx2_nix_mc_addr_list_uninstall(eth_dev);
 cq_fini:
        oxt2_nix_unregister_cq_irqs(eth_dev);
 q_irq_fini:
 cq_fini:
        oxt2_nix_unregister_cq_irqs(eth_dev);
 q_irq_fini:
@@ -1952,6 +1961,7 @@ static const struct eth_dev_ops otx2_eth_dev_ops = {
        .mac_addr_add             = otx2_nix_mac_addr_add,
        .mac_addr_remove          = otx2_nix_mac_addr_del,
        .mac_addr_set             = otx2_nix_mac_addr_set,
        .mac_addr_add             = otx2_nix_mac_addr_add,
        .mac_addr_remove          = otx2_nix_mac_addr_del,
        .mac_addr_set             = otx2_nix_mac_addr_set,
+       .set_mc_addr_list         = otx2_nix_set_mc_addr_list,
        .promiscuous_enable       = otx2_nix_promisc_enable,
        .promiscuous_disable      = otx2_nix_promisc_disable,
        .allmulticast_enable      = otx2_nix_allmulticast_enable,
        .promiscuous_enable       = otx2_nix_promisc_enable,
        .promiscuous_disable      = otx2_nix_promisc_disable,
        .allmulticast_enable      = otx2_nix_allmulticast_enable,
@@ -2169,6 +2179,8 @@ otx2_eth_dev_init(struct rte_eth_dev *eth_dev)
        if (rc)
                goto free_mac_addrs;
 
        if (rc)
                goto free_mac_addrs;
 
+       otx2_nix_mc_filter_init(dev);
+
        otx2_nix_dbg("Port=%d pf=%d vf=%d ver=%s msix_off=%d hwcap=0x%" PRIx64
                     " rxoffload_capa=0x%" PRIx64 " txoffload_capa=0x%" PRIx64,
                     eth_dev->data->port_id, dev->pf, dev->vf,
        otx2_nix_dbg("Port=%d pf=%d vf=%d ver=%s msix_off=%d hwcap=0x%" PRIx64
                     " rxoffload_capa=0x%" PRIx64 " txoffload_capa=0x%" PRIx64,
                     eth_dev->data->port_id, dev->pf, dev->vf,
@@ -2216,6 +2228,9 @@ otx2_eth_dev_uninit(struct rte_eth_dev *eth_dev, bool mbox_close)
        /* Disable other rte_flow entries */
        otx2_flow_fini(dev);
 
        /* Disable other rte_flow entries */
        otx2_flow_fini(dev);
 
+       /* Free multicast filter list */
+       otx2_nix_mc_filter_fini(dev);
+
        /* Disable PTP if already enabled */
        if (otx2_ethdev_is_ptp_en(dev))
                otx2_nix_timesync_disable(eth_dev);
        /* Disable PTP if already enabled */
        if (otx2_ethdev_is_ptp_en(dev))
                otx2_nix_timesync_disable(eth_dev);
index 33fa0c6..4d9ed48 100644 (file)
@@ -207,6 +207,14 @@ struct vlan_mkex_info {
        uint64_t lb_lt_offset;
 };
 
        uint64_t lb_lt_offset;
 };
 
+struct mcast_entry {
+       struct rte_ether_addr mcast_mac;
+       uint16_t mcam_index;
+       TAILQ_ENTRY(mcast_entry) next;
+};
+
+TAILQ_HEAD(otx2_nix_mc_filter_tbl, mcast_entry);
+
 struct vlan_entry {
        uint32_t mcam_idx;
        uint16_t vlan_id;
 struct vlan_entry {
        uint32_t mcam_idx;
        uint16_t vlan_id;
@@ -311,6 +319,8 @@ struct otx2_eth_dev {
        struct rte_timecounter  tx_tstamp_tc;
        double clk_freq_mult;
        uint64_t clk_delta;
        struct rte_timecounter  tx_tstamp_tc;
        double clk_freq_mult;
        uint64_t clk_delta;
+       bool mc_tbl_set;
+       struct otx2_nix_mc_filter_tbl mc_fltr_tbl;
 } __rte_cache_aligned;
 
 struct otx2_eth_txq {
 } __rte_cache_aligned;
 
 struct otx2_eth_txq {
@@ -393,6 +403,15 @@ int otx2_nix_tx_queue_start(struct rte_eth_dev *eth_dev, uint16_t qidx);
 int otx2_nix_tx_queue_stop(struct rte_eth_dev *eth_dev, uint16_t qidx);
 uint64_t otx2_nix_rxq_mbuf_setup(struct otx2_eth_dev *dev, uint16_t port_id);
 
 int otx2_nix_tx_queue_stop(struct rte_eth_dev *eth_dev, uint16_t qidx);
 uint64_t otx2_nix_rxq_mbuf_setup(struct otx2_eth_dev *dev, uint16_t port_id);
 
+/* Multicast filter APIs */
+void otx2_nix_mc_filter_init(struct otx2_eth_dev *dev);
+void otx2_nix_mc_filter_fini(struct otx2_eth_dev *dev);
+int otx2_nix_mc_addr_list_install(struct rte_eth_dev *eth_dev);
+int otx2_nix_mc_addr_list_uninstall(struct rte_eth_dev *eth_dev);
+int otx2_nix_set_mc_addr_list(struct rte_eth_dev *eth_dev,
+                             struct rte_ether_addr *mc_addr_set,
+                             uint32_t nb_mc_addr);
+
 /* MTU */
 int otx2_nix_mtu_set(struct rte_eth_dev *eth_dev, uint16_t mtu);
 int otx2_nix_recalc_mtu(struct rte_eth_dev *eth_dev);
 /* MTU */
 int otx2_nix_mtu_set(struct rte_eth_dev *eth_dev, uint16_t mtu);
 int otx2_nix_recalc_mtu(struct rte_eth_dev *eth_dev);
diff --git a/drivers/net/octeontx2/otx2_mcast.c b/drivers/net/octeontx2/otx2_mcast.c
new file mode 100644 (file)
index 0000000..f84aa1b
--- /dev/null
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2019 Marvell International Ltd.
+ */
+
+#include "otx2_ethdev.h"
+
+static int
+nix_mc_addr_list_free(struct otx2_eth_dev *dev, uint32_t entry_count)
+{
+       struct npc_mcam_free_entry_req *req;
+       struct otx2_mbox *mbox = dev->mbox;
+       struct mcast_entry *entry;
+       int rc = 0;
+
+       if (entry_count == 0)
+               goto exit;
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+               req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+               req->entry = entry->mcam_index;
+
+               rc = otx2_mbox_process_msg(mbox, NULL);
+               if (rc < 0)
+                       goto exit;
+
+               TAILQ_REMOVE(&dev->mc_fltr_tbl, entry, next);
+               rte_free(entry);
+               entry_count--;
+
+               if (entry_count == 0)
+                       break;
+       }
+
+       if (entry == NULL)
+               dev->mc_tbl_set = false;
+
+exit:
+       return rc;
+}
+
+static int
+nix_hw_update_mc_addr_list(struct rte_eth_dev *eth_dev)
+{
+       struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+       struct otx2_npc_flow_info *npc = &dev->npc_flow;
+       volatile uint8_t *key_data, *key_mask;
+       struct npc_mcam_write_entry_req *req;
+       struct otx2_mbox *mbox = dev->mbox;
+       struct npc_xtract_info *x_info;
+       uint64_t mcam_data, mcam_mask;
+       struct mcast_entry *entry;
+       otx2_dxcfg_t *ld_cfg;
+       uint8_t *mac_addr;
+       uint64_t action;
+       int idx, rc = 0;
+
+       ld_cfg = &npc->prx_dxcfg;
+       /* Get ETH layer profile info for populating mcam entries */
+       x_info = &(*ld_cfg)[NPC_MCAM_RX][NPC_LID_LA][NPC_LT_LA_ETHER].xtract[0];
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+               req = otx2_mbox_alloc_msg_npc_mcam_write_entry(mbox);
+               if (req == NULL) {
+                       /* The mbox memory buffer can be full.
+                        * Flush it and retry
+                        */
+                       otx2_mbox_msg_send(mbox, 0);
+                       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+                       if (rc < 0)
+                               goto exit;
+
+                       req = otx2_mbox_alloc_msg_npc_mcam_write_entry(mbox);
+                       if (req == NULL) {
+                               rc = -ENOMEM;
+                               goto exit;
+                       }
+               }
+               req->entry = entry->mcam_index;
+               req->intf = NPC_MCAM_RX;
+               req->enable_entry = 1;
+
+               /* Channel base extracted to KW0[11:0] */
+               req->entry_data.kw[0] = dev->rx_chan_base;
+               req->entry_data.kw_mask[0] = RTE_LEN2MASK(12, uint64_t);
+
+               /* Update mcam address */
+               key_data = (volatile uint8_t *)req->entry_data.kw;
+               key_mask = (volatile uint8_t *)req->entry_data.kw_mask;
+
+               mcam_data = 0ull;
+               mcam_mask = RTE_LEN2MASK(48, uint64_t);
+               mac_addr = &entry->mcast_mac.addr_bytes[0];
+               for (idx = RTE_ETHER_ADDR_LEN - 1; idx >= 0; idx--)
+                       mcam_data |= ((uint64_t)*mac_addr++) << (8 * idx);
+
+               otx2_mbox_memcpy(key_data + x_info->key_off,
+                                &mcam_data, x_info->len);
+               otx2_mbox_memcpy(key_mask + x_info->key_off,
+                                &mcam_mask, x_info->len);
+
+               action = NIX_RX_ACTIONOP_UCAST;
+
+               if (eth_dev->data->dev_conf.rxmode.mq_mode == ETH_MQ_RX_RSS) {
+                       action = NIX_RX_ACTIONOP_RSS;
+                       action |= (uint64_t)(dev->rss_info.alg_idx) << 56;
+               }
+
+               action |= ((uint64_t)otx2_pfvf_func(dev->pf, dev->vf)) << 4;
+               req->entry_data.action = action;
+       }
+
+       otx2_mbox_msg_send(mbox, 0);
+       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+       return rc;
+}
+
+int
+otx2_nix_mc_addr_list_install(struct rte_eth_dev *eth_dev)
+{
+       struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+       struct npc_mcam_alloc_entry_req *req;
+       struct npc_mcam_alloc_entry_rsp *rsp;
+       struct otx2_mbox *mbox = dev->mbox;
+       uint32_t entry_count = 0, idx  = 0;
+       struct mcast_entry *entry;
+       int rc = 0;
+
+       if (!dev->mc_tbl_set)
+               return 0;
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+               entry_count++;
+
+       req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(mbox);
+       req->priority = NPC_MCAM_ANY_PRIO;
+       req->count = entry_count;
+
+       rc = otx2_mbox_process_msg(mbox, (void *)&rsp);
+       if (rc || rsp->count  < entry_count) {
+               otx2_err("Failed to allocate required mcam entries");
+               goto exit;
+       }
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+               entry->mcam_index = rsp->entry_list[idx];
+
+       rc = nix_hw_update_mc_addr_list(eth_dev);
+
+exit:
+       return rc;
+}
+
+int
+otx2_nix_mc_addr_list_uninstall(struct rte_eth_dev *eth_dev)
+{
+       struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+       struct npc_mcam_free_entry_req *req;
+       struct otx2_mbox *mbox = dev->mbox;
+       struct mcast_entry *entry;
+       int rc = 0;
+
+       if (!dev->mc_tbl_set)
+               return 0;
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+               req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+               if (req == NULL) {
+                       otx2_mbox_msg_send(mbox, 0);
+                       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+                       if (rc < 0)
+                               goto exit;
+
+                       req = otx2_mbox_alloc_msg_npc_mcam_free_entry(mbox);
+                       if (req == NULL) {
+                               rc = -ENOMEM;
+                               goto exit;
+                       }
+               }
+               req->entry = entry->mcam_index;
+       }
+
+       otx2_mbox_msg_send(mbox, 0);
+       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+       return rc;
+}
+
+static int
+nix_setup_mc_addr_list(struct otx2_eth_dev *dev,
+                      struct rte_ether_addr *mc_addr_set)
+{
+       struct npc_mcam_ena_dis_entry_req *req;
+       struct otx2_mbox *mbox = dev->mbox;
+       struct mcast_entry *entry;
+       uint32_t idx = 0;
+       int rc = 0;
+
+       /* Populate PMD's mcast list with given mcast mac addresses and
+        * disable all mcam entries pertaining to the mcast list.
+        */
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next) {
+               rte_memcpy(&entry->mcast_mac, &mc_addr_set[idx++],
+                          RTE_ETHER_ADDR_LEN);
+
+               req = otx2_mbox_alloc_msg_npc_mcam_dis_entry(mbox);
+               if (req == NULL) {
+                       otx2_mbox_msg_send(mbox, 0);
+                       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+                       if (rc < 0)
+                               goto exit;
+
+                       req = otx2_mbox_alloc_msg_npc_mcam_dis_entry(mbox);
+                       if (req == NULL) {
+                               rc = -ENOMEM;
+                               goto exit;
+                       }
+               }
+               req->entry = entry->mcam_index;
+       }
+
+       otx2_mbox_msg_send(mbox, 0);
+       rc = otx2_mbox_wait_for_rsp(mbox, 0);
+
+exit:
+       return rc;
+}
+
+int
+otx2_nix_set_mc_addr_list(struct rte_eth_dev *eth_dev,
+                         struct rte_ether_addr *mc_addr_set,
+                         uint32_t nb_mc_addr)
+{
+       struct otx2_eth_dev *dev = otx2_eth_pmd_priv(eth_dev);
+       struct npc_mcam_alloc_entry_req *req;
+       struct npc_mcam_alloc_entry_rsp *rsp;
+       struct otx2_mbox *mbox = dev->mbox;
+       uint32_t idx, priv_count = 0;
+       struct mcast_entry *entry;
+       int rc = 0;
+
+       if (otx2_dev_is_vf(dev))
+               return -ENOTSUP;
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+               priv_count++;
+
+       if (nb_mc_addr == 0 || mc_addr_set == NULL) {
+               /* Free existing list if new list is null */
+               nb_mc_addr = priv_count;
+               goto exit;
+       }
+
+       for (idx = 0; idx < nb_mc_addr; idx++) {
+               if (!rte_is_multicast_ether_addr(&mc_addr_set[idx]))
+                       return -EINVAL;
+       }
+
+       /* New list is bigger than the existing list,
+        * allocate mcam entries for the extra entries.
+        */
+       if (nb_mc_addr > priv_count) {
+               req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(mbox);
+               req->priority = NPC_MCAM_ANY_PRIO;
+               req->count = nb_mc_addr - priv_count;
+
+               rc = otx2_mbox_process_msg(mbox, (void *)&rsp);
+               if (rc || (rsp->count + priv_count < nb_mc_addr)) {
+                       otx2_err("Failed to allocate required entries");
+                       nb_mc_addr = priv_count;
+                       goto exit;
+               }
+
+               /* Append new mcam entries to the existing mc list */
+               for (idx = 0; idx < rsp->count; idx++) {
+                       entry = rte_zmalloc("otx2_nix_mc_entry",
+                                           sizeof(struct mcast_entry), 0);
+                       if (!entry) {
+                               otx2_err("Failed to allocate memory");
+                               nb_mc_addr = priv_count;
+                               rc = -ENOMEM;
+                               goto exit;
+                       }
+                       entry->mcam_index = rsp->entry_list[idx];
+                       TAILQ_INSERT_HEAD(&dev->mc_fltr_tbl, entry, next);
+               }
+       } else {
+               /* Free the extra mcam entries if the new list is smaller
+                * than exiting list.
+                */
+               nix_mc_addr_list_free(dev, priv_count - nb_mc_addr);
+       }
+
+
+       /* Now mc_fltr_tbl has the required number of mcam entries,
+        * Traverse through it and add new multicast filter table entries.
+        */
+       rc = nix_setup_mc_addr_list(dev, mc_addr_set);
+       if (rc < 0)
+               goto exit;
+
+       rc = nix_hw_update_mc_addr_list(eth_dev);
+       if (rc < 0)
+               goto exit;
+
+       dev->mc_tbl_set = true;
+
+       return 0;
+
+exit:
+       nix_mc_addr_list_free(dev, nb_mc_addr);
+       return rc;
+}
+
+void
+otx2_nix_mc_filter_init(struct otx2_eth_dev *dev)
+{
+       if (otx2_dev_is_vf(dev))
+               return;
+
+       TAILQ_INIT(&dev->mc_fltr_tbl);
+}
+
+void
+otx2_nix_mc_filter_fini(struct otx2_eth_dev *dev)
+{
+       struct mcast_entry *entry;
+       uint32_t count = 0;
+
+       if (otx2_dev_is_vf(dev))
+               return;
+
+       TAILQ_FOREACH(entry, &dev->mc_fltr_tbl, next)
+               count++;
+
+       nix_mc_addr_list_free(dev, count);
+}