From 6fda3f0ddda9127c8562e724e3abc0f808c0bfd5 Mon Sep 17 00:00:00 2001 From: Shagun Agrawal Date: Mon, 27 Aug 2018 18:22:31 +0530 Subject: [PATCH] net/cxgbe: add API to program hardware MPS table Add API to program and manage hardware Multi Port Switch table. MPS holds destination MAC addresses to be matched against incoming packets for further rule processing. Packets not matching any entry in MPS table will be dropped by default, unless the underlying port is in promiscuous mode. Signed-off-by: Shagun Agrawal Signed-off-by: Rahul Lakkireddy --- drivers/net/cxgbe/Makefile | 1 + drivers/net/cxgbe/base/adapter.h | 1 + drivers/net/cxgbe/base/common.h | 6 + drivers/net/cxgbe/base/t4_hw.c | 106 +++++++++++ drivers/net/cxgbe/base/t4_regs.h | 8 + drivers/net/cxgbe/base/t4fw_interface.h | 24 +++ drivers/net/cxgbe/cxgbe_main.c | 7 + drivers/net/cxgbe/meson.build | 1 + drivers/net/cxgbe/mps_tcam.c | 243 ++++++++++++++++++++++++ drivers/net/cxgbe/mps_tcam.h | 52 +++++ 10 files changed, 449 insertions(+) create mode 100644 drivers/net/cxgbe/mps_tcam.c create mode 100644 drivers/net/cxgbe/mps_tcam.h diff --git a/drivers/net/cxgbe/Makefile b/drivers/net/cxgbe/Makefile index d75b070f34..68466f13ee 100644 --- a/drivers/net/cxgbe/Makefile +++ b/drivers/net/cxgbe/Makefile @@ -53,6 +53,7 @@ SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += cxgbe_filter.c SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += cxgbe_flow.c SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += t4_hw.c SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += clip_tbl.c +SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += mps_tcam.c SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += l2t.c SRCS-$(CONFIG_RTE_LIBRTE_CXGBE_PMD) += t4vf_hw.c diff --git a/drivers/net/cxgbe/base/adapter.h b/drivers/net/cxgbe/base/adapter.h index 9f4a9653c8..47cfc5f5fd 100644 --- a/drivers/net/cxgbe/base/adapter.h +++ b/drivers/net/cxgbe/base/adapter.h @@ -328,6 +328,7 @@ struct adapter { unsigned int l2t_end; /* Layer 2 table end */ struct clip_tbl *clipt; /* CLIP table */ struct l2t_data *l2t; /* Layer 2 table */ + struct mpstcam_table *mpstcam; struct tid_info tids; /* Info used to access TID related tables */ }; diff --git a/drivers/net/cxgbe/base/common.h b/drivers/net/cxgbe/base/common.h index 157201da27..9f57568501 100644 --- a/drivers/net/cxgbe/base/common.h +++ b/drivers/net/cxgbe/base/common.h @@ -388,6 +388,12 @@ int t4_free_vi(struct adapter *adap, unsigned int mbox, int t4_set_rxmode(struct adapter *adap, unsigned int mbox, unsigned int viid, int mtu, int promisc, int all_multi, int bcast, int vlanex, bool sleep_ok); +int t4_free_raw_mac_filt(struct adapter *adap, unsigned int viid, + const u8 *addr, const u8 *mask, unsigned int idx, + u8 lookup_type, u8 port_id, bool sleep_ok); +int t4_alloc_raw_mac_filt(struct adapter *adap, unsigned int viid, + const u8 *addr, const u8 *mask, unsigned int idx, + u8 lookup_type, u8 port_id, bool sleep_ok); int t4_change_mac(struct adapter *adap, unsigned int mbox, unsigned int viid, int idx, const u8 *addr, bool persist, bool add_smt); int t4_enable_vi_params(struct adapter *adap, unsigned int mbox, diff --git a/drivers/net/cxgbe/base/t4_hw.c b/drivers/net/cxgbe/base/t4_hw.c index 31762c9c55..d60894115c 100644 --- a/drivers/net/cxgbe/base/t4_hw.c +++ b/drivers/net/cxgbe/base/t4_hw.c @@ -4161,6 +4161,112 @@ int t4_set_rxmode(struct adapter *adap, unsigned int mbox, unsigned int viid, return t4vf_wr_mbox(adap, &c, sizeof(c), NULL); } +/** + * t4_alloc_raw_mac_filt - Adds a raw mac entry in mps tcam + * @adap: the adapter + * @viid: the VI id + * @mac: the MAC address + * @mask: the mask + * @idx: index at which to add this entry + * @port_id: the port index + * @lookup_type: MAC address for inner (1) or outer (0) header + * @sleep_ok: call is allowed to sleep + * + * Adds the mac entry at the specified index using raw mac interface. + * + * Returns a negative error number or the allocated index for this mac. + */ +int t4_alloc_raw_mac_filt(struct adapter *adap, unsigned int viid, + const u8 *addr, const u8 *mask, unsigned int idx, + u8 lookup_type, u8 port_id, bool sleep_ok) +{ + int ret = 0; + struct fw_vi_mac_cmd c; + struct fw_vi_mac_raw *p = &c.u.raw; + u32 val; + + memset(&c, 0, sizeof(c)); + c.op_to_viid = cpu_to_be32(V_FW_CMD_OP(FW_VI_MAC_CMD) | + F_FW_CMD_REQUEST | F_FW_CMD_WRITE | + V_FW_VI_MAC_CMD_VIID(viid)); + val = V_FW_CMD_LEN16(1) | + V_FW_VI_MAC_CMD_ENTRY_TYPE(FW_VI_MAC_TYPE_RAW); + c.freemacs_to_len16 = cpu_to_be32(val); + + /* Specify that this is an inner mac address */ + p->raw_idx_pkd = cpu_to_be32(V_FW_VI_MAC_CMD_RAW_IDX(idx)); + + /* Lookup Type. Outer header: 0, Inner header: 1 */ + p->data0_pkd = cpu_to_be32(V_DATALKPTYPE(lookup_type) | + V_DATAPORTNUM(port_id)); + /* Lookup mask and port mask */ + p->data0m_pkd = cpu_to_be64(V_DATALKPTYPE(M_DATALKPTYPE) | + V_DATAPORTNUM(M_DATAPORTNUM)); + + /* Copy the address and the mask */ + memcpy((u8 *)&p->data1[0] + 2, addr, ETHER_ADDR_LEN); + memcpy((u8 *)&p->data1m[0] + 2, mask, ETHER_ADDR_LEN); + + ret = t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, sleep_ok); + if (ret == 0) { + ret = G_FW_VI_MAC_CMD_RAW_IDX(be32_to_cpu(p->raw_idx_pkd)); + if (ret != (int)idx) + ret = -ENOMEM; + } + + return ret; +} + +/** + * t4_free_raw_mac_filt - Frees a raw mac entry in mps tcam + * @adap: the adapter + * @viid: the VI id + * @addr: the MAC address + * @mask: the mask + * @idx: index of the entry in mps tcam + * @lookup_type: MAC address for inner (1) or outer (0) header + * @port_id: the port index + * @sleep_ok: call is allowed to sleep + * + * Removes the mac entry at the specified index using raw mac interface. + * + * Returns a negative error number on failure. + */ +int t4_free_raw_mac_filt(struct adapter *adap, unsigned int viid, + const u8 *addr, const u8 *mask, unsigned int idx, + u8 lookup_type, u8 port_id, bool sleep_ok) +{ + struct fw_vi_mac_cmd c; + struct fw_vi_mac_raw *p = &c.u.raw; + u32 raw; + + memset(&c, 0, sizeof(c)); + c.op_to_viid = cpu_to_be32(V_FW_CMD_OP(FW_VI_MAC_CMD) | + F_FW_CMD_REQUEST | F_FW_CMD_WRITE | + V_FW_CMD_EXEC(0) | + V_FW_VI_MAC_CMD_VIID(viid)); + raw = V_FW_VI_MAC_CMD_ENTRY_TYPE(FW_VI_MAC_TYPE_RAW); + c.freemacs_to_len16 = cpu_to_be32(V_FW_VI_MAC_CMD_FREEMACS(0) | + raw | + V_FW_CMD_LEN16(1)); + + p->raw_idx_pkd = cpu_to_be32(V_FW_VI_MAC_CMD_RAW_IDX(idx) | + FW_VI_MAC_ID_BASED_FREE); + + /* Lookup Type. Outer header: 0, Inner header: 1 */ + p->data0_pkd = cpu_to_be32(V_DATALKPTYPE(lookup_type) | + V_DATAPORTNUM(port_id)); + /* Lookup mask and port mask */ + p->data0m_pkd = cpu_to_be64(V_DATALKPTYPE(M_DATALKPTYPE) | + V_DATAPORTNUM(M_DATAPORTNUM)); + + /* Copy the address and the mask */ + memcpy((u8 *)&p->data1[0] + 2, addr, ETHER_ADDR_LEN); + memcpy((u8 *)&p->data1m[0] + 2, mask, ETHER_ADDR_LEN); + + return t4_wr_mbox_meat(adap, adap->mbox, &c, sizeof(c), &c, sleep_ok); +} + /** * t4_change_mac - modifies the exact-match filter for a MAC address * @adap: the adapter diff --git a/drivers/net/cxgbe/base/t4_regs.h b/drivers/net/cxgbe/base/t4_regs.h index 6f872edc25..af8c741e29 100644 --- a/drivers/net/cxgbe/base/t4_regs.h +++ b/drivers/net/cxgbe/base/t4_regs.h @@ -45,6 +45,14 @@ #define MPS_T5_CLS_SRAM_H(idx) (A_MPS_T5_CLS_SRAM_H + (idx) * 8) #define NUM_MPS_T5_CLS_SRAM_H_INSTANCES 512 +#define S_DATAPORTNUM 12 +#define M_DATAPORTNUM 0xfU +#define V_DATAPORTNUM(x) ((x) << S_DATAPORTNUM) + +#define S_DATALKPTYPE 10 +#define M_DATALKPTYPE 0x3U +#define V_DATALKPTYPE(x) ((x) << S_DATALKPTYPE) + /* registers for module SGE */ #define SGE_BASE_ADDR 0x1000 diff --git a/drivers/net/cxgbe/base/t4fw_interface.h b/drivers/net/cxgbe/base/t4fw_interface.h index 1c08637bb5..e2d2ee8976 100644 --- a/drivers/net/cxgbe/base/t4fw_interface.h +++ b/drivers/net/cxgbe/base/t4fw_interface.h @@ -1282,12 +1282,17 @@ struct fw_vi_cmd { /* Special VI_MAC command index ids */ #define FW_VI_MAC_ADD_MAC 0x3FF #define FW_VI_MAC_ADD_PERSIST_MAC 0x3FE +#define FW_VI_MAC_ID_BASED_FREE 0x3FC enum fw_vi_mac_smac { FW_VI_MAC_MPS_TCAM_ENTRY, FW_VI_MAC_SMT_AND_MPSTCAM }; +enum fw_vi_mac_entry_types { + FW_VI_MAC_TYPE_RAW = 0x2, +}; + struct fw_vi_mac_cmd { __be32 op_to_viid; __be32 freemacs_to_len16; @@ -1299,6 +1304,13 @@ struct fw_vi_mac_cmd { struct fw_vi_mac_hash { __be64 hashvec; } hash; + struct fw_vi_mac_raw { + __be32 raw_idx_pkd; + __be32 data0_pkd; + __be32 data1[2]; + __be64 data0m_pkd; + __be32 data1m[2]; + } raw; } u; }; @@ -1308,6 +1320,12 @@ struct fw_vi_mac_cmd { #define G_FW_VI_MAC_CMD_VIID(x) \ (((x) >> S_FW_VI_MAC_CMD_VIID) & M_FW_VI_MAC_CMD_VIID) +#define S_FW_VI_MAC_CMD_FREEMACS 31 +#define V_FW_VI_MAC_CMD_FREEMACS(x) ((x) << S_FW_VI_MAC_CMD_FREEMACS) + +#define S_FW_VI_MAC_CMD_ENTRY_TYPE 23 +#define V_FW_VI_MAC_CMD_ENTRY_TYPE(x) ((x) << S_FW_VI_MAC_CMD_ENTRY_TYPE) + #define S_FW_VI_MAC_CMD_VALID 15 #define M_FW_VI_MAC_CMD_VALID 0x1 #define V_FW_VI_MAC_CMD_VALID(x) ((x) << S_FW_VI_MAC_CMD_VALID) @@ -1327,6 +1345,12 @@ struct fw_vi_mac_cmd { #define G_FW_VI_MAC_CMD_IDX(x) \ (((x) >> S_FW_VI_MAC_CMD_IDX) & M_FW_VI_MAC_CMD_IDX) +#define S_FW_VI_MAC_CMD_RAW_IDX 16 +#define M_FW_VI_MAC_CMD_RAW_IDX 0xffff +#define V_FW_VI_MAC_CMD_RAW_IDX(x) ((x) << S_FW_VI_MAC_CMD_RAW_IDX) +#define G_FW_VI_MAC_CMD_RAW_IDX(x) \ + (((x) >> S_FW_VI_MAC_CMD_RAW_IDX) & M_FW_VI_MAC_CMD_RAW_IDX) + struct fw_vi_rxmode_cmd { __be32 op_to_viid; __be32 retval_len16; diff --git a/drivers/net/cxgbe/cxgbe_main.c b/drivers/net/cxgbe/cxgbe_main.c index be2bc4213b..20d2de442e 100644 --- a/drivers/net/cxgbe/cxgbe_main.c +++ b/drivers/net/cxgbe/cxgbe_main.c @@ -39,6 +39,7 @@ #include "cxgbe.h" #include "clip_tbl.h" #include "l2t.h" +#include "mps_tcam.h" /** * Allocate a chunk of memory. The allocated memory is cleared. @@ -1689,6 +1690,7 @@ void cxgbe_close(struct adapter *adapter) if (adapter->flags & FULL_INIT_DONE) { tid_free(&adapter->tids); + t4_cleanup_mpstcam(adapter); t4_cleanup_clip_tbl(adapter); t4_cleanup_l2t(adapter); if (is_pf4(adapter)) @@ -1877,6 +1879,11 @@ int cxgbe_probe(struct adapter *adapter) "filter support disabled. Continuing\n"); } + adapter->mpstcam = t4_init_mpstcam(adapter); + if (!adapter->mpstcam) + dev_warn(adapter, "could not allocate mps tcam table." + " Continuing\n"); + if (is_hashfilter(adapter)) { if (t4_read_reg(adapter, A_LE_DB_CONFIG) & F_HASHEN) { u32 hash_base, hash_reg; diff --git a/drivers/net/cxgbe/meson.build b/drivers/net/cxgbe/meson.build index f6a442cb8a..c51af26e9b 100644 --- a/drivers/net/cxgbe/meson.build +++ b/drivers/net/cxgbe/meson.build @@ -9,6 +9,7 @@ sources = files('cxgbe_ethdev.c', 'cxgbe_filter.c', 'cxgbe_flow.c', 'clip_tbl.c', + 'mps_tcam.c', 'l2t.c', 'base/t4_hw.c', 'base/t4vf_hw.c') diff --git a/drivers/net/cxgbe/mps_tcam.c b/drivers/net/cxgbe/mps_tcam.c new file mode 100644 index 0000000000..02ec69a922 --- /dev/null +++ b/drivers/net/cxgbe/mps_tcam.c @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2018 Chelsio Communications. + * All rights reserved. + */ + +#include "mps_tcam.h" + +static inline bool +match_entry(struct mps_tcam_entry *entry, const u8 *eth_addr, const u8 *mask) +{ + if (!memcmp(eth_addr, entry->eth_addr, ETHER_ADDR_LEN) && + !memcmp(mask, entry->mask, ETHER_ADDR_LEN)) + return true; + return false; +} + +static int cxgbe_update_free_idx(struct mpstcam_table *t) +{ + struct mps_tcam_entry *entry = t->entry; + u16 i, next = t->free_idx + 1; + + if (entry[t->free_idx].state == MPS_ENTRY_UNUSED) + /* You are already pointing to a free entry !! */ + return 0; + + /* loop, till we don't rollback to same index where we started */ + for (i = next; i != t->free_idx; i++) { + if (i == t->size) + /* rollback and search free entry from start */ + i = 0; + + if (entry[i].state == MPS_ENTRY_UNUSED) { + t->free_idx = i; + return 0; + } + } + + return -1; /* table is full */ +} + +static struct mps_tcam_entry * +cxgbe_mpstcam_lookup(struct mpstcam_table *t, const u8 *eth_addr, + const u8 *mask) +{ + struct mps_tcam_entry *entry = t->entry; + int i; + + if (!entry) + return NULL; + + for (i = 0; i < t->size; i++) { + if (entry[i].state == MPS_ENTRY_UNUSED) + continue; /* entry is not being used */ + if (match_entry(&entry[i], eth_addr, mask)) + return &entry[i]; + } + + return NULL; +} + +int cxgbe_mpstcam_alloc(struct port_info *pi, const u8 *eth_addr, + const u8 *mask) +{ + struct adapter *adap = pi->adapter; + struct mpstcam_table *mpstcam = adap->mpstcam; + struct mps_tcam_entry *entry; + int ret; + + if (!adap->mpstcam) { + dev_err(adap, "mpstcam table is not available\n"); + return -EOPNOTSUPP; + } + + /* If entry already present, return it. */ + t4_os_write_lock(&mpstcam->lock); + entry = cxgbe_mpstcam_lookup(adap->mpstcam, eth_addr, mask); + if (entry) { + rte_atomic32_add(&entry->refcnt, 1); + t4_os_write_unlock(&mpstcam->lock); + return entry->idx; + } + + if (mpstcam->full) { + t4_os_write_unlock(&mpstcam->lock); + dev_err(adap, "mps-tcam table is full\n"); + return -ENOMEM; + } + + ret = t4_alloc_raw_mac_filt(adap, pi->viid, eth_addr, mask, + mpstcam->free_idx, 0, pi->port_id, false); + if (ret <= 0) { + t4_os_write_unlock(&mpstcam->lock); + return ret; + } + + /* Fill in the new values */ + entry = &mpstcam->entry[ret]; + memcpy(entry->eth_addr, eth_addr, ETHER_ADDR_LEN); + memcpy(entry->mask, mask, ETHER_ADDR_LEN); + rte_atomic32_set(&entry->refcnt, 1); + entry->state = MPS_ENTRY_USED; + + if (cxgbe_update_free_idx(mpstcam)) + mpstcam->full = true; + + t4_os_write_unlock(&mpstcam->lock); + return ret; +} + +int cxgbe_mpstcam_modify(struct port_info *pi, int idx, const u8 *addr) +{ + struct adapter *adap = pi->adapter; + struct mpstcam_table *mpstcam = adap->mpstcam; + struct mps_tcam_entry *entry; + + if (!mpstcam) + return -EOPNOTSUPP; + t4_os_write_lock(&mpstcam->lock); + if (idx != -1 && idx >= mpstcam->size) { + t4_os_write_unlock(&mpstcam->lock); + return -EINVAL; + } + if (idx >= 0) { + entry = &mpstcam->entry[idx]; + /* user wants to modify an existing entry. + * verify if entry exists + */ + if (entry->state != MPS_ENTRY_USED) { + t4_os_write_unlock(&mpstcam->lock); + return -EINVAL; + } + } + + idx = t4_change_mac(adap, adap->mbox, pi->viid, idx, addr, true, true); + if (idx < 0) { + t4_os_write_unlock(&mpstcam->lock); + return idx; + } + + /* idx can now be different from what user provided */ + entry = &mpstcam->entry[idx]; + memcpy(entry->eth_addr, addr, ETHER_ADDR_LEN); + /* NOTE: we have considered the case that idx returned by t4_change_mac + * will be different from the user provided value only if user + * provided value is -1 + */ + if (entry->state == MPS_ENTRY_UNUSED) { + rte_atomic32_set(&entry->refcnt, 1); + entry->state = MPS_ENTRY_USED; + } + + if (cxgbe_update_free_idx(mpstcam)) + mpstcam->full = true; + + t4_os_write_unlock(&mpstcam->lock); + return idx; +} + +/** + * hold appropriate locks while calling this. + */ +static inline void reset_mpstcam_entry(struct mps_tcam_entry *entry) +{ + memset(entry->eth_addr, 0, ETHER_ADDR_LEN); + memset(entry->mask, 0, ETHER_ADDR_LEN); + rte_atomic32_clear(&entry->refcnt); + entry->state = MPS_ENTRY_UNUSED; +} + +/** + * ret < 0: fatal error + * ret = 0: entry removed in h/w + * ret > 0: updated refcount. + */ +int cxgbe_mpstcam_remove(struct port_info *pi, u16 idx) +{ + struct adapter *adap = pi->adapter; + struct mpstcam_table *t = adap->mpstcam; + struct mps_tcam_entry *entry; + int ret; + + if (!t) + return -EOPNOTSUPP; + t4_os_write_lock(&t->lock); + entry = &t->entry[idx]; + if (entry->state == MPS_ENTRY_UNUSED) { + t4_os_write_unlock(&t->lock); + return -EINVAL; + } + + if (rte_atomic32_read(&entry->refcnt) == 1) + ret = t4_free_raw_mac_filt(adap, pi->viid, entry->eth_addr, + entry->mask, idx, 1, pi->port_id, + false); + else + ret = rte_atomic32_sub_return(&entry->refcnt, 1); + + if (ret == 0) { + reset_mpstcam_entry(entry); + t->full = false; /* We have atleast 1 free entry */ + cxgbe_update_free_idx(t); + } + + t4_os_write_unlock(&t->lock); + return ret; +} + +struct mpstcam_table *t4_init_mpstcam(struct adapter *adap) +{ + struct mpstcam_table *t; + int i; + u16 size = adap->params.arch.mps_tcam_size; + + t = t4_os_alloc(sizeof(*t) + size * sizeof(struct mps_tcam_entry)); + if (!t) + return NULL; + + t4_os_rwlock_init(&t->lock); + t->full = false; + t->size = size; + + for (i = 0; i < size; i++) { + reset_mpstcam_entry(&t->entry[i]); + t->entry[i].mpstcam = t; + t->entry[i].idx = i; + } + + /* first entry is used by chip. this is overwritten only + * in t4_cleanup_mpstcam() + */ + t->entry[0].state = MPS_ENTRY_USED; + t->free_idx = 1; + + return t; +} + +void t4_cleanup_mpstcam(struct adapter *adap) +{ + if (adap->mpstcam) { + t4_os_free(adap->mpstcam->entry); + t4_os_free(adap->mpstcam); + } +} diff --git a/drivers/net/cxgbe/mps_tcam.h b/drivers/net/cxgbe/mps_tcam.h new file mode 100644 index 0000000000..c3d6fe0d76 --- /dev/null +++ b/drivers/net/cxgbe/mps_tcam.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright(c) 2018 Chelsio Communications. + * All rights reserved. + */ + +#ifndef _CXGBE_MPSTCAM_H_ +#define _CXGBE_MPSTCAM_H_ + +#include "common.h" + +enum { + MPS_ENTRY_UNUSED, /* Keep this first so memset 0 renders + * the correct state. Other states can + * be added in future like MPS_ENTRY_BUSY + * to reduce contention while mboxing + * the request to f/w or to denote attributes + * for a specific entry + */ + MPS_ENTRY_USED, +}; + +struct mps_tcam_entry { + u8 state; + u16 idx; + + /* add data here which uniquely defines an entry */ + u8 eth_addr[ETHER_ADDR_LEN]; + u8 mask[ETHER_ADDR_LEN]; + + struct mpstcam_table *mpstcam; /* backptr */ + rte_atomic32_t refcnt; +}; + +struct mpstcam_table { + u16 size; + rte_rwlock_t lock; + u16 free_idx; /* next free index */ + bool full; /* since free index can be present + * anywhere in the table, size and + * free_idx cannot alone determine + * if the table is full + */ + struct mps_tcam_entry entry[0]; +}; + +struct mpstcam_table *t4_init_mpstcam(struct adapter *adap); +void t4_cleanup_mpstcam(struct adapter *adap); +int cxgbe_mpstcam_alloc(struct port_info *pi, const u8 *mac, const u8 *mask); +int cxgbe_mpstcam_remove(struct port_info *pi, u16 idx); +int cxgbe_mpstcam_modify(struct port_info *pi, int idx, const u8 *addr); + +#endif /* _CXGBE_MPSTCAM_H_ */