linux/drivers/net/ethernet/chelsio/cxgb4/cxgb4_dcb.c

1282 lines
33 KiB
C
Raw Normal View History

treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 291 Based on 2 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms and conditions of the gnu general public license version 2 as published by the free software foundation this program is distributed in the hope it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details the full gnu general public license is included in this distribution in the file called copying this program is free software you can redistribute it and or modify it under the terms and conditions of the gnu general public license version 2 as published by the free software foundation this program is distributed in the hope [that] it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details the full gnu general public license is included in this distribution in the file called copying extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 57 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190529141901.515993066@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-29 22:18:05 +08:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013-2014 Chelsio Communications. All rights reserved.
*
* Written by Anish Bhatt (anish@chelsio.com)
* Casey Leedom (leedom@chelsio.com)
*/
#include "cxgb4.h"
/* DCBx version control
*/
const char * const dcb_ver_array[] = {
"Unknown",
"DCBx-CIN",
"DCBx-CEE 1.01",
"DCBx-IEEE",
"", "", "",
"Auto Negotiated"
};
static inline bool cxgb4_dcb_state_synced(enum cxgb4_dcb_state state)
{
if (state == CXGB4_DCB_STATE_FW_ALLSYNCED ||
state == CXGB4_DCB_STATE_HOST)
return true;
else
return false;
}
/* Initialize a port's Data Center Bridging state.
*/
void cxgb4_dcb_state_init(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
int version_temp = dcb->dcb_version;
memset(dcb, 0, sizeof(struct port_dcb_info));
dcb->state = CXGB4_DCB_STATE_START;
if (version_temp)
dcb->dcb_version = version_temp;
netdev_dbg(dev, "%s: Initializing DCB state for port[%d]\n",
__func__, pi->port_id);
}
void cxgb4_dcb_version_init(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
/* Any writes here are only done on kernels that exlicitly need
* a specific version, say < 2.6.38 which only support CEE
*/
dcb->dcb_version = FW_PORT_DCB_VER_AUTO;
}
static void cxgb4_dcb_cleanup_apps(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
struct port_dcb_info *dcb = &pi->dcb;
struct dcb_app app;
int i, err;
/* zero priority implies remove */
app.priority = 0;
for (i = 0; i < CXGB4_MAX_DCBX_APP_SUPPORTED; i++) {
/* Check if app list is exhausted */
if (!dcb->app_priority[i].protocolid)
break;
app.protocol = dcb->app_priority[i].protocolid;
if (dcb->dcb_version == FW_PORT_DCB_VER_IEEE) {
app.priority = dcb->app_priority[i].user_prio_map;
app.selector = dcb->app_priority[i].sel_field + 1;
err = dcb_ieee_delapp(dev, &app);
} else {
app.selector = !!(dcb->app_priority[i].sel_field);
err = dcb_setapp(dev, &app);
}
if (err) {
dev_err(adap->pdev_dev,
"Failed DCB Clear %s Application Priority: sel=%d, prot=%d, , err=%d\n",
dcb_ver_array[dcb->dcb_version], app.selector,
app.protocol, -err);
break;
}
}
}
/* Reset a port's Data Center Bridging state. Typically used after a
* Link Down event.
*/
void cxgb4_dcb_reset(struct net_device *dev)
{
cxgb4_dcb_cleanup_apps(dev);
cxgb4_dcb_state_init(dev);
}
/* update the dcb port support, if version is IEEE then set it to
* FW_PORT_DCB_VER_IEEE and if DCB_CAP_DCBX_VER_CEE is already set then
* clear that. and if it is set to CEE then set dcb supported to
* DCB_CAP_DCBX_VER_CEE & if DCB_CAP_DCBX_VER_IEEE is set, clear it
*/
static inline void cxgb4_dcb_update_support(struct port_dcb_info *dcb)
{
if (dcb->dcb_version == FW_PORT_DCB_VER_IEEE) {
if (dcb->supported & DCB_CAP_DCBX_VER_CEE)
dcb->supported &= ~DCB_CAP_DCBX_VER_CEE;
dcb->supported |= DCB_CAP_DCBX_VER_IEEE;
} else if (dcb->dcb_version == FW_PORT_DCB_VER_CEE1D01) {
if (dcb->supported & DCB_CAP_DCBX_VER_IEEE)
dcb->supported &= ~DCB_CAP_DCBX_VER_IEEE;
dcb->supported |= DCB_CAP_DCBX_VER_CEE;
}
}
/* Finite State machine for Data Center Bridging.
*/
void cxgb4_dcb_state_fsm(struct net_device *dev,
enum cxgb4_dcb_state_input transition_to)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
struct adapter *adap = pi->adapter;
enum cxgb4_dcb_state current_state = dcb->state;
netdev_dbg(dev, "%s: State change from %d to %d for %s\n",
__func__, dcb->state, transition_to, dev->name);
switch (current_state) {
case CXGB4_DCB_STATE_START: {
switch (transition_to) {
case CXGB4_DCB_INPUT_FW_DISABLED: {
/* we're going to use Host DCB */
dcb->state = CXGB4_DCB_STATE_HOST;
dcb->supported = CXGB4_DCBX_HOST_SUPPORT;
break;
}
case CXGB4_DCB_INPUT_FW_ENABLED: {
/* we're going to use Firmware DCB */
dcb->state = CXGB4_DCB_STATE_FW_INCOMPLETE;
dcb->supported = DCB_CAP_DCBX_LLD_MANAGED;
if (dcb->dcb_version == FW_PORT_DCB_VER_IEEE)
dcb->supported |= DCB_CAP_DCBX_VER_IEEE;
else
dcb->supported |= DCB_CAP_DCBX_VER_CEE;
break;
}
case CXGB4_DCB_INPUT_FW_INCOMPLETE: {
/* expected transition */
break;
}
case CXGB4_DCB_INPUT_FW_ALLSYNCED: {
dcb->state = CXGB4_DCB_STATE_FW_ALLSYNCED;
break;
}
default:
goto bad_state_input;
}
break;
}
case CXGB4_DCB_STATE_FW_INCOMPLETE: {
if (transition_to != CXGB4_DCB_INPUT_FW_DISABLED) {
/* during this CXGB4_DCB_STATE_FW_INCOMPLETE state,
* check if the dcb version is changed (there can be
* mismatch in default config & the negotiated switch
* configuration at FW, so update the dcb support
* accordingly.
*/
cxgb4_dcb_update_support(dcb);
}
switch (transition_to) {
case CXGB4_DCB_INPUT_FW_ENABLED: {
/* we're alreaady in firmware DCB mode */
break;
}
case CXGB4_DCB_INPUT_FW_INCOMPLETE: {
/* we're already incomplete */
break;
}
case CXGB4_DCB_INPUT_FW_ALLSYNCED: {
dcb->state = CXGB4_DCB_STATE_FW_ALLSYNCED;
dcb->enabled = 1;
linkwatch_fire_event(dev);
break;
}
default:
goto bad_state_input;
}
break;
}
case CXGB4_DCB_STATE_FW_ALLSYNCED: {
switch (transition_to) {
case CXGB4_DCB_INPUT_FW_ENABLED: {
/* we're alreaady in firmware DCB mode */
break;
}
case CXGB4_DCB_INPUT_FW_INCOMPLETE: {
/* We were successfully running with firmware DCB but
* now it's telling us that it's in an "incomplete
* state. We need to reset back to a ground state
* of incomplete.
*/
cxgb4_dcb_reset(dev);
dcb->state = CXGB4_DCB_STATE_FW_INCOMPLETE;
dcb->supported = CXGB4_DCBX_FW_SUPPORT;
linkwatch_fire_event(dev);
break;
}
case CXGB4_DCB_INPUT_FW_ALLSYNCED: {
/* we're already all sync'ed
* this is only applicable for IEEE or
* when another VI already completed negotiaton
*/
dcb->enabled = 1;
linkwatch_fire_event(dev);
break;
}
default:
goto bad_state_input;
}
break;
}
case CXGB4_DCB_STATE_HOST: {
switch (transition_to) {
case CXGB4_DCB_INPUT_FW_DISABLED: {
/* we're alreaady in Host DCB mode */
break;
}
default:
goto bad_state_input;
}
break;
}
default:
goto bad_state_transition;
}
return;
bad_state_input:
dev_err(adap->pdev_dev, "cxgb4_dcb_state_fsm: illegal input symbol %d\n",
transition_to);
return;
bad_state_transition:
dev_err(adap->pdev_dev, "cxgb4_dcb_state_fsm: bad state transition, state = %d, input = %d\n",
current_state, transition_to);
}
/* Handle a DCB/DCBX update message from the firmware.
*/
void cxgb4_dcb_handle_fw_update(struct adapter *adap,
const struct fw_port_cmd *pcmd)
{
const union fw_port_dcb *fwdcb = &pcmd->u.dcb;
int port = FW_PORT_CMD_PORTID_G(be32_to_cpu(pcmd->op_to_portid));
struct net_device *dev = adap->port[adap->chan_map[port]];
struct port_info *pi = netdev_priv(dev);
struct port_dcb_info *dcb = &pi->dcb;
int dcb_type = pcmd->u.dcb.pgid.type;
int dcb_running_version;
/* Handle Firmware DCB Control messages separately since they drive
* our state machine.
*/
if (dcb_type == FW_PORT_DCB_TYPE_CONTROL) {
enum cxgb4_dcb_state_input input =
((pcmd->u.dcb.control.all_syncd_pkd &
FW_PORT_CMD_ALL_SYNCD_F)
? CXGB4_DCB_INPUT_FW_ALLSYNCED
: CXGB4_DCB_INPUT_FW_INCOMPLETE);
if (dcb->dcb_version != FW_PORT_DCB_VER_UNKNOWN) {
dcb_running_version = FW_PORT_CMD_DCB_VERSION_G(
be16_to_cpu(
pcmd->u.dcb.control.dcb_version_to_app_state));
if (dcb_running_version == FW_PORT_DCB_VER_CEE1D01 ||
dcb_running_version == FW_PORT_DCB_VER_IEEE) {
dcb->dcb_version = dcb_running_version;
dev_warn(adap->pdev_dev, "Interface %s is running %s\n",
dev->name,
dcb_ver_array[dcb->dcb_version]);
} else {
dev_warn(adap->pdev_dev,
"Something screwed up, requested firmware for %s, but firmware returned %s instead\n",
dcb_ver_array[dcb->dcb_version],
dcb_ver_array[dcb_running_version]);
dcb->dcb_version = FW_PORT_DCB_VER_UNKNOWN;
}
}
cxgb4_dcb_state_fsm(dev, input);
return;
}
/* It's weird, and almost certainly an error, to get Firmware DCB
* messages when we either haven't been told whether we're going to be
* doing Host or Firmware DCB; and even worse when we've been told
* that we're doing Host DCB!
*/
if (dcb->state == CXGB4_DCB_STATE_START ||
dcb->state == CXGB4_DCB_STATE_HOST) {
dev_err(adap->pdev_dev, "Receiving Firmware DCB messages in State %d\n",
dcb->state);
return;
}
/* Now handle the general Firmware DCB update messages ...
*/
switch (dcb_type) {
case FW_PORT_DCB_TYPE_PGID:
dcb->pgid = be32_to_cpu(fwdcb->pgid.pgid);
dcb->msgs |= CXGB4_DCB_FW_PGID;
break;
case FW_PORT_DCB_TYPE_PGRATE:
dcb->pg_num_tcs_supported = fwdcb->pgrate.num_tcs_supported;
memcpy(dcb->pgrate, &fwdcb->pgrate.pgrate,
sizeof(dcb->pgrate));
memcpy(dcb->tsa, &fwdcb->pgrate.tsa,
sizeof(dcb->tsa));
dcb->msgs |= CXGB4_DCB_FW_PGRATE;
if (dcb->msgs & CXGB4_DCB_FW_PGID)
IEEE_FAUX_SYNC(dev, dcb);
break;
case FW_PORT_DCB_TYPE_PRIORATE:
memcpy(dcb->priorate, &fwdcb->priorate.strict_priorate,
sizeof(dcb->priorate));
dcb->msgs |= CXGB4_DCB_FW_PRIORATE;
break;
case FW_PORT_DCB_TYPE_PFC:
dcb->pfcen = fwdcb->pfc.pfcen;
dcb->pfc_num_tcs_supported = fwdcb->pfc.max_pfc_tcs;
dcb->msgs |= CXGB4_DCB_FW_PFC;
IEEE_FAUX_SYNC(dev, dcb);
break;
case FW_PORT_DCB_TYPE_APP_ID: {
const struct fw_port_app_priority *fwap = &fwdcb->app_priority;
int idx = fwap->idx;
struct app_priority *ap = &dcb->app_priority[idx];
struct dcb_app app = {
.protocol = be16_to_cpu(fwap->protocolid),
};
int err;
/* Convert from firmware format to relevant format
* when using app selector
*/
if (dcb->dcb_version == FW_PORT_DCB_VER_IEEE) {
app.selector = (fwap->sel_field + 1);
app.priority = ffs(fwap->user_prio_map) - 1;
err = dcb_ieee_setapp(dev, &app);
IEEE_FAUX_SYNC(dev, dcb);
} else {
/* Default is CEE */
app.selector = !!(fwap->sel_field);
app.priority = fwap->user_prio_map;
err = dcb_setapp(dev, &app);
}
if (err)
dev_err(adap->pdev_dev,
"Failed DCB Set Application Priority: sel=%d, prot=%d, prio=%d, err=%d\n",
app.selector, app.protocol, app.priority, -err);
ap->user_prio_map = fwap->user_prio_map;
ap->sel_field = fwap->sel_field;
ap->protocolid = be16_to_cpu(fwap->protocolid);
dcb->msgs |= CXGB4_DCB_FW_APP_ID;
break;
}
default:
dev_err(adap->pdev_dev, "Unknown DCB update type received %x\n",
dcb_type);
break;
}
}
/* Data Center Bridging netlink operations.
*/
/* Get current DCB enabled/disabled state.
*/
static u8 cxgb4_getstate(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
return pi->dcb.enabled;
}
/* Set DCB enabled/disabled.
*/
static u8 cxgb4_setstate(struct net_device *dev, u8 enabled)
{
struct port_info *pi = netdev2pinfo(dev);
/* If DCBx is host-managed, dcb is enabled by outside lldp agents */
if (pi->dcb.state == CXGB4_DCB_STATE_HOST) {
pi->dcb.enabled = enabled;
return 0;
}
/* Firmware doesn't provide any mechanism to control the DCB state.
*/
if (enabled != (pi->dcb.state == CXGB4_DCB_STATE_FW_ALLSYNCED))
return 1;
return 0;
}
static void cxgb4_getpgtccfg(struct net_device *dev, int tc,
u8 *prio_type, u8 *pgid, u8 *bw_per,
u8 *up_tc_map, int local)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int err;
*prio_type = *pgid = *bw_per = *up_tc_map = 0;
if (local)
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
else
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgid.type = FW_PORT_DCB_TYPE_PGID;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGID failed with %d\n", -err);
return;
}
*pgid = (be32_to_cpu(pcmd.u.dcb.pgid.pgid) >> (tc * 4)) & 0xf;
if (local)
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
else
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return;
}
*bw_per = pcmd.u.dcb.pgrate.pgrate[*pgid];
*up_tc_map = (1 << tc);
/* prio_type is link strict */
if (*pgid != 0xF)
*prio_type = 0x2;
}
static void cxgb4_getpgtccfg_tx(struct net_device *dev, int tc,
u8 *prio_type, u8 *pgid, u8 *bw_per,
u8 *up_tc_map)
{
/* tc 0 is written at MSB position */
return cxgb4_getpgtccfg(dev, (7 - tc), prio_type, pgid, bw_per,
up_tc_map, 1);
}
static void cxgb4_getpgtccfg_rx(struct net_device *dev, int tc,
u8 *prio_type, u8 *pgid, u8 *bw_per,
u8 *up_tc_map)
{
/* tc 0 is written at MSB position */
return cxgb4_getpgtccfg(dev, (7 - tc), prio_type, pgid, bw_per,
up_tc_map, 0);
}
static void cxgb4_setpgtccfg_tx(struct net_device *dev, int tc,
u8 prio_type, u8 pgid, u8 bw_per,
u8 up_tc_map)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int fw_tc = 7 - tc;
u32 _pgid;
int err;
if (pgid == DCB_ATTR_VALUE_UNDEFINED)
return;
if (bw_per == DCB_ATTR_VALUE_UNDEFINED)
return;
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgid.type = FW_PORT_DCB_TYPE_PGID;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGID failed with %d\n", -err);
return;
}
_pgid = be32_to_cpu(pcmd.u.dcb.pgid.pgid);
_pgid &= ~(0xF << (fw_tc * 4));
_pgid |= pgid << (fw_tc * 4);
pcmd.u.dcb.pgid.pgid = cpu_to_be32(_pgid);
INIT_PORT_DCB_WRITE_CMD(pcmd, pi->port_id);
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB write PGID failed with %d\n",
-err);
return;
}
memset(&pcmd, 0, sizeof(struct fw_port_cmd));
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return;
}
pcmd.u.dcb.pgrate.pgrate[pgid] = bw_per;
INIT_PORT_DCB_WRITE_CMD(pcmd, pi->port_id);
if (pi->dcb.state == CXGB4_DCB_STATE_HOST)
pcmd.op_to_portid |= cpu_to_be32(FW_PORT_CMD_APPLY_F);
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS)
dev_err(adap->pdev_dev, "DCB write PGRATE failed with %d\n",
-err);
}
static void cxgb4_getpgbwgcfg(struct net_device *dev, int pgid, u8 *bw_per,
int local)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int err;
if (local)
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
else
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return;
}
*bw_per = pcmd.u.dcb.pgrate.pgrate[pgid];
}
static void cxgb4_getpgbwgcfg_tx(struct net_device *dev, int pgid, u8 *bw_per)
{
return cxgb4_getpgbwgcfg(dev, pgid, bw_per, 1);
}
static void cxgb4_getpgbwgcfg_rx(struct net_device *dev, int pgid, u8 *bw_per)
{
return cxgb4_getpgbwgcfg(dev, pgid, bw_per, 0);
}
static void cxgb4_setpgbwgcfg_tx(struct net_device *dev, int pgid,
u8 bw_per)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int err;
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return;
}
pcmd.u.dcb.pgrate.pgrate[pgid] = bw_per;
INIT_PORT_DCB_WRITE_CMD(pcmd, pi->port_id);
if (pi->dcb.state == CXGB4_DCB_STATE_HOST)
pcmd.op_to_portid |= cpu_to_be32(FW_PORT_CMD_APPLY_F);
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS)
dev_err(adap->pdev_dev, "DCB write PGRATE failed with %d\n",
-err);
}
/* Return whether the specified Traffic Class Priority has Priority Pause
* Frames enabled.
*/
static void cxgb4_getpfccfg(struct net_device *dev, int priority, u8 *pfccfg)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
if (!cxgb4_dcb_state_synced(dcb->state) ||
priority >= CXGB4_MAX_PRIORITY)
*pfccfg = 0;
else
*pfccfg = (pi->dcb.pfcen >> (7 - priority)) & 1;
}
/* Enable/disable Priority Pause Frames for the specified Traffic Class
* Priority.
*/
static void cxgb4_setpfccfg(struct net_device *dev, int priority, u8 pfccfg)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int err;
if (!cxgb4_dcb_state_synced(pi->dcb.state) ||
priority >= CXGB4_MAX_PRIORITY)
return;
INIT_PORT_DCB_WRITE_CMD(pcmd, pi->port_id);
if (pi->dcb.state == CXGB4_DCB_STATE_HOST)
pcmd.op_to_portid |= cpu_to_be32(FW_PORT_CMD_APPLY_F);
pcmd.u.dcb.pfc.type = FW_PORT_DCB_TYPE_PFC;
pcmd.u.dcb.pfc.pfcen = pi->dcb.pfcen;
if (pfccfg)
pcmd.u.dcb.pfc.pfcen |= (1 << (7 - priority));
else
pcmd.u.dcb.pfc.pfcen &= (~(1 << (7 - priority)));
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB PFC write failed with %d\n", -err);
return;
}
pi->dcb.pfcen = pcmd.u.dcb.pfc.pfcen;
}
static u8 cxgb4_setall(struct net_device *dev)
{
return 0;
}
/* Return DCB capabilities.
*/
static u8 cxgb4_getcap(struct net_device *dev, int cap_id, u8 *caps)
{
struct port_info *pi = netdev2pinfo(dev);
switch (cap_id) {
case DCB_CAP_ATTR_PG:
case DCB_CAP_ATTR_PFC:
*caps = true;
break;
case DCB_CAP_ATTR_PG_TCS:
/* 8 priorities for PG represented by bitmap */
*caps = 0x80;
break;
case DCB_CAP_ATTR_PFC_TCS:
/* 8 priorities for PFC represented by bitmap */
*caps = 0x80;
break;
case DCB_CAP_ATTR_GSP:
*caps = true;
break;
case DCB_CAP_ATTR_UP2TC:
case DCB_CAP_ATTR_BCN:
*caps = false;
break;
case DCB_CAP_ATTR_DCBX:
*caps = pi->dcb.supported;
break;
default:
*caps = false;
}
return 0;
}
/* Return the number of Traffic Classes for the indicated Traffic Class ID.
*/
static int cxgb4_getnumtcs(struct net_device *dev, int tcs_id, u8 *num)
{
struct port_info *pi = netdev2pinfo(dev);
switch (tcs_id) {
case DCB_NUMTCS_ATTR_PG:
if (pi->dcb.msgs & CXGB4_DCB_FW_PGRATE)
*num = pi->dcb.pg_num_tcs_supported;
else
*num = 0x8;
break;
case DCB_NUMTCS_ATTR_PFC:
*num = 0x8;
break;
default:
return -EINVAL;
}
return 0;
}
/* Set the number of Traffic Classes supported for the indicated Traffic Class
* ID.
*/
static int cxgb4_setnumtcs(struct net_device *dev, int tcs_id, u8 num)
{
/* Setting the number of Traffic Classes isn't supported.
*/
return -ENOSYS;
}
/* Return whether Priority Flow Control is enabled. */
static u8 cxgb4_getpfcstate(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return false;
return pi->dcb.pfcen != 0;
}
/* Enable/disable Priority Flow Control. */
static void cxgb4_setpfcstate(struct net_device *dev, u8 state)
{
/* We can't enable/disable Priority Flow Control but we also can't
* return an error ...
*/
}
/* Return the Application User Priority Map associated with the specified
* Application ID.
*/
static int __cxgb4_getapp(struct net_device *dev, u8 app_idtype, u16 app_id,
int peer)
{
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int i;
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return 0;
for (i = 0; i < CXGB4_MAX_DCBX_APP_SUPPORTED; i++) {
struct fw_port_cmd pcmd;
int err;
if (peer)
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
else
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
pcmd.u.dcb.app_priority.type = FW_PORT_DCB_TYPE_APP_ID;
pcmd.u.dcb.app_priority.idx = i;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB APP read failed with %d\n",
-err);
return err;
}
if (be16_to_cpu(pcmd.u.dcb.app_priority.protocolid) == app_id)
if (pcmd.u.dcb.app_priority.sel_field == app_idtype)
return pcmd.u.dcb.app_priority.user_prio_map;
/* exhausted app list */
if (!pcmd.u.dcb.app_priority.protocolid)
break;
}
return -EEXIST;
}
/* Return the Application User Priority Map associated with the specified
* Application ID.
*/
static int cxgb4_getapp(struct net_device *dev, u8 app_idtype, u16 app_id)
{
/* Convert app_idtype to firmware format before querying */
return __cxgb4_getapp(dev, app_idtype == DCB_APP_IDTYPE_ETHTYPE ?
app_idtype : 3, app_id, 0);
}
/* Write a new Application User Priority Map for the specified Application ID
*/
static int __cxgb4_setapp(struct net_device *dev, u8 app_idtype, u16 app_id,
u8 app_prio)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int i, err;
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return -EINVAL;
/* DCB info gets thrown away on link up */
if (!netif_carrier_ok(dev))
return -ENOLINK;
for (i = 0; i < CXGB4_MAX_DCBX_APP_SUPPORTED; i++) {
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
pcmd.u.dcb.app_priority.type = FW_PORT_DCB_TYPE_APP_ID;
pcmd.u.dcb.app_priority.idx = i;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB app table read failed with %d\n",
-err);
return err;
}
if (be16_to_cpu(pcmd.u.dcb.app_priority.protocolid) == app_id) {
/* overwrite existing app table */
pcmd.u.dcb.app_priority.protocolid = 0;
break;
}
/* find first empty slot */
if (!pcmd.u.dcb.app_priority.protocolid)
break;
}
if (i == CXGB4_MAX_DCBX_APP_SUPPORTED) {
/* no empty slots available */
dev_err(adap->pdev_dev, "DCB app table full\n");
return -EBUSY;
}
/* write out new app table entry */
INIT_PORT_DCB_WRITE_CMD(pcmd, pi->port_id);
if (pi->dcb.state == CXGB4_DCB_STATE_HOST)
pcmd.op_to_portid |= cpu_to_be32(FW_PORT_CMD_APPLY_F);
pcmd.u.dcb.app_priority.type = FW_PORT_DCB_TYPE_APP_ID;
pcmd.u.dcb.app_priority.protocolid = cpu_to_be16(app_id);
pcmd.u.dcb.app_priority.sel_field = app_idtype;
pcmd.u.dcb.app_priority.user_prio_map = app_prio;
pcmd.u.dcb.app_priority.idx = i;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB app table write failed with %d\n",
-err);
return err;
}
return 0;
}
/* Priority for CEE inside dcb_app is bitmask, with 0 being an invalid value */
static int cxgb4_setapp(struct net_device *dev, u8 app_idtype, u16 app_id,
u8 app_prio)
{
int ret;
struct dcb_app app = {
.selector = app_idtype,
.protocol = app_id,
.priority = app_prio,
};
if (app_idtype != DCB_APP_IDTYPE_ETHTYPE &&
app_idtype != DCB_APP_IDTYPE_PORTNUM)
return -EINVAL;
/* Convert app_idtype to a format that firmware understands */
ret = __cxgb4_setapp(dev, app_idtype == DCB_APP_IDTYPE_ETHTYPE ?
app_idtype : 3, app_id, app_prio);
if (ret)
return ret;
return dcb_setapp(dev, &app);
}
/* Return whether IEEE Data Center Bridging has been negotiated.
*/
static inline int
cxgb4_ieee_negotiation_complete(struct net_device *dev,
enum cxgb4_dcb_fw_msgs dcb_subtype)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
if (dcb->state == CXGB4_DCB_STATE_FW_ALLSYNCED)
if (dcb_subtype && !(dcb->msgs & dcb_subtype))
return 0;
return (cxgb4_dcb_state_synced(dcb->state) &&
(dcb->supported & DCB_CAP_DCBX_VER_IEEE));
}
static int cxgb4_ieee_read_ets(struct net_device *dev, struct ieee_ets *ets,
int local)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
struct adapter *adap = pi->adapter;
uint32_t tc_info;
struct fw_port_cmd pcmd;
int i, bwg, err;
if (!(dcb->msgs & (CXGB4_DCB_FW_PGID | CXGB4_DCB_FW_PGRATE)))
return 0;
ets->ets_cap = dcb->pg_num_tcs_supported;
if (local) {
ets->willing = 1;
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
} else {
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
}
pcmd.u.dcb.pgid.type = FW_PORT_DCB_TYPE_PGID;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGID failed with %d\n", -err);
return err;
}
tc_info = be32_to_cpu(pcmd.u.dcb.pgid.pgid);
if (local)
INIT_PORT_DCB_READ_LOCAL_CMD(pcmd, pi->port_id);
else
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return err;
}
for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
bwg = (tc_info >> ((7 - i) * 4)) & 0xF;
ets->prio_tc[i] = bwg;
ets->tc_tx_bw[i] = pcmd.u.dcb.pgrate.pgrate[i];
ets->tc_rx_bw[i] = ets->tc_tx_bw[i];
ets->tc_tsa[i] = pcmd.u.dcb.pgrate.tsa[i];
}
return 0;
}
static int cxgb4_ieee_get_ets(struct net_device *dev, struct ieee_ets *ets)
{
return cxgb4_ieee_read_ets(dev, ets, 1);
}
/* We reuse this for peer PFC as well, as we can't have it enabled one way */
static int cxgb4_ieee_get_pfc(struct net_device *dev, struct ieee_pfc *pfc)
{
struct port_info *pi = netdev2pinfo(dev);
struct port_dcb_info *dcb = &pi->dcb;
memset(pfc, 0, sizeof(struct ieee_pfc));
if (!(dcb->msgs & CXGB4_DCB_FW_PFC))
return 0;
pfc->pfc_cap = dcb->pfc_num_tcs_supported;
pfc->pfc_en = bitswap_1(dcb->pfcen);
return 0;
}
static int cxgb4_ieee_peer_ets(struct net_device *dev, struct ieee_ets *ets)
{
return cxgb4_ieee_read_ets(dev, ets, 0);
}
/* Fill in the Application User Priority Map associated with the
* specified Application.
* Priority for IEEE dcb_app is an integer, with 0 being a valid value
*/
static int cxgb4_ieee_getapp(struct net_device *dev, struct dcb_app *app)
{
int prio;
if (!cxgb4_ieee_negotiation_complete(dev, CXGB4_DCB_FW_APP_ID))
return -EINVAL;
if (!(app->selector && app->protocol))
return -EINVAL;
/* Try querying firmware first, use firmware format */
prio = __cxgb4_getapp(dev, app->selector - 1, app->protocol, 0);
if (prio < 0)
prio = dcb_ieee_getapp_mask(dev, app);
app->priority = ffs(prio) - 1;
return 0;
}
/* Write a new Application User Priority Map for the specified Application ID.
* Priority for IEEE dcb_app is an integer, with 0 being a valid value
*/
static int cxgb4_ieee_setapp(struct net_device *dev, struct dcb_app *app)
{
int ret;
if (!cxgb4_ieee_negotiation_complete(dev, CXGB4_DCB_FW_APP_ID))
return -EINVAL;
if (!(app->selector && app->protocol))
return -EINVAL;
if (!(app->selector > IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
app->selector < IEEE_8021QAZ_APP_SEL_ANY))
return -EINVAL;
/* change selector to a format that firmware understands */
ret = __cxgb4_setapp(dev, app->selector - 1, app->protocol,
(1 << app->priority));
if (ret)
return ret;
return dcb_ieee_setapp(dev, app);
}
/* Return our DCBX parameters.
*/
static u8 cxgb4_getdcbx(struct net_device *dev)
{
struct port_info *pi = netdev2pinfo(dev);
/* This is already set by cxgb4_set_dcb_caps, so just return it */
return pi->dcb.supported;
}
/* Set our DCBX parameters.
*/
static u8 cxgb4_setdcbx(struct net_device *dev, u8 dcb_request)
{
struct port_info *pi = netdev2pinfo(dev);
/* Filter out requests which exceed our capabilities.
*/
if ((dcb_request & (CXGB4_DCBX_FW_SUPPORT | CXGB4_DCBX_HOST_SUPPORT))
!= dcb_request)
return 1;
/* Can't enable DCB if we haven't successfully negotiated it.
*/
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return 1;
/* There's currently no mechanism to allow for the firmware DCBX
* negotiation to be changed from the Host Driver. If the caller
* requests exactly the same parameters that we already have then
* we'll allow them to be successfully "set" ...
*/
if (dcb_request != pi->dcb.supported)
return 1;
pi->dcb.supported = dcb_request;
return 0;
}
static int cxgb4_getpeer_app(struct net_device *dev,
struct dcb_peer_app_info *info, u16 *app_count)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int i, err = 0;
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return 1;
info->willing = 0;
info->error = 0;
*app_count = 0;
for (i = 0; i < CXGB4_MAX_DCBX_APP_SUPPORTED; i++) {
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.app_priority.type = FW_PORT_DCB_TYPE_APP_ID;
pcmd.u.dcb.app_priority.idx = *app_count;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB app table read failed with %d\n",
-err);
return err;
}
/* find first empty slot */
if (!pcmd.u.dcb.app_priority.protocolid)
break;
}
*app_count = i;
return err;
}
static int cxgb4_getpeerapp_tbl(struct net_device *dev, struct dcb_app *table)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
int i, err = 0;
if (!cxgb4_dcb_state_synced(pi->dcb.state))
return 1;
for (i = 0; i < CXGB4_MAX_DCBX_APP_SUPPORTED; i++) {
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.app_priority.type = FW_PORT_DCB_TYPE_APP_ID;
pcmd.u.dcb.app_priority.idx = i;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB app table read failed with %d\n",
-err);
return err;
}
/* find first empty slot */
if (!pcmd.u.dcb.app_priority.protocolid)
break;
table[i].selector = (pcmd.u.dcb.app_priority.sel_field + 1);
table[i].protocol =
be16_to_cpu(pcmd.u.dcb.app_priority.protocolid);
table[i].priority =
ffs(pcmd.u.dcb.app_priority.user_prio_map) - 1;
}
return err;
}
/* Return Priority Group information.
*/
static int cxgb4_cee_peer_getpg(struct net_device *dev, struct cee_pg *pg)
{
struct fw_port_cmd pcmd;
struct port_info *pi = netdev2pinfo(dev);
struct adapter *adap = pi->adapter;
u32 pgid;
int i, err;
/* We're always "willing" -- the Switch Fabric always dictates the
* DCBX parameters to us.
*/
pg->willing = true;
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgid.type = FW_PORT_DCB_TYPE_PGID;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGID failed with %d\n", -err);
return err;
}
pgid = be32_to_cpu(pcmd.u.dcb.pgid.pgid);
for (i = 0; i < CXGB4_MAX_PRIORITY; i++)
pg->prio_pg[7 - i] = (pgid >> (i * 4)) & 0xF;
INIT_PORT_DCB_READ_PEER_CMD(pcmd, pi->port_id);
pcmd.u.dcb.pgrate.type = FW_PORT_DCB_TYPE_PGRATE;
err = t4_wr_mbox(adap, adap->mbox, &pcmd, sizeof(pcmd), &pcmd);
if (err != FW_PORT_DCB_CFG_SUCCESS) {
dev_err(adap->pdev_dev, "DCB read PGRATE failed with %d\n",
-err);
return err;
}
for (i = 0; i < CXGB4_MAX_PRIORITY; i++)
pg->pg_bw[i] = pcmd.u.dcb.pgrate.pgrate[i];
pg->tcs_supported = pcmd.u.dcb.pgrate.num_tcs_supported;
return 0;
}
/* Return Priority Flow Control information.
*/
static int cxgb4_cee_peer_getpfc(struct net_device *dev, struct cee_pfc *pfc)
{
struct port_info *pi = netdev2pinfo(dev);
cxgb4_getnumtcs(dev, DCB_NUMTCS_ATTR_PFC, &(pfc->tcs_supported));
/* Firmware sends this to us in a formwat that is a bit flipped version
* of spec, correct it before we send it to host. This is taken care of
* by bit shifting in other uses of pfcen
*/
pfc->pfc_en = bitswap_1(pi->dcb.pfcen);
pfc->tcs_supported = pi->dcb.pfc_num_tcs_supported;
return 0;
}
const struct dcbnl_rtnl_ops cxgb4_dcb_ops = {
.ieee_getets = cxgb4_ieee_get_ets,
.ieee_getpfc = cxgb4_ieee_get_pfc,
.ieee_getapp = cxgb4_ieee_getapp,
.ieee_setapp = cxgb4_ieee_setapp,
.ieee_peer_getets = cxgb4_ieee_peer_ets,
.ieee_peer_getpfc = cxgb4_ieee_get_pfc,
/* CEE std */
.getstate = cxgb4_getstate,
.setstate = cxgb4_setstate,
.getpgtccfgtx = cxgb4_getpgtccfg_tx,
.getpgbwgcfgtx = cxgb4_getpgbwgcfg_tx,
.getpgtccfgrx = cxgb4_getpgtccfg_rx,
.getpgbwgcfgrx = cxgb4_getpgbwgcfg_rx,
.setpgtccfgtx = cxgb4_setpgtccfg_tx,
.setpgbwgcfgtx = cxgb4_setpgbwgcfg_tx,
.setpfccfg = cxgb4_setpfccfg,
.getpfccfg = cxgb4_getpfccfg,
.setall = cxgb4_setall,
.getcap = cxgb4_getcap,
.getnumtcs = cxgb4_getnumtcs,
.setnumtcs = cxgb4_setnumtcs,
.getpfcstate = cxgb4_getpfcstate,
.setpfcstate = cxgb4_setpfcstate,
.getapp = cxgb4_getapp,
.setapp = cxgb4_setapp,
/* DCBX configuration */
.getdcbx = cxgb4_getdcbx,
.setdcbx = cxgb4_setdcbx,
/* peer apps */
.peer_getappinfo = cxgb4_getpeer_app,
.peer_getapptable = cxgb4_getpeerapp_tbl,
/* CEE peer */
.cee_peer_getpg = cxgb4_cee_peer_getpg,
.cee_peer_getpfc = cxgb4_cee_peer_getpfc,
};