root/drivers/net/ethernet/chelsio/cxgb4/cxgb4_dcb.c
// 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,
};