| // SPDX-License-Identifier: GPL-2.0 | 
| /* Copyright(c) 2013 - 2018 Intel Corporation. */ | 
|   | 
| #ifdef CONFIG_I40E_DCB | 
| #include "i40e.h" | 
| #include <net/dcbnl.h> | 
|   | 
| /** | 
|  * i40e_get_pfc_delay - retrieve PFC Link Delay | 
|  * @hw: pointer to hardware struct | 
|  * @delay: holds the PFC Link delay value | 
|  * | 
|  * Returns PFC Link Delay from the PRTDCB_GENC.PFCLDA | 
|  **/ | 
| static void i40e_get_pfc_delay(struct i40e_hw *hw, u16 *delay) | 
| { | 
|     u32 val; | 
|   | 
|     val = rd32(hw, I40E_PRTDCB_GENC); | 
|     *delay = (u16)((val & I40E_PRTDCB_GENC_PFCLDA_MASK) >> | 
|                I40E_PRTDCB_GENC_PFCLDA_SHIFT); | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_ieee_getets - retrieve local IEEE ETS configuration | 
|  * @dev: the corresponding netdev | 
|  * @ets: structure to hold the ETS information | 
|  * | 
|  * Returns local IEEE ETS configuration | 
|  **/ | 
| static int i40e_dcbnl_ieee_getets(struct net_device *dev, | 
|                   struct ieee_ets *ets) | 
| { | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|     struct i40e_dcbx_config *dcbxcfg; | 
|     struct i40e_hw *hw = &pf->hw; | 
|   | 
|     if (!(pf->dcbx_cap & DCB_CAP_DCBX_VER_IEEE)) | 
|         return -EINVAL; | 
|   | 
|     dcbxcfg = &hw->local_dcbx_config; | 
|     ets->willing = dcbxcfg->etscfg.willing; | 
|     ets->ets_cap = dcbxcfg->etscfg.maxtcs; | 
|     ets->cbs = dcbxcfg->etscfg.cbs; | 
|     memcpy(ets->tc_tx_bw, dcbxcfg->etscfg.tcbwtable, | 
|         sizeof(ets->tc_tx_bw)); | 
|     memcpy(ets->tc_rx_bw, dcbxcfg->etscfg.tcbwtable, | 
|         sizeof(ets->tc_rx_bw)); | 
|     memcpy(ets->tc_tsa, dcbxcfg->etscfg.tsatable, | 
|         sizeof(ets->tc_tsa)); | 
|     memcpy(ets->prio_tc, dcbxcfg->etscfg.prioritytable, | 
|         sizeof(ets->prio_tc)); | 
|     memcpy(ets->tc_reco_bw, dcbxcfg->etsrec.tcbwtable, | 
|         sizeof(ets->tc_reco_bw)); | 
|     memcpy(ets->tc_reco_tsa, dcbxcfg->etsrec.tsatable, | 
|         sizeof(ets->tc_reco_tsa)); | 
|     memcpy(ets->reco_prio_tc, dcbxcfg->etscfg.prioritytable, | 
|         sizeof(ets->reco_prio_tc)); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_ieee_getpfc - retrieve local IEEE PFC configuration | 
|  * @dev: the corresponding netdev | 
|  * @pfc: structure to hold the PFC information | 
|  * | 
|  * Returns local IEEE PFC configuration | 
|  **/ | 
| static int i40e_dcbnl_ieee_getpfc(struct net_device *dev, | 
|                   struct ieee_pfc *pfc) | 
| { | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|     struct i40e_dcbx_config *dcbxcfg; | 
|     struct i40e_hw *hw = &pf->hw; | 
|     int i; | 
|   | 
|     if (!(pf->dcbx_cap & DCB_CAP_DCBX_VER_IEEE)) | 
|         return -EINVAL; | 
|   | 
|     dcbxcfg = &hw->local_dcbx_config; | 
|     pfc->pfc_cap = dcbxcfg->pfc.pfccap; | 
|     pfc->pfc_en = dcbxcfg->pfc.pfcenable; | 
|     pfc->mbc = dcbxcfg->pfc.mbc; | 
|     i40e_get_pfc_delay(hw, &pfc->delay); | 
|   | 
|     /* Get Requests/Indicatiosn */ | 
|     for (i = 0; i < I40E_MAX_TRAFFIC_CLASS; i++) { | 
|         pfc->requests[i] = pf->stats.priority_xoff_tx[i]; | 
|         pfc->indications[i] = pf->stats.priority_xoff_rx[i]; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_getdcbx - retrieve current DCBx capability | 
|  * @dev: the corresponding netdev | 
|  * | 
|  * Returns DCBx capability features | 
|  **/ | 
| static u8 i40e_dcbnl_getdcbx(struct net_device *dev) | 
| { | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|   | 
|     return pf->dcbx_cap; | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_get_perm_hw_addr - MAC address used by DCBx | 
|  * @dev: the corresponding netdev | 
|  * @perm_addr: buffer to store the MAC address | 
|  * | 
|  * Returns the SAN MAC address used for LLDP exchange | 
|  **/ | 
| static void i40e_dcbnl_get_perm_hw_addr(struct net_device *dev, | 
|                     u8 *perm_addr) | 
| { | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|     int i, j; | 
|   | 
|     memset(perm_addr, 0xff, MAX_ADDR_LEN); | 
|   | 
|     for (i = 0; i < dev->addr_len; i++) | 
|         perm_addr[i] = pf->hw.mac.perm_addr[i]; | 
|   | 
|     for (j = 0; j < dev->addr_len; j++, i++) | 
|         perm_addr[i] = pf->hw.mac.san_addr[j]; | 
| } | 
|   | 
| static const struct dcbnl_rtnl_ops dcbnl_ops = { | 
|     .ieee_getets    = i40e_dcbnl_ieee_getets, | 
|     .ieee_getpfc    = i40e_dcbnl_ieee_getpfc, | 
|     .getdcbx    = i40e_dcbnl_getdcbx, | 
|     .getpermhwaddr  = i40e_dcbnl_get_perm_hw_addr, | 
| }; | 
|   | 
| /** | 
|  * i40e_dcbnl_set_all - set all the apps and ieee data from DCBx config | 
|  * @vsi: the corresponding vsi | 
|  * | 
|  * Set up all the IEEE APPs in the DCBNL App Table and generate event for | 
|  * other settings | 
|  **/ | 
| void i40e_dcbnl_set_all(struct i40e_vsi *vsi) | 
| { | 
|     struct net_device *dev = vsi->netdev; | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|     struct i40e_dcbx_config *dcbxcfg; | 
|     struct i40e_hw *hw = &pf->hw; | 
|     struct dcb_app sapp; | 
|     u8 prio, tc_map; | 
|     int i; | 
|   | 
|     /* DCB not enabled */ | 
|     if (!(pf->flags & I40E_FLAG_DCB_ENABLED)) | 
|         return; | 
|   | 
|     /* MFP mode but not an iSCSI PF so return */ | 
|     if ((pf->flags & I40E_FLAG_MFP_ENABLED) && !(pf->hw.func_caps.iscsi)) | 
|         return; | 
|   | 
|     dcbxcfg = &hw->local_dcbx_config; | 
|   | 
|     /* Set up all the App TLVs if DCBx is negotiated */ | 
|     for (i = 0; i < dcbxcfg->numapps; i++) { | 
|         prio = dcbxcfg->app[i].priority; | 
|         tc_map = BIT(dcbxcfg->etscfg.prioritytable[prio]); | 
|   | 
|         /* Add APP only if the TC is enabled for this VSI */ | 
|         if (tc_map & vsi->tc_config.enabled_tc) { | 
|             sapp.selector = dcbxcfg->app[i].selector; | 
|             sapp.protocol = dcbxcfg->app[i].protocolid; | 
|             sapp.priority = prio; | 
|             dcb_ieee_setapp(dev, &sapp); | 
|         } | 
|     } | 
|   | 
|     /* Notify user-space of the changes */ | 
|     dcbnl_ieee_notify(dev, RTM_SETDCB, DCB_CMD_IEEE_SET, 0, 0); | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_vsi_del_app - Delete APP for given VSI | 
|  * @vsi: the corresponding vsi | 
|  * @app: APP to delete | 
|  * | 
|  * Delete given APP from the DCBNL APP table for given | 
|  * VSI | 
|  **/ | 
| static int i40e_dcbnl_vsi_del_app(struct i40e_vsi *vsi, | 
|                   struct i40e_dcb_app_priority_table *app) | 
| { | 
|     struct net_device *dev = vsi->netdev; | 
|     struct dcb_app sapp; | 
|   | 
|     if (!dev) | 
|         return -EINVAL; | 
|   | 
|     sapp.selector = app->selector; | 
|     sapp.protocol = app->protocolid; | 
|     sapp.priority = app->priority; | 
|     return dcb_ieee_delapp(dev, &sapp); | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_del_app - Delete APP on all VSIs | 
|  * @pf: the corresponding PF | 
|  * @app: APP to delete | 
|  * | 
|  * Delete given APP from all the VSIs for given PF | 
|  **/ | 
| static void i40e_dcbnl_del_app(struct i40e_pf *pf, | 
|                    struct i40e_dcb_app_priority_table *app) | 
| { | 
|     int v, err; | 
|   | 
|     for (v = 0; v < pf->num_alloc_vsi; v++) { | 
|         if (pf->vsi[v] && pf->vsi[v]->netdev) { | 
|             err = i40e_dcbnl_vsi_del_app(pf->vsi[v], app); | 
|             dev_dbg(&pf->pdev->dev, "Deleting app for VSI seid=%d err=%d sel=%d proto=0x%x prio=%d\n", | 
|                 pf->vsi[v]->seid, err, app->selector, | 
|                 app->protocolid, app->priority); | 
|         } | 
|     } | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_find_app - Search APP in given DCB config | 
|  * @cfg: DCBX configuration data | 
|  * @app: APP to search for | 
|  * | 
|  * Find given APP in the DCB configuration | 
|  **/ | 
| static bool i40e_dcbnl_find_app(struct i40e_dcbx_config *cfg, | 
|                 struct i40e_dcb_app_priority_table *app) | 
| { | 
|     int i; | 
|   | 
|     for (i = 0; i < cfg->numapps; i++) { | 
|         if (app->selector == cfg->app[i].selector && | 
|             app->protocolid == cfg->app[i].protocolid && | 
|             app->priority == cfg->app[i].priority) | 
|             return true; | 
|     } | 
|   | 
|     return false; | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_flush_apps - Delete all removed APPs | 
|  * @pf: the corresponding PF | 
|  * @old_cfg: old DCBX configuration data | 
|  * @new_cfg: new DCBX configuration data | 
|  * | 
|  * Find and delete all APPs that are not present in the passed | 
|  * DCB configuration | 
|  **/ | 
| void i40e_dcbnl_flush_apps(struct i40e_pf *pf, | 
|                struct i40e_dcbx_config *old_cfg, | 
|                struct i40e_dcbx_config *new_cfg) | 
| { | 
|     struct i40e_dcb_app_priority_table app; | 
|     int i; | 
|   | 
|     /* MFP mode but not an iSCSI PF so return */ | 
|     if ((pf->flags & I40E_FLAG_MFP_ENABLED) && !(pf->hw.func_caps.iscsi)) | 
|         return; | 
|   | 
|     for (i = 0; i < old_cfg->numapps; i++) { | 
|         app = old_cfg->app[i]; | 
|         /* The APP is not available anymore delete it */ | 
|         if (!i40e_dcbnl_find_app(new_cfg, &app)) | 
|             i40e_dcbnl_del_app(pf, &app); | 
|     } | 
| } | 
|   | 
| /** | 
|  * i40e_dcbnl_setup - DCBNL setup | 
|  * @vsi: the corresponding vsi | 
|  * | 
|  * Set up DCBNL ops and initial APP TLVs | 
|  **/ | 
| void i40e_dcbnl_setup(struct i40e_vsi *vsi) | 
| { | 
|     struct net_device *dev = vsi->netdev; | 
|     struct i40e_pf *pf = i40e_netdev_to_pf(dev); | 
|   | 
|     /* Not DCB capable */ | 
|     if (!(pf->flags & I40E_FLAG_DCB_CAPABLE)) | 
|         return; | 
|   | 
|     dev->dcbnl_ops = &dcbnl_ops; | 
|   | 
|     /* Set initial IEEE DCB settings */ | 
|     i40e_dcbnl_set_all(vsi); | 
| } | 
| #endif /* CONFIG_I40E_DCB */ |