net: aquantia: synchronized flow control between mac/phy
authorIgor Russkikh <Igor.Russkikh@aquantia.com>
Fri, 9 Nov 2018 11:53:56 +0000 (11:53 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 9 Nov 2018 23:38:10 +0000 (15:38 -0800)
Flow control statuses were not synchronized between blocks,
that caused packets/link drop on some corner cases, when
MAC sent PFC although Phy was not expecting these to come.

Driver should readout the negotiated FC from phy and
configure RX block accordigly.

This is done on each link change event with information from FW.

Fixes: 288551de45aa ("net: aquantia: Implement rx/tx flow control ethtools callback")
Signed-off-by: Igor Russkikh <igor.russkikh@aquantia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/aquantia/atlantic/aq_ethtool.c
drivers/net/ethernet/aquantia/atlantic/aq_hw.h
drivers/net/ethernet/aquantia/atlantic/aq_nic.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_b0.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils_fw2x.c

index 6a633c70f603d7b70c3819b20f73d4b4f1405e64..99ef1daaa4d8027636cc5b0f7f542b7961f6764e 100644 (file)
@@ -407,13 +407,13 @@ static void aq_ethtool_get_pauseparam(struct net_device *ndev,
                                      struct ethtool_pauseparam *pause)
 {
        struct aq_nic_s *aq_nic = netdev_priv(ndev);
+       u32 fc = aq_nic->aq_nic_cfg.flow_control;
 
        pause->autoneg = 0;
 
-       if (aq_nic->aq_hw->aq_nic_cfg->flow_control & AQ_NIC_FC_RX)
-               pause->rx_pause = 1;
-       if (aq_nic->aq_hw->aq_nic_cfg->flow_control & AQ_NIC_FC_TX)
-               pause->tx_pause = 1;
+       pause->rx_pause = !!(fc & AQ_NIC_FC_RX);
+       pause->tx_pause = !!(fc & AQ_NIC_FC_TX);
+
 }
 
 static int aq_ethtool_set_pauseparam(struct net_device *ndev,
index e8689241204e9086fdb2c8750402ff7467e129ac..7ec8d24b2b0b8c1ee235fb2a3be102710d8818c1 100644 (file)
@@ -204,6 +204,7 @@ struct aq_hw_ops {
 
        int (*hw_get_fw_version)(struct aq_hw_s *self, u32 *fw_version);
 
+       int (*hw_set_fc)(struct aq_hw_s *self, u32 fc, u32 tc);
 };
 
 struct aq_fw_ops {
@@ -226,6 +227,8 @@ struct aq_fw_ops {
 
        int (*update_stats)(struct aq_hw_s *self);
 
+       u32 (*get_flow_control)(struct aq_hw_s *self, u32 *fcmode);
+
        int (*set_flow_control)(struct aq_hw_s *self);
 
        int (*set_power)(struct aq_hw_s *self, unsigned int power_state,
index 5fed244466871cd69b764ffdfcf55d92a9043763..0011a3f2f67272d27a4be0e83dc02ed2eb86f6c0 100644 (file)
@@ -124,6 +124,7 @@ void aq_nic_cfg_start(struct aq_nic_s *self)
 static int aq_nic_update_link_status(struct aq_nic_s *self)
 {
        int err = self->aq_fw_ops->update_link_status(self->aq_hw);
+       u32 fc = 0;
 
        if (err)
                return err;
@@ -133,6 +134,15 @@ static int aq_nic_update_link_status(struct aq_nic_s *self)
                        AQ_CFG_DRV_NAME, self->link_status.mbps,
                        self->aq_hw->aq_link_status.mbps);
                aq_nic_update_interrupt_moderation_settings(self);
+
+               /* Driver has to update flow control settings on RX block
+                * on any link event.
+                * We should query FW whether it negotiated FC.
+                */
+               if (self->aq_fw_ops->get_flow_control)
+                       self->aq_fw_ops->get_flow_control(self->aq_hw, &fc);
+               if (self->aq_hw_ops->hw_set_fc)
+                       self->aq_hw_ops->hw_set_fc(self->aq_hw, fc, 0);
        }
 
        self->link_status = self->aq_hw->aq_link_status;
@@ -772,7 +782,9 @@ void aq_nic_get_link_ksettings(struct aq_nic_s *self,
                ethtool_link_ksettings_add_link_mode(cmd, advertising,
                                                     Pause);
 
-       if (self->aq_nic_cfg.flow_control & AQ_NIC_FC_TX)
+       /* Asym is when either RX or TX, but not both */
+       if (!!(self->aq_nic_cfg.flow_control & AQ_NIC_FC_TX) ^
+           !!(self->aq_nic_cfg.flow_control & AQ_NIC_FC_RX))
                ethtool_link_ksettings_add_link_mode(cmd, advertising,
                                                     Asym_Pause);
 
index 76d25d594a0f62fedf507e50b58701933b1872c2..119265762b0c4800aaa8b7503e3df859e16b00ad 100644 (file)
@@ -100,12 +100,17 @@ static int hw_atl_b0_hw_reset(struct aq_hw_s *self)
        return err;
 }
 
+static int hw_atl_b0_set_fc(struct aq_hw_s *self, u32 fc, u32 tc)
+{
+       hw_atl_rpb_rx_xoff_en_per_tc_set(self, !!(fc & AQ_NIC_FC_RX), tc);
+       return 0;
+}
+
 static int hw_atl_b0_hw_qos_set(struct aq_hw_s *self)
 {
        u32 tc = 0U;
        u32 buff_size = 0U;
        unsigned int i_priority = 0U;
-       bool is_rx_flow_control = false;
 
        /* TPS Descriptor rate init */
        hw_atl_tps_tx_pkt_shed_desc_rate_curr_time_res_set(self, 0x0U);
@@ -138,7 +143,6 @@ static int hw_atl_b0_hw_qos_set(struct aq_hw_s *self)
 
        /* QoS Rx buf size per TC */
        tc = 0;
-       is_rx_flow_control = (AQ_NIC_FC_RX & self->aq_nic_cfg->flow_control);
        buff_size = HW_ATL_B0_RXBUF_MAX;
 
        hw_atl_rpb_rx_pkt_buff_size_per_tc_set(self, buff_size, tc);
@@ -150,7 +154,8 @@ static int hw_atl_b0_hw_qos_set(struct aq_hw_s *self)
                                                   (buff_size *
                                                   (1024U / 32U) * 50U) /
                                                   100U, tc);
-       hw_atl_rpb_rx_xoff_en_per_tc_set(self, is_rx_flow_control ? 1U : 0U, tc);
+
+       hw_atl_b0_set_fc(self, self->aq_nic_cfg->flow_control, tc);
 
        /* QoS 802.1p priority -> TC mapping */
        for (i_priority = 8U; i_priority--;)
@@ -963,4 +968,5 @@ const struct aq_hw_ops hw_atl_ops_b0 = {
        .hw_get_regs                 = hw_atl_utils_hw_get_regs,
        .hw_get_hw_stats             = hw_atl_utils_get_hw_stats,
        .hw_get_fw_version           = hw_atl_utils_get_fw_version,
+       .hw_set_fc                   = hw_atl_b0_set_fc,
 };
index 096ca5730887c1d4a47861c3a6a456cd240b2bd1..7de3220d9cab7bf99109145c1ccef4a3ea898d87 100644 (file)
@@ -30,6 +30,8 @@
 #define HW_ATL_FW2X_MPI_STATE_ADDR     0x370
 #define HW_ATL_FW2X_MPI_STATE2_ADDR    0x374
 
+#define HW_ATL_FW2X_CAP_PAUSE            BIT(CAPS_HI_PAUSE)
+#define HW_ATL_FW2X_CAP_ASYM_PAUSE       BIT(CAPS_HI_ASYMMETRIC_PAUSE)
 #define HW_ATL_FW2X_CAP_SLEEP_PROXY      BIT(CAPS_HI_SLEEP_PROXY)
 #define HW_ATL_FW2X_CAP_WOL              BIT(CAPS_HI_WOL)
 
@@ -451,6 +453,24 @@ static int aq_fw2x_set_flow_control(struct aq_hw_s *self)
        return 0;
 }
 
+static u32 aq_fw2x_get_flow_control(struct aq_hw_s *self, u32 *fcmode)
+{
+       u32 mpi_state = aq_hw_read_reg(self, HW_ATL_FW2X_MPI_STATE2_ADDR);
+
+       if (mpi_state & HW_ATL_FW2X_CAP_PAUSE)
+               if (mpi_state & HW_ATL_FW2X_CAP_ASYM_PAUSE)
+                       *fcmode = AQ_NIC_FC_RX;
+               else
+                       *fcmode = AQ_NIC_FC_RX | AQ_NIC_FC_TX;
+       else
+               if (mpi_state & HW_ATL_FW2X_CAP_ASYM_PAUSE)
+                       *fcmode = AQ_NIC_FC_TX;
+               else
+                       *fcmode = 0;
+
+       return 0;
+}
+
 const struct aq_fw_ops aq_fw_2x_ops = {
        .init = aq_fw2x_init,
        .deinit = aq_fw2x_deinit,
@@ -465,4 +485,5 @@ const struct aq_fw_ops aq_fw_2x_ops = {
        .set_eee_rate = aq_fw2x_set_eee_rate,
        .get_eee_rate = aq_fw2x_get_eee_rate,
        .set_flow_control = aq_fw2x_set_flow_control,
+       .get_flow_control = aq_fw2x_get_flow_control
 };