net: ethernet: mediatek: add ethtool functions to configure RX flows of HW LRO
authorNelson Chang <nelson.chang@mediatek.com>
Sat, 17 Sep 2016 15:50:56 +0000 (23:50 +0800)
committerDavid S. Miller <davem@davemloft.net>
Mon, 19 Sep 2016 05:40:47 +0000 (01:40 -0400)
The codes add ethtool functions to set RX flows for HW LRO. Because the
HW LRO hardware can only recognize the destination IP of TCP/IP RX flows,
the ethtool command to add HW LRO flow is as below:
ethtool -N [devname] flow-type tcp4 dst-ip [ip_addr] loc [0~1]

Otherwise, cause the hardware can set total four destination IPs, each
GMAC (GMAC1/GMAC2) can set two IPs separately at most.

Signed-off-by: Nelson Chang <nelson.chang@mediatek.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mediatek/mtk_eth_soc.c

index 18600cbbb907aa97d22d909cc22c4b46a0be3d80..481f3609a1ea23a5bc727a173528696d2ea59ac2 100644 (file)
@@ -1357,6 +1357,182 @@ static void mtk_hwlro_rx_uninit(struct mtk_eth *eth)
        mtk_w32(eth, 0, MTK_PDMA_LRO_CTRL_DW0);
 }
 
+static void mtk_hwlro_val_ipaddr(struct mtk_eth *eth, int idx, __be32 ip)
+{
+       u32 reg_val;
+
+       reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
+
+       /* invalidate the IP setting */
+       mtk_w32(eth, (reg_val & ~MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+
+       mtk_w32(eth, ip, MTK_LRO_DIP_DW0_CFG(idx));
+
+       /* validate the IP setting */
+       mtk_w32(eth, (reg_val | MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+}
+
+static void mtk_hwlro_inval_ipaddr(struct mtk_eth *eth, int idx)
+{
+       u32 reg_val;
+
+       reg_val = mtk_r32(eth, MTK_LRO_CTRL_DW2_CFG(idx));
+
+       /* invalidate the IP setting */
+       mtk_w32(eth, (reg_val & ~MTK_RING_MYIP_VLD), MTK_LRO_CTRL_DW2_CFG(idx));
+
+       mtk_w32(eth, 0, MTK_LRO_DIP_DW0_CFG(idx));
+}
+
+static int mtk_hwlro_get_ip_cnt(struct mtk_mac *mac)
+{
+       int cnt = 0;
+       int i;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               if (mac->hwlro_ip[i])
+                       cnt++;
+       }
+
+       return cnt;
+}
+
+static int mtk_hwlro_add_ipaddr(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int hwlro_idx;
+
+       if ((fsp->flow_type != TCP_V4_FLOW) ||
+           (!fsp->h_u.tcp_ip4_spec.ip4dst) ||
+           (fsp->location > 1))
+               return -EINVAL;
+
+       mac->hwlro_ip[fsp->location] = htonl(fsp->h_u.tcp_ip4_spec.ip4dst);
+       hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + fsp->location;
+
+       mac->hwlro_ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+       mtk_hwlro_val_ipaddr(eth, hwlro_idx, mac->hwlro_ip[fsp->location]);
+
+       return 0;
+}
+
+static int mtk_hwlro_del_ipaddr(struct net_device *dev,
+                               struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int hwlro_idx;
+
+       if (fsp->location > 1)
+               return -EINVAL;
+
+       mac->hwlro_ip[fsp->location] = 0;
+       hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + fsp->location;
+
+       mac->hwlro_ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+       mtk_hwlro_inval_ipaddr(eth, hwlro_idx);
+
+       return 0;
+}
+
+static void mtk_hwlro_netdev_disable(struct net_device *dev)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct mtk_eth *eth = mac->hw;
+       int i, hwlro_idx;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               mac->hwlro_ip[i] = 0;
+               hwlro_idx = (mac->id * MTK_MAX_LRO_IP_CNT) + i;
+
+               mtk_hwlro_inval_ipaddr(eth, hwlro_idx);
+       }
+
+       mac->hwlro_ip_cnt = 0;
+}
+
+static int mtk_hwlro_get_fdir_entry(struct net_device *dev,
+                                   struct ethtool_rxnfc *cmd)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+
+       /* only tcp dst ipv4 is meaningful, others are meaningless */
+       fsp->flow_type = TCP_V4_FLOW;
+       fsp->h_u.tcp_ip4_spec.ip4dst = ntohl(mac->hwlro_ip[fsp->location]);
+       fsp->m_u.tcp_ip4_spec.ip4dst = 0;
+
+       fsp->h_u.tcp_ip4_spec.ip4src = 0;
+       fsp->m_u.tcp_ip4_spec.ip4src = 0xffffffff;
+       fsp->h_u.tcp_ip4_spec.psrc = 0;
+       fsp->m_u.tcp_ip4_spec.psrc = 0xffff;
+       fsp->h_u.tcp_ip4_spec.pdst = 0;
+       fsp->m_u.tcp_ip4_spec.pdst = 0xffff;
+       fsp->h_u.tcp_ip4_spec.tos = 0;
+       fsp->m_u.tcp_ip4_spec.tos = 0xff;
+
+       return 0;
+}
+
+static int mtk_hwlro_get_fdir_all(struct net_device *dev,
+                                 struct ethtool_rxnfc *cmd,
+                                 u32 *rule_locs)
+{
+       struct mtk_mac *mac = netdev_priv(dev);
+       int cnt = 0;
+       int i;
+
+       for (i = 0; i < MTK_MAX_LRO_IP_CNT; i++) {
+               if (mac->hwlro_ip[i]) {
+                       rule_locs[cnt] = i;
+                       cnt++;
+               }
+       }
+
+       cmd->rule_cnt = cnt;
+
+       return 0;
+}
+
+static netdev_features_t mtk_fix_features(struct net_device *dev,
+                                         netdev_features_t features)
+{
+       if (!(features & NETIF_F_LRO)) {
+               struct mtk_mac *mac = netdev_priv(dev);
+               int ip_cnt = mtk_hwlro_get_ip_cnt(mac);
+
+               if (ip_cnt) {
+                       netdev_info(dev, "RX flow is programmed, LRO should keep on\n");
+
+                       features |= NETIF_F_LRO;
+               }
+       }
+
+       return features;
+}
+
+static int mtk_set_features(struct net_device *dev, netdev_features_t features)
+{
+       int err = 0;
+
+       if (!((dev->features ^ features) & NETIF_F_LRO))
+               return 0;
+
+       if (!(features & NETIF_F_LRO))
+               mtk_hwlro_netdev_disable(dev);
+
+       return err;
+}
+
 /* wait for DMA to finish whatever it is doing before we start using it again */
 static int mtk_dma_busy_wait(struct mtk_eth *eth)
 {
@@ -1971,6 +2147,62 @@ static void mtk_get_ethtool_stats(struct net_device *dev,
        } while (u64_stats_fetch_retry_irq(&hwstats->syncp, start));
 }
 
+static int mtk_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+                        u32 *rule_locs)
+{
+       int ret = -EOPNOTSUPP;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_GRXRINGS:
+               if (dev->features & NETIF_F_LRO) {
+                       cmd->data = MTK_MAX_RX_RING_NUM;
+                       ret = 0;
+               }
+               break;
+       case ETHTOOL_GRXCLSRLCNT:
+               if (dev->features & NETIF_F_LRO) {
+                       struct mtk_mac *mac = netdev_priv(dev);
+
+                       cmd->rule_cnt = mac->hwlro_ip_cnt;
+                       ret = 0;
+               }
+               break;
+       case ETHTOOL_GRXCLSRULE:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_get_fdir_entry(dev, cmd);
+               break;
+       case ETHTOOL_GRXCLSRLALL:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_get_fdir_all(dev, cmd,
+                                                    rule_locs);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static int mtk_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+       int ret = -EOPNOTSUPP;
+
+       switch (cmd->cmd) {
+       case ETHTOOL_SRXCLSRLINS:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_add_ipaddr(dev, cmd);
+               break;
+       case ETHTOOL_SRXCLSRLDEL:
+               if (dev->features & NETIF_F_LRO)
+                       ret = mtk_hwlro_del_ipaddr(dev, cmd);
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
 static const struct ethtool_ops mtk_ethtool_ops = {
        .get_settings           = mtk_get_settings,
        .set_settings           = mtk_set_settings,
@@ -1982,6 +2214,8 @@ static const struct ethtool_ops mtk_ethtool_ops = {
        .get_strings            = mtk_get_strings,
        .get_sset_count         = mtk_get_sset_count,
        .get_ethtool_stats      = mtk_get_ethtool_stats,
+       .get_rxnfc              = mtk_get_rxnfc,
+       .set_rxnfc              = mtk_set_rxnfc,
 };
 
 static const struct net_device_ops mtk_netdev_ops = {
@@ -1996,6 +2230,8 @@ static const struct net_device_ops mtk_netdev_ops = {
        .ndo_change_mtu         = eth_change_mtu,
        .ndo_tx_timeout         = mtk_tx_timeout,
        .ndo_get_stats64        = mtk_get_stats64,
+       .ndo_fix_features       = mtk_fix_features,
+       .ndo_set_features       = mtk_set_features,
 #ifdef CONFIG_NET_POLL_CONTROLLER
        .ndo_poll_controller    = mtk_poll_controller,
 #endif