The driver relies on netdev_get_num_tc() to get the number of HW offloaded mqprio TCs to allocate and free TX rings. This won't work and can potentially crash the system if software mqprio or taprio TCs have been setup. netdev_get_num_tc() will return the number of software TCs and it may cause the driver to allocate or free more TX rings that it should. Fix it by adding a bp->num_tc field to store the number of HW offload mqprio TCs for the device. Use bp->num_tc instead of netdev_get_num_tc(). This fixes a crash like this: BUG: kernel NULL pointer dereference, address: 0000000000000000 PGD 42b8404067 P4D 0 Oops: 0000 [#1] PREEMPT SMP NOPTI CPU: 120 PID: 8661 Comm: ifconfig Kdump: loaded Tainted: G OE 5.18.16 #1 Hardware name: Lenovo ThinkSystem SR650 V3/SB27A92818, BIOS ESE114N-2.12 04/25/2023 RIP: 0010:bnxt_hwrm_cp_ring_alloc_p5+0x10/0x90 [bnxt_en] Code: 41 5c 41 5d 41 5e c3 cc cc cc cc 41 8b 44 24 08 66 89 03 eb c6 e8 b0 f1 7d db 0f 1f 44 00 00 41 56 41 55 41 54 55 48 89 fd 53 <48> 8b 06 48 89 f3 48 81 c6 28 01 00 00 0f b6 96 13 ff ff ff 44 8b RSP: 0018:ff65907660d1fa88 EFLAGS: 00010202 RAX: 0000000000000010 RBX: ff4dde1d907e4980 RCX: f400000000000000 RDX: 0000000000000010 RSI: 0000000000000000 RDI: ff4dde1d907e4980 RBP: ff4dde1d907e4980 R08: 000000000000000f R09: 0000000000000000 R10: ff4dde5f02671800 R11: 0000000000000008 R12: 0000000088888889 R13: 0500000000000000 R14: 00f0000000000000 R15: ff4dde5f02671800 FS: 00007f4b126b5740(0000) GS:ff4dde9bff600000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000000 CR3: 000000416f9c6002 CR4: 0000000000771ee0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe07f0 DR7: 0000000000000400 PKRU: 55555554 Call Trace: <TASK> bnxt_hwrm_ring_alloc+0x204/0x770 [bnxt_en] bnxt_init_chip+0x4d/0x680 [bnxt_en] ? bnxt_poll+0x1a0/0x1a0 [bnxt_en] __bnxt_open_nic+0xd2/0x740 [bnxt_en] bnxt_open+0x10b/0x220 [bnxt_en] ? raw_notifier_call_chain+0x41/0x60 __dev_open+0xf3/0x1b0 __dev_change_flags+0x1db/0x250 dev_change_flags+0x21/0x60 devinet_ioctl+0x590/0x720 ? avc_has_extended_perms+0x1b7/0x420 ? _copy_from_user+0x3a/0x60 inet_ioctl+0x189/0x1c0 ? wp_page_copy+0x45a/0x6e0 sock_do_ioctl+0x42/0xf0 ? ioctl_has_perm.constprop.0.isra.0+0xbd/0x120 sock_ioctl+0x1ce/0x2e0 __x64_sys_ioctl+0x87/0xc0 do_syscall_64+0x59/0x90 ? syscall_exit_work+0x103/0x130 ? syscall_exit_to_user_mode+0x12/0x30 ? do_syscall_64+0x69/0x90 ? exc_page_fault+0x62/0x150 Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Reviewed-by: Damodharam Ammepalli <damodharam.ammepalli@broadcom.com> Reviewed-by: Andy Gospodarek <andrew.gospodarek@broadcom.com> Signed-off-by: Michael Chan <michael.chan@broadcom.com> Link: https://lore.kernel.org/r/20240117234515.226944-6-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
800 lines
18 KiB
C
800 lines
18 KiB
C
/* Broadcom NetXtreme-C/E network driver.
|
|
*
|
|
* Copyright (c) 2014-2016 Broadcom Corporation
|
|
* Copyright (c) 2016-2017 Broadcom Limited
|
|
*
|
|
* 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/netdevice.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <rdma/ib_verbs.h>
|
|
#include "bnxt_hsi.h"
|
|
#include "bnxt.h"
|
|
#include "bnxt_hwrm.h"
|
|
#include "bnxt_dcb.h"
|
|
|
|
#ifdef CONFIG_BNXT_DCB
|
|
static int bnxt_queue_to_tc(struct bnxt *bp, u8 queue_id)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < bp->max_tc; i++) {
|
|
if (bp->q_info[i].queue_id == queue_id) {
|
|
for (j = 0; j < bp->max_tc; j++) {
|
|
if (bp->tc_to_qidx[j] == i)
|
|
return j;
|
|
}
|
|
}
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_pri2cos_cfg(struct bnxt *bp, struct ieee_ets *ets)
|
|
{
|
|
struct hwrm_queue_pri2cos_cfg_input *req;
|
|
u8 *pri2cos;
|
|
int rc, i;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_PRI2COS_CFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
req->flags = cpu_to_le32(QUEUE_PRI2COS_CFG_REQ_FLAGS_PATH_BIDIR |
|
|
QUEUE_PRI2COS_CFG_REQ_FLAGS_IVLAN);
|
|
|
|
pri2cos = &req->pri0_cos_queue_id;
|
|
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
|
|
u8 qidx;
|
|
|
|
req->enables |= cpu_to_le32(
|
|
QUEUE_PRI2COS_CFG_REQ_ENABLES_PRI0_COS_QUEUE_ID << i);
|
|
|
|
qidx = bp->tc_to_qidx[ets->prio_tc[i]];
|
|
pri2cos[i] = bp->q_info[qidx].queue_id;
|
|
}
|
|
return hwrm_req_send(bp, req);
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_pri2cos_qcfg(struct bnxt *bp, struct ieee_ets *ets)
|
|
{
|
|
struct hwrm_queue_pri2cos_qcfg_output *resp;
|
|
struct hwrm_queue_pri2cos_qcfg_input *req;
|
|
int rc;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_PRI2COS_QCFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
req->flags = cpu_to_le32(QUEUE_PRI2COS_QCFG_REQ_FLAGS_IVLAN);
|
|
resp = hwrm_req_hold(bp, req);
|
|
rc = hwrm_req_send(bp, req);
|
|
if (!rc) {
|
|
u8 *pri2cos = &resp->pri0_cos_queue_id;
|
|
int i;
|
|
|
|
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
|
|
u8 queue_id = pri2cos[i];
|
|
int tc;
|
|
|
|
tc = bnxt_queue_to_tc(bp, queue_id);
|
|
if (tc >= 0)
|
|
ets->prio_tc[i] = tc;
|
|
}
|
|
}
|
|
hwrm_req_drop(bp, req);
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_cos2bw_cfg(struct bnxt *bp, struct ieee_ets *ets,
|
|
u8 max_tc)
|
|
{
|
|
struct hwrm_queue_cos2bw_cfg_input *req;
|
|
struct bnxt_cos2bw_cfg cos2bw;
|
|
int rc, i;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_COS2BW_CFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
for (i = 0; i < max_tc; i++) {
|
|
u8 qidx = bp->tc_to_qidx[i];
|
|
|
|
req->enables |= cpu_to_le32(
|
|
QUEUE_COS2BW_CFG_REQ_ENABLES_COS_QUEUE_ID0_VALID <<
|
|
qidx);
|
|
|
|
memset(&cos2bw, 0, sizeof(cos2bw));
|
|
cos2bw.queue_id = bp->q_info[qidx].queue_id;
|
|
if (ets->tc_tsa[i] == IEEE_8021QAZ_TSA_STRICT) {
|
|
cos2bw.tsa =
|
|
QUEUE_COS2BW_QCFG_RESP_QUEUE_ID0_TSA_ASSIGN_SP;
|
|
cos2bw.pri_lvl = i;
|
|
} else {
|
|
cos2bw.tsa =
|
|
QUEUE_COS2BW_QCFG_RESP_QUEUE_ID0_TSA_ASSIGN_ETS;
|
|
cos2bw.bw_weight = ets->tc_tx_bw[i];
|
|
/* older firmware requires min_bw to be set to the
|
|
* same weight value in percent.
|
|
*/
|
|
cos2bw.min_bw =
|
|
cpu_to_le32((ets->tc_tx_bw[i] * 100) |
|
|
BW_VALUE_UNIT_PERCENT1_100);
|
|
}
|
|
if (qidx == 0) {
|
|
req->queue_id0 = cos2bw.queue_id;
|
|
req->queue_id0_min_bw = cos2bw.min_bw;
|
|
req->queue_id0_max_bw = cos2bw.max_bw;
|
|
req->queue_id0_tsa_assign = cos2bw.tsa;
|
|
req->queue_id0_pri_lvl = cos2bw.pri_lvl;
|
|
req->queue_id0_bw_weight = cos2bw.bw_weight;
|
|
} else {
|
|
memcpy(&req->cfg[i - 1], &cos2bw.cfg, sizeof(cos2bw.cfg));
|
|
}
|
|
}
|
|
return hwrm_req_send(bp, req);
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_cos2bw_qcfg(struct bnxt *bp, struct ieee_ets *ets)
|
|
{
|
|
struct hwrm_queue_cos2bw_qcfg_output *resp;
|
|
struct hwrm_queue_cos2bw_qcfg_input *req;
|
|
struct bnxt_cos2bw_cfg cos2bw;
|
|
int rc, i;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_COS2BW_QCFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
resp = hwrm_req_hold(bp, req);
|
|
rc = hwrm_req_send(bp, req);
|
|
if (rc) {
|
|
hwrm_req_drop(bp, req);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < bp->max_tc; i++) {
|
|
int tc;
|
|
|
|
if (i == 0) {
|
|
cos2bw.queue_id = resp->queue_id0;
|
|
cos2bw.min_bw = resp->queue_id0_min_bw;
|
|
cos2bw.max_bw = resp->queue_id0_max_bw;
|
|
cos2bw.tsa = resp->queue_id0_tsa_assign;
|
|
cos2bw.pri_lvl = resp->queue_id0_pri_lvl;
|
|
cos2bw.bw_weight = resp->queue_id0_bw_weight;
|
|
} else {
|
|
memcpy(&cos2bw.cfg, &resp->cfg[i - 1], sizeof(cos2bw.cfg));
|
|
}
|
|
|
|
tc = bnxt_queue_to_tc(bp, cos2bw.queue_id);
|
|
if (tc < 0)
|
|
continue;
|
|
|
|
if (cos2bw.tsa ==
|
|
QUEUE_COS2BW_QCFG_RESP_QUEUE_ID0_TSA_ASSIGN_SP) {
|
|
ets->tc_tsa[tc] = IEEE_8021QAZ_TSA_STRICT;
|
|
} else {
|
|
ets->tc_tsa[tc] = IEEE_8021QAZ_TSA_ETS;
|
|
ets->tc_tx_bw[tc] = cos2bw.bw_weight;
|
|
}
|
|
}
|
|
hwrm_req_drop(bp, req);
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_queue_remap(struct bnxt *bp, unsigned int lltc_mask)
|
|
{
|
|
unsigned long qmap = 0;
|
|
int max = bp->max_tc;
|
|
int i, j, rc;
|
|
|
|
/* Assign lossless TCs first */
|
|
for (i = 0, j = 0; i < max; ) {
|
|
if (lltc_mask & (1 << i)) {
|
|
if (BNXT_LLQ(bp->q_info[j].queue_profile)) {
|
|
bp->tc_to_qidx[i] = j;
|
|
__set_bit(j, &qmap);
|
|
i++;
|
|
}
|
|
j++;
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
for (i = 0, j = 0; i < max; i++) {
|
|
if (lltc_mask & (1 << i))
|
|
continue;
|
|
j = find_next_zero_bit(&qmap, max, j);
|
|
bp->tc_to_qidx[i] = j;
|
|
__set_bit(j, &qmap);
|
|
j++;
|
|
}
|
|
|
|
if (netif_running(bp->dev)) {
|
|
bnxt_close_nic(bp, false, false);
|
|
rc = bnxt_open_nic(bp, false, false);
|
|
if (rc) {
|
|
netdev_warn(bp->dev, "failed to open NIC, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
if (bp->ieee_ets) {
|
|
int tc = bp->num_tc;
|
|
|
|
if (!tc)
|
|
tc = 1;
|
|
rc = bnxt_hwrm_queue_cos2bw_cfg(bp, bp->ieee_ets, tc);
|
|
if (rc) {
|
|
netdev_warn(bp->dev, "failed to config BW, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = bnxt_hwrm_queue_pri2cos_cfg(bp, bp->ieee_ets);
|
|
if (rc) {
|
|
netdev_warn(bp->dev, "failed to config prio, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_pfc_cfg(struct bnxt *bp, struct ieee_pfc *pfc)
|
|
{
|
|
struct hwrm_queue_pfcenable_cfg_input *req;
|
|
struct ieee_ets *my_ets = bp->ieee_ets;
|
|
unsigned int tc_mask = 0, pri_mask = 0;
|
|
u8 i, pri, lltc_count = 0;
|
|
bool need_q_remap = false;
|
|
int rc;
|
|
|
|
if (!my_ets)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < bp->max_tc; i++) {
|
|
for (pri = 0; pri < IEEE_8021QAZ_MAX_TCS; pri++) {
|
|
if ((pfc->pfc_en & (1 << pri)) &&
|
|
(my_ets->prio_tc[pri] == i)) {
|
|
pri_mask |= 1 << pri;
|
|
tc_mask |= 1 << i;
|
|
}
|
|
}
|
|
if (tc_mask & (1 << i))
|
|
lltc_count++;
|
|
}
|
|
if (lltc_count > bp->max_lltc)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < bp->max_tc; i++) {
|
|
if (tc_mask & (1 << i)) {
|
|
u8 qidx = bp->tc_to_qidx[i];
|
|
|
|
if (!BNXT_LLQ(bp->q_info[qidx].queue_profile)) {
|
|
need_q_remap = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (need_q_remap)
|
|
bnxt_queue_remap(bp, tc_mask);
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_PFCENABLE_CFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
req->flags = cpu_to_le32(pri_mask);
|
|
return hwrm_req_send(bp, req);
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_pfc_qcfg(struct bnxt *bp, struct ieee_pfc *pfc)
|
|
{
|
|
struct hwrm_queue_pfcenable_qcfg_output *resp;
|
|
struct hwrm_queue_pfcenable_qcfg_input *req;
|
|
u8 pri_mask;
|
|
int rc;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_PFCENABLE_QCFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
resp = hwrm_req_hold(bp, req);
|
|
rc = hwrm_req_send(bp, req);
|
|
if (rc) {
|
|
hwrm_req_drop(bp, req);
|
|
return rc;
|
|
}
|
|
|
|
pri_mask = le32_to_cpu(resp->flags);
|
|
pfc->pfc_en = pri_mask;
|
|
hwrm_req_drop(bp, req);
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_hwrm_set_dcbx_app(struct bnxt *bp, struct dcb_app *app,
|
|
bool add)
|
|
{
|
|
struct hwrm_fw_set_structured_data_input *set;
|
|
struct hwrm_fw_get_structured_data_input *get;
|
|
struct hwrm_struct_data_dcbx_app *fw_app;
|
|
struct hwrm_struct_hdr *data;
|
|
dma_addr_t mapping;
|
|
size_t data_len;
|
|
int rc, n, i;
|
|
|
|
if (bp->hwrm_spec_code < 0x10601)
|
|
return 0;
|
|
|
|
rc = hwrm_req_init(bp, get, HWRM_FW_GET_STRUCTURED_DATA);
|
|
if (rc)
|
|
return rc;
|
|
|
|
hwrm_req_hold(bp, get);
|
|
hwrm_req_alloc_flags(bp, get, GFP_KERNEL | __GFP_ZERO);
|
|
|
|
n = IEEE_8021QAZ_MAX_TCS;
|
|
data_len = sizeof(*data) + sizeof(*fw_app) * n;
|
|
data = hwrm_req_dma_slice(bp, get, data_len, &mapping);
|
|
if (!data) {
|
|
rc = -ENOMEM;
|
|
goto set_app_exit;
|
|
}
|
|
|
|
get->dest_data_addr = cpu_to_le64(mapping);
|
|
get->structure_id = cpu_to_le16(STRUCT_HDR_STRUCT_ID_DCBX_APP);
|
|
get->subtype = cpu_to_le16(HWRM_STRUCT_DATA_SUBTYPE_HOST_OPERATIONAL);
|
|
get->count = 0;
|
|
rc = hwrm_req_send(bp, get);
|
|
if (rc)
|
|
goto set_app_exit;
|
|
|
|
fw_app = (struct hwrm_struct_data_dcbx_app *)(data + 1);
|
|
|
|
if (data->struct_id != cpu_to_le16(STRUCT_HDR_STRUCT_ID_DCBX_APP)) {
|
|
rc = -ENODEV;
|
|
goto set_app_exit;
|
|
}
|
|
|
|
n = data->count;
|
|
for (i = 0; i < n; i++, fw_app++) {
|
|
if (fw_app->protocol_id == cpu_to_be16(app->protocol) &&
|
|
fw_app->protocol_selector == app->selector &&
|
|
fw_app->priority == app->priority) {
|
|
if (add)
|
|
goto set_app_exit;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (add) {
|
|
/* append */
|
|
n++;
|
|
fw_app->protocol_id = cpu_to_be16(app->protocol);
|
|
fw_app->protocol_selector = app->selector;
|
|
fw_app->priority = app->priority;
|
|
fw_app->valid = 1;
|
|
} else {
|
|
size_t len = 0;
|
|
|
|
/* not found, nothing to delete */
|
|
if (n == i)
|
|
goto set_app_exit;
|
|
|
|
len = (n - 1 - i) * sizeof(*fw_app);
|
|
if (len)
|
|
memmove(fw_app, fw_app + 1, len);
|
|
n--;
|
|
memset(fw_app + n, 0, sizeof(*fw_app));
|
|
}
|
|
data->count = n;
|
|
data->len = cpu_to_le16(sizeof(*fw_app) * n);
|
|
data->subtype = cpu_to_le16(HWRM_STRUCT_DATA_SUBTYPE_HOST_OPERATIONAL);
|
|
|
|
rc = hwrm_req_init(bp, set, HWRM_FW_SET_STRUCTURED_DATA);
|
|
if (rc)
|
|
goto set_app_exit;
|
|
|
|
set->src_data_addr = cpu_to_le64(mapping);
|
|
set->data_len = cpu_to_le16(sizeof(*data) + sizeof(*fw_app) * n);
|
|
set->hdr_cnt = 1;
|
|
rc = hwrm_req_send(bp, set);
|
|
|
|
set_app_exit:
|
|
hwrm_req_drop(bp, get); /* dropping get request and associated slice */
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_dscp_qcaps(struct bnxt *bp)
|
|
{
|
|
struct hwrm_queue_dscp_qcaps_output *resp;
|
|
struct hwrm_queue_dscp_qcaps_input *req;
|
|
int rc;
|
|
|
|
bp->max_dscp_value = 0;
|
|
if (bp->hwrm_spec_code < 0x10800 || BNXT_VF(bp))
|
|
return 0;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_DSCP_QCAPS);
|
|
if (rc)
|
|
return rc;
|
|
|
|
resp = hwrm_req_hold(bp, req);
|
|
rc = hwrm_req_send_silent(bp, req);
|
|
if (!rc) {
|
|
bp->max_dscp_value = (1 << resp->num_dscp_bits) - 1;
|
|
if (bp->max_dscp_value < 0x3f)
|
|
bp->max_dscp_value = 0;
|
|
}
|
|
hwrm_req_drop(bp, req);
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_hwrm_queue_dscp2pri_cfg(struct bnxt *bp, struct dcb_app *app,
|
|
bool add)
|
|
{
|
|
struct hwrm_queue_dscp2pri_cfg_input *req;
|
|
struct bnxt_dscp2pri_entry *dscp2pri;
|
|
dma_addr_t mapping;
|
|
int rc;
|
|
|
|
if (bp->hwrm_spec_code < 0x10800)
|
|
return 0;
|
|
|
|
rc = hwrm_req_init(bp, req, HWRM_QUEUE_DSCP2PRI_CFG);
|
|
if (rc)
|
|
return rc;
|
|
|
|
dscp2pri = hwrm_req_dma_slice(bp, req, sizeof(*dscp2pri), &mapping);
|
|
if (!dscp2pri) {
|
|
hwrm_req_drop(bp, req);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->src_data_addr = cpu_to_le64(mapping);
|
|
dscp2pri->dscp = app->protocol;
|
|
if (add)
|
|
dscp2pri->mask = 0x3f;
|
|
else
|
|
dscp2pri->mask = 0;
|
|
dscp2pri->pri = app->priority;
|
|
req->entry_cnt = cpu_to_le16(1);
|
|
rc = hwrm_req_send(bp, req);
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_ets_validate(struct bnxt *bp, struct ieee_ets *ets, u8 *tc)
|
|
{
|
|
int total_ets_bw = 0;
|
|
bool zero = false;
|
|
u8 max_tc = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
|
|
if (ets->prio_tc[i] > bp->max_tc) {
|
|
netdev_err(bp->dev, "priority to TC mapping exceeds TC count %d\n",
|
|
ets->prio_tc[i]);
|
|
return -EINVAL;
|
|
}
|
|
if (ets->prio_tc[i] > max_tc)
|
|
max_tc = ets->prio_tc[i];
|
|
|
|
if ((ets->tc_tx_bw[i] || ets->tc_tsa[i]) && i > bp->max_tc)
|
|
return -EINVAL;
|
|
|
|
switch (ets->tc_tsa[i]) {
|
|
case IEEE_8021QAZ_TSA_STRICT:
|
|
break;
|
|
case IEEE_8021QAZ_TSA_ETS:
|
|
total_ets_bw += ets->tc_tx_bw[i];
|
|
zero = zero || !ets->tc_tx_bw[i];
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
}
|
|
if (total_ets_bw > 100) {
|
|
netdev_warn(bp->dev, "rejecting ETS config exceeding available bandwidth\n");
|
|
return -EINVAL;
|
|
}
|
|
if (zero && total_ets_bw == 100) {
|
|
netdev_warn(bp->dev, "rejecting ETS config starving a TC\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (max_tc >= bp->max_tc)
|
|
*tc = bp->max_tc;
|
|
else
|
|
*tc = max_tc + 1;
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_getets(struct net_device *dev, struct ieee_ets *ets)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
struct ieee_ets *my_ets = bp->ieee_ets;
|
|
int rc;
|
|
|
|
ets->ets_cap = bp->max_tc;
|
|
|
|
if (!my_ets) {
|
|
if (bp->dcbx_cap & DCB_CAP_DCBX_HOST)
|
|
return 0;
|
|
|
|
my_ets = kzalloc(sizeof(*my_ets), GFP_KERNEL);
|
|
if (!my_ets)
|
|
return -ENOMEM;
|
|
rc = bnxt_hwrm_queue_cos2bw_qcfg(bp, my_ets);
|
|
if (rc)
|
|
goto error;
|
|
rc = bnxt_hwrm_queue_pri2cos_qcfg(bp, my_ets);
|
|
if (rc)
|
|
goto error;
|
|
|
|
/* cache result */
|
|
bp->ieee_ets = my_ets;
|
|
}
|
|
|
|
ets->cbs = my_ets->cbs;
|
|
memcpy(ets->tc_tx_bw, my_ets->tc_tx_bw, sizeof(ets->tc_tx_bw));
|
|
memcpy(ets->tc_rx_bw, my_ets->tc_rx_bw, sizeof(ets->tc_rx_bw));
|
|
memcpy(ets->tc_tsa, my_ets->tc_tsa, sizeof(ets->tc_tsa));
|
|
memcpy(ets->prio_tc, my_ets->prio_tc, sizeof(ets->prio_tc));
|
|
return 0;
|
|
error:
|
|
kfree(my_ets);
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_setets(struct net_device *dev, struct ieee_ets *ets)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
struct ieee_ets *my_ets = bp->ieee_ets;
|
|
u8 max_tc = 0;
|
|
int rc, i;
|
|
|
|
if (!(bp->dcbx_cap & DCB_CAP_DCBX_VER_IEEE) ||
|
|
!(bp->dcbx_cap & DCB_CAP_DCBX_HOST))
|
|
return -EINVAL;
|
|
|
|
rc = bnxt_ets_validate(bp, ets, &max_tc);
|
|
if (!rc) {
|
|
if (!my_ets) {
|
|
my_ets = kzalloc(sizeof(*my_ets), GFP_KERNEL);
|
|
if (!my_ets)
|
|
return -ENOMEM;
|
|
/* initialize PRI2TC mappings to invalid value */
|
|
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++)
|
|
my_ets->prio_tc[i] = IEEE_8021QAZ_MAX_TCS;
|
|
bp->ieee_ets = my_ets;
|
|
}
|
|
rc = bnxt_setup_mq_tc(dev, max_tc);
|
|
if (rc)
|
|
return rc;
|
|
rc = bnxt_hwrm_queue_cos2bw_cfg(bp, ets, max_tc);
|
|
if (rc)
|
|
return rc;
|
|
rc = bnxt_hwrm_queue_pri2cos_cfg(bp, ets);
|
|
if (rc)
|
|
return rc;
|
|
memcpy(my_ets, ets, sizeof(*my_ets));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_getpfc(struct net_device *dev, struct ieee_pfc *pfc)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
__le64 *stats = bp->port_stats.hw_stats;
|
|
struct ieee_pfc *my_pfc = bp->ieee_pfc;
|
|
long rx_off, tx_off;
|
|
int i, rc;
|
|
|
|
pfc->pfc_cap = bp->max_lltc;
|
|
|
|
if (!my_pfc) {
|
|
if (bp->dcbx_cap & DCB_CAP_DCBX_HOST)
|
|
return 0;
|
|
|
|
my_pfc = kzalloc(sizeof(*my_pfc), GFP_KERNEL);
|
|
if (!my_pfc)
|
|
return 0;
|
|
bp->ieee_pfc = my_pfc;
|
|
rc = bnxt_hwrm_queue_pfc_qcfg(bp, my_pfc);
|
|
if (rc)
|
|
return 0;
|
|
}
|
|
|
|
pfc->pfc_en = my_pfc->pfc_en;
|
|
pfc->mbc = my_pfc->mbc;
|
|
pfc->delay = my_pfc->delay;
|
|
|
|
if (!stats)
|
|
return 0;
|
|
|
|
rx_off = BNXT_RX_STATS_OFFSET(rx_pfc_ena_frames_pri0);
|
|
tx_off = BNXT_TX_STATS_OFFSET(tx_pfc_ena_frames_pri0);
|
|
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++, rx_off++, tx_off++) {
|
|
pfc->requests[i] = le64_to_cpu(*(stats + tx_off));
|
|
pfc->indications[i] = le64_to_cpu(*(stats + rx_off));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_setpfc(struct net_device *dev, struct ieee_pfc *pfc)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
struct ieee_pfc *my_pfc = bp->ieee_pfc;
|
|
int rc;
|
|
|
|
if (!(bp->dcbx_cap & DCB_CAP_DCBX_VER_IEEE) ||
|
|
!(bp->dcbx_cap & DCB_CAP_DCBX_HOST) ||
|
|
(bp->phy_flags & BNXT_PHY_FL_NO_PAUSE))
|
|
return -EINVAL;
|
|
|
|
if (!my_pfc) {
|
|
my_pfc = kzalloc(sizeof(*my_pfc), GFP_KERNEL);
|
|
if (!my_pfc)
|
|
return -ENOMEM;
|
|
bp->ieee_pfc = my_pfc;
|
|
}
|
|
rc = bnxt_hwrm_queue_pfc_cfg(bp, pfc);
|
|
if (!rc)
|
|
memcpy(my_pfc, pfc, sizeof(*my_pfc));
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_dscp_app_prep(struct bnxt *bp, struct dcb_app *app)
|
|
{
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP) {
|
|
if (!bp->max_dscp_value)
|
|
return -ENOTSUPP;
|
|
if (app->protocol > bp->max_dscp_value)
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_setapp(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
int rc;
|
|
|
|
if (!(bp->dcbx_cap & DCB_CAP_DCBX_VER_IEEE) ||
|
|
!(bp->dcbx_cap & DCB_CAP_DCBX_HOST))
|
|
return -EINVAL;
|
|
|
|
rc = bnxt_dcbnl_ieee_dscp_app_prep(bp, app);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = dcb_ieee_setapp(dev, app);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if ((app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
|
|
app->protocol == ETH_P_IBOE) ||
|
|
(app->selector == IEEE_8021QAZ_APP_SEL_DGRAM &&
|
|
app->protocol == ROCE_V2_UDP_DPORT))
|
|
rc = bnxt_hwrm_set_dcbx_app(bp, app, true);
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
rc = bnxt_hwrm_queue_dscp2pri_cfg(bp, app, true);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int bnxt_dcbnl_ieee_delapp(struct net_device *dev, struct dcb_app *app)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
int rc;
|
|
|
|
if (!(bp->dcbx_cap & DCB_CAP_DCBX_VER_IEEE) ||
|
|
!(bp->dcbx_cap & DCB_CAP_DCBX_HOST))
|
|
return -EINVAL;
|
|
|
|
rc = bnxt_dcbnl_ieee_dscp_app_prep(bp, app);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = dcb_ieee_delapp(dev, app);
|
|
if (rc)
|
|
return rc;
|
|
if ((app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
|
|
app->protocol == ETH_P_IBOE) ||
|
|
(app->selector == IEEE_8021QAZ_APP_SEL_DGRAM &&
|
|
app->protocol == ROCE_V2_UDP_DPORT))
|
|
rc = bnxt_hwrm_set_dcbx_app(bp, app, false);
|
|
|
|
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
|
|
rc = bnxt_hwrm_queue_dscp2pri_cfg(bp, app, false);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static u8 bnxt_dcbnl_getdcbx(struct net_device *dev)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
|
|
return bp->dcbx_cap;
|
|
}
|
|
|
|
static u8 bnxt_dcbnl_setdcbx(struct net_device *dev, u8 mode)
|
|
{
|
|
struct bnxt *bp = netdev_priv(dev);
|
|
|
|
/* All firmware DCBX settings are set in NVRAM */
|
|
if (bp->dcbx_cap & DCB_CAP_DCBX_LLD_MANAGED)
|
|
return 1;
|
|
|
|
if (mode & DCB_CAP_DCBX_HOST) {
|
|
if (BNXT_VF(bp) || (bp->fw_cap & BNXT_FW_CAP_LLDP_AGENT))
|
|
return 1;
|
|
|
|
/* only support IEEE */
|
|
if ((mode & DCB_CAP_DCBX_VER_CEE) ||
|
|
!(mode & DCB_CAP_DCBX_VER_IEEE))
|
|
return 1;
|
|
}
|
|
|
|
if (mode == bp->dcbx_cap)
|
|
return 0;
|
|
|
|
bp->dcbx_cap = mode;
|
|
return 0;
|
|
}
|
|
|
|
static const struct dcbnl_rtnl_ops dcbnl_ops = {
|
|
.ieee_getets = bnxt_dcbnl_ieee_getets,
|
|
.ieee_setets = bnxt_dcbnl_ieee_setets,
|
|
.ieee_getpfc = bnxt_dcbnl_ieee_getpfc,
|
|
.ieee_setpfc = bnxt_dcbnl_ieee_setpfc,
|
|
.ieee_setapp = bnxt_dcbnl_ieee_setapp,
|
|
.ieee_delapp = bnxt_dcbnl_ieee_delapp,
|
|
.getdcbx = bnxt_dcbnl_getdcbx,
|
|
.setdcbx = bnxt_dcbnl_setdcbx,
|
|
};
|
|
|
|
void bnxt_dcb_init(struct bnxt *bp)
|
|
{
|
|
bp->dcbx_cap = 0;
|
|
if (bp->hwrm_spec_code < 0x10501)
|
|
return;
|
|
|
|
bnxt_hwrm_queue_dscp_qcaps(bp);
|
|
bp->dcbx_cap = DCB_CAP_DCBX_VER_IEEE;
|
|
if (BNXT_PF(bp) && !(bp->fw_cap & BNXT_FW_CAP_LLDP_AGENT))
|
|
bp->dcbx_cap |= DCB_CAP_DCBX_HOST;
|
|
else if (bp->fw_cap & BNXT_FW_CAP_DCBX_AGENT)
|
|
bp->dcbx_cap |= DCB_CAP_DCBX_LLD_MANAGED;
|
|
bp->dev->dcbnl_ops = &dcbnl_ops;
|
|
}
|
|
|
|
void bnxt_dcb_free(struct bnxt *bp)
|
|
{
|
|
kfree(bp->ieee_pfc);
|
|
kfree(bp->ieee_ets);
|
|
bp->ieee_pfc = NULL;
|
|
bp->ieee_ets = NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
void bnxt_dcb_init(struct bnxt *bp)
|
|
{
|
|
}
|
|
|
|
void bnxt_dcb_free(struct bnxt *bp)
|
|
{
|
|
}
|
|
|
|
#endif
|