| /* |
| * Copyright (C) 2013-2014 Chelsio Communications. All rights reserved. |
| * |
| * Written by Anish Bhatt (anish@chelsio.com) |
| * Casey Leedom (leedom@chelsio.com) |
| * |
| * 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". |
| * |
| */ |
| |
| #include "cxgb4.h" |
| |
| /* DCBx version control |
| */ |
| char *dcb_ver_array[] = { |
| "Unknown", |
| "DCBx-CIN", |
| "DCBx-CEE 1.01", |
| "DCBx-IEEE", |
| "", "", "", |
| "Auto Negotiated" |
| }; |
| |
| /* Initialize a port's Data Center Bridging state. Typically used after a |
| * Link Down event. |
| */ |
| 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.selector = dcb->app_priority[i].sel_field + 1; |
| err = dcb_ieee_setapp(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; |
| } |
| } |
| } |
| |
| /* 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 = CXGB4_DCBX_FW_SUPPORT; |
| 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: { |
| 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_cleanup_apps(dev); |
| cxgb4_dcb_state_init(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_GET(be32_to_cpu(pcmd->op_to_portid)); |
| struct net_device *dev = adap->port[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) |
| ? CXGB4_DCB_STATE_FW_ALLSYNCED |
| : CXGB4_DCB_STATE_FW_INCOMPLETE); |
| |
| if (dcb->dcb_version != FW_PORT_DCB_VER_UNKNOWN) { |
| dcb_running_version = FW_PORT_CMD_DCB_VERSION_GET( |
| 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; |
| |
| 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 */ |
| *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) |
| { |
| return cxgb4_getpgtccfg(dev, 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) |
| { |
| return cxgb4_getpgtccfg(dev, 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; |
| 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 << (tc * 4)); |
| _pgid |= pgid << (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); |
| |
| 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); |
| |
| 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 (dcb->state != CXGB4_DCB_STATE_FW_ALLSYNCED || |
| priority >= CXGB4_MAX_PRIORITY) |
| *pfccfg = 0; |
| else |
| *pfccfg = (pi->dcb.pfcen >> 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED || |
| 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); |
| |
| 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 << priority); |
| else |
| pcmd.u.dcb.pfc.pfcen &= (~(1 << 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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) |
| { |
| return __cxgb4_getapp(dev, app_idtype, 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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); |
| |
| 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_subtype && !(dcb->msgs & dcb_subtype)) |
| return 0; |
| |
| return (dcb->state == CXGB4_DCB_STATE_FW_ALLSYNCED && |
| (dcb->supported & DCB_CAP_DCBX_VER_IEEE)); |
| } |
| |
| /* 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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 (pi->dcb.state != CXGB4_DCB_STATE_FW_ALLSYNCED) |
| 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; |
| 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[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]; |
| |
| 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)); |
| pfc->pfc_en = pi->dcb.pfcen; |
| |
| return 0; |
| } |
| |
| const struct dcbnl_rtnl_ops cxgb4_dcb_ops = { |
| .ieee_getapp = cxgb4_ieee_getapp, |
| .ieee_setapp = cxgb4_ieee_setapp, |
| |
| /* 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, |
| }; |