Files
linux/drivers/net/ethernet/wangxun/txgbe/txgbe_ethtool.c
Linus Torvalds bf4afc53b7 Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
This was done entirely with mindless brute force, using

    git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
        xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'

to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.

Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.

For the same reason the 'flex' versions will be done as a separate
conversion.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-02-21 17:09:51 -08:00

611 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/pci.h>
#include <linux/phylink.h>
#include <linux/netdevice.h>
#include "../libwx/wx_ethtool.h"
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "txgbe_type.h"
#include "txgbe_fdir.h"
#include "txgbe_aml.h"
#include "txgbe_ethtool.h"
int txgbe_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct wx *wx = netdev_priv(netdev);
struct txgbe *txgbe = wx->priv;
int err;
err = wx_get_link_ksettings(netdev, cmd);
if (err)
return err;
if (wx->mac.type == wx_mac_sp)
return 0;
cmd->base.port = txgbe->link_port;
cmd->base.autoneg = phylink_test(txgbe->advertising, Autoneg) ?
AUTONEG_ENABLE : AUTONEG_DISABLE;
linkmode_copy(cmd->link_modes.supported, txgbe->link_support);
linkmode_copy(cmd->link_modes.advertising, txgbe->advertising);
return 0;
}
static int txgbe_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
u32 new_rx_count, new_tx_count;
struct wx_ring *temp_ring;
int i, err = 0;
new_tx_count = clamp_t(u32, ring->tx_pending, WX_MIN_TXD, WX_MAX_TXD);
new_tx_count = ALIGN(new_tx_count, WX_REQ_TX_DESCRIPTOR_MULTIPLE);
new_rx_count = clamp_t(u32, ring->rx_pending, WX_MIN_RXD, WX_MAX_RXD);
new_rx_count = ALIGN(new_rx_count, WX_REQ_RX_DESCRIPTOR_MULTIPLE);
if (new_tx_count == wx->tx_ring_count &&
new_rx_count == wx->rx_ring_count)
return 0;
err = wx_set_state_reset(wx);
if (err)
return err;
if (!netif_running(wx->netdev)) {
for (i = 0; i < wx->num_tx_queues; i++)
wx->tx_ring[i]->count = new_tx_count;
for (i = 0; i < wx->num_rx_queues; i++)
wx->rx_ring[i]->count = new_rx_count;
wx->tx_ring_count = new_tx_count;
wx->rx_ring_count = new_rx_count;
goto clear_reset;
}
/* allocate temporary buffer to store rings in */
i = max_t(int, wx->num_tx_queues, wx->num_rx_queues);
temp_ring = kvmalloc_objs(struct wx_ring, i);
if (!temp_ring) {
err = -ENOMEM;
goto clear_reset;
}
txgbe_down(wx);
wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring);
kvfree(temp_ring);
txgbe_up(wx);
clear_reset:
clear_bit(WX_STATE_RESETTING, wx->state);
return err;
}
static int txgbe_set_channels(struct net_device *dev,
struct ethtool_channels *ch)
{
int err;
err = wx_set_channels(dev, ch);
if (err < 0)
return err;
/* use setup TC to update any traffic class queue mapping */
return txgbe_setup_tc(dev, netdev_get_num_tc(dev));
}
static int txgbe_get_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
union txgbe_atr_input *mask = &txgbe->fdir_mask;
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (fsp->location <= rule->sw_idx)
break;
}
if (!rule || fsp->location != rule->sw_idx)
return -EINVAL;
/* set flow type field */
switch (rule->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
fsp->flow_type = TCP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
fsp->flow_type = UDP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
fsp->flow_type = SCTP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
fsp->flow_type = IP_USER_FLOW;
fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
fsp->h_u.usr_ip4_spec.proto = 0;
fsp->m_u.usr_ip4_spec.proto = 0;
break;
default:
return -EINVAL;
}
fsp->h_u.tcp_ip4_spec.psrc = rule->filter.formatted.src_port;
fsp->m_u.tcp_ip4_spec.psrc = mask->formatted.src_port;
fsp->h_u.tcp_ip4_spec.pdst = rule->filter.formatted.dst_port;
fsp->m_u.tcp_ip4_spec.pdst = mask->formatted.dst_port;
fsp->h_u.tcp_ip4_spec.ip4src = rule->filter.formatted.src_ip[0];
fsp->m_u.tcp_ip4_spec.ip4src = mask->formatted.src_ip[0];
fsp->h_u.tcp_ip4_spec.ip4dst = rule->filter.formatted.dst_ip[0];
fsp->m_u.tcp_ip4_spec.ip4dst = mask->formatted.dst_ip[0];
fsp->h_ext.vlan_etype = rule->filter.formatted.flex_bytes;
fsp->m_ext.vlan_etype = mask->formatted.flex_bytes;
fsp->h_ext.data[1] = htonl(rule->filter.formatted.vm_pool);
fsp->m_ext.data[1] = htonl(mask->formatted.vm_pool);
fsp->flow_type |= FLOW_EXT;
/* record action */
if (rule->action == TXGBE_RDB_FDIR_DROP_QUEUE)
fsp->ring_cookie = RX_CLS_FLOW_DISC;
else
fsp->ring_cookie = rule->action;
return 0;
}
static int txgbe_get_ethtool_fdir_all(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct txgbe_fdir_filter *rule;
struct hlist_node *node;
int cnt = 0;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (cnt == cmd->rule_cnt)
return -EMSGSIZE;
rule_locs[cnt] = rule->sw_idx;
cnt++;
}
cmd->rule_cnt = cnt;
return 0;
}
static u32 txgbe_get_rx_ring_count(struct net_device *dev)
{
struct wx *wx = netdev_priv(dev);
return wx->num_rx_queues;
}
static int txgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = txgbe->fdir_filter_count;
ret = 0;
break;
case ETHTOOL_GRXCLSRULE:
ret = txgbe_get_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_GRXCLSRLALL:
ret = txgbe_get_ethtool_fdir_all(txgbe, cmd, (u32 *)rule_locs);
break;
default:
break;
}
return ret;
}
static int txgbe_flowspec_to_flow_type(struct ethtool_rx_flow_spec *fsp,
u8 *flow_type)
{
switch (fsp->flow_type & ~FLOW_EXT) {
case TCP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case UDP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case SCTP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case IP_USER_FLOW:
switch (fsp->h_u.usr_ip4_spec.proto) {
case IPPROTO_TCP:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case IPPROTO_UDP:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case IPPROTO_SCTP:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case 0:
if (!fsp->m_u.usr_ip4_spec.proto) {
*flow_type = TXGBE_ATR_FLOW_TYPE_IPV4;
break;
}
fallthrough;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
return 0;
}
static bool txgbe_match_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input)
{
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node2;
hlist_for_each_entry_safe(rule, node2, &txgbe->fdir_filter_list,
fdir_node) {
if (rule->filter.formatted.bkt_hash ==
input->filter.formatted.bkt_hash &&
rule->action == input->action) {
wx_dbg(txgbe->wx, "FDIR entry already exist\n");
return true;
}
}
return false;
}
static int txgbe_update_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input,
u16 sw_idx)
{
struct hlist_node *node = NULL, *parent = NULL;
struct txgbe_fdir_filter *rule;
struct wx *wx = txgbe->wx;
bool deleted = false;
int err;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
/* hash found, or no matching entry */
if (rule->sw_idx >= sw_idx)
break;
parent = node;
}
/* if there is an old rule occupying our place remove it */
if (rule && rule->sw_idx == sw_idx) {
/* hardware filters are only configured when interface is up,
* and we should not issue filter commands while the interface
* is down
*/
if (netif_running(wx->netdev) &&
(!input || rule->filter.formatted.bkt_hash !=
input->filter.formatted.bkt_hash)) {
err = txgbe_fdir_erase_perfect_filter(wx,
&rule->filter,
sw_idx);
if (err)
return -EINVAL;
}
hlist_del(&rule->fdir_node);
kfree(rule);
txgbe->fdir_filter_count--;
deleted = true;
}
/* If we weren't given an input, then this was a request to delete a
* filter. We should return -EINVAL if the filter wasn't found, but
* return 0 if the rule was successfully deleted.
*/
if (!input)
return deleted ? 0 : -EINVAL;
/* initialize node and set software index */
INIT_HLIST_NODE(&input->fdir_node);
/* add filter to the list */
if (parent)
hlist_add_behind(&input->fdir_node, parent);
else
hlist_add_head(&input->fdir_node,
&txgbe->fdir_filter_list);
/* update counts */
txgbe->fdir_filter_count++;
return 0;
}
static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
struct txgbe_fdir_filter *input;
union txgbe_atr_input mask;
struct wx *wx = txgbe->wx;
int err = -EINVAL;
u16 ptype = 0;
u8 queue;
if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
return -EOPNOTSUPP;
/* ring_cookie is a masked into a set of queues and txgbe pools or
* we use drop index
*/
if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
queue = TXGBE_RDB_FDIR_DROP_QUEUE;
} else {
u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
u8 vf = ethtool_get_flow_spec_ring_vf(fsp->ring_cookie);
if (!vf && ring >= wx->num_rx_queues)
return -EINVAL;
else if (vf && (vf > wx->num_vfs ||
ring >= wx->num_rx_queues_per_pool))
return -EINVAL;
/* Map the ring onto the absolute queue index */
if (!vf)
queue = wx->rx_ring[ring]->reg_idx;
else
queue = ((vf - 1) * wx->num_rx_queues_per_pool) + ring;
}
/* Don't allow indexes to exist outside of available space */
if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
wx_err(wx, "Location out of range\n");
return -EINVAL;
}
input = kzalloc_obj(*input, GFP_ATOMIC);
if (!input)
return -ENOMEM;
memset(&mask, 0, sizeof(union txgbe_atr_input));
/* set SW index */
input->sw_idx = fsp->location;
/* record flow type */
if (txgbe_flowspec_to_flow_type(fsp,
&input->filter.formatted.flow_type)) {
wx_err(wx, "Unrecognized flow type\n");
goto err_out;
}
mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
TXGBE_ATR_L4TYPE_MASK;
if (input->filter.formatted.flow_type == TXGBE_ATR_FLOW_TYPE_IPV4)
mask.formatted.flow_type &= TXGBE_ATR_L4TYPE_IPV6_MASK;
/* Copy input into formatted structures */
input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
if (fsp->flow_type & FLOW_EXT) {
input->filter.formatted.vm_pool =
(unsigned char)ntohl(fsp->h_ext.data[1]);
mask.formatted.vm_pool =
(unsigned char)ntohl(fsp->m_ext.data[1]);
input->filter.formatted.flex_bytes =
fsp->h_ext.vlan_etype;
mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
}
switch (input->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
ptype = WX_PTYPE_L2_IPV4_TCP;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
ptype = WX_PTYPE_L2_IPV4_UDP;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
ptype = WX_PTYPE_L2_IPV4_SCTP;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
ptype = WX_PTYPE_L2_IPV4;
break;
default:
break;
}
input->filter.formatted.vlan_id = htons(ptype);
if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
mask.formatted.vlan_id = htons(0xFFFF);
else
mask.formatted.vlan_id = htons(0xFFF8);
/* determine if we need to drop or route the packet */
if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
else
input->action = fsp->ring_cookie;
spin_lock(&txgbe->fdir_perfect_lock);
if (hlist_empty(&txgbe->fdir_filter_list)) {
/* save mask and program input mask into HW */
memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
err = txgbe_fdir_set_input_mask(wx, &mask);
if (err)
goto err_unlock;
} else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
wx_err(wx, "Hardware only supports one mask per port. To change the mask you must first delete all the rules.\n");
goto err_unlock;
}
/* apply mask and compute/store hash */
txgbe_atr_compute_perfect_hash(&input->filter, &mask);
/* check if new entry does not exist on filter list */
if (txgbe_match_ethtool_fdir_entry(txgbe, input))
goto err_unlock;
/* only program filters to hardware if the net device is running, as
* we store the filters in the Rx buffer which is not allocated when
* the device is down
*/
if (netif_running(wx->netdev)) {
err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
input->sw_idx, queue);
if (err)
goto err_unlock;
}
txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
spin_unlock(&txgbe->fdir_perfect_lock);
return 0;
err_unlock:
spin_unlock(&txgbe->fdir_perfect_lock);
err_out:
kfree(input);
return err;
}
static int txgbe_del_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
int err = 0;
spin_lock(&txgbe->fdir_perfect_lock);
err = txgbe_update_ethtool_fdir_entry(txgbe, NULL, fsp->location);
spin_unlock(&txgbe->fdir_perfect_lock);
return err;
}
static int txgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
ret = txgbe_add_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_SRXCLSRLDEL:
ret = txgbe_del_ethtool_fdir_entry(txgbe, cmd);
break;
default:
break;
}
return ret;
}
static int
txgbe_get_module_eeprom_by_page(struct net_device *netdev,
const struct ethtool_module_eeprom *page_data,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
struct txgbe_hic_i2c_read buffer;
int err;
if (!test_bit(WX_FLAG_SWFW_RING, wx->flags))
return -EOPNOTSUPP;
buffer.length = cpu_to_be32(page_data->length);
buffer.offset = cpu_to_be32(page_data->offset);
buffer.page = page_data->page;
buffer.bank = page_data->bank;
buffer.i2c_address = page_data->i2c_address;
err = txgbe_read_eeprom_hostif(wx, &buffer, page_data->length,
page_data->data);
if (err) {
wx_err(wx, "Failed to read module EEPROM\n");
return err;
}
return page_data->length;
}
static const struct ethtool_ops txgbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ |
ETHTOOL_COALESCE_USE_ADAPTIVE,
.get_drvinfo = wx_get_drvinfo,
.nway_reset = wx_nway_reset,
.get_link = ethtool_op_get_link,
.get_link_ksettings = txgbe_get_link_ksettings,
.set_link_ksettings = wx_set_link_ksettings,
.get_sset_count = wx_get_sset_count,
.get_strings = wx_get_strings,
.get_ethtool_stats = wx_get_ethtool_stats,
.get_eth_mac_stats = wx_get_mac_stats,
.get_pause_stats = wx_get_pause_stats,
.get_pauseparam = wx_get_pauseparam,
.set_pauseparam = wx_set_pauseparam,
.get_ringparam = wx_get_ringparam,
.set_ringparam = txgbe_set_ringparam,
.get_coalesce = wx_get_coalesce,
.set_coalesce = wx_set_coalesce,
.get_channels = wx_get_channels,
.set_channels = txgbe_set_channels,
.get_rxnfc = txgbe_get_rxnfc,
.set_rxnfc = txgbe_set_rxnfc,
.get_rx_ring_count = txgbe_get_rx_ring_count,
.get_rxfh_fields = wx_get_rxfh_fields,
.set_rxfh_fields = wx_set_rxfh_fields,
.get_rxfh_indir_size = wx_rss_indir_size,
.get_rxfh_key_size = wx_get_rxfh_key_size,
.get_rxfh = wx_get_rxfh,
.set_rxfh = wx_set_rxfh,
.get_msglevel = wx_get_msglevel,
.set_msglevel = wx_set_msglevel,
.get_ts_info = wx_get_ts_info,
.get_ts_stats = wx_get_ptp_stats,
.get_module_eeprom_by_page = txgbe_get_module_eeprom_by_page,
};
void txgbe_set_ethtool_ops(struct net_device *netdev)
{
netdev->ethtool_ops = &txgbe_ethtool_ops;
}