| // 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 */ |