// SPDX-License-Identifier: GPL-2.0-only /* ################################################################################ # # r8168 is the Linux device driver released for Realtek Gigabit Ethernet # controllers with PCI-Express interface. # # Copyright(c) 2024 Realtek Semiconductor Corp. All rights reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see . # # Author: # Realtek NIC software team # No. 2, Innovation Road II, Hsinchu Science Park, Hsinchu 300, Taiwan # ################################################################################ */ /************************************************************************************ * This product is covered by one or more of the following patents: * US6,570,884, US6,115,776, and US6,327,625. ***********************************************************************************/ #include #include "r8168.h" enum rtl8168_rss_register_content { /* RSS */ RSS_CTRL_TCP_IPV4_SUPP = (1 << 0), RSS_CTRL_IPV4_SUPP = (1 << 1), RSS_CTRL_TCP_IPV6_SUPP = (1 << 2), RSS_CTRL_IPV6_SUPP = (1 << 3), RSS_CTRL_IPV6_EXT_SUPP = (1 << 4), RSS_CTRL_TCP_IPV6_EXT_SUPP = (1 << 5), RSS_HALF_SUPP = (1 << 7), RSS_QUAD_CPU_EN = (1 << 16), RSS_HQ_Q_SUP_R = (1 << 31), }; static int rtl8168_get_rss_hash_opts(struct rtl8168_private *tp, struct ethtool_rxnfc *cmd) { cmd->data = 0; /* Report default options for RSS */ switch (cmd->flow_type) { case TCP_V4_FLOW: cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3; fallthrough; case IPV4_FLOW: cmd->data |= RXH_IP_SRC | RXH_IP_DST; break; case TCP_V6_FLOW: cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3; fallthrough; case IPV6_FLOW: cmd->data |= RXH_IP_SRC | RXH_IP_DST; break; default: return -EINVAL; } return 0; } int rtl8168_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd, u32 *rule_locs) { struct rtl8168_private *tp = netdev_priv(dev); int ret = -EOPNOTSUPP; netif_info(tp, drv, tp->dev, "rss get rxnfc\n"); if (!(dev->features & NETIF_F_RXHASH)) return ret; switch (cmd->cmd) { case ETHTOOL_GRXRINGS: cmd->data = rtl8168_tot_rx_rings(tp); ret = 0; break; case ETHTOOL_GRXFH: ret = rtl8168_get_rss_hash_opts(tp, cmd); break; default: break; } return ret; } u32 rtl8168_rss_indir_tbl_entries(struct rtl8168_private *tp) { return tp->HwSuppIndirTblEntries; } #define RSS_MASK_BITS_OFFSET (8) static int _rtl8168_set_rss_hash_opt(struct rtl8168_private *tp) { u32 hash_mask_len; u32 rss_ctrl; /* Perform hash on these packet types */ rss_ctrl = RSS_CTRL_TCP_IPV4_SUPP | RSS_CTRL_IPV4_SUPP | RSS_CTRL_IPV6_SUPP | RSS_CTRL_IPV6_EXT_SUPP | RSS_CTRL_TCP_IPV6_SUPP | RSS_CTRL_TCP_IPV6_EXT_SUPP; if (R8168_MULTI_RSS_4Q(tp)) rss_ctrl |= RSS_QUAD_CPU_EN; hash_mask_len = ilog2(rtl8168_rss_indir_tbl_entries(tp)); hash_mask_len &= (BIT_0 | BIT_1 | BIT_2); rss_ctrl |= hash_mask_len << RSS_MASK_BITS_OFFSET; rtl8168_eri_write(tp, RSS_CTRL_8168, 4, rss_ctrl, ERIAR_ExGMAC); return 0; } static int rtl8168_set_rss_hash_opt(struct rtl8168_private *tp, struct ethtool_rxnfc *nfc) { u32 rss_flags = tp->rss_flags; netif_info(tp, drv, tp->dev, "rss set hash\n"); /* * RSS does not support anything other than hashing * to queues on src and dst IPs and ports */ if (nfc->data & ~(RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3)) return -EINVAL; switch (nfc->flow_type) { case TCP_V4_FLOW: case TCP_V6_FLOW: if (!(nfc->data & RXH_IP_SRC) || !(nfc->data & RXH_IP_DST) || !(nfc->data & RXH_L4_B_0_1) || !(nfc->data & RXH_L4_B_2_3)) return -EINVAL; break; case SCTP_V4_FLOW: case AH_ESP_V4_FLOW: case AH_V4_FLOW: case ESP_V4_FLOW: case SCTP_V6_FLOW: case AH_ESP_V6_FLOW: case AH_V6_FLOW: case ESP_V6_FLOW: case IP_USER_FLOW: case ETHER_FLOW: /* RSS is not supported for these protocols */ if (nfc->data) { netif_err(tp, drv, tp->dev, "Command parameters not supported\n"); return -EINVAL; } return 0; default: return -EINVAL; } /* if we changed something we need to update flags */ if (rss_flags != tp->rss_flags) { u32 rss_ctrl = rtl8168_eri_read(tp, RSS_CTRL_8168, 4, ERIAR_ExGMAC); tp->rss_flags = rss_flags; /* Perform hash on these packet types */ rss_ctrl |= RSS_CTRL_TCP_IPV4_SUPP | RSS_CTRL_IPV4_SUPP | RSS_CTRL_IPV6_SUPP | RSS_CTRL_IPV6_EXT_SUPP | RSS_CTRL_TCP_IPV6_SUPP | RSS_CTRL_TCP_IPV6_EXT_SUPP; if (R8168_MULTI_RSS_4Q(tp)) rss_ctrl |= RSS_QUAD_CPU_EN; else rss_ctrl &= ~RSS_QUAD_CPU_EN; rtl8168_eri_write(tp, RSS_CTRL_8168, 4, rss_ctrl, ERIAR_ExGMAC); } return 0; } int rtl8168_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd) { struct rtl8168_private *tp = netdev_priv(dev); int ret = -EOPNOTSUPP; netif_info(tp, drv, tp->dev, "rss set rxnfc\n"); if (!(dev->features & NETIF_F_RXHASH)) return ret; switch (cmd->cmd) { case ETHTOOL_SRXFH: ret = rtl8168_set_rss_hash_opt(tp, cmd); break; default: break; } return ret; } static u32 _rtl8168_get_rxfh_key_size(struct rtl8168_private *tp) { return sizeof(tp->rss_key); } u32 rtl8168_get_rxfh_key_size(struct net_device *dev) { struct rtl8168_private *tp = netdev_priv(dev); netif_info(tp, drv, tp->dev, "rss get key size\n"); if (!(dev->features & NETIF_F_RXHASH)) return 0; return _rtl8168_get_rxfh_key_size(tp); } u32 rtl8168_rss_indir_size(struct net_device *dev) { struct rtl8168_private *tp = netdev_priv(dev); netif_info(tp, drv, tp->dev, "rss get indir tbl size\n"); if (!(dev->features & NETIF_F_RXHASH)) return 0; return rtl8168_rss_indir_tbl_entries(tp); } static void rtl8168_get_reta(struct rtl8168_private *tp, u32 *indir) { int i, reta_size = rtl8168_rss_indir_tbl_entries(tp); for (i = 0; i < reta_size; i++) indir[i] = tp->rss_indir_tbl[i]; } static u32 rtl8168_rss_key_reg(struct rtl8168_private *tp) { return RSS_KEY_8168; } static u32 rtl8168_rss_indir_tbl_reg(struct rtl8168_private *tp) { return Rss_indir_tbl; } static void rtl8168_store_reta(struct rtl8168_private *tp) { u32 reta_entries = rtl8168_rss_indir_tbl_entries(tp); u16 indir_tbl_reg = rtl8168_rss_indir_tbl_reg(tp); u32 hw_indir[RTL8168_RSS_INDIR_TBL_SIZE] = {0}; u8 *indir = tp->rss_indir_tbl; u32 bit_on_cnt = 0x00000001; u32 i, j; /* Mapping redirection table to HW */ for (i = 0, j = 0; i < reta_entries; i++) { if ((indir[i] & 2) && R8168_MULTI_RSS_4Q(tp)) hw_indir[j + 4] |= bit_on_cnt; if (indir[i] & 1) hw_indir[j] |= bit_on_cnt; if (bit_on_cnt == 0x80000000) { bit_on_cnt = 0x00000001; j++; continue; } bit_on_cnt <<= 1; } /* Write redirection table to HW */ for (i = 0; i < RTL8168_RSS_INDIR_TBL_SIZE; i++) RTL_W32(tp, indir_tbl_reg + i*4, hw_indir[i]); } static void rtl8168_store_rss_key(struct rtl8168_private *tp) { const u16 rss_key_reg = rtl8168_rss_key_reg(tp); u32 i, rss_key_size = _rtl8168_get_rxfh_key_size(tp); u32 *rss_key = (u32*)tp->rss_key; /* Write redirection table to HW */ for (i = 0; i < rss_key_size; i+=4) rtl8168_eri_write(tp, rss_key_reg + i, 4, *rss_key++, ERIAR_ExGMAC); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6,8,0) int rtl8168_get_rxfh(struct net_device *dev, struct ethtool_rxfh_param *rxfh) { struct rtl8168_private *tp = netdev_priv(dev); netif_info(tp, drv, tp->dev, "rss get rxfh\n"); if (!(dev->features & NETIF_F_RXHASH)) return -EOPNOTSUPP; rxfh->hfunc = ETH_RSS_HASH_TOP; if (rxfh->indir) rtl8168_get_reta(tp, rxfh->indir); if (rxfh->key) memcpy(rxfh->key, tp->rss_key, RTL8168_RSS_KEY_SIZE); return 0; } int rtl8168_set_rxfh(struct net_device *dev, struct ethtool_rxfh_param *rxfh, struct netlink_ext_ack *extack) { struct rtl8168_private *tp = netdev_priv(dev); u32 reta_entries = rtl8168_rss_indir_tbl_entries(tp); int i; netif_info(tp, drv, tp->dev, "rss set rxfh\n"); /* We require at least one supported parameter to be changed and no * change in any of the unsupported parameters */ if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE && rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; /* Fill out the redirection table */ if (rxfh->indir) { int max_queues = tp->num_rx_rings; /* Verify user input. */ for (i = 0; i < reta_entries; i++) if (rxfh->indir[i] >= max_queues) return -EINVAL; for (i = 0; i < reta_entries; i++) tp->rss_indir_tbl[i] = rxfh->indir[i]; } /* Fill out the rss hash key */ if (rxfh->key) memcpy(tp->rss_key, rxfh->key, RTL8168_RSS_KEY_SIZE); rtl8168_store_reta(tp); rtl8168_store_rss_key(tp); return 0; } #else int rtl8168_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, u8 *hfunc) { struct rtl8168_private *tp = netdev_priv(dev); netif_info(tp, drv, tp->dev, "rss get rxfh\n"); if (!(dev->features & NETIF_F_RXHASH)) return -EOPNOTSUPP; if (hfunc) *hfunc = ETH_RSS_HASH_TOP; if (indir) rtl8168_get_reta(tp, indir); if (key) memcpy(key, tp->rss_key, RTL8168_RSS_KEY_SIZE); return 0; } int rtl8168_set_rxfh(struct net_device *dev, const u32 *indir, const u8 *key, const u8 hfunc) { struct rtl8168_private *tp = netdev_priv(dev); u32 reta_entries = rtl8168_rss_indir_tbl_entries(tp); int i; netif_info(tp, drv, tp->dev, "rss set rxfh\n"); /* We require at least one supported parameter to be changed and no * change in any of the unsupported parameters */ if (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP; /* Fill out the redirection table */ if (indir) { int max_queues = tp->num_rx_rings; /* Verify user input. */ for (i = 0; i < reta_entries; i++) if (indir[i] >= max_queues) return -EINVAL; for (i = 0; i < reta_entries; i++) tp->rss_indir_tbl[i] = indir[i]; } /* Fill out the rss hash key */ if (key) memcpy(tp->rss_key, key, RTL8168_RSS_KEY_SIZE); rtl8168_store_reta(tp); rtl8168_store_rss_key(tp); return 0; } #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6,8,0) */ static u32 rtl8168_get_rx_desc_hash(struct rtl8168_private *tp, struct RxDescV2 *desc) { if (!desc->RSSResult) udelay(1); return le32_to_cpu(desc->RSSResult); } #define RXS_8168_RSS_IPV4 BIT(17) #define RXS_8168_RSS_IPV6 BIT(18) #define RXS_8168_RSS_TCP BIT(19) #define RTL8168_RXS_RSS_L3_TYPE_MASK (RXS_8168_RSS_IPV4 | RXS_8168_RSS_IPV6) #define RTL8168_RXS_RSS_L4_TYPE_MASK (RXS_8168_RSS_TCP) void rtl8168_rx_hash(struct rtl8168_private *tp, struct RxDescV2 *desc, struct sk_buff *skb) { u32 rss_header_info; if (!(tp->dev->features & NETIF_F_RXHASH)) return; rss_header_info = le32_to_cpu(desc->opts2); if (!(rss_header_info & RTL8168_RXS_RSS_L3_TYPE_MASK)) return; skb_set_hash(skb, rtl8168_get_rx_desc_hash(tp, desc), (RTL8168_RXS_RSS_L4_TYPE_MASK & rss_header_info) ? PKT_HASH_TYPE_L4 : PKT_HASH_TYPE_L3); } void rtl8168_disable_rss(struct rtl8168_private *tp) { rtl8168_eri_write(tp, RSS_CTRL_8168, 4, 0x00000000, ERIAR_ExGMAC); } void _rtl8168_config_rss(struct rtl8168_private *tp) { _rtl8168_set_rss_hash_opt(tp); rtl8168_store_reta(tp); rtl8168_store_rss_key(tp); } void rtl8168_config_rss(struct rtl8168_private *tp) { if (!HW_RSS_SUPPORT_RSS(tp)) return; if (!tp->EnableRss) { rtl8168_disable_rss(tp); return; } _rtl8168_config_rss(tp); } void rtl8168_init_rss(struct rtl8168_private *tp) { int i; for (i = 0; i < rtl8168_rss_indir_tbl_entries(tp); i++) tp->rss_indir_tbl[i] = ethtool_rxfh_indir_default(i, tp->num_rx_rings); netdev_rss_key_fill(tp->rss_key, RTL8168_RSS_KEY_SIZE); }