mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

If ntuple filters count is modified followed by
unicast filters count using devlink then the ntuple count
set by user is ignored and all the ntuple filters are
being reallocated. Fix this by storing the ntuple count
set by user. Without this patch, say if user tries
to modify ntuple count as 8 followed by ucast filter count as 4
using devlink commands then ntuple count is being reverted to
default value 16 i.e, not retaining user set value 8.
Fixes: 39c469188b
("octeontx2-pf: Add ucast filter count configurability via devlink.")
Signed-off-by: Subbaraya Sundeep <sbhatta@marvell.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/1747054357-5850-1-git-send-email-sbhatta@marvell.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
1526 lines
39 KiB
C
1526 lines
39 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Marvell RVU Ethernet driver
|
|
*
|
|
* Copyright (C) 2020 Marvell.
|
|
*
|
|
*/
|
|
|
|
#include <net/ipv6.h>
|
|
#include <linux/sort.h>
|
|
|
|
#include "otx2_common.h"
|
|
|
|
#define OTX2_DEFAULT_ACTION 0x1
|
|
|
|
struct otx2_flow {
|
|
struct ethtool_rx_flow_spec flow_spec;
|
|
struct list_head list;
|
|
u32 location;
|
|
u32 entry;
|
|
bool is_vf;
|
|
u8 rss_ctx_id;
|
|
#define DMAC_FILTER_RULE BIT(0)
|
|
#define PFC_FLOWCTRL_RULE BIT(1)
|
|
u16 rule_type;
|
|
int vf;
|
|
};
|
|
|
|
enum dmac_req {
|
|
DMAC_ADDR_UPDATE,
|
|
DMAC_ADDR_DEL
|
|
};
|
|
|
|
static void otx2_clear_ntuple_flow_info(struct otx2_nic *pfvf, struct otx2_flow_config *flow_cfg)
|
|
{
|
|
devm_kfree(pfvf->dev, flow_cfg->flow_ent);
|
|
flow_cfg->flow_ent = NULL;
|
|
flow_cfg->max_flows = 0;
|
|
}
|
|
|
|
static int otx2_free_ntuple_mcam_entries(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_mcam_free_entry_req *req;
|
|
int ent, err;
|
|
|
|
if (!flow_cfg->max_flows)
|
|
return 0;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
for (ent = 0; ent < flow_cfg->max_flows; ent++) {
|
|
req = otx2_mbox_alloc_msg_npc_mcam_free_entry(&pfvf->mbox);
|
|
if (!req)
|
|
break;
|
|
|
|
req->entry = flow_cfg->flow_ent[ent];
|
|
|
|
/* Send message to AF to free MCAM entries */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
if (err)
|
|
break;
|
|
}
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
otx2_clear_ntuple_flow_info(pfvf, flow_cfg);
|
|
return 0;
|
|
}
|
|
|
|
int otx2_alloc_mcam_entries(struct otx2_nic *pfvf, u16 count)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_mcam_alloc_entry_req *req;
|
|
struct npc_mcam_alloc_entry_rsp *rsp;
|
|
int ent, allocated = 0;
|
|
|
|
/* Free current ones and allocate new ones with requested count */
|
|
otx2_free_ntuple_mcam_entries(pfvf);
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
flow_cfg->flow_ent = devm_kmalloc_array(pfvf->dev, count,
|
|
sizeof(u16), GFP_KERNEL);
|
|
if (!flow_cfg->flow_ent) {
|
|
netdev_err(pfvf->netdev,
|
|
"%s: Unable to allocate memory for flow entries\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
|
|
/* In a single request a max of NPC_MAX_NONCONTIG_ENTRIES MCAM entries
|
|
* can only be allocated.
|
|
*/
|
|
while (allocated < count) {
|
|
req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(&pfvf->mbox);
|
|
if (!req)
|
|
goto exit;
|
|
|
|
req->contig = false;
|
|
req->count = (count - allocated) > NPC_MAX_NONCONTIG_ENTRIES ?
|
|
NPC_MAX_NONCONTIG_ENTRIES : count - allocated;
|
|
|
|
/* Allocate higher priority entries for PFs, so that VF's entries
|
|
* will be on top of PF.
|
|
*/
|
|
if (!is_otx2_vf(pfvf->pcifunc)) {
|
|
req->priority = NPC_MCAM_HIGHER_PRIO;
|
|
req->ref_entry = flow_cfg->def_ent[0];
|
|
}
|
|
|
|
/* Send message to AF */
|
|
if (otx2_sync_mbox_msg(&pfvf->mbox))
|
|
goto exit;
|
|
|
|
rsp = (struct npc_mcam_alloc_entry_rsp *)otx2_mbox_get_rsp
|
|
(&pfvf->mbox.mbox, 0, &req->hdr);
|
|
if (IS_ERR(rsp))
|
|
goto exit;
|
|
|
|
for (ent = 0; ent < rsp->count; ent++)
|
|
flow_cfg->flow_ent[ent + allocated] = rsp->entry_list[ent];
|
|
|
|
allocated += rsp->count;
|
|
|
|
/* If this request is not fulfilled, no need to send
|
|
* further requests.
|
|
*/
|
|
if (rsp->count != req->count)
|
|
break;
|
|
}
|
|
|
|
/* Multiple MCAM entry alloc requests could result in non-sequential
|
|
* MCAM entries in the flow_ent[] array. Sort them in an ascending order,
|
|
* otherwise user installed ntuple filter index and MCAM entry index will
|
|
* not be in sync.
|
|
*/
|
|
if (allocated)
|
|
sort(&flow_cfg->flow_ent[0], allocated,
|
|
sizeof(flow_cfg->flow_ent[0]), mcam_entry_cmp, NULL);
|
|
|
|
exit:
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
|
|
flow_cfg->max_flows = allocated;
|
|
|
|
if (allocated) {
|
|
pfvf->flags |= OTX2_FLAG_MCAM_ENTRIES_ALLOC;
|
|
pfvf->flags |= OTX2_FLAG_NTUPLE_SUPPORT;
|
|
}
|
|
|
|
if (allocated != count)
|
|
netdev_info(pfvf->netdev,
|
|
"Unable to allocate %d MCAM entries, got only %d\n",
|
|
count, allocated);
|
|
return allocated;
|
|
}
|
|
EXPORT_SYMBOL(otx2_alloc_mcam_entries);
|
|
|
|
int otx2_mcam_entry_init(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_get_field_status_req *freq;
|
|
struct npc_get_field_status_rsp *frsp;
|
|
struct npc_mcam_alloc_entry_req *req;
|
|
struct npc_mcam_alloc_entry_rsp *rsp;
|
|
int vf_vlan_max_flows;
|
|
int ent, count;
|
|
|
|
vf_vlan_max_flows = pfvf->total_vfs * OTX2_PER_VF_VLAN_FLOWS;
|
|
count = flow_cfg->ucast_flt_cnt +
|
|
OTX2_MAX_VLAN_FLOWS + vf_vlan_max_flows;
|
|
|
|
flow_cfg->def_ent = devm_kmalloc_array(pfvf->dev, count,
|
|
sizeof(u16), GFP_KERNEL);
|
|
if (!flow_cfg->def_ent)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
|
|
req = otx2_mbox_alloc_msg_npc_mcam_alloc_entry(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->contig = false;
|
|
req->count = count;
|
|
|
|
/* Send message to AF */
|
|
if (otx2_sync_mbox_msg(&pfvf->mbox)) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rsp = (struct npc_mcam_alloc_entry_rsp *)otx2_mbox_get_rsp
|
|
(&pfvf->mbox.mbox, 0, &req->hdr);
|
|
if (IS_ERR(rsp)) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return PTR_ERR(rsp);
|
|
}
|
|
|
|
if (rsp->count != req->count) {
|
|
netdev_info(pfvf->netdev,
|
|
"Unable to allocate MCAM entries for ucast, vlan and vf_vlan\n");
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
devm_kfree(pfvf->dev, flow_cfg->def_ent);
|
|
return 0;
|
|
}
|
|
|
|
for (ent = 0; ent < rsp->count; ent++)
|
|
flow_cfg->def_ent[ent] = rsp->entry_list[ent];
|
|
|
|
flow_cfg->vf_vlan_offset = 0;
|
|
flow_cfg->unicast_offset = vf_vlan_max_flows;
|
|
flow_cfg->rx_vlan_offset = flow_cfg->unicast_offset +
|
|
flow_cfg->ucast_flt_cnt;
|
|
pfvf->flags |= OTX2_FLAG_UCAST_FLTR_SUPPORT;
|
|
|
|
/* Check if NPC_DMAC field is supported
|
|
* by the mkex profile before setting VLAN support flag.
|
|
*/
|
|
freq = otx2_mbox_alloc_msg_npc_get_field_status(&pfvf->mbox);
|
|
if (!freq) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
freq->field = NPC_DMAC;
|
|
if (otx2_sync_mbox_msg(&pfvf->mbox)) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
frsp = (struct npc_get_field_status_rsp *)otx2_mbox_get_rsp
|
|
(&pfvf->mbox.mbox, 0, &freq->hdr);
|
|
if (IS_ERR(frsp)) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return PTR_ERR(frsp);
|
|
}
|
|
|
|
if (frsp->enable) {
|
|
pfvf->flags |= OTX2_FLAG_RX_VLAN_SUPPORT;
|
|
pfvf->flags |= OTX2_FLAG_VF_VLAN_SUPPORT;
|
|
}
|
|
|
|
pfvf->flags |= OTX2_FLAG_MCAM_ENTRIES_ALLOC;
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
|
|
/* Allocate entries for Ntuple filters */
|
|
count = otx2_alloc_mcam_entries(pfvf, flow_cfg->ntuple_cnt);
|
|
if (count <= 0) {
|
|
otx2_clear_ntuple_flow_info(pfvf, flow_cfg);
|
|
return 0;
|
|
}
|
|
|
|
pfvf->flags |= OTX2_FLAG_TC_FLOWER_SUPPORT;
|
|
|
|
refcount_set(&flow_cfg->mark_flows, 1);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(otx2_mcam_entry_init);
|
|
|
|
/* TODO : revisit on size */
|
|
#define OTX2_DMAC_FLTR_BITMAP_SZ (4 * 2048 + 32)
|
|
|
|
int otx2vf_mcam_flow_init(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg;
|
|
|
|
pfvf->flow_cfg = devm_kzalloc(pfvf->dev,
|
|
sizeof(struct otx2_flow_config),
|
|
GFP_KERNEL);
|
|
if (!pfvf->flow_cfg)
|
|
return -ENOMEM;
|
|
|
|
pfvf->flow_cfg->dmacflt_bmap = devm_kcalloc(pfvf->dev,
|
|
BITS_TO_LONGS(OTX2_DMAC_FLTR_BITMAP_SZ),
|
|
sizeof(long), GFP_KERNEL);
|
|
if (!pfvf->flow_cfg->dmacflt_bmap)
|
|
return -ENOMEM;
|
|
|
|
flow_cfg = pfvf->flow_cfg;
|
|
INIT_LIST_HEAD(&flow_cfg->flow_list);
|
|
INIT_LIST_HEAD(&flow_cfg->flow_list_tc);
|
|
flow_cfg->max_flows = 0;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(otx2vf_mcam_flow_init);
|
|
|
|
int otx2_mcam_flow_init(struct otx2_nic *pf)
|
|
{
|
|
int err;
|
|
|
|
pf->flow_cfg = devm_kzalloc(pf->dev, sizeof(struct otx2_flow_config),
|
|
GFP_KERNEL);
|
|
if (!pf->flow_cfg)
|
|
return -ENOMEM;
|
|
|
|
pf->flow_cfg->dmacflt_bmap = devm_kcalloc(pf->dev,
|
|
BITS_TO_LONGS(OTX2_DMAC_FLTR_BITMAP_SZ),
|
|
sizeof(long), GFP_KERNEL);
|
|
if (!pf->flow_cfg->dmacflt_bmap)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&pf->flow_cfg->flow_list);
|
|
INIT_LIST_HEAD(&pf->flow_cfg->flow_list_tc);
|
|
|
|
pf->flow_cfg->ucast_flt_cnt = OTX2_DEFAULT_UNICAST_FLOWS;
|
|
pf->flow_cfg->ntuple_cnt = OTX2_DEFAULT_FLOWCOUNT;
|
|
|
|
/* Allocate bare minimum number of MCAM entries needed for
|
|
* unicast and ntuple filters.
|
|
*/
|
|
err = otx2_mcam_entry_init(pf);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Check if MCAM entries are allocate or not */
|
|
if (!(pf->flags & OTX2_FLAG_UCAST_FLTR_SUPPORT))
|
|
return 0;
|
|
|
|
pf->mac_table = devm_kzalloc(pf->dev, sizeof(struct otx2_mac_table)
|
|
* pf->flow_cfg->ucast_flt_cnt, GFP_KERNEL);
|
|
if (!pf->mac_table)
|
|
return -ENOMEM;
|
|
|
|
otx2_dmacflt_get_max_cnt(pf);
|
|
|
|
/* DMAC filters are not allocated */
|
|
if (!pf->flow_cfg->dmacflt_max_flows)
|
|
return 0;
|
|
|
|
pf->flow_cfg->bmap_to_dmacindex =
|
|
devm_kzalloc(pf->dev, sizeof(u32) *
|
|
pf->flow_cfg->dmacflt_max_flows,
|
|
GFP_KERNEL);
|
|
|
|
if (!pf->flow_cfg->bmap_to_dmacindex)
|
|
return -ENOMEM;
|
|
|
|
pf->flags |= OTX2_FLAG_DMACFLTR_SUPPORT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void otx2_mcam_flow_del(struct otx2_nic *pf)
|
|
{
|
|
otx2_destroy_mcam_flows(pf);
|
|
}
|
|
EXPORT_SYMBOL(otx2_mcam_flow_del);
|
|
|
|
/* On success adds mcam entry
|
|
* On failure enable promisous mode
|
|
*/
|
|
static int otx2_do_add_macfilter(struct otx2_nic *pf, const u8 *mac)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pf->flow_cfg;
|
|
struct npc_install_flow_req *req;
|
|
int err, i;
|
|
|
|
if (!(pf->flags & OTX2_FLAG_UCAST_FLTR_SUPPORT))
|
|
return -ENOMEM;
|
|
|
|
/* dont have free mcam entries or uc list is greater than alloted */
|
|
if (netdev_uc_count(pf->netdev) > pf->flow_cfg->ucast_flt_cnt)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&pf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_install_flow(&pf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* unicast offset starts with 32 0..31 for ntuple */
|
|
for (i = 0; i < pf->flow_cfg->ucast_flt_cnt; i++) {
|
|
if (pf->mac_table[i].inuse)
|
|
continue;
|
|
ether_addr_copy(pf->mac_table[i].addr, mac);
|
|
pf->mac_table[i].inuse = true;
|
|
pf->mac_table[i].mcam_entry =
|
|
flow_cfg->def_ent[i + flow_cfg->unicast_offset];
|
|
req->entry = pf->mac_table[i].mcam_entry;
|
|
break;
|
|
}
|
|
|
|
ether_addr_copy(req->packet.dmac, mac);
|
|
eth_broadcast_addr((u8 *)&req->mask.dmac);
|
|
req->features = BIT_ULL(NPC_DMAC);
|
|
req->channel = pf->hw.rx_chan_base;
|
|
req->intf = NIX_INTF_RX;
|
|
req->op = NIX_RX_ACTION_DEFAULT;
|
|
req->set_cntr = 1;
|
|
|
|
err = otx2_sync_mbox_msg(&pf->mbox);
|
|
mutex_unlock(&pf->mbox.lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
int otx2_add_macfilter(struct net_device *netdev, const u8 *mac)
|
|
{
|
|
struct otx2_nic *pf = netdev_priv(netdev);
|
|
|
|
if (!bitmap_empty(pf->flow_cfg->dmacflt_bmap,
|
|
pf->flow_cfg->dmacflt_max_flows))
|
|
netdev_warn(netdev,
|
|
"Add %pM to CGX/RPM DMAC filters list as well\n",
|
|
mac);
|
|
|
|
return otx2_do_add_macfilter(pf, mac);
|
|
}
|
|
|
|
static bool otx2_get_mcamentry_for_mac(struct otx2_nic *pf, const u8 *mac,
|
|
int *mcam_entry)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < pf->flow_cfg->ucast_flt_cnt; i++) {
|
|
if (!pf->mac_table[i].inuse)
|
|
continue;
|
|
|
|
if (ether_addr_equal(pf->mac_table[i].addr, mac)) {
|
|
*mcam_entry = pf->mac_table[i].mcam_entry;
|
|
pf->mac_table[i].inuse = false;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int otx2_del_macfilter(struct net_device *netdev, const u8 *mac)
|
|
{
|
|
struct otx2_nic *pf = netdev_priv(netdev);
|
|
struct npc_delete_flow_req *req;
|
|
int err, mcam_entry;
|
|
|
|
/* check does mcam entry exists for given mac */
|
|
if (!otx2_get_mcamentry_for_mac(pf, mac, &mcam_entry))
|
|
return 0;
|
|
|
|
mutex_lock(&pf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_delete_flow(&pf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
req->entry = mcam_entry;
|
|
/* Send message to AF */
|
|
err = otx2_sync_mbox_msg(&pf->mbox);
|
|
mutex_unlock(&pf->mbox.lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct otx2_flow *otx2_find_flow(struct otx2_nic *pfvf, u32 location)
|
|
{
|
|
struct otx2_flow *iter;
|
|
|
|
list_for_each_entry(iter, &pfvf->flow_cfg->flow_list, list) {
|
|
if (iter->location == location)
|
|
return iter;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void otx2_add_flow_to_list(struct otx2_nic *pfvf, struct otx2_flow *flow)
|
|
{
|
|
struct list_head *head = &pfvf->flow_cfg->flow_list;
|
|
struct otx2_flow *iter;
|
|
|
|
list_for_each_entry(iter, &pfvf->flow_cfg->flow_list, list) {
|
|
if (iter->location > flow->location)
|
|
break;
|
|
head = &iter->list;
|
|
}
|
|
|
|
list_add(&flow->list, head);
|
|
}
|
|
|
|
int otx2_get_maxflows(struct otx2_flow_config *flow_cfg)
|
|
{
|
|
if (!flow_cfg)
|
|
return 0;
|
|
|
|
if (flow_cfg->nr_flows == flow_cfg->max_flows ||
|
|
!bitmap_empty(flow_cfg->dmacflt_bmap,
|
|
flow_cfg->dmacflt_max_flows))
|
|
return flow_cfg->max_flows + flow_cfg->dmacflt_max_flows;
|
|
else
|
|
return flow_cfg->max_flows;
|
|
}
|
|
EXPORT_SYMBOL(otx2_get_maxflows);
|
|
|
|
int otx2_get_flow(struct otx2_nic *pfvf, struct ethtool_rxnfc *nfc,
|
|
u32 location)
|
|
{
|
|
struct otx2_flow *iter;
|
|
|
|
if (location >= otx2_get_maxflows(pfvf->flow_cfg))
|
|
return -EINVAL;
|
|
|
|
list_for_each_entry(iter, &pfvf->flow_cfg->flow_list, list) {
|
|
if (iter->location == location) {
|
|
nfc->fs = iter->flow_spec;
|
|
nfc->rss_context = iter->rss_ctx_id;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
int otx2_get_all_flows(struct otx2_nic *pfvf, struct ethtool_rxnfc *nfc,
|
|
u32 *rule_locs)
|
|
{
|
|
u32 rule_cnt = nfc->rule_cnt;
|
|
u32 location = 0;
|
|
int idx = 0;
|
|
int err = 0;
|
|
|
|
nfc->data = otx2_get_maxflows(pfvf->flow_cfg);
|
|
while ((!err || err == -ENOENT) && idx < rule_cnt) {
|
|
err = otx2_get_flow(pfvf, nfc, location);
|
|
if (!err)
|
|
rule_locs[idx++] = location;
|
|
location++;
|
|
}
|
|
nfc->rule_cnt = rule_cnt;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int otx2_prepare_ipv4_flow(struct ethtool_rx_flow_spec *fsp,
|
|
struct npc_install_flow_req *req,
|
|
u32 flow_type)
|
|
{
|
|
struct ethtool_usrip4_spec *ipv4_usr_mask = &fsp->m_u.usr_ip4_spec;
|
|
struct ethtool_usrip4_spec *ipv4_usr_hdr = &fsp->h_u.usr_ip4_spec;
|
|
struct ethtool_tcpip4_spec *ipv4_l4_mask = &fsp->m_u.tcp_ip4_spec;
|
|
struct ethtool_tcpip4_spec *ipv4_l4_hdr = &fsp->h_u.tcp_ip4_spec;
|
|
struct ethtool_ah_espip4_spec *ah_esp_hdr = &fsp->h_u.ah_ip4_spec;
|
|
struct ethtool_ah_espip4_spec *ah_esp_mask = &fsp->m_u.ah_ip4_spec;
|
|
struct flow_msg *pmask = &req->mask;
|
|
struct flow_msg *pkt = &req->packet;
|
|
|
|
switch (flow_type) {
|
|
case IP_USER_FLOW:
|
|
if (ipv4_usr_mask->ip4src) {
|
|
memcpy(&pkt->ip4src, &ipv4_usr_hdr->ip4src,
|
|
sizeof(pkt->ip4src));
|
|
memcpy(&pmask->ip4src, &ipv4_usr_mask->ip4src,
|
|
sizeof(pmask->ip4src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV4);
|
|
}
|
|
if (ipv4_usr_mask->ip4dst) {
|
|
memcpy(&pkt->ip4dst, &ipv4_usr_hdr->ip4dst,
|
|
sizeof(pkt->ip4dst));
|
|
memcpy(&pmask->ip4dst, &ipv4_usr_mask->ip4dst,
|
|
sizeof(pmask->ip4dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV4);
|
|
}
|
|
if (ipv4_usr_mask->tos) {
|
|
pkt->tos = ipv4_usr_hdr->tos;
|
|
pmask->tos = ipv4_usr_mask->tos;
|
|
req->features |= BIT_ULL(NPC_TOS);
|
|
}
|
|
if (ipv4_usr_mask->proto) {
|
|
switch (ipv4_usr_hdr->proto) {
|
|
case IPPROTO_ICMP:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_ICMP);
|
|
break;
|
|
case IPPROTO_TCP:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_TCP);
|
|
break;
|
|
case IPPROTO_UDP:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_UDP);
|
|
break;
|
|
case IPPROTO_SCTP:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_SCTP);
|
|
break;
|
|
case IPPROTO_AH:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_AH);
|
|
break;
|
|
case IPPROTO_ESP:
|
|
req->features |= BIT_ULL(NPC_IPPROTO_ESP);
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
pkt->etype = cpu_to_be16(ETH_P_IP);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
break;
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
case SCTP_V4_FLOW:
|
|
pkt->etype = cpu_to_be16(ETH_P_IP);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
if (ipv4_l4_mask->ip4src) {
|
|
memcpy(&pkt->ip4src, &ipv4_l4_hdr->ip4src,
|
|
sizeof(pkt->ip4src));
|
|
memcpy(&pmask->ip4src, &ipv4_l4_mask->ip4src,
|
|
sizeof(pmask->ip4src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV4);
|
|
}
|
|
if (ipv4_l4_mask->ip4dst) {
|
|
memcpy(&pkt->ip4dst, &ipv4_l4_hdr->ip4dst,
|
|
sizeof(pkt->ip4dst));
|
|
memcpy(&pmask->ip4dst, &ipv4_l4_mask->ip4dst,
|
|
sizeof(pmask->ip4dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV4);
|
|
}
|
|
if (ipv4_l4_mask->tos) {
|
|
pkt->tos = ipv4_l4_hdr->tos;
|
|
pmask->tos = ipv4_l4_mask->tos;
|
|
req->features |= BIT_ULL(NPC_TOS);
|
|
}
|
|
if (ipv4_l4_mask->psrc) {
|
|
memcpy(&pkt->sport, &ipv4_l4_hdr->psrc,
|
|
sizeof(pkt->sport));
|
|
memcpy(&pmask->sport, &ipv4_l4_mask->psrc,
|
|
sizeof(pmask->sport));
|
|
if (flow_type == UDP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_SPORT_UDP);
|
|
else if (flow_type == TCP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_SPORT_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_SPORT_SCTP);
|
|
}
|
|
if (ipv4_l4_mask->pdst) {
|
|
memcpy(&pkt->dport, &ipv4_l4_hdr->pdst,
|
|
sizeof(pkt->dport));
|
|
memcpy(&pmask->dport, &ipv4_l4_mask->pdst,
|
|
sizeof(pmask->dport));
|
|
if (flow_type == UDP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_DPORT_UDP);
|
|
else if (flow_type == TCP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_DPORT_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_DPORT_SCTP);
|
|
}
|
|
if (flow_type == UDP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_UDP);
|
|
else if (flow_type == TCP_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_IPPROTO_SCTP);
|
|
break;
|
|
case AH_V4_FLOW:
|
|
case ESP_V4_FLOW:
|
|
pkt->etype = cpu_to_be16(ETH_P_IP);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
if (ah_esp_mask->ip4src) {
|
|
memcpy(&pkt->ip4src, &ah_esp_hdr->ip4src,
|
|
sizeof(pkt->ip4src));
|
|
memcpy(&pmask->ip4src, &ah_esp_mask->ip4src,
|
|
sizeof(pmask->ip4src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV4);
|
|
}
|
|
if (ah_esp_mask->ip4dst) {
|
|
memcpy(&pkt->ip4dst, &ah_esp_hdr->ip4dst,
|
|
sizeof(pkt->ip4dst));
|
|
memcpy(&pmask->ip4dst, &ah_esp_mask->ip4dst,
|
|
sizeof(pmask->ip4dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV4);
|
|
}
|
|
if (ah_esp_mask->tos) {
|
|
pkt->tos = ah_esp_hdr->tos;
|
|
pmask->tos = ah_esp_mask->tos;
|
|
req->features |= BIT_ULL(NPC_TOS);
|
|
}
|
|
|
|
/* NPC profile doesn't extract AH/ESP header fields */
|
|
if (ah_esp_mask->spi & ah_esp_hdr->spi)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (flow_type == AH_V4_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_AH);
|
|
else
|
|
req->features |= BIT_ULL(NPC_IPPROTO_ESP);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_prepare_ipv6_flow(struct ethtool_rx_flow_spec *fsp,
|
|
struct npc_install_flow_req *req,
|
|
u32 flow_type)
|
|
{
|
|
struct ethtool_usrip6_spec *ipv6_usr_mask = &fsp->m_u.usr_ip6_spec;
|
|
struct ethtool_usrip6_spec *ipv6_usr_hdr = &fsp->h_u.usr_ip6_spec;
|
|
struct ethtool_tcpip6_spec *ipv6_l4_mask = &fsp->m_u.tcp_ip6_spec;
|
|
struct ethtool_tcpip6_spec *ipv6_l4_hdr = &fsp->h_u.tcp_ip6_spec;
|
|
struct ethtool_ah_espip6_spec *ah_esp_hdr = &fsp->h_u.ah_ip6_spec;
|
|
struct ethtool_ah_espip6_spec *ah_esp_mask = &fsp->m_u.ah_ip6_spec;
|
|
struct flow_msg *pmask = &req->mask;
|
|
struct flow_msg *pkt = &req->packet;
|
|
|
|
switch (flow_type) {
|
|
case IPV6_USER_FLOW:
|
|
if (!ipv6_addr_any((struct in6_addr *)ipv6_usr_mask->ip6src)) {
|
|
memcpy(&pkt->ip6src, &ipv6_usr_hdr->ip6src,
|
|
sizeof(pkt->ip6src));
|
|
memcpy(&pmask->ip6src, &ipv6_usr_mask->ip6src,
|
|
sizeof(pmask->ip6src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV6);
|
|
}
|
|
if (!ipv6_addr_any((struct in6_addr *)ipv6_usr_mask->ip6dst)) {
|
|
memcpy(&pkt->ip6dst, &ipv6_usr_hdr->ip6dst,
|
|
sizeof(pkt->ip6dst));
|
|
memcpy(&pmask->ip6dst, &ipv6_usr_mask->ip6dst,
|
|
sizeof(pmask->ip6dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV6);
|
|
}
|
|
if (ipv6_usr_hdr->l4_proto == IPPROTO_FRAGMENT) {
|
|
pkt->next_header = ipv6_usr_hdr->l4_proto;
|
|
pmask->next_header = ipv6_usr_mask->l4_proto;
|
|
req->features |= BIT_ULL(NPC_IPFRAG_IPV6);
|
|
}
|
|
pkt->etype = cpu_to_be16(ETH_P_IPV6);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
case SCTP_V6_FLOW:
|
|
pkt->etype = cpu_to_be16(ETH_P_IPV6);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
if (!ipv6_addr_any((struct in6_addr *)ipv6_l4_mask->ip6src)) {
|
|
memcpy(&pkt->ip6src, &ipv6_l4_hdr->ip6src,
|
|
sizeof(pkt->ip6src));
|
|
memcpy(&pmask->ip6src, &ipv6_l4_mask->ip6src,
|
|
sizeof(pmask->ip6src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV6);
|
|
}
|
|
if (!ipv6_addr_any((struct in6_addr *)ipv6_l4_mask->ip6dst)) {
|
|
memcpy(&pkt->ip6dst, &ipv6_l4_hdr->ip6dst,
|
|
sizeof(pkt->ip6dst));
|
|
memcpy(&pmask->ip6dst, &ipv6_l4_mask->ip6dst,
|
|
sizeof(pmask->ip6dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV6);
|
|
}
|
|
if (ipv6_l4_mask->psrc) {
|
|
memcpy(&pkt->sport, &ipv6_l4_hdr->psrc,
|
|
sizeof(pkt->sport));
|
|
memcpy(&pmask->sport, &ipv6_l4_mask->psrc,
|
|
sizeof(pmask->sport));
|
|
if (flow_type == UDP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_SPORT_UDP);
|
|
else if (flow_type == TCP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_SPORT_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_SPORT_SCTP);
|
|
}
|
|
if (ipv6_l4_mask->pdst) {
|
|
memcpy(&pkt->dport, &ipv6_l4_hdr->pdst,
|
|
sizeof(pkt->dport));
|
|
memcpy(&pmask->dport, &ipv6_l4_mask->pdst,
|
|
sizeof(pmask->dport));
|
|
if (flow_type == UDP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_DPORT_UDP);
|
|
else if (flow_type == TCP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_DPORT_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_DPORT_SCTP);
|
|
}
|
|
if (flow_type == UDP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_UDP);
|
|
else if (flow_type == TCP_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_TCP);
|
|
else
|
|
req->features |= BIT_ULL(NPC_IPPROTO_SCTP);
|
|
break;
|
|
case AH_V6_FLOW:
|
|
case ESP_V6_FLOW:
|
|
pkt->etype = cpu_to_be16(ETH_P_IPV6);
|
|
pmask->etype = cpu_to_be16(0xFFFF);
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
if (!ipv6_addr_any((struct in6_addr *)ah_esp_hdr->ip6src)) {
|
|
memcpy(&pkt->ip6src, &ah_esp_hdr->ip6src,
|
|
sizeof(pkt->ip6src));
|
|
memcpy(&pmask->ip6src, &ah_esp_mask->ip6src,
|
|
sizeof(pmask->ip6src));
|
|
req->features |= BIT_ULL(NPC_SIP_IPV6);
|
|
}
|
|
if (!ipv6_addr_any((struct in6_addr *)ah_esp_hdr->ip6dst)) {
|
|
memcpy(&pkt->ip6dst, &ah_esp_hdr->ip6dst,
|
|
sizeof(pkt->ip6dst));
|
|
memcpy(&pmask->ip6dst, &ah_esp_mask->ip6dst,
|
|
sizeof(pmask->ip6dst));
|
|
req->features |= BIT_ULL(NPC_DIP_IPV6);
|
|
}
|
|
|
|
/* NPC profile doesn't extract AH/ESP header fields */
|
|
if ((ah_esp_mask->spi & ah_esp_hdr->spi) ||
|
|
(ah_esp_mask->tclass & ah_esp_hdr->tclass))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (flow_type == AH_V6_FLOW)
|
|
req->features |= BIT_ULL(NPC_IPPROTO_AH);
|
|
else
|
|
req->features |= BIT_ULL(NPC_IPPROTO_ESP);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_prepare_flow_request(struct ethtool_rx_flow_spec *fsp,
|
|
struct npc_install_flow_req *req)
|
|
{
|
|
struct ethhdr *eth_mask = &fsp->m_u.ether_spec;
|
|
struct ethhdr *eth_hdr = &fsp->h_u.ether_spec;
|
|
struct flow_msg *pmask = &req->mask;
|
|
struct flow_msg *pkt = &req->packet;
|
|
u32 flow_type;
|
|
int ret;
|
|
|
|
flow_type = fsp->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
|
|
switch (flow_type) {
|
|
/* bits not set in mask are don't care */
|
|
case ETHER_FLOW:
|
|
if (!is_zero_ether_addr(eth_mask->h_source)) {
|
|
ether_addr_copy(pkt->smac, eth_hdr->h_source);
|
|
ether_addr_copy(pmask->smac, eth_mask->h_source);
|
|
req->features |= BIT_ULL(NPC_SMAC);
|
|
}
|
|
if (!is_zero_ether_addr(eth_mask->h_dest)) {
|
|
ether_addr_copy(pkt->dmac, eth_hdr->h_dest);
|
|
ether_addr_copy(pmask->dmac, eth_mask->h_dest);
|
|
req->features |= BIT_ULL(NPC_DMAC);
|
|
}
|
|
if (eth_hdr->h_proto) {
|
|
memcpy(&pkt->etype, ð_hdr->h_proto,
|
|
sizeof(pkt->etype));
|
|
memcpy(&pmask->etype, ð_mask->h_proto,
|
|
sizeof(pmask->etype));
|
|
req->features |= BIT_ULL(NPC_ETYPE);
|
|
}
|
|
break;
|
|
case IP_USER_FLOW:
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
case SCTP_V4_FLOW:
|
|
case AH_V4_FLOW:
|
|
case ESP_V4_FLOW:
|
|
ret = otx2_prepare_ipv4_flow(fsp, req, flow_type);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case IPV6_USER_FLOW:
|
|
case TCP_V6_FLOW:
|
|
case UDP_V6_FLOW:
|
|
case SCTP_V6_FLOW:
|
|
case AH_V6_FLOW:
|
|
case ESP_V6_FLOW:
|
|
ret = otx2_prepare_ipv6_flow(fsp, req, flow_type);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (fsp->flow_type & FLOW_EXT) {
|
|
u16 vlan_etype;
|
|
|
|
if (fsp->m_ext.vlan_etype) {
|
|
/* Partial masks not supported */
|
|
if (be16_to_cpu(fsp->m_ext.vlan_etype) != 0xFFFF)
|
|
return -EINVAL;
|
|
|
|
vlan_etype = be16_to_cpu(fsp->h_ext.vlan_etype);
|
|
|
|
/* Drop rule with vlan_etype == 802.1Q
|
|
* and vlan_id == 0 is not supported
|
|
*/
|
|
if (vlan_etype == ETH_P_8021Q && !fsp->m_ext.vlan_tci &&
|
|
fsp->ring_cookie == RX_CLS_FLOW_DISC)
|
|
return -EINVAL;
|
|
|
|
/* Only ETH_P_8021Q and ETH_P_802AD types supported */
|
|
if (vlan_etype != ETH_P_8021Q &&
|
|
vlan_etype != ETH_P_8021AD)
|
|
return -EINVAL;
|
|
|
|
memcpy(&pkt->vlan_etype, &fsp->h_ext.vlan_etype,
|
|
sizeof(pkt->vlan_etype));
|
|
memcpy(&pmask->vlan_etype, &fsp->m_ext.vlan_etype,
|
|
sizeof(pmask->vlan_etype));
|
|
|
|
if (vlan_etype == ETH_P_8021Q)
|
|
req->features |= BIT_ULL(NPC_VLAN_ETYPE_CTAG);
|
|
else
|
|
req->features |= BIT_ULL(NPC_VLAN_ETYPE_STAG);
|
|
}
|
|
|
|
if (fsp->m_ext.vlan_tci) {
|
|
memcpy(&pkt->vlan_tci, &fsp->h_ext.vlan_tci,
|
|
sizeof(pkt->vlan_tci));
|
|
memcpy(&pmask->vlan_tci, &fsp->m_ext.vlan_tci,
|
|
sizeof(pmask->vlan_tci));
|
|
req->features |= BIT_ULL(NPC_OUTER_VID);
|
|
}
|
|
|
|
if (fsp->m_ext.data[1]) {
|
|
if (flow_type == IP_USER_FLOW) {
|
|
if (be32_to_cpu(fsp->h_ext.data[1]) != IPV4_FLAG_MORE)
|
|
return -EINVAL;
|
|
|
|
pkt->ip_flag = be32_to_cpu(fsp->h_ext.data[1]);
|
|
pmask->ip_flag = be32_to_cpu(fsp->m_ext.data[1]);
|
|
req->features |= BIT_ULL(NPC_IPFRAG_IPV4);
|
|
} else if (fsp->h_ext.data[1] ==
|
|
cpu_to_be32(OTX2_DEFAULT_ACTION)) {
|
|
/* Not Drop/Direct to queue but use action
|
|
* in default entry
|
|
*/
|
|
req->op = NIX_RX_ACTION_DEFAULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fsp->flow_type & FLOW_MAC_EXT &&
|
|
!is_zero_ether_addr(fsp->m_ext.h_dest)) {
|
|
ether_addr_copy(pkt->dmac, fsp->h_ext.h_dest);
|
|
ether_addr_copy(pmask->dmac, fsp->m_ext.h_dest);
|
|
req->features |= BIT_ULL(NPC_DMAC);
|
|
}
|
|
|
|
if (!req->features)
|
|
return -EOPNOTSUPP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_is_flow_rule_dmacfilter(struct otx2_nic *pfvf,
|
|
struct ethtool_rx_flow_spec *fsp)
|
|
{
|
|
struct ethhdr *eth_mask = &fsp->m_u.ether_spec;
|
|
struct ethhdr *eth_hdr = &fsp->h_u.ether_spec;
|
|
u64 ring_cookie = fsp->ring_cookie;
|
|
u32 flow_type;
|
|
|
|
if (!(pfvf->flags & OTX2_FLAG_DMACFLTR_SUPPORT))
|
|
return false;
|
|
|
|
flow_type = fsp->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS);
|
|
|
|
/* CGX/RPM block dmac filtering configured for white listing
|
|
* check for action other than DROP
|
|
*/
|
|
if (flow_type == ETHER_FLOW && ring_cookie != RX_CLS_FLOW_DISC &&
|
|
!ethtool_get_flow_spec_ring_vf(ring_cookie)) {
|
|
if (is_zero_ether_addr(eth_mask->h_dest) &&
|
|
is_valid_ether_addr(eth_hdr->h_dest))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int otx2_add_flow_msg(struct otx2_nic *pfvf, struct otx2_flow *flow)
|
|
{
|
|
u64 ring_cookie = flow->flow_spec.ring_cookie;
|
|
#ifdef CONFIG_DCB
|
|
int vlan_prio, qidx, pfc_rule = 0;
|
|
#endif
|
|
struct npc_install_flow_req *req;
|
|
int err, vf = 0;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_install_flow(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = otx2_prepare_flow_request(&flow->flow_spec, req);
|
|
if (err) {
|
|
/* free the allocated msg above */
|
|
otx2_mbox_reset(&pfvf->mbox.mbox, 0);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
req->entry = flow->entry;
|
|
req->intf = NIX_INTF_RX;
|
|
req->set_cntr = 1;
|
|
req->channel = pfvf->hw.rx_chan_base;
|
|
if (ring_cookie == RX_CLS_FLOW_DISC) {
|
|
req->op = NIX_RX_ACTIONOP_DROP;
|
|
} else {
|
|
/* change to unicast only if action of default entry is not
|
|
* requested by user
|
|
*/
|
|
if (flow->flow_spec.flow_type & FLOW_RSS) {
|
|
req->op = NIX_RX_ACTIONOP_RSS;
|
|
req->index = flow->rss_ctx_id;
|
|
req->flow_key_alg = pfvf->hw.flowkey_alg_idx;
|
|
} else {
|
|
req->op = NIX_RX_ACTIONOP_UCAST;
|
|
req->index = ethtool_get_flow_spec_ring(ring_cookie);
|
|
}
|
|
vf = ethtool_get_flow_spec_ring_vf(ring_cookie);
|
|
if (vf > pci_num_vf(pfvf->pdev)) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_DCB
|
|
/* Identify PFC rule if PFC enabled and ntuple rule is vlan */
|
|
if (!vf && (req->features & BIT_ULL(NPC_OUTER_VID)) &&
|
|
pfvf->pfc_en && req->op != NIX_RX_ACTIONOP_RSS) {
|
|
vlan_prio = ntohs(req->packet.vlan_tci) &
|
|
ntohs(req->mask.vlan_tci);
|
|
|
|
/* Get the priority */
|
|
vlan_prio >>= 13;
|
|
flow->rule_type |= PFC_FLOWCTRL_RULE;
|
|
/* Check if PFC enabled for this priority */
|
|
if (pfvf->pfc_en & BIT(vlan_prio)) {
|
|
pfc_rule = true;
|
|
qidx = req->index;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ethtool ring_cookie has (VF + 1) for VF */
|
|
if (vf) {
|
|
req->vf = vf;
|
|
flow->is_vf = true;
|
|
flow->vf = vf;
|
|
}
|
|
|
|
/* Send message to AF */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
|
|
#ifdef CONFIG_DCB
|
|
if (!err && pfc_rule)
|
|
otx2_update_bpid_in_rqctx(pfvf, vlan_prio, qidx, true);
|
|
#endif
|
|
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
static int otx2_add_flow_with_pfmac(struct otx2_nic *pfvf,
|
|
struct otx2_flow *flow)
|
|
{
|
|
struct otx2_flow *pf_mac;
|
|
struct ethhdr *eth_hdr;
|
|
|
|
pf_mac = kzalloc(sizeof(*pf_mac), GFP_KERNEL);
|
|
if (!pf_mac)
|
|
return -ENOMEM;
|
|
|
|
pf_mac->entry = 0;
|
|
pf_mac->rule_type |= DMAC_FILTER_RULE;
|
|
pf_mac->location = pfvf->flow_cfg->max_flows;
|
|
memcpy(&pf_mac->flow_spec, &flow->flow_spec,
|
|
sizeof(struct ethtool_rx_flow_spec));
|
|
pf_mac->flow_spec.location = pf_mac->location;
|
|
|
|
/* Copy PF mac address */
|
|
eth_hdr = &pf_mac->flow_spec.h_u.ether_spec;
|
|
ether_addr_copy(eth_hdr->h_dest, pfvf->netdev->dev_addr);
|
|
|
|
/* Install DMAC filter with PF mac address */
|
|
otx2_dmacflt_add(pfvf, eth_hdr->h_dest, 0);
|
|
|
|
otx2_add_flow_to_list(pfvf, pf_mac);
|
|
pfvf->flow_cfg->nr_flows++;
|
|
set_bit(0, pfvf->flow_cfg->dmacflt_bmap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_add_flow(struct otx2_nic *pfvf, struct ethtool_rxnfc *nfc)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct ethtool_rx_flow_spec *fsp = &nfc->fs;
|
|
struct otx2_flow *flow;
|
|
struct ethhdr *eth_hdr;
|
|
bool new = false;
|
|
int err = 0;
|
|
u64 vf_num;
|
|
u32 ring;
|
|
|
|
if (!flow_cfg->max_flows) {
|
|
netdev_err(pfvf->netdev,
|
|
"Ntuple rule count is 0, allocate and retry\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
|
|
if (!(pfvf->flags & OTX2_FLAG_NTUPLE_SUPPORT))
|
|
return -ENOMEM;
|
|
|
|
/* Number of queues on a VF can be greater or less than
|
|
* the PF's queue. Hence no need to check for the
|
|
* queue count. Hence no need to check queue count if PF
|
|
* is installing for its VF. Below is the expected vf_num value
|
|
* based on the ethtool commands.
|
|
*
|
|
* e.g.
|
|
* 1. ethtool -U <netdev> ... action -1 ==> vf_num:255
|
|
* 2. ethtool -U <netdev> ... action <queue_num> ==> vf_num:0
|
|
* 3. ethtool -U <netdev> ... vf <vf_idx> queue <queue_num> ==>
|
|
* vf_num:vf_idx+1
|
|
*/
|
|
vf_num = ethtool_get_flow_spec_ring_vf(fsp->ring_cookie);
|
|
if (!is_otx2_vf(pfvf->pcifunc) && !vf_num &&
|
|
ring >= pfvf->hw.rx_queues && fsp->ring_cookie != RX_CLS_FLOW_DISC)
|
|
return -EINVAL;
|
|
|
|
if (fsp->location >= otx2_get_maxflows(flow_cfg))
|
|
return -EINVAL;
|
|
|
|
flow = otx2_find_flow(pfvf, fsp->location);
|
|
if (!flow) {
|
|
flow = kzalloc(sizeof(*flow), GFP_KERNEL);
|
|
if (!flow)
|
|
return -ENOMEM;
|
|
flow->location = fsp->location;
|
|
flow->entry = flow_cfg->flow_ent[flow->location];
|
|
new = true;
|
|
}
|
|
/* struct copy */
|
|
flow->flow_spec = *fsp;
|
|
|
|
if (fsp->flow_type & FLOW_RSS)
|
|
flow->rss_ctx_id = nfc->rss_context;
|
|
|
|
if (otx2_is_flow_rule_dmacfilter(pfvf, &flow->flow_spec)) {
|
|
eth_hdr = &flow->flow_spec.h_u.ether_spec;
|
|
|
|
/* Sync dmac filter table with updated fields */
|
|
if (flow->rule_type & DMAC_FILTER_RULE)
|
|
return otx2_dmacflt_update(pfvf, eth_hdr->h_dest,
|
|
flow->entry);
|
|
|
|
if (bitmap_full(flow_cfg->dmacflt_bmap,
|
|
flow_cfg->dmacflt_max_flows)) {
|
|
netdev_warn(pfvf->netdev,
|
|
"Can't insert the rule %d as max allowed dmac filters are %d\n",
|
|
flow->location +
|
|
flow_cfg->dmacflt_max_flows,
|
|
flow_cfg->dmacflt_max_flows);
|
|
err = -EINVAL;
|
|
if (new)
|
|
kfree(flow);
|
|
return err;
|
|
}
|
|
|
|
/* Install PF mac address to DMAC filter list */
|
|
if (!test_bit(0, flow_cfg->dmacflt_bmap))
|
|
otx2_add_flow_with_pfmac(pfvf, flow);
|
|
|
|
flow->rule_type |= DMAC_FILTER_RULE;
|
|
flow->entry = find_first_zero_bit(flow_cfg->dmacflt_bmap,
|
|
flow_cfg->dmacflt_max_flows);
|
|
fsp->location = flow_cfg->max_flows + flow->entry;
|
|
flow->flow_spec.location = fsp->location;
|
|
flow->location = fsp->location;
|
|
|
|
set_bit(flow->entry, flow_cfg->dmacflt_bmap);
|
|
otx2_dmacflt_add(pfvf, eth_hdr->h_dest, flow->entry);
|
|
|
|
} else {
|
|
if (flow->location >= pfvf->flow_cfg->max_flows) {
|
|
netdev_warn(pfvf->netdev,
|
|
"Can't insert non dmac ntuple rule at %d, allowed range %d-0\n",
|
|
flow->location,
|
|
flow_cfg->max_flows - 1);
|
|
err = -EINVAL;
|
|
} else {
|
|
err = otx2_add_flow_msg(pfvf, flow);
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
if (err == MBOX_MSG_INVALID)
|
|
err = -EINVAL;
|
|
if (new)
|
|
kfree(flow);
|
|
return err;
|
|
}
|
|
|
|
/* add the new flow installed to list */
|
|
if (new) {
|
|
otx2_add_flow_to_list(pfvf, flow);
|
|
flow_cfg->nr_flows++;
|
|
}
|
|
|
|
if (flow->is_vf)
|
|
netdev_info(pfvf->netdev,
|
|
"Make sure that VF's queue number is within its queue limit\n");
|
|
return 0;
|
|
}
|
|
|
|
static int otx2_remove_flow_msg(struct otx2_nic *pfvf, u16 entry, bool all)
|
|
{
|
|
struct npc_delete_flow_req *req;
|
|
int err;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_delete_flow(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->entry = entry;
|
|
if (all)
|
|
req->all = 1;
|
|
|
|
/* Send message to AF */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
static void otx2_update_rem_pfmac(struct otx2_nic *pfvf, int req)
|
|
{
|
|
struct otx2_flow *iter;
|
|
struct ethhdr *eth_hdr;
|
|
bool found = false;
|
|
|
|
list_for_each_entry(iter, &pfvf->flow_cfg->flow_list, list) {
|
|
if ((iter->rule_type & DMAC_FILTER_RULE) && iter->entry == 0) {
|
|
eth_hdr = &iter->flow_spec.h_u.ether_spec;
|
|
if (req == DMAC_ADDR_DEL) {
|
|
otx2_dmacflt_remove(pfvf, eth_hdr->h_dest,
|
|
0);
|
|
clear_bit(0, pfvf->flow_cfg->dmacflt_bmap);
|
|
found = true;
|
|
} else {
|
|
ether_addr_copy(eth_hdr->h_dest,
|
|
pfvf->netdev->dev_addr);
|
|
|
|
otx2_dmacflt_update(pfvf, eth_hdr->h_dest, 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
list_del(&iter->list);
|
|
kfree(iter);
|
|
pfvf->flow_cfg->nr_flows--;
|
|
}
|
|
}
|
|
|
|
int otx2_remove_flow(struct otx2_nic *pfvf, u32 location)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct otx2_flow *flow;
|
|
int err;
|
|
|
|
if (location >= otx2_get_maxflows(flow_cfg))
|
|
return -EINVAL;
|
|
|
|
flow = otx2_find_flow(pfvf, location);
|
|
if (!flow)
|
|
return -ENOENT;
|
|
|
|
if (flow->rule_type & DMAC_FILTER_RULE) {
|
|
struct ethhdr *eth_hdr = &flow->flow_spec.h_u.ether_spec;
|
|
|
|
/* user not allowed to remove dmac filter with interface mac */
|
|
if (ether_addr_equal(pfvf->netdev->dev_addr, eth_hdr->h_dest))
|
|
return -EPERM;
|
|
|
|
err = otx2_dmacflt_remove(pfvf, eth_hdr->h_dest,
|
|
flow->entry);
|
|
clear_bit(flow->entry, flow_cfg->dmacflt_bmap);
|
|
/* If all dmac filters are removed delete macfilter with
|
|
* interface mac address and configure CGX/RPM block in
|
|
* promiscuous mode
|
|
*/
|
|
if (bitmap_weight(flow_cfg->dmacflt_bmap,
|
|
flow_cfg->dmacflt_max_flows) == 1)
|
|
otx2_update_rem_pfmac(pfvf, DMAC_ADDR_DEL);
|
|
} else {
|
|
#ifdef CONFIG_DCB
|
|
if (flow->rule_type & PFC_FLOWCTRL_RULE)
|
|
otx2_update_bpid_in_rqctx(pfvf, 0,
|
|
flow->flow_spec.ring_cookie,
|
|
false);
|
|
#endif
|
|
|
|
err = otx2_remove_flow_msg(pfvf, flow->entry, false);
|
|
}
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
list_del(&flow->list);
|
|
kfree(flow);
|
|
flow_cfg->nr_flows--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void otx2_rss_ctx_flow_del(struct otx2_nic *pfvf, int ctx_id)
|
|
{
|
|
struct otx2_flow *flow, *tmp;
|
|
int err;
|
|
|
|
list_for_each_entry_safe(flow, tmp, &pfvf->flow_cfg->flow_list, list) {
|
|
if (flow->rss_ctx_id != ctx_id)
|
|
continue;
|
|
err = otx2_remove_flow(pfvf, flow->location);
|
|
if (err)
|
|
netdev_warn(pfvf->netdev,
|
|
"Can't delete the rule %d associated with this rss group err:%d",
|
|
flow->location, err);
|
|
}
|
|
}
|
|
|
|
int otx2_destroy_ntuple_flows(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_delete_flow_req *req;
|
|
struct otx2_flow *iter, *tmp;
|
|
int err;
|
|
|
|
if (!(pfvf->flags & OTX2_FLAG_NTUPLE_SUPPORT))
|
|
return 0;
|
|
|
|
if (!flow_cfg->max_flows)
|
|
return 0;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_delete_flow(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->start = flow_cfg->flow_ent[0];
|
|
req->end = flow_cfg->flow_ent[flow_cfg->max_flows - 1];
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
|
|
list_for_each_entry_safe(iter, tmp, &flow_cfg->flow_list, list) {
|
|
list_del(&iter->list);
|
|
kfree(iter);
|
|
flow_cfg->nr_flows--;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int otx2_destroy_mcam_flows(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_mcam_free_entry_req *req;
|
|
struct otx2_flow *iter, *tmp;
|
|
int err;
|
|
|
|
if (!(pfvf->flags & OTX2_FLAG_MCAM_ENTRIES_ALLOC))
|
|
return 0;
|
|
|
|
/* remove all flows */
|
|
err = otx2_remove_flow_msg(pfvf, 0, true);
|
|
if (err)
|
|
return err;
|
|
|
|
list_for_each_entry_safe(iter, tmp, &flow_cfg->flow_list, list) {
|
|
list_del(&iter->list);
|
|
kfree(iter);
|
|
flow_cfg->nr_flows--;
|
|
}
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_mcam_free_entry(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->all = 1;
|
|
/* Send message to AF to free MCAM entries */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
if (err) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
pfvf->flags &= ~OTX2_FLAG_MCAM_ENTRIES_ALLOC;
|
|
flow_cfg->max_flows = 0;
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int otx2_install_rxvlan_offload_flow(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_install_flow_req *req;
|
|
int err;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_install_flow(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->entry = flow_cfg->def_ent[flow_cfg->rx_vlan_offset];
|
|
req->intf = NIX_INTF_RX;
|
|
ether_addr_copy(req->packet.dmac, pfvf->netdev->dev_addr);
|
|
eth_broadcast_addr((u8 *)&req->mask.dmac);
|
|
req->channel = pfvf->hw.rx_chan_base;
|
|
req->op = NIX_RX_ACTION_DEFAULT;
|
|
req->features = BIT_ULL(NPC_OUTER_VID) | BIT_ULL(NPC_DMAC);
|
|
req->vtag0_valid = true;
|
|
req->vtag0_type = NIX_AF_LFX_RX_VTAG_TYPE0;
|
|
|
|
/* Send message to AF */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
static int otx2_delete_rxvlan_offload_flow(struct otx2_nic *pfvf)
|
|
{
|
|
struct otx2_flow_config *flow_cfg = pfvf->flow_cfg;
|
|
struct npc_delete_flow_req *req;
|
|
int err;
|
|
|
|
mutex_lock(&pfvf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_npc_delete_flow(&pfvf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->entry = flow_cfg->def_ent[flow_cfg->rx_vlan_offset];
|
|
/* Send message to AF */
|
|
err = otx2_sync_mbox_msg(&pfvf->mbox);
|
|
mutex_unlock(&pfvf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
int otx2_enable_rxvlan(struct otx2_nic *pf, bool enable)
|
|
{
|
|
struct nix_vtag_config *req;
|
|
struct mbox_msghdr *rsp_hdr;
|
|
int err;
|
|
|
|
/* Dont have enough mcam entries */
|
|
if (!(pf->flags & OTX2_FLAG_RX_VLAN_SUPPORT))
|
|
return -ENOMEM;
|
|
|
|
if (enable) {
|
|
err = otx2_install_rxvlan_offload_flow(pf);
|
|
if (err)
|
|
return err;
|
|
} else {
|
|
err = otx2_delete_rxvlan_offload_flow(pf);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
mutex_lock(&pf->mbox.lock);
|
|
req = otx2_mbox_alloc_msg_nix_vtag_cfg(&pf->mbox);
|
|
if (!req) {
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* config strip, capture and size */
|
|
req->vtag_size = VTAGSIZE_T4;
|
|
req->cfg_type = 1; /* rx vlan cfg */
|
|
req->rx.vtag_type = NIX_AF_LFX_RX_VTAG_TYPE0;
|
|
req->rx.strip_vtag = enable;
|
|
req->rx.capture_vtag = enable;
|
|
|
|
err = otx2_sync_mbox_msg(&pf->mbox);
|
|
if (err) {
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return err;
|
|
}
|
|
|
|
rsp_hdr = otx2_mbox_get_rsp(&pf->mbox.mbox, 0, &req->hdr);
|
|
if (IS_ERR(rsp_hdr)) {
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return PTR_ERR(rsp_hdr);
|
|
}
|
|
|
|
mutex_unlock(&pf->mbox.lock);
|
|
return rsp_hdr->rc;
|
|
}
|
|
|
|
void otx2_dmacflt_reinstall_flows(struct otx2_nic *pf)
|
|
{
|
|
struct otx2_flow *iter;
|
|
struct ethhdr *eth_hdr;
|
|
|
|
list_for_each_entry(iter, &pf->flow_cfg->flow_list, list) {
|
|
if (iter->rule_type & DMAC_FILTER_RULE) {
|
|
eth_hdr = &iter->flow_spec.h_u.ether_spec;
|
|
otx2_dmacflt_add(pf, eth_hdr->h_dest,
|
|
iter->entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
void otx2_dmacflt_update_pfmac_flow(struct otx2_nic *pfvf)
|
|
{
|
|
otx2_update_rem_pfmac(pfvf, DMAC_ADDR_UPDATE);
|
|
}
|