OpenCloudOS-Kernel/drivers/thirdparty/bnxt/bnxt_udcc.c

1132 lines
31 KiB
C

/* Broadcom NetXtreme-C/E network driver.
*
* Copyright (c) 2023 Broadcom Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*/
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/bitmap.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/hashtable.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <net/inet_hashtables.h>
#include <net/inet6_hashtables.h>
#include "bnxt_compat.h"
#include "bnxt_hsi.h"
#include "bnxt.h"
#include "bnxt_hwrm.h"
#include "ulp_generic_flow_offload.h"
#include "ulp_udcc.h"
#include "bnxt_tf_ulp.h"
#include "bnxt_udcc.h"
#include "bnxt_debugfs.h"
#include "bnxt_nic_flow.h"
#if defined(CONFIG_BNXT_FLOWER_OFFLOAD)
static int bnxt_tf_ulp_flow_delete(struct bnxt *bp, struct bnxt_udcc_session_entry *entry);
static int bnxt_hwrm_udcc_qcfg(struct bnxt *bp)
{
struct hwrm_udcc_qcfg_output *resp;
struct hwrm_udcc_qcfg_input *req;
int rc;
rc = hwrm_req_init(bp, req, HWRM_UDCC_QCFG);
if (rc)
return rc;
req->target_id = cpu_to_le16(0xffff);
resp = hwrm_req_hold(bp, req);
rc = hwrm_req_send(bp, req);
if (rc)
goto udcc_qcfg_exit;
bp->udcc_info->mode = resp->udcc_mode;
netdev_info(bp->dev, "UDCC mode: %s!!!\n",
bp->udcc_info->mode ? "Enabled" : "Disabled");
udcc_qcfg_exit:
hwrm_req_drop(bp, req);
return rc;
}
int bnxt_alloc_udcc_info(struct bnxt *bp)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
struct hwrm_udcc_qcaps_output *resp;
struct hwrm_func_qcaps_input *req;
int rc;
if (BNXT_VF(bp) || !BNXT_UDCC_CAP(bp))
return 0;
if (udcc)
return 0;
rc = hwrm_req_init(bp, req, HWRM_UDCC_QCAPS);
if (rc)
return rc;
req->fid = cpu_to_le16(0xffff);
resp = hwrm_req_hold(bp, req);
rc = hwrm_req_send(bp, req);
if (rc)
goto exit;
udcc = kzalloc(sizeof(*udcc), GFP_KERNEL);
if (!udcc)
goto exit;
udcc->max_sessions = le16_to_cpu(resp->max_sessions);
udcc->max_comp_cfg_xfer = le16_to_cpu(resp->max_comp_cfg_xfer);
udcc->max_comp_data_xfer = le16_to_cpu(resp->max_comp_data_xfer);
udcc->session_type = resp->session_type;
mutex_init(&udcc->session_db_lock);
bp->udcc_info = udcc;
netdev_info(bp->dev, "UDCC capability: %s max %d sessions\n",
udcc->session_type ? "per-QP" : "per-DestIP",
udcc->max_sessions);
rc = bnxt_hwrm_udcc_qcfg(bp);
if (rc) {
kfree(udcc);
goto exit;
}
netdev_dbg(bp->dev, "%s(): udcc_info initialized!\n", __func__);
exit:
hwrm_req_drop(bp, req);
return rc;
}
int bnxt_hwrm_udcc_session_query(struct bnxt *bp, u32 session_id,
struct hwrm_udcc_session_query_output *resp_out)
{
struct hwrm_udcc_session_query_input *req;
struct hwrm_udcc_session_query_output *resp;
int rc;
rc = hwrm_req_init(bp, req, HWRM_UDCC_SESSION_QUERY);
if (rc)
return rc;
req->session_id = cpu_to_le16(session_id);
resp = hwrm_req_hold(bp, req);
rc = hwrm_req_send(bp, req);
if (rc)
goto udcc_query_exit;
memcpy(resp_out, resp, sizeof(struct hwrm_udcc_session_query_output));
udcc_query_exit:
hwrm_req_drop(bp, req);
return rc;
}
static int bnxt_hwrm_udcc_session_qcfg(struct bnxt *bp, struct bnxt_udcc_session_entry *entry)
{
struct hwrm_udcc_session_qcfg_output *resp;
struct hwrm_udcc_session_qcfg_input *req;
int rc;
rc = hwrm_req_init(bp, req, HWRM_UDCC_SESSION_QCFG);
if (rc)
return rc;
req->session_id = cpu_to_le16(entry->session_id);
resp = hwrm_req_hold(bp, req);
rc = hwrm_req_send(bp, req);
if (rc)
goto udcc_qcfg_exit;
ether_addr_copy(entry->dest_mac, resp->dest_mac);
ether_addr_copy(entry->src_mac, resp->src_mac);
memcpy(entry->dst_ip.s6_addr32, resp->dest_ip, sizeof(resp->dest_ip));
entry->dest_qp_num = le32_to_cpu(resp->dest_qp_num);
entry->src_qp_num = le32_to_cpu(resp->src_qp_num);
udcc_qcfg_exit:
hwrm_req_drop(bp, req);
return rc;
}
static int bnxt_hwrm_udcc_session_cfg(struct bnxt *bp, struct bnxt_udcc_session_entry *entry)
{
struct hwrm_udcc_session_cfg_input *req;
int rc = 0;
rc = hwrm_req_init(bp, req, HWRM_UDCC_SESSION_CFG);
if (rc)
return rc;
req->session_id = cpu_to_le16(entry->session_id);
if (entry->state != UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED) {
req->enables = cpu_to_le32(UDCC_SESSION_CFG_REQ_ENABLES_SESSION_STATE);
goto session_state;
}
req->enables = cpu_to_le32(UDCC_SESSION_CFG_REQ_ENABLES_SESSION_STATE |
UDCC_SESSION_CFG_REQ_ENABLES_DEST_MAC |
UDCC_SESSION_CFG_REQ_ENABLES_SRC_MAC |
UDCC_SESSION_CFG_REQ_ENABLES_TX_STATS_RECORD |
UDCC_SESSION_CFG_REQ_ENABLES_RX_STATS_RECORD);
if (is_valid_ether_addr(entry->dst_mac_mod) &&
is_valid_ether_addr(entry->src_mac_mod)) {
ether_addr_copy(req->dest_mac, entry->dst_mac_mod);
ether_addr_copy(req->src_mac, entry->src_mac_mod);
} else {
ether_addr_copy(req->dest_mac, entry->dest_mac);
ether_addr_copy(req->src_mac, entry->src_mac);
}
req->tx_stats_record = cpu_to_le32((u32)entry->tx_counter_hndl);
req->rx_stats_record = cpu_to_le32((u32)entry->rx_counter_hndl);
session_state:
req->session_state = entry->state;
return hwrm_req_send(bp, req);
}
#define ACT_OFFS_MASK 0x6ffffff
#define TSID_SHIFT 26
#define TSID_MASK 0x1f
/* This function converts the provided tfc action handle to the UDCC
* action handle required by the firmware. The action handle consists
* of an 8 byte offset in the lower 26 bits and the table scope id in
* the upper 6 bits.
*/
static int bnxt_tfc_counter_update(struct bnxt *bp, u64 *counter_hndl)
{
u64 val = 0;
u8 tsid = 0;
int rc = 0;
rc = bnxt_ulp_cntxt_tsid_get(bp->ulp_ctx, &tsid);
if (rc) {
netdev_dbg(bp->dev, "%s:Invalid tsid, cannot update counter_hndl rc=%d\n",
__func__, rc);
return rc;
}
netdev_dbg(bp->dev, "%s: counter_hndl(%llx)\n", __func__, *counter_hndl);
val = *counter_hndl;
/* 32B offset to 8B offset */
val = val << 2;
val &= ACT_OFFS_MASK;
val |= (tsid & TSID_MASK) << TSID_SHIFT;
*counter_hndl = val;
netdev_dbg(bp->dev, "%s:counter_hndl update tsid(%d) counter_hndl(%llx)\n",
__func__, tsid, *counter_hndl);
return rc;
}
static u8 bnxt_ulp_gen_l3_ipv6_addr_em_mask[] = {
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff
};
static int bnxt_udcc_flows_create_p7(struct bnxt *bp, struct bnxt_udcc_session_entry *entry)
{
struct bnxt_ulp_gen_bth_hdr bth_spec = { 0 }, bth_mask = { 0 };
struct bnxt_ulp_gen_ipv6_hdr v6_spec = { 0 }, v6_mask = { 0 };
bool per_qp_session = BNXT_UDCC_SESSION_PER_QP(bp);
struct bnxt_ulp_gen_l2_hdr_parms l2_parms = { 0 };
struct bnxt_ulp_gen_l3_hdr_parms l3_parms = { 0 };
struct bnxt_ulp_gen_l4_hdr_parms l4_parms = { 0 };
struct bnxt_ulp_gen_action_parms actions = { 0 };
struct bnxt_ulp_gen_flow_parms parms = { 0 };
/* These would normally be preset and passed to the upper layer */
/* u32 dst_qpn = cpu_to_be32(entry->dest_qp_num); */
u32 src_qpn = cpu_to_be32(entry->src_qp_num);
u32 msk_qpn = cpu_to_be32(0xffffffff);
u16 op_code = cpu_to_be16(0x81); /* RoCE CNP */
u16 op_code_mask = cpu_to_be16(0xffff);
u8 l4_proto = IPPROTO_UDP;
u8 l4_proto_mask = 0xff;
__le64 l2_filter_id = 0;
int rc;
/* the source mac from the session is the dmac of the l2 filter */
rc = bnxt_nic_flow_dmac_filter_get(bp, entry->src_mac, &l2_filter_id);
if (rc) {
netdev_warn(bp->dev, "UDCC l2 filter mac check failed rc=%d\n", rc);
return rc;
}
l2_parms.type = BNXT_ULP_GEN_L2_L2_FILTER_ID;
l2_parms.l2_filter_id = &l2_filter_id;
/* Pack the L3 Data */
v6_spec.proto6 = &l4_proto;
v6_mask.proto6 = &l4_proto_mask;
v6_spec.dip6 = NULL;
v6_mask.dip6 = NULL;
v6_spec.sip6 = entry->dst_ip.s6_addr;
v6_mask.sip6 = bnxt_ulp_gen_l3_ipv6_addr_em_mask;
l3_parms.type = BNXT_ULP_GEN_L3_IPV6;
l3_parms.v6_spec = &v6_spec;
l3_parms.v6_mask = &v6_mask;
/* Pack the L4 Data */
bth_spec.op_code = &op_code;
bth_mask.op_code = &op_code_mask;
bth_spec.dst_qpn = NULL;
bth_mask.dst_qpn = NULL;
if (per_qp_session) {
bth_spec.dst_qpn = &src_qpn;
bth_mask.dst_qpn = &msk_qpn;
}
l4_parms.type = BNXT_ULP_GEN_L4_BTH;
l4_parms.bth_spec = &bth_spec;
l4_parms.bth_mask = &bth_mask;
/* Pack the actions NIC template will use RoCE VNIC by default */
actions.enables = BNXT_ULP_GEN_ACTION_ENABLES_DROP |
BNXT_ULP_GEN_ACTION_ENABLES_COUNT;
actions.dst_fid = bp->pf.fw_fid;
parms.dir = BNXT_ULP_GEN_RX;
parms.flow_id = &entry->rx_flow_id;
parms.counter_hndl = &entry->rx_counter_hndl;
parms.l2 = &l2_parms;
parms.l3 = &l3_parms;
parms.l4 = &l4_parms;
parms.actions = &actions;
parms.priority = 2; /* must be higher priority than NIC flow CNP */
rc = bnxt_ulp_gen_flow_create(bp, bp->pf.fw_fid, &parms);
if (rc) {
netdev_warn(bp->dev, "UDCC TFC flow creation failed rc=%d\n", rc);
return rc;
}
netdev_dbg(bp->dev, "UDCC Add Rx flow for session_id: %d flow_id: %d, counter: 0x%llx\n",
entry->session_id,
entry->rx_flow_id,
entry->rx_counter_hndl);
bnxt_tfc_counter_update(bp, &entry->rx_counter_hndl);
return rc;
}
static int bnxt_udcc_rx_flow_create_v6(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
struct bnxt_ulp_gen_bth_hdr bth_spec = { 0 }, bth_mask = { 0 };
struct bnxt_ulp_gen_ipv6_hdr v6_spec = { 0 }, v6_mask = { 0 };
bool per_qp_session = BNXT_UDCC_SESSION_PER_QP(bp);
struct bnxt_ulp_gen_l2_hdr_parms l2_parms = { 0 };
struct bnxt_ulp_gen_l3_hdr_parms l3_parms = { 0 };
struct bnxt_ulp_gen_l4_hdr_parms l4_parms = { 0 };
struct bnxt_ulp_gen_action_parms actions = { 0 };
struct bnxt_ulp_gen_flow_parms parms = { 0 };
/* These would normally be preset and passed to the upper layer */
u32 src_qpn = cpu_to_be32(entry->src_qp_num);
u32 msk_qpn = cpu_to_be32(0xffffffff);
u16 op_code = cpu_to_be16(0x81); /* RoCE CNP */
u16 op_code_mask = cpu_to_be16(0xffff);
u8 l4_proto = IPPROTO_UDP;
u8 l4_proto_mask = 0xff;
int rc;
/* Pack the L2 Data - Don't fill l2_spec for now */
l2_parms.type = BNXT_ULP_GEN_L2_L2_HDR;
/* Pack the L3 Data */
v6_spec.proto6 = &l4_proto;
v6_mask.proto6 = &l4_proto_mask;
v6_spec.dip6 = NULL;
v6_mask.dip6 = NULL;
v6_spec.sip6 = entry->dst_ip.s6_addr;
v6_mask.sip6 = bnxt_ulp_gen_l3_ipv6_addr_em_mask;
l3_parms.type = BNXT_ULP_GEN_L3_IPV6;
l3_parms.v6_spec = &v6_spec;
l3_parms.v6_mask = &v6_mask;
/* Pack the L4 Data */
bth_spec.op_code = &op_code;
bth_mask.op_code = &op_code_mask;
bth_spec.dst_qpn = NULL;
bth_mask.dst_qpn = NULL;
if (per_qp_session) {
bth_spec.dst_qpn = &src_qpn;
bth_mask.dst_qpn = &msk_qpn;
}
l4_parms.type = BNXT_ULP_GEN_L4_BTH;
l4_parms.bth_spec = &bth_spec;
l4_parms.bth_mask = &bth_mask;
/* Pack the actions */
actions.enables = BNXT_ULP_GEN_ACTION_ENABLES_REDIRECT |
BNXT_ULP_GEN_ACTION_ENABLES_DROP |
BNXT_ULP_GEN_ACTION_ENABLES_COUNT;
actions.dst_fid = bp->pf.fw_fid;
parms.dir = BNXT_ULP_GEN_RX;
parms.flow_id = &entry->rx_flow_id;
parms.counter_hndl = &entry->rx_counter_hndl;
parms.l2 = &l2_parms;
parms.l3 = &l3_parms;
parms.l4 = &l4_parms;
parms.actions = &actions;
rc = bnxt_ulp_gen_flow_create(bp, bp->pf.fw_fid, &parms);
if (rc)
return rc;
netdev_dbg(bp->dev, "UDCC Add Rx flow for session_id: %d flow_id: %d, counter: 0x%llx\n",
entry->session_id,
entry->rx_flow_id,
entry->rx_counter_hndl);
return rc;
}
static int bnxt_udcc_tx_flow_create_v6(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
struct bnxt_ulp_gen_bth_hdr bth_spec = { 0 }, bth_mask = { 0 };
struct bnxt_ulp_gen_ipv6_hdr v6_spec = { 0 }, v6_mask = { 0 };
bool per_qp_session = BNXT_UDCC_SESSION_PER_QP(bp);
struct bnxt_ulp_gen_l2_hdr_parms l2_parms = { 0 };
struct bnxt_ulp_gen_l3_hdr_parms l3_parms = { 0 };
struct bnxt_ulp_gen_l4_hdr_parms l4_parms = { 0 };
struct bnxt_ulp_gen_action_parms actions = { 0 };
struct bnxt_ulp_gen_flow_parms parms = { 0 };
/* These would normally be preset and passed to the upper layer */
u32 dst_qpn = cpu_to_be32(entry->dest_qp_num);
u32 msk_qpn = cpu_to_be32(0xffffffff);
u8 l4_proto = IPPROTO_UDP;
u8 l4_proto_mask = 0xff;
int rc;
/* Pack the L2 Data - Don't fill l2_spec for now */
l2_parms.type = BNXT_ULP_GEN_L2_L2_HDR;
/* Pack the L3 Data */
v6_spec.proto6 = &l4_proto;
v6_mask.proto6 = &l4_proto_mask;
v6_spec.sip6 = NULL;
v6_mask.sip6 = NULL;
v6_spec.dip6 = entry->dst_ip.s6_addr;
v6_mask.dip6 = bnxt_ulp_gen_l3_ipv6_addr_em_mask;
l3_parms.type = BNXT_ULP_GEN_L3_IPV6;
l3_parms.v6_spec = &v6_spec;
l3_parms.v6_mask = &v6_mask;
/* Pack the L4 Data */
bth_spec.op_code = NULL;
bth_mask.op_code = NULL;
bth_spec.dst_qpn = NULL;
bth_mask.dst_qpn = NULL;
if (per_qp_session) {
bth_spec.dst_qpn = &dst_qpn;
bth_mask.dst_qpn = &msk_qpn;
}
l4_parms.type = BNXT_ULP_GEN_L4_BTH;
l4_parms.bth_spec = &bth_spec;
l4_parms.bth_mask = &bth_mask;
/* Pack the actions */
actions.enables = BNXT_ULP_GEN_ACTION_ENABLES_REDIRECT |
BNXT_ULP_GEN_ACTION_ENABLES_SET_SMAC |
BNXT_ULP_GEN_ACTION_ENABLES_SET_DMAC |
BNXT_ULP_GEN_ACTION_ENABLES_COUNT;
actions.dst_fid = bp->pf.fw_fid;
if (is_valid_ether_addr(entry->dst_mac_mod) &&
is_valid_ether_addr(entry->src_mac_mod)) {
ether_addr_copy(actions.dmac, entry->dst_mac_mod);
ether_addr_copy(actions.smac, entry->src_mac_mod);
} else {
/* PF case (non-switchdev): zero smac and dmac modify.
* Just use the smac dmac given by FW in the entry.
*/
ether_addr_copy(actions.dmac, entry->dest_mac);
ether_addr_copy(actions.smac, entry->src_mac);
}
parms.dir = BNXT_ULP_GEN_TX;
parms.flow_id = &entry->tx_flow_id;
parms.counter_hndl = &entry->tx_counter_hndl;
parms.l2 = &l2_parms;
parms.l3 = &l3_parms;
parms.l4 = &l4_parms;
parms.actions = &actions;
rc = bnxt_ulp_gen_flow_create(bp, bp->pf.fw_fid, &parms);
if (rc)
return rc;
netdev_dbg(bp->dev, "UDCC Add Tx flow for session_id: %d flow_id: %d, counter: 0x%llx\n",
entry->session_id,
entry->tx_flow_id,
entry->tx_counter_hndl);
return rc;
}
static u8 bnxt_ulp_gen_l3_ipv4_addr_em_mask[] = { 0xff, 0xff, 0xff, 0xff };
static int bnxt_udcc_rx_flow_create_v4(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
struct bnxt_ulp_gen_bth_hdr bth_spec = { 0 }, bth_mask = { 0 };
struct bnxt_ulp_gen_ipv4_hdr v4_spec = { 0 }, v4_mask = { 0 };
bool per_qp_session = BNXT_UDCC_SESSION_PER_QP(bp);
struct bnxt_ulp_gen_l2_hdr_parms l2_parms = { 0 };
struct bnxt_ulp_gen_l3_hdr_parms l3_parms = { 0 };
struct bnxt_ulp_gen_l4_hdr_parms l4_parms = { 0 };
struct bnxt_ulp_gen_action_parms actions = { 0 };
struct bnxt_ulp_gen_flow_parms parms = { 0 };
/* These would normally be preset and passed to the upper layer */
u32 src_qpn = cpu_to_be32(entry->src_qp_num);
u32 msk_qpn = cpu_to_be32(0xffffffff);
u16 op_code = cpu_to_be16(0x81); /* RoCE CNP */
u16 op_code_mask = cpu_to_be16(0xffff);
u8 l4_proto = IPPROTO_UDP;
u8 l4_proto_mask = 0xff;
int rc;
/* Pack the L2 Data - Don't fill l2_spec for now */
l2_parms.type = BNXT_ULP_GEN_L2_L2_HDR;
/* Pack the L3 Data */
v4_spec.proto = &l4_proto;
v4_mask.proto = &l4_proto_mask;
v4_spec.dip = NULL;
v4_mask.dip = NULL;
v4_spec.sip = (u32 *)&entry->dst_ip.s6_addr32[3];
v4_mask.sip = (u32 *)bnxt_ulp_gen_l3_ipv4_addr_em_mask;
l3_parms.type = BNXT_ULP_GEN_L3_IPV4;
l3_parms.v4_spec = &v4_spec;
l3_parms.v4_mask = &v4_mask;
/* Pack the L4 Data */
bth_spec.op_code = &op_code;
bth_mask.op_code = &op_code_mask;
bth_spec.dst_qpn = NULL;
bth_mask.dst_qpn = NULL;
if (per_qp_session) {
bth_spec.dst_qpn = &src_qpn;
bth_mask.dst_qpn = &msk_qpn;
}
l4_parms.type = BNXT_ULP_GEN_L4_BTH;
l4_parms.bth_spec = &bth_spec;
l4_parms.bth_mask = &bth_mask;
/* Pack the actions */
actions.enables = BNXT_ULP_GEN_ACTION_ENABLES_REDIRECT |
BNXT_ULP_GEN_ACTION_ENABLES_DROP |
BNXT_ULP_GEN_ACTION_ENABLES_COUNT;
actions.dst_fid = bp->pf.fw_fid;
parms.dir = BNXT_ULP_GEN_RX;
parms.flow_id = &entry->rx_flow_id;
parms.counter_hndl = &entry->rx_counter_hndl;
parms.l2 = &l2_parms;
parms.l3 = &l3_parms;
parms.l4 = &l4_parms;
parms.actions = &actions;
rc = bnxt_ulp_gen_flow_create(bp, bp->pf.fw_fid, &parms);
if (rc)
return rc;
netdev_dbg(bp->dev, "UDCC Add Rx flow for session_id: %d flow_id: %d, counter: 0x%llx\n",
entry->session_id,
entry->rx_flow_id,
entry->rx_counter_hndl);
return rc;
}
static int bnxt_udcc_tx_flow_create_v4(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
struct bnxt_ulp_gen_bth_hdr bth_spec = { 0 }, bth_mask = { 0 };
struct bnxt_ulp_gen_ipv4_hdr v4_spec = { 0 }, v4_mask = { 0 };
bool per_qp_session = BNXT_UDCC_SESSION_PER_QP(bp);
struct bnxt_ulp_gen_l2_hdr_parms l2_parms = { 0 };
struct bnxt_ulp_gen_l3_hdr_parms l3_parms = { 0 };
struct bnxt_ulp_gen_l4_hdr_parms l4_parms = { 0 };
struct bnxt_ulp_gen_action_parms actions = { 0 };
struct bnxt_ulp_gen_flow_parms parms = { 0 };
/* These would normally be preset and passed to the upper layer */
u32 dst_qpn = cpu_to_be32(entry->dest_qp_num);
u32 msk_qpn = cpu_to_be32(0xffffffff);
u8 l4_proto = IPPROTO_UDP;
u8 l4_proto_mask = 0xff;
int rc;
/* Pack the L2 Data - Don't fill l2_spec for now */
l2_parms.type = BNXT_ULP_GEN_L2_L2_HDR;
/* Pack the L3 Data */
v4_spec.proto = &l4_proto;
v4_mask.proto = &l4_proto_mask;
v4_spec.sip = NULL;
v4_mask.sip = NULL;
v4_spec.dip = (u32 *)&entry->dst_ip.s6_addr32[3];
v4_mask.dip = (u32 *)bnxt_ulp_gen_l3_ipv4_addr_em_mask;
l3_parms.type = BNXT_ULP_GEN_L3_IPV4;
l3_parms.v4_spec = &v4_spec;
l3_parms.v4_mask = &v4_mask;
/* Pack the L4 Data */
bth_spec.op_code = NULL;
bth_mask.op_code = NULL;
bth_spec.dst_qpn = NULL;
bth_mask.dst_qpn = NULL;
if (per_qp_session) {
bth_spec.dst_qpn = &dst_qpn;
bth_mask.dst_qpn = &msk_qpn;
}
l4_parms.type = BNXT_ULP_GEN_L4_BTH;
l4_parms.bth_spec = &bth_spec;
l4_parms.bth_mask = &bth_mask;
/* Pack the actions */
actions.enables = BNXT_ULP_GEN_ACTION_ENABLES_REDIRECT |
BNXT_ULP_GEN_ACTION_ENABLES_SET_SMAC |
BNXT_ULP_GEN_ACTION_ENABLES_SET_DMAC |
BNXT_ULP_GEN_ACTION_ENABLES_COUNT;
actions.dst_fid = bp->pf.fw_fid;
if (is_valid_ether_addr(entry->dst_mac_mod) &&
is_valid_ether_addr(entry->src_mac_mod)) {
ether_addr_copy(actions.dmac, entry->dst_mac_mod);
ether_addr_copy(actions.smac, entry->src_mac_mod);
} else {
/* PF case (non-switchdev): zero smac and dmac modify.
* Just use the smac dmac given by FW in the entry.
*/
ether_addr_copy(actions.dmac, entry->dest_mac);
ether_addr_copy(actions.smac, entry->src_mac);
}
parms.dir = BNXT_ULP_GEN_TX;
parms.flow_id = &entry->tx_flow_id;
parms.counter_hndl = &entry->tx_counter_hndl;
parms.l2 = &l2_parms;
parms.l3 = &l3_parms;
parms.l4 = &l4_parms;
parms.actions = &actions;
rc = bnxt_ulp_gen_flow_create(bp, bp->pf.fw_fid, &parms);
if (rc)
return rc;
netdev_dbg(bp->dev, "UDCC Add Tx flow for session_id: %d flow_id: %d, counter: 0x%llx\n",
entry->session_id,
entry->tx_flow_id,
entry->tx_counter_hndl);
return rc;
}
static int bnxt_udcc_flows_create_v6(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
int rc;
rc = bnxt_udcc_rx_flow_create_v6(bp, entry);
if (rc)
return rc;
rc = bnxt_udcc_tx_flow_create_v6(bp, entry);
return rc;
}
static int bnxt_udcc_flows_create_v4(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
int rc;
rc = bnxt_udcc_rx_flow_create_v4(bp, entry);
if (rc)
return rc;
rc = bnxt_udcc_tx_flow_create_v4(bp, entry);
return rc;
}
static int bnxt_udcc_flows_create(struct bnxt *bp,
struct bnxt_udcc_session_entry *entry)
{
if (entry->v4_dst)
return bnxt_udcc_flows_create_v4(bp, entry);
return bnxt_udcc_flows_create_v6(bp, entry);
}
/* The dip gets encoded as the RoCEv2 GID. The third integer
* should be FFFF0000 if the encoded address is IPv4.
* Example: GID: ::ffff:171.16.10.1
*/
#define BNXT_UDCC_DIP_V4_MASK 0xFFFF0000
static bool bnxt_is_udcc_dip_ipv4(struct bnxt *bp, struct in6_addr *dip)
{
netdev_dbg(bp->dev, "%s: s6_addr32[0]: 0x%x s6_addr32[1]: 0x%x\n",
__func__, dip->s6_addr32[0], dip->s6_addr32[1]);
netdev_dbg(bp->dev, "%s: s6_addr32[2]: 0x%x s6_addr32[3]: 0x%x\n",
__func__, dip->s6_addr32[2], dip->s6_addr32[3]);
if ((dip->s6_addr32[2] & BNXT_UDCC_DIP_V4_MASK) ==
BNXT_UDCC_DIP_V4_MASK)
return true;
return false;
}
/* Insert a new session entry into the database */
static int bnxt_udcc_create_session(struct bnxt *bp, u32 session_id)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
struct bnxt_udcc_session_entry *entry;
int rc;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
entry->session_id = session_id;
/* ====================================================================
* 1.Issue HWRM_UDCC_SESSION_QCFG to get the session details
*
* 2.Use the returned DIP to invoke TF API to get flow_ids/counter_hndls
* for Tx/Tx
* a) Use the DIP to query the smac/dmac - TF API
* b) Add a Tx flow using DIP, action_param - modify dmac/smac,count
* c) Add a Rx flow using DIP as SIP, match: CNP, action: count
*
* 3. Issue HWRM_UDCC_SESSION_CFG to update the FW
*/
rc = bnxt_hwrm_udcc_session_qcfg(bp, entry);
if (rc)
goto create_sess_exit1;
if (BNXT_CHIP_P7(bp)) {
rc = bnxt_udcc_flows_create_p7(bp, entry);
if (rc) {
netdev_warn(bp->dev, "UDCC flow create failed rc=%d\n", rc);
goto create_sess_exit1;
}
} else {
entry->v4_dst = bnxt_is_udcc_dip_ipv4(bp, &entry->dst_ip);
rc = bnxt_ulp_udcc_v6_subnet_check(bp, bp->pf.fw_fid, &entry->dst_ip,
entry->dst_mac_mod,
entry->src_mac_mod);
if (rc) {
if (rc != -ENOENT) {
netdev_warn(bp->dev, "UDCC subnet check failed rc=%d\n", rc);
goto create_sess_exit1;
}
entry->skip_subnet_checking = true;
}
rc = bnxt_udcc_flows_create(bp, entry);
if (rc)
goto create_sess_exit1;
}
entry->state = UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED;
rc = bnxt_hwrm_udcc_session_cfg(bp, entry);
if (rc)
goto create_sess_exit2;
mutex_lock(&udcc->session_db_lock);
udcc->session_db[session_id] = entry;
udcc->session_count++;
mutex_unlock(&udcc->session_db_lock);
bnxt_debugfs_create_udcc_session(bp, session_id);
return 0;
create_sess_exit2:
bnxt_tf_ulp_flow_delete(bp, entry);
create_sess_exit1:
entry->state = UDCC_SESSION_CFG_REQ_SESSION_STATE_FLOW_NOT_CREATED;
bnxt_hwrm_udcc_session_cfg(bp, entry);
kfree(entry);
return rc;
}
static int bnxt_tf_ulp_flow_delete(struct bnxt *bp, struct bnxt_udcc_session_entry *entry)
{
int rc = 0;
/* Delete the TF flows for Rx/Tx */
if (entry->rx_flow_id) {
rc = bnxt_ulp_gen_flow_destroy(bp, bp->pf.fw_fid,
entry->rx_flow_id);
if (!rc) {
netdev_dbg(bp->dev,
"UDCC Delete Rx flow_id: %d session: %d\n",
entry->rx_flow_id, entry->session_id);
} else {
netdev_dbg(bp->dev,
"UDCC Delete Rx flow_id: %d failed rc: %d\n",
entry->rx_flow_id, rc);
}
entry->rx_flow_id = 0;
entry->rx_counter_hndl = 0;
}
if (entry->tx_flow_id) {
rc = bnxt_ulp_gen_flow_destroy(bp, bp->pf.fw_fid,
entry->tx_flow_id);
if (!rc) {
netdev_dbg(bp->dev,
"UDCC Delete Tx flow_id: %d session: %d\n",
entry->tx_flow_id, entry->session_id);
} else {
netdev_dbg(bp->dev,
"UDCC Delete Tx flow_id: %d failed rc: %d\n",
entry->tx_flow_id, rc);
}
entry->tx_flow_id = 0;
entry->tx_counter_hndl = 0;
}
return rc;
}
void bnxt_udcc_session_debugfs_add(struct bnxt *bp)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
struct bnxt_udcc_session_entry *entry;
int i;
if (!udcc || !udcc->session_count)
return;
mutex_lock(&udcc->session_db_lock);
for (i = 0; i < BNXT_UDCC_MAX_SESSIONS; i++) {
entry = udcc->session_db[i];
if (!entry)
continue;
if (entry->state == UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED)
bnxt_debugfs_create_udcc_session(bp, i);
}
mutex_unlock(&udcc->session_db_lock);
}
void bnxt_udcc_session_debugfs_cleanup(struct bnxt *bp)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
struct bnxt_udcc_session_entry *entry;
int i;
if (!udcc || !udcc->session_count)
return;
mutex_lock(&udcc->session_db_lock);
for (i = 0; i < BNXT_UDCC_MAX_SESSIONS; i++) {
entry = udcc->session_db[i];
if (!entry)
continue;
if (entry->state == UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED)
bnxt_debugfs_delete_udcc_session(bp, i);
}
mutex_unlock(&udcc->session_db_lock);
}
static int bnxt_udcc_delete_session(struct bnxt *bp, u32 session_id, bool cleanup)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
struct bnxt_udcc_session_entry *entry;
int rc = 0;
mutex_lock(&udcc->session_db_lock);
entry = udcc->session_db[session_id];
if (!entry) {
rc = -ENOENT;
goto exit;
}
rc = bnxt_tf_ulp_flow_delete(bp, entry);
if (rc) {
netdev_dbg(bp->dev,
"Failed to delete UDCC flows, session: %d\n",
session_id);
}
/* No need to issue udcc_session_cfg command when
* firmware is in reset state.
*/
if (test_bit(BNXT_STATE_IN_FW_RESET, &bp->state) || cleanup)
goto cleanup_udcc_session;
entry->state = UDCC_SESSION_CFG_REQ_SESSION_STATE_FLOW_HAS_BEEN_DELETED;
rc = bnxt_hwrm_udcc_session_cfg(bp, entry);
if (rc) {
netdev_dbg(bp->dev, "Failed to delete UDCC session: %d\n",
session_id);
goto exit;
}
cleanup_udcc_session:
bnxt_debugfs_delete_udcc_session(bp, session_id);
kfree(entry);
udcc->session_db[session_id] = NULL;
udcc->session_count--;
netdev_dbg(bp->dev, "Deleted UDCC session: %d\n", session_id);
exit:
mutex_unlock(&udcc->session_db_lock);
return rc;
}
void bnxt_udcc_session_db_cleanup(struct bnxt *bp)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
int i;
if (!udcc)
return;
for (i = 0; i < BNXT_UDCC_MAX_SESSIONS; i++)
bnxt_udcc_delete_session(bp, i, false);
}
void bnxt_udcc_update_session(struct bnxt *bp, bool suspend)
{
u8 tf_event;
if (!bp->udcc_info)
return;
if (suspend)
tf_event = BNXT_UDCC_INFO_TF_EVENT_SUSPEND;
else
tf_event = BNXT_UDCC_INFO_TF_EVENT_UNSUSPEND;
if (test_and_set_bit(tf_event, &bp->udcc_info->tf_events))
return;
bnxt_queue_udcc_work(bp, BNXT_UDCC_MAX_SESSIONS + 1,
BNXT_UDCC_SESSION_UPDATE, suspend);
}
static void bnxt_udcc_suspend_session(struct bnxt *bp,
u8 orig_state,
struct bnxt_udcc_session_entry *entry)
{
int rc;
bnxt_tf_ulp_flow_delete(bp, entry);
entry->state = !UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED;
rc = bnxt_hwrm_udcc_session_cfg(bp, entry);
if (rc) {
netdev_warn(bp->dev, "UDCC failed to suspend session: %d\n",
entry->session_id);
entry->state = orig_state;
} else {
netdev_dbg(bp->dev, "UDCC update session: %d is SUSPENDED\n",
entry->session_id);
}
bnxt_debugfs_delete_udcc_session(bp, entry->session_id);
}
static void bnxt_udcc_unsuspend_session(struct bnxt *bp,
u8 orig_state,
struct bnxt_udcc_session_entry *entry)
{
int rc;
bnxt_udcc_flows_create(bp, entry);
entry->state = UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED;
rc = bnxt_hwrm_udcc_session_cfg(bp, entry);
if (rc) {
netdev_warn(bp->dev, "UDCC failed to unsuspend session: %d\n",
entry->session_id);
entry->state = orig_state;
} else {
netdev_dbg(bp->dev, "UDCC update session: %d is UNSUSPENDED\n",
entry->session_id);
}
bnxt_debugfs_create_udcc_session(bp, entry->session_id);
}
static void __bnxt_udcc_update_session(struct bnxt *bp, bool suspend)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
u8 orig_state;
bool found;
int i;
mutex_lock(&udcc->session_db_lock);
if (!udcc->session_count)
goto exit;
for (i = 0; i < BNXT_UDCC_MAX_SESSIONS; i++) {
struct bnxt_udcc_session_entry *entry;
u8 dmac[ETH_ALEN] = {0};
u8 smac[ETH_ALEN] = {0};
entry = udcc->session_db[i];
if (!entry)
continue;
if (entry->skip_subnet_checking)
continue;
found = !bnxt_ulp_udcc_v6_subnet_check(bp, bp->pf.fw_fid,
&entry->dst_ip,
dmac,
smac);
orig_state = entry->state;
if (suspend && found &&
orig_state == UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED) {
if ((!ether_addr_equal(entry->dst_mac_mod, dmac)) ||
(!ether_addr_equal(entry->src_mac_mod, smac))) {
/* Update the mod dmac and smac */
ether_addr_copy(entry->dst_mac_mod, dmac);
ether_addr_copy(entry->src_mac_mod, smac);
/* Suspend and Unsuspend if action changed */
bnxt_udcc_suspend_session(bp, orig_state, entry);
bnxt_udcc_unsuspend_session(bp, orig_state, entry);
}
} else if (suspend && !found &&
orig_state == UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED) {
/* Suspend */
bnxt_udcc_suspend_session(bp, orig_state, entry);
} else if (!suspend && found &&
orig_state == !UDCC_SESSION_CFG_REQ_SESSION_STATE_ENABLED) {
/* Unsuspend */
bnxt_udcc_unsuspend_session(bp, orig_state, entry);
}
}
exit:
mutex_unlock(&udcc->session_db_lock);
}
void bnxt_udcc_task(struct work_struct *work)
{
struct bnxt_udcc_work *udcc_work =
container_of(work, struct bnxt_udcc_work, work);
struct bnxt *bp = udcc_work->bp;
set_bit(BNXT_STATE_IN_UDCC_TASK, &bp->state);
/* Adding memory barrier to set the IN_UDCC_TASK bit first */
smp_mb__after_atomic();
if (!test_bit(BNXT_STATE_OPEN, &bp->state)) {
clear_bit(BNXT_STATE_IN_UDCC_TASK, &bp->state);
return;
}
switch (udcc_work->session_opcode) {
case BNXT_UDCC_SESSION_CREATE:
bnxt_udcc_create_session(bp, udcc_work->session_id);
break;
case BNXT_UDCC_SESSION_DELETE:
bnxt_udcc_delete_session(bp, udcc_work->session_id, false);
break;
case BNXT_UDCC_SESSION_UPDATE:
/* Check whether the BNXT_UDCC_SESSION_UPDATE event is from TF or Firmware.
* Clear the tf_events bits only if this event is from TF.
*/
if (udcc_work->session_id == BNXT_UDCC_MAX_SESSIONS + 1) {
/* Since UDCC session update events are not specific to a particular
* session, we might end up missing an update for a different session
* (e.g different subnet), if we are already in the middle of processing
* in __bnxt_udcc_update_session(). To avoid this, clear the bit first
* before we enter __bnxt_udcc_update_session() to allow a subsequent
* event to schedule the task again.
*/
if (udcc_work->session_suspend)
clear_bit(BNXT_UDCC_INFO_TF_EVENT_SUSPEND,
&bp->udcc_info->tf_events);
else
clear_bit(BNXT_UDCC_INFO_TF_EVENT_UNSUSPEND,
&bp->udcc_info->tf_events);
}
__bnxt_udcc_update_session(bp, udcc_work->session_suspend);
break;
default:
netdev_warn(bp->dev, "Invalid UDCC session opcode session_id: %d\n",
udcc_work->session_id);
}
/* Complete all memory stores before setting bit. */
smp_mb__before_atomic();
clear_bit(BNXT_STATE_IN_UDCC_TASK, &bp->state);
kfree(udcc_work);
}
void bnxt_free_udcc_info(struct bnxt *bp)
{
struct bnxt_udcc_info *udcc = bp->udcc_info;
int i;
if (!udcc)
return;
for (i = 0; i < BNXT_UDCC_MAX_SESSIONS; i++)
bnxt_udcc_delete_session(bp, i, true);
kfree(udcc);
bp->udcc_info = NULL;
netdev_dbg(bp->dev, "%s(): udcc_info freed up!\n", __func__);
}
#else /* if defined(CONFIG_BNXT_FLOWER_OFFLOAD) */
void bnxt_free_udcc_info(struct bnxt *bp)
{
}
int bnxt_alloc_udcc_info(struct bnxt *bp)
{
return 0;
}
void bnxt_udcc_task(struct work_struct *work)
{
}
void bnxt_udcc_session_db_cleanup(struct bnxt *bp)
{
}
void bnxt_udcc_session_debugfs_add(struct bnxt *bp)
{
}
void bnxt_udcc_session_debugfs_cleanup(struct bnxt *bp)
{
}
#endif /* if defined(CONFIG_BNXT_FLOWER_OFFLOAD) */