net/hns3: support fixed link speed
[dpdk.git] / drivers / net / hns3 / hns3_ethdev.c
index fe46d78..1832d26 100644 (file)
@@ -2473,12 +2473,6 @@ hns3_dev_configure(struct rte_eth_dev *dev)
        }
 
        hw->adapter_state = HNS3_NIC_CONFIGURING;
-       if (conf->link_speeds & ETH_LINK_SPEED_FIXED) {
-               hns3_err(hw, "setting link speed/duplex not supported");
-               ret = -EINVAL;
-               goto cfg_err;
-       }
-
        if ((uint32_t)mq_mode & ETH_MQ_RX_DCB_FLAG) {
                ret = hns3_check_dcb_cfg(dev);
                if (ret)
@@ -2850,8 +2844,7 @@ hns3_setup_linkstatus(struct rte_eth_dev *eth_dev,
 
        new_link->link_duplex = mac->link_duplex;
        new_link->link_status = mac->link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
-       new_link->link_autoneg =
-           !(eth_dev->data->dev_conf.link_speeds & ETH_LINK_SPEED_FIXED);
+       new_link->link_autoneg = mac->link_autoneg;
 }
 
 static int
@@ -4605,6 +4598,9 @@ hns3_get_sfp_info(struct hns3_hw *hw, struct hns3_mac *mac_info)
                mac_info->query_type = HNS3_ACTIVE_QUERY;
                mac_info->supported_speed =
                                        rte_le_to_cpu_32(resp->supported_speed);
+               mac_info->support_autoneg = resp->autoneg_ability;
+               mac_info->link_autoneg = (resp->autoneg == 0) ? ETH_LINK_FIXED
+                                       : ETH_LINK_AUTONEG;
        } else {
                mac_info->query_type = HNS3_DEFAULT_QUERY;
        }
@@ -4685,6 +4681,8 @@ hns3_update_fiber_link_info(struct hns3_hw *hw)
 
                mac->link_speed = mac_info.link_speed;
                mac->supported_speed = mac_info.supported_speed;
+               mac->support_autoneg = mac_info.support_autoneg;
+               mac->link_autoneg = mac_info.link_autoneg;
 
                return 0;
        }
@@ -5084,6 +5082,24 @@ hns3_get_port_supported_speed(struct rte_eth_dev *eth_dev)
        return 0;
 }
 
+static void
+hns3_get_fc_autoneg_capability(struct hns3_adapter *hns)
+{
+       struct hns3_mac *mac = &hns->hw.mac;
+
+       if (mac->media_type == HNS3_MEDIA_TYPE_COPPER) {
+               hns->pf.support_fc_autoneg = true;
+               return;
+       }
+
+       /*
+        * Flow control auto-negotiation requires the cooperation of the driver
+        * and firmware. Currently, the optical port does not support flow
+        * control auto-negotiation.
+        */
+       hns->pf.support_fc_autoneg = false;
+}
+
 static int
 hns3_init_pf(struct rte_eth_dev *eth_dev)
 {
@@ -5191,6 +5207,8 @@ hns3_init_pf(struct rte_eth_dev *eth_dev)
                goto err_supported_speed;
        }
 
+       hns3_get_fc_autoneg_capability(hns);
+
        hns3_tm_conf_init(eth_dev);
 
        return 0;
@@ -5247,6 +5265,276 @@ hns3_uninit_pf(struct rte_eth_dev *eth_dev)
        hw->io_base = NULL;
 }
 
+static uint32_t
+hns3_convert_link_speeds2bitmap_copper(uint32_t link_speeds)
+{
+       uint32_t speed_bit;
+
+       switch (link_speeds & ~ETH_LINK_SPEED_FIXED) {
+       case ETH_LINK_SPEED_10M:
+               speed_bit = HNS3_PHY_LINK_SPEED_10M_BIT;
+               break;
+       case ETH_LINK_SPEED_10M_HD:
+               speed_bit = HNS3_PHY_LINK_SPEED_10M_HD_BIT;
+               break;
+       case ETH_LINK_SPEED_100M:
+               speed_bit = HNS3_PHY_LINK_SPEED_100M_BIT;
+               break;
+       case ETH_LINK_SPEED_100M_HD:
+               speed_bit = HNS3_PHY_LINK_SPEED_100M_HD_BIT;
+               break;
+       case ETH_LINK_SPEED_1G:
+               speed_bit = HNS3_PHY_LINK_SPEED_1000M_BIT;
+               break;
+       default:
+               speed_bit = 0;
+               break;
+       }
+
+       return speed_bit;
+}
+
+static uint32_t
+hns3_convert_link_speeds2bitmap_fiber(uint32_t link_speeds)
+{
+       uint32_t speed_bit;
+
+       switch (link_speeds & ~ETH_LINK_SPEED_FIXED) {
+       case ETH_LINK_SPEED_1G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_1G_BIT;
+               break;
+       case ETH_LINK_SPEED_10G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_10G_BIT;
+               break;
+       case ETH_LINK_SPEED_25G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_25G_BIT;
+               break;
+       case ETH_LINK_SPEED_40G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_40G_BIT;
+               break;
+       case ETH_LINK_SPEED_50G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_50G_BIT;
+               break;
+       case ETH_LINK_SPEED_100G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_100G_BIT;
+               break;
+       case ETH_LINK_SPEED_200G:
+               speed_bit = HNS3_FIBER_LINK_SPEED_200G_BIT;
+               break;
+       default:
+               speed_bit = 0;
+               break;
+       }
+
+       return speed_bit;
+}
+
+static int
+hns3_check_port_speed(struct hns3_hw *hw, uint32_t link_speeds)
+{
+       struct hns3_mac *mac = &hw->mac;
+       uint32_t supported_speed = mac->supported_speed;
+       uint32_t speed_bit = 0;
+
+       if (mac->media_type == HNS3_MEDIA_TYPE_COPPER)
+               speed_bit = hns3_convert_link_speeds2bitmap_copper(link_speeds);
+       else if (mac->media_type == HNS3_MEDIA_TYPE_FIBER)
+               speed_bit = hns3_convert_link_speeds2bitmap_fiber(link_speeds);
+
+       if (!(speed_bit & supported_speed)) {
+               hns3_err(hw, "link_speeds(0x%x) exceeds the supported speed capability or is incorrect.",
+                        link_speeds);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static inline uint32_t
+hns3_get_link_speed(uint32_t link_speeds)
+{
+       uint32_t speed = ETH_SPEED_NUM_NONE;
+
+       if (link_speeds & ETH_LINK_SPEED_10M ||
+           link_speeds & ETH_LINK_SPEED_10M_HD)
+               speed = ETH_SPEED_NUM_10M;
+       if (link_speeds & ETH_LINK_SPEED_100M ||
+           link_speeds & ETH_LINK_SPEED_100M_HD)
+               speed = ETH_SPEED_NUM_100M;
+       if (link_speeds & ETH_LINK_SPEED_1G)
+               speed = ETH_SPEED_NUM_1G;
+       if (link_speeds & ETH_LINK_SPEED_10G)
+               speed = ETH_SPEED_NUM_10G;
+       if (link_speeds & ETH_LINK_SPEED_25G)
+               speed = ETH_SPEED_NUM_25G;
+       if (link_speeds & ETH_LINK_SPEED_40G)
+               speed = ETH_SPEED_NUM_40G;
+       if (link_speeds & ETH_LINK_SPEED_50G)
+               speed = ETH_SPEED_NUM_50G;
+       if (link_speeds & ETH_LINK_SPEED_100G)
+               speed = ETH_SPEED_NUM_100G;
+       if (link_speeds & ETH_LINK_SPEED_200G)
+               speed = ETH_SPEED_NUM_200G;
+
+       return speed;
+}
+
+static uint8_t
+hns3_get_link_duplex(uint32_t link_speeds)
+{
+       if ((link_speeds & ETH_LINK_SPEED_10M_HD) ||
+           (link_speeds & ETH_LINK_SPEED_100M_HD))
+               return ETH_LINK_HALF_DUPLEX;
+       else
+               return ETH_LINK_FULL_DUPLEX;
+}
+
+static int
+hns3_set_copper_port_link_speed(struct hns3_hw *hw,
+                               struct hns3_set_link_speed_cfg *cfg)
+{
+       struct hns3_cmd_desc desc[HNS3_PHY_PARAM_CFG_BD_NUM];
+       struct hns3_phy_params_bd0_cmd *req;
+       uint16_t i;
+
+       for (i = 0; i < HNS3_PHY_PARAM_CFG_BD_NUM - 1; i++) {
+               hns3_cmd_setup_basic_desc(&desc[i], HNS3_OPC_PHY_PARAM_CFG,
+                                         false);
+               desc[i].flag |= rte_cpu_to_le_16(HNS3_CMD_FLAG_NEXT);
+       }
+       hns3_cmd_setup_basic_desc(&desc[i], HNS3_OPC_PHY_PARAM_CFG, false);
+       req = (struct hns3_phy_params_bd0_cmd *)desc[0].data;
+       req->autoneg = cfg->autoneg;
+
+       /*
+        * The full speed capability is used to negotiate when
+        * auto-negotiation is enabled.
+        */
+       if (cfg->autoneg) {
+               req->advertising = HNS3_PHY_LINK_SPEED_10M_BIT |
+                                   HNS3_PHY_LINK_SPEED_10M_HD_BIT |
+                                   HNS3_PHY_LINK_SPEED_100M_BIT |
+                                   HNS3_PHY_LINK_SPEED_100M_HD_BIT |
+                                   HNS3_PHY_LINK_SPEED_1000M_BIT;
+       } else {
+               req->speed = cfg->speed;
+               req->duplex = cfg->duplex;
+       }
+
+       return hns3_cmd_send(hw, desc, HNS3_PHY_PARAM_CFG_BD_NUM);
+}
+
+static int
+hns3_set_autoneg(struct hns3_hw *hw, bool enable)
+{
+       struct hns3_config_auto_neg_cmd *req;
+       struct hns3_cmd_desc desc;
+       uint32_t flag = 0;
+       int ret;
+
+       hns3_cmd_setup_basic_desc(&desc, HNS3_OPC_CONFIG_AN_MODE, false);
+
+       req = (struct hns3_config_auto_neg_cmd *)desc.data;
+       if (enable)
+               hns3_set_bit(flag, HNS3_MAC_CFG_AN_EN_B, 1);
+       req->cfg_an_cmd_flag = rte_cpu_to_le_32(flag);
+
+       ret = hns3_cmd_send(hw, &desc, 1);
+       if (ret)
+               hns3_err(hw, "autoneg set cmd failed, ret = %d.", ret);
+
+       return ret;
+}
+
+static int
+hns3_set_fiber_port_link_speed(struct hns3_hw *hw,
+                              struct hns3_set_link_speed_cfg *cfg)
+{
+       int ret;
+
+       if (hw->mac.support_autoneg) {
+               ret = hns3_set_autoneg(hw, cfg->autoneg);
+               if (ret) {
+                       hns3_err(hw, "failed to configure auto-negotiation.");
+                       return ret;
+               }
+
+               /*
+                * To enable auto-negotiation, we only need to open the switch
+                * of auto-negotiation, then firmware sets all speed
+                * capabilities.
+                */
+               if (cfg->autoneg)
+                       return 0;
+       }
+
+       /*
+        * Some hardware doesn't support auto-negotiation, but users may not
+        * configure link_speeds (default 0), which means auto-negotiation
+        * In this case, a warning message need to be printed, instead of
+        * an error.
+        */
+       if (cfg->autoneg) {
+               hns3_warn(hw, "auto-negotiation is not supported.");
+               return 0;
+       }
+
+       return hns3_cfg_mac_speed_dup(hw, cfg->speed, cfg->duplex);
+}
+
+static int
+hns3_set_port_link_speed(struct hns3_hw *hw,
+                        struct hns3_set_link_speed_cfg *cfg)
+{
+       int ret;
+
+       if (hw->mac.media_type == HNS3_MEDIA_TYPE_COPPER) {
+#if defined(RTE_HNS3_ONLY_1630_FPGA)
+               struct hns3_pf *pf = HNS3_DEV_HW_TO_PF(hw);
+               if (pf->is_tmp_phy)
+                       return 0;
+#endif
+
+               ret = hns3_set_copper_port_link_speed(hw, cfg);
+               if (ret) {
+                       hns3_err(hw, "failed to set copper port link speed,"
+                                "ret = %d.", ret);
+                       return ret;
+               }
+       } else if (hw->mac.media_type == HNS3_MEDIA_TYPE_FIBER) {
+               ret = hns3_set_fiber_port_link_speed(hw, cfg);
+               if (ret) {
+                       hns3_err(hw, "failed to set fiber port link speed,"
+                                "ret = %d.", ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int
+hns3_apply_link_speed(struct hns3_hw *hw)
+{
+       struct rte_eth_conf *conf = &hw->data->dev_conf;
+       struct hns3_set_link_speed_cfg cfg;
+       int ret;
+
+       memset(&cfg, 0, sizeof(struct hns3_set_link_speed_cfg));
+       cfg.autoneg = (conf->link_speeds == ETH_LINK_SPEED_AUTONEG) ?
+                       ETH_LINK_AUTONEG : ETH_LINK_FIXED;
+       if (cfg.autoneg != ETH_LINK_AUTONEG) {
+               ret = hns3_check_port_speed(hw, conf->link_speeds);
+               if (ret)
+                       return ret;
+
+               cfg.speed = hns3_get_link_speed(conf->link_speeds);
+               cfg.duplex = hns3_get_link_duplex(conf->link_speeds);
+       }
+
+       return hns3_set_port_link_speed(hw, &cfg);
+}
+
 static int
 hns3_do_start(struct hns3_adapter *hns, bool reset_queue)
 {
@@ -5280,9 +5568,15 @@ hns3_do_start(struct hns3_adapter *hns, bool reset_queue)
                PMD_INIT_LOG(ERR, "failed to enable MAC, ret = %d", ret);
                goto err_config_mac_mode;
        }
+
+       ret = hns3_apply_link_speed(hw);
+       if (ret)
+               goto err_config_mac_mode;
+
        return 0;
 
 err_config_mac_mode:
+       (void)hns3_cfg_mac_mode(hw, false);
        hns3_dev_release_mbufs(hns);
        /*
         * Here is exception handling, hns3_reset_all_tqps will have the
@@ -5613,19 +5907,102 @@ hns3_dev_close(struct rte_eth_dev *eth_dev)
        return ret;
 }
 
-static int
-hns3_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+static void
+hns3_get_autoneg_rxtx_pause_copper(struct hns3_hw *hw, bool *rx_pause,
+                                  bool *tx_pause)
+{
+       struct hns3_mac *mac = &hw->mac;
+       uint32_t advertising = mac->advertising;
+       uint32_t lp_advertising = mac->lp_advertising;
+       *rx_pause = false;
+       *tx_pause = false;
+
+       if (advertising & lp_advertising & HNS3_PHY_LINK_MODE_PAUSE_BIT) {
+               *rx_pause = true;
+               *tx_pause = true;
+       } else if (advertising & lp_advertising &
+                  HNS3_PHY_LINK_MODE_ASYM_PAUSE_BIT) {
+               if (advertising & HNS3_PHY_LINK_MODE_PAUSE_BIT)
+                       *rx_pause = true;
+               else if (lp_advertising & HNS3_PHY_LINK_MODE_PAUSE_BIT)
+                       *tx_pause = true;
+       }
+}
+
+static enum hns3_fc_mode
+hns3_get_autoneg_fc_mode(struct hns3_hw *hw)
+{
+       enum hns3_fc_mode current_mode;
+       bool rx_pause = false;
+       bool tx_pause = false;
+
+       switch (hw->mac.media_type) {
+       case HNS3_MEDIA_TYPE_COPPER:
+               hns3_get_autoneg_rxtx_pause_copper(hw, &rx_pause, &tx_pause);
+               break;
+
+       /*
+        * Flow control auto-negotiation is not supported for fiber and
+        * backpalne media type.
+        */
+       case HNS3_MEDIA_TYPE_FIBER:
+       case HNS3_MEDIA_TYPE_BACKPLANE:
+               hns3_err(hw, "autoneg FC mode can't be obtained, but flow control auto-negotiation is enabled.");
+               current_mode = hw->requested_fc_mode;
+               goto out;
+       default:
+               hns3_err(hw, "autoneg FC mode can't be obtained for unknown media type(%u).",
+                        hw->mac.media_type);
+               current_mode = HNS3_FC_NONE;
+               goto out;
+       }
+
+       if (rx_pause && tx_pause)
+               current_mode = HNS3_FC_FULL;
+       else if (rx_pause)
+               current_mode = HNS3_FC_RX_PAUSE;
+       else if (tx_pause)
+               current_mode = HNS3_FC_TX_PAUSE;
+       else
+               current_mode = HNS3_FC_NONE;
+
+out:
+       return current_mode;
+}
+
+static enum hns3_fc_mode
+hns3_get_current_fc_mode(struct rte_eth_dev *dev)
 {
        struct hns3_hw *hw = HNS3_DEV_PRIVATE_TO_HW(dev->data->dev_private);
        struct hns3_pf *pf = HNS3_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+       struct hns3_mac *mac = &hw->mac;
 
-       fc_conf->pause_time = pf->pause_time;
+       /*
+        * When the flow control mode is obtained, the device may not complete
+        * auto-negotiation. It is necessary to wait for link establishment.
+        */
+       (void)hns3_dev_link_update(dev, 1);
 
        /*
-        * If fc auto-negotiation is not supported, the configured fc mode
-        * from user is the current fc mode.
+        * If the link auto-negotiation of the nic is disabled, or the flow
+        * control auto-negotiation is not supported, the forced flow control
+        * mode is used.
         */
-       switch (hw->requested_fc_mode) {
+       if (mac->link_autoneg == 0 || !pf->support_fc_autoneg)
+               return hw->requested_fc_mode;
+
+       return hns3_get_autoneg_fc_mode(hw);
+}
+
+static int
+hns3_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+       struct hns3_hw *hw = HNS3_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+       struct hns3_pf *pf = HNS3_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+       enum hns3_fc_mode current_mode;
+
+       current_mode = hns3_get_current_fc_mode(dev);
+       switch (current_mode) {
        case HNS3_FC_FULL:
                fc_conf->mode = RTE_FC_FULL;
                break;
@@ -5641,6 +6018,9 @@ hns3_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
                break;
        }
 
+       fc_conf->pause_time = pf->pause_time;
+       fc_conf->autoneg = pf->support_fc_autoneg ? hw->mac.link_autoneg : 0;
+
        return 0;
 }
 
@@ -5668,6 +6048,41 @@ hns3_get_fc_mode(struct hns3_hw *hw, enum rte_eth_fc_mode mode)
        }
 }
 
+static int
+hns3_check_fc_autoneg_valid(struct hns3_hw *hw, uint8_t autoneg)
+{
+       struct hns3_pf *pf = HNS3_DEV_HW_TO_PF(hw);
+
+       if (!pf->support_fc_autoneg) {
+               if (autoneg != 0) {
+                       hns3_err(hw, "unsupported fc auto-negotiation setting.");
+                       return -EOPNOTSUPP;
+               }
+
+               /*
+                * Flow control auto-negotiation of the NIC is not supported,
+                * but other auto-negotiation features may be supported.
+                */
+               if (autoneg != hw->mac.link_autoneg) {
+                       hns3_err(hw, "please use 'link_speeds' in struct rte_eth_conf to disable autoneg!");
+                       return -EOPNOTSUPP;
+               }
+
+               return 0;
+       }
+
+       /*
+        * If flow control auto-negotiation of the NIC is supported, all
+        * auto-negotiation features are supported.
+        */
+       if (autoneg != hw->mac.link_autoneg) {
+               hns3_err(hw, "please use 'link_speeds' in struct rte_eth_conf to change autoneg!");
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
 static int
 hns3_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
 {
@@ -5683,10 +6098,11 @@ hns3_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
                         fc_conf->send_xon, fc_conf->mac_ctrl_frame_fwd);
                return -EINVAL;
        }
-       if (fc_conf->autoneg) {
-               hns3_err(hw, "Unsupported fc auto-negotiation setting.");
-               return -EINVAL;
-       }
+
+       ret = hns3_check_fc_autoneg_valid(hw, fc_conf->autoneg);
+       if (ret)
+               return ret;
+
        if (!fc_conf->pause_time) {
                hns3_err(hw, "Invalid pause time %u setting.",
                         fc_conf->pause_time);