mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-29 22:14:41 +08:00
d5ec9e2ce4
After OS requests to down a link on a physical network port, the traffic is no longer being processed but the physical link with a link partner is still established. Currently there is a feature (Link down on close) which allows to physically bring the link down (after OS request). With this patch new feature with similar capability is introduced: TOTAL_PORT_SHUTDOWN Allows to physically disable the link on the NIC's port. If enabled, (after link down request from the OS) no link, traffic or led activity is possible on that port. If I40E_FLAG_TOTAL_PORT_SHUTDOWN is enabled, the I40E_FLAG_LINK_DOWN_ON_CLOSE_ENABLED must be explicitly forced to true and cannot be disabled at that time. The functionalities are exclusive in terms of configuration, but they also have similar behavior (allowing to disable physical link of the port), with following differences: - LINK_DOWN_ON_CLOSE_ENABLED is configurable at host OS run-time and is supported by whole family of 7xx Intel Ethernet Controllers - TOTAL_PORT_SHUTDOWN may be enabled only before OS loads (in BIOS) only if motherboard's BIOS and NIC's FW has support of it - when LINK_DOWN_ON_CLOSE_ENABLED is used, the link is being brought down by sending phy_type=0 to NIC's FW - when TOTAL_PORT_SHUTDOWN is used, phy_type is not altered, instead the link is being brought down by clearing bit (I40E_AQ_PHY_ENABLE_LINK) in abilities field of i40e_aq_set_phy_config structure Introduced changes: - new private flag I40E_FLAG_TOTAL_PORT_SHUTDOWN for handling the feature - probe of NVM if the feature was enabled at driver's port initialization - special handling on link-down procedure to let FW physically shutdown the port if the feature was enabled Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com> Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com> Tested-by: Andrew Bowers <andrewx.bowers@intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
5324 lines
162 KiB
C
5324 lines
162 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright(c) 2013 - 2018 Intel Corporation. */
|
|
|
|
/* ethtool support for i40e */
|
|
|
|
#include "i40e.h"
|
|
#include "i40e_diag.h"
|
|
#include "i40e_txrx_common.h"
|
|
|
|
/* ethtool statistics helpers */
|
|
|
|
/**
|
|
* struct i40e_stats - definition for an ethtool statistic
|
|
* @stat_string: statistic name to display in ethtool -S output
|
|
* @sizeof_stat: the sizeof() the stat, must be no greater than sizeof(u64)
|
|
* @stat_offset: offsetof() the stat from a base pointer
|
|
*
|
|
* This structure defines a statistic to be added to the ethtool stats buffer.
|
|
* It defines a statistic as offset from a common base pointer. Stats should
|
|
* be defined in constant arrays using the I40E_STAT macro, with every element
|
|
* of the array using the same _type for calculating the sizeof_stat and
|
|
* stat_offset.
|
|
*
|
|
* The @sizeof_stat is expected to be sizeof(u8), sizeof(u16), sizeof(u32) or
|
|
* sizeof(u64). Other sizes are not expected and will produce a WARN_ONCE from
|
|
* the i40e_add_ethtool_stat() helper function.
|
|
*
|
|
* The @stat_string is interpreted as a format string, allowing formatted
|
|
* values to be inserted while looping over multiple structures for a given
|
|
* statistics array. Thus, every statistic string in an array should have the
|
|
* same type and number of format specifiers, to be formatted by variadic
|
|
* arguments to the i40e_add_stat_string() helper function.
|
|
**/
|
|
struct i40e_stats {
|
|
char stat_string[ETH_GSTRING_LEN];
|
|
int sizeof_stat;
|
|
int stat_offset;
|
|
};
|
|
|
|
/* Helper macro to define an i40e_stat structure with proper size and type.
|
|
* Use this when defining constant statistics arrays. Note that @_type expects
|
|
* only a type name and is used multiple times.
|
|
*/
|
|
#define I40E_STAT(_type, _name, _stat) { \
|
|
.stat_string = _name, \
|
|
.sizeof_stat = sizeof_field(_type, _stat), \
|
|
.stat_offset = offsetof(_type, _stat) \
|
|
}
|
|
|
|
/* Helper macro for defining some statistics directly copied from the netdev
|
|
* stats structure.
|
|
*/
|
|
#define I40E_NETDEV_STAT(_net_stat) \
|
|
I40E_STAT(struct rtnl_link_stats64, #_net_stat, _net_stat)
|
|
|
|
/* Helper macro for defining some statistics related to queues */
|
|
#define I40E_QUEUE_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_ring, _name, _stat)
|
|
|
|
/* Stats associated with a Tx or Rx ring */
|
|
static const struct i40e_stats i40e_gstrings_queue_stats[] = {
|
|
I40E_QUEUE_STAT("%s-%u.packets", stats.packets),
|
|
I40E_QUEUE_STAT("%s-%u.bytes", stats.bytes),
|
|
};
|
|
|
|
/**
|
|
* i40e_add_one_ethtool_stat - copy the stat into the supplied buffer
|
|
* @data: location to store the stat value
|
|
* @pointer: basis for where to copy from
|
|
* @stat: the stat definition
|
|
*
|
|
* Copies the stat data defined by the pointer and stat structure pair into
|
|
* the memory supplied as data. Used to implement i40e_add_ethtool_stats and
|
|
* i40e_add_queue_stats. If the pointer is null, data will be zero'd.
|
|
*/
|
|
static void
|
|
i40e_add_one_ethtool_stat(u64 *data, void *pointer,
|
|
const struct i40e_stats *stat)
|
|
{
|
|
char *p;
|
|
|
|
if (!pointer) {
|
|
/* ensure that the ethtool data buffer is zero'd for any stats
|
|
* which don't have a valid pointer.
|
|
*/
|
|
*data = 0;
|
|
return;
|
|
}
|
|
|
|
p = (char *)pointer + stat->stat_offset;
|
|
switch (stat->sizeof_stat) {
|
|
case sizeof(u64):
|
|
*data = *((u64 *)p);
|
|
break;
|
|
case sizeof(u32):
|
|
*data = *((u32 *)p);
|
|
break;
|
|
case sizeof(u16):
|
|
*data = *((u16 *)p);
|
|
break;
|
|
case sizeof(u8):
|
|
*data = *((u8 *)p);
|
|
break;
|
|
default:
|
|
WARN_ONCE(1, "unexpected stat size for %s",
|
|
stat->stat_string);
|
|
*data = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* __i40e_add_ethtool_stats - copy stats into the ethtool supplied buffer
|
|
* @data: ethtool stats buffer
|
|
* @pointer: location to copy stats from
|
|
* @stats: array of stats to copy
|
|
* @size: the size of the stats definition
|
|
*
|
|
* Copy the stats defined by the stats array using the pointer as a base into
|
|
* the data buffer supplied by ethtool. Updates the data pointer to point to
|
|
* the next empty location for successive calls to __i40e_add_ethtool_stats.
|
|
* If pointer is null, set the data values to zero and update the pointer to
|
|
* skip these stats.
|
|
**/
|
|
static void
|
|
__i40e_add_ethtool_stats(u64 **data, void *pointer,
|
|
const struct i40e_stats stats[],
|
|
const unsigned int size)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
i40e_add_one_ethtool_stat((*data)++, pointer, &stats[i]);
|
|
}
|
|
|
|
/**
|
|
* i40e_add_ethtool_stats - copy stats into ethtool supplied buffer
|
|
* @data: ethtool stats buffer
|
|
* @pointer: location where stats are stored
|
|
* @stats: static const array of stat definitions
|
|
*
|
|
* Macro to ease the use of __i40e_add_ethtool_stats by taking a static
|
|
* constant stats array and passing the ARRAY_SIZE(). This avoids typos by
|
|
* ensuring that we pass the size associated with the given stats array.
|
|
*
|
|
* The parameter @stats is evaluated twice, so parameters with side effects
|
|
* should be avoided.
|
|
**/
|
|
#define i40e_add_ethtool_stats(data, pointer, stats) \
|
|
__i40e_add_ethtool_stats(data, pointer, stats, ARRAY_SIZE(stats))
|
|
|
|
/**
|
|
* i40e_add_queue_stats - copy queue statistics into supplied buffer
|
|
* @data: ethtool stats buffer
|
|
* @ring: the ring to copy
|
|
*
|
|
* Queue statistics must be copied while protected by
|
|
* u64_stats_fetch_begin_irq, so we can't directly use i40e_add_ethtool_stats.
|
|
* Assumes that queue stats are defined in i40e_gstrings_queue_stats. If the
|
|
* ring pointer is null, zero out the queue stat values and update the data
|
|
* pointer. Otherwise safely copy the stats from the ring into the supplied
|
|
* buffer and update the data pointer when finished.
|
|
*
|
|
* This function expects to be called while under rcu_read_lock().
|
|
**/
|
|
static void
|
|
i40e_add_queue_stats(u64 **data, struct i40e_ring *ring)
|
|
{
|
|
const unsigned int size = ARRAY_SIZE(i40e_gstrings_queue_stats);
|
|
const struct i40e_stats *stats = i40e_gstrings_queue_stats;
|
|
unsigned int start;
|
|
unsigned int i;
|
|
|
|
/* To avoid invalid statistics values, ensure that we keep retrying
|
|
* the copy until we get a consistent value according to
|
|
* u64_stats_fetch_retry_irq. But first, make sure our ring is
|
|
* non-null before attempting to access its syncp.
|
|
*/
|
|
do {
|
|
start = !ring ? 0 : u64_stats_fetch_begin_irq(&ring->syncp);
|
|
for (i = 0; i < size; i++) {
|
|
i40e_add_one_ethtool_stat(&(*data)[i], ring,
|
|
&stats[i]);
|
|
}
|
|
} while (ring && u64_stats_fetch_retry_irq(&ring->syncp, start));
|
|
|
|
/* Once we successfully copy the stats in, update the data pointer */
|
|
*data += size;
|
|
}
|
|
|
|
/**
|
|
* __i40e_add_stat_strings - copy stat strings into ethtool buffer
|
|
* @p: ethtool supplied buffer
|
|
* @stats: stat definitions array
|
|
* @size: size of the stats array
|
|
*
|
|
* Format and copy the strings described by stats into the buffer pointed at
|
|
* by p.
|
|
**/
|
|
static void __i40e_add_stat_strings(u8 **p, const struct i40e_stats stats[],
|
|
const unsigned int size, ...)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
va_list args;
|
|
|
|
va_start(args, size);
|
|
vsnprintf(*p, ETH_GSTRING_LEN, stats[i].stat_string, args);
|
|
*p += ETH_GSTRING_LEN;
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 40e_add_stat_strings - copy stat strings into ethtool buffer
|
|
* @p: ethtool supplied buffer
|
|
* @stats: stat definitions array
|
|
*
|
|
* Format and copy the strings described by the const static stats value into
|
|
* the buffer pointed at by p.
|
|
*
|
|
* The parameter @stats is evaluated twice, so parameters with side effects
|
|
* should be avoided. Additionally, stats must be an array such that
|
|
* ARRAY_SIZE can be called on it.
|
|
**/
|
|
#define i40e_add_stat_strings(p, stats, ...) \
|
|
__i40e_add_stat_strings(p, stats, ARRAY_SIZE(stats), ## __VA_ARGS__)
|
|
|
|
#define I40E_PF_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_pf, _name, _stat)
|
|
#define I40E_VSI_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_vsi, _name, _stat)
|
|
#define I40E_VEB_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_veb, _name, _stat)
|
|
#define I40E_PFC_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_pfc_stats, _name, _stat)
|
|
#define I40E_QUEUE_STAT(_name, _stat) \
|
|
I40E_STAT(struct i40e_ring, _name, _stat)
|
|
|
|
static const struct i40e_stats i40e_gstrings_net_stats[] = {
|
|
I40E_NETDEV_STAT(rx_packets),
|
|
I40E_NETDEV_STAT(tx_packets),
|
|
I40E_NETDEV_STAT(rx_bytes),
|
|
I40E_NETDEV_STAT(tx_bytes),
|
|
I40E_NETDEV_STAT(rx_errors),
|
|
I40E_NETDEV_STAT(tx_errors),
|
|
I40E_NETDEV_STAT(rx_dropped),
|
|
I40E_NETDEV_STAT(tx_dropped),
|
|
I40E_NETDEV_STAT(collisions),
|
|
I40E_NETDEV_STAT(rx_length_errors),
|
|
I40E_NETDEV_STAT(rx_crc_errors),
|
|
};
|
|
|
|
static const struct i40e_stats i40e_gstrings_veb_stats[] = {
|
|
I40E_VEB_STAT("veb.rx_bytes", stats.rx_bytes),
|
|
I40E_VEB_STAT("veb.tx_bytes", stats.tx_bytes),
|
|
I40E_VEB_STAT("veb.rx_unicast", stats.rx_unicast),
|
|
I40E_VEB_STAT("veb.tx_unicast", stats.tx_unicast),
|
|
I40E_VEB_STAT("veb.rx_multicast", stats.rx_multicast),
|
|
I40E_VEB_STAT("veb.tx_multicast", stats.tx_multicast),
|
|
I40E_VEB_STAT("veb.rx_broadcast", stats.rx_broadcast),
|
|
I40E_VEB_STAT("veb.tx_broadcast", stats.tx_broadcast),
|
|
I40E_VEB_STAT("veb.rx_discards", stats.rx_discards),
|
|
I40E_VEB_STAT("veb.tx_discards", stats.tx_discards),
|
|
I40E_VEB_STAT("veb.tx_errors", stats.tx_errors),
|
|
I40E_VEB_STAT("veb.rx_unknown_protocol", stats.rx_unknown_protocol),
|
|
};
|
|
|
|
static const struct i40e_stats i40e_gstrings_veb_tc_stats[] = {
|
|
I40E_VEB_STAT("veb.tc_%u_tx_packets", tc_stats.tc_tx_packets),
|
|
I40E_VEB_STAT("veb.tc_%u_tx_bytes", tc_stats.tc_tx_bytes),
|
|
I40E_VEB_STAT("veb.tc_%u_rx_packets", tc_stats.tc_rx_packets),
|
|
I40E_VEB_STAT("veb.tc_%u_rx_bytes", tc_stats.tc_rx_bytes),
|
|
};
|
|
|
|
static const struct i40e_stats i40e_gstrings_misc_stats[] = {
|
|
I40E_VSI_STAT("rx_unicast", eth_stats.rx_unicast),
|
|
I40E_VSI_STAT("tx_unicast", eth_stats.tx_unicast),
|
|
I40E_VSI_STAT("rx_multicast", eth_stats.rx_multicast),
|
|
I40E_VSI_STAT("tx_multicast", eth_stats.tx_multicast),
|
|
I40E_VSI_STAT("rx_broadcast", eth_stats.rx_broadcast),
|
|
I40E_VSI_STAT("tx_broadcast", eth_stats.tx_broadcast),
|
|
I40E_VSI_STAT("rx_unknown_protocol", eth_stats.rx_unknown_protocol),
|
|
I40E_VSI_STAT("tx_linearize", tx_linearize),
|
|
I40E_VSI_STAT("tx_force_wb", tx_force_wb),
|
|
I40E_VSI_STAT("tx_busy", tx_busy),
|
|
I40E_VSI_STAT("rx_alloc_fail", rx_buf_failed),
|
|
I40E_VSI_STAT("rx_pg_alloc_fail", rx_page_failed),
|
|
};
|
|
|
|
/* These PF_STATs might look like duplicates of some NETDEV_STATs,
|
|
* but they are separate. This device supports Virtualization, and
|
|
* as such might have several netdevs supporting VMDq and FCoE going
|
|
* through a single port. The NETDEV_STATs are for individual netdevs
|
|
* seen at the top of the stack, and the PF_STATs are for the physical
|
|
* function at the bottom of the stack hosting those netdevs.
|
|
*
|
|
* The PF_STATs are appended to the netdev stats only when ethtool -S
|
|
* is queried on the base PF netdev, not on the VMDq or FCoE netdev.
|
|
*/
|
|
static const struct i40e_stats i40e_gstrings_stats[] = {
|
|
I40E_PF_STAT("port.rx_bytes", stats.eth.rx_bytes),
|
|
I40E_PF_STAT("port.tx_bytes", stats.eth.tx_bytes),
|
|
I40E_PF_STAT("port.rx_unicast", stats.eth.rx_unicast),
|
|
I40E_PF_STAT("port.tx_unicast", stats.eth.tx_unicast),
|
|
I40E_PF_STAT("port.rx_multicast", stats.eth.rx_multicast),
|
|
I40E_PF_STAT("port.tx_multicast", stats.eth.tx_multicast),
|
|
I40E_PF_STAT("port.rx_broadcast", stats.eth.rx_broadcast),
|
|
I40E_PF_STAT("port.tx_broadcast", stats.eth.tx_broadcast),
|
|
I40E_PF_STAT("port.tx_errors", stats.eth.tx_errors),
|
|
I40E_PF_STAT("port.rx_dropped", stats.eth.rx_discards),
|
|
I40E_PF_STAT("port.tx_dropped_link_down", stats.tx_dropped_link_down),
|
|
I40E_PF_STAT("port.rx_crc_errors", stats.crc_errors),
|
|
I40E_PF_STAT("port.illegal_bytes", stats.illegal_bytes),
|
|
I40E_PF_STAT("port.mac_local_faults", stats.mac_local_faults),
|
|
I40E_PF_STAT("port.mac_remote_faults", stats.mac_remote_faults),
|
|
I40E_PF_STAT("port.tx_timeout", tx_timeout_count),
|
|
I40E_PF_STAT("port.rx_csum_bad", hw_csum_rx_error),
|
|
I40E_PF_STAT("port.rx_length_errors", stats.rx_length_errors),
|
|
I40E_PF_STAT("port.link_xon_rx", stats.link_xon_rx),
|
|
I40E_PF_STAT("port.link_xoff_rx", stats.link_xoff_rx),
|
|
I40E_PF_STAT("port.link_xon_tx", stats.link_xon_tx),
|
|
I40E_PF_STAT("port.link_xoff_tx", stats.link_xoff_tx),
|
|
I40E_PF_STAT("port.rx_size_64", stats.rx_size_64),
|
|
I40E_PF_STAT("port.rx_size_127", stats.rx_size_127),
|
|
I40E_PF_STAT("port.rx_size_255", stats.rx_size_255),
|
|
I40E_PF_STAT("port.rx_size_511", stats.rx_size_511),
|
|
I40E_PF_STAT("port.rx_size_1023", stats.rx_size_1023),
|
|
I40E_PF_STAT("port.rx_size_1522", stats.rx_size_1522),
|
|
I40E_PF_STAT("port.rx_size_big", stats.rx_size_big),
|
|
I40E_PF_STAT("port.tx_size_64", stats.tx_size_64),
|
|
I40E_PF_STAT("port.tx_size_127", stats.tx_size_127),
|
|
I40E_PF_STAT("port.tx_size_255", stats.tx_size_255),
|
|
I40E_PF_STAT("port.tx_size_511", stats.tx_size_511),
|
|
I40E_PF_STAT("port.tx_size_1023", stats.tx_size_1023),
|
|
I40E_PF_STAT("port.tx_size_1522", stats.tx_size_1522),
|
|
I40E_PF_STAT("port.tx_size_big", stats.tx_size_big),
|
|
I40E_PF_STAT("port.rx_undersize", stats.rx_undersize),
|
|
I40E_PF_STAT("port.rx_fragments", stats.rx_fragments),
|
|
I40E_PF_STAT("port.rx_oversize", stats.rx_oversize),
|
|
I40E_PF_STAT("port.rx_jabber", stats.rx_jabber),
|
|
I40E_PF_STAT("port.VF_admin_queue_requests", vf_aq_requests),
|
|
I40E_PF_STAT("port.arq_overflows", arq_overflows),
|
|
I40E_PF_STAT("port.tx_hwtstamp_timeouts", tx_hwtstamp_timeouts),
|
|
I40E_PF_STAT("port.rx_hwtstamp_cleared", rx_hwtstamp_cleared),
|
|
I40E_PF_STAT("port.tx_hwtstamp_skipped", tx_hwtstamp_skipped),
|
|
I40E_PF_STAT("port.fdir_flush_cnt", fd_flush_cnt),
|
|
I40E_PF_STAT("port.fdir_atr_match", stats.fd_atr_match),
|
|
I40E_PF_STAT("port.fdir_atr_tunnel_match", stats.fd_atr_tunnel_match),
|
|
I40E_PF_STAT("port.fdir_atr_status", stats.fd_atr_status),
|
|
I40E_PF_STAT("port.fdir_sb_match", stats.fd_sb_match),
|
|
I40E_PF_STAT("port.fdir_sb_status", stats.fd_sb_status),
|
|
|
|
/* LPI stats */
|
|
I40E_PF_STAT("port.tx_lpi_status", stats.tx_lpi_status),
|
|
I40E_PF_STAT("port.rx_lpi_status", stats.rx_lpi_status),
|
|
I40E_PF_STAT("port.tx_lpi_count", stats.tx_lpi_count),
|
|
I40E_PF_STAT("port.rx_lpi_count", stats.rx_lpi_count),
|
|
};
|
|
|
|
struct i40e_pfc_stats {
|
|
u64 priority_xon_rx;
|
|
u64 priority_xoff_rx;
|
|
u64 priority_xon_tx;
|
|
u64 priority_xoff_tx;
|
|
u64 priority_xon_2_xoff;
|
|
};
|
|
|
|
static const struct i40e_stats i40e_gstrings_pfc_stats[] = {
|
|
I40E_PFC_STAT("port.tx_priority_%u_xon_tx", priority_xon_tx),
|
|
I40E_PFC_STAT("port.tx_priority_%u_xoff_tx", priority_xoff_tx),
|
|
I40E_PFC_STAT("port.rx_priority_%u_xon_rx", priority_xon_rx),
|
|
I40E_PFC_STAT("port.rx_priority_%u_xoff_rx", priority_xoff_rx),
|
|
I40E_PFC_STAT("port.rx_priority_%u_xon_2_xoff", priority_xon_2_xoff),
|
|
};
|
|
|
|
#define I40E_NETDEV_STATS_LEN ARRAY_SIZE(i40e_gstrings_net_stats)
|
|
|
|
#define I40E_MISC_STATS_LEN ARRAY_SIZE(i40e_gstrings_misc_stats)
|
|
|
|
#define I40E_VSI_STATS_LEN (I40E_NETDEV_STATS_LEN + I40E_MISC_STATS_LEN)
|
|
|
|
#define I40E_PFC_STATS_LEN (ARRAY_SIZE(i40e_gstrings_pfc_stats) * \
|
|
I40E_MAX_USER_PRIORITY)
|
|
|
|
#define I40E_VEB_STATS_LEN (ARRAY_SIZE(i40e_gstrings_veb_stats) + \
|
|
(ARRAY_SIZE(i40e_gstrings_veb_tc_stats) * \
|
|
I40E_MAX_TRAFFIC_CLASS))
|
|
|
|
#define I40E_GLOBAL_STATS_LEN ARRAY_SIZE(i40e_gstrings_stats)
|
|
|
|
#define I40E_PF_STATS_LEN (I40E_GLOBAL_STATS_LEN + \
|
|
I40E_PFC_STATS_LEN + \
|
|
I40E_VEB_STATS_LEN + \
|
|
I40E_VSI_STATS_LEN)
|
|
|
|
/* Length of stats for a single queue */
|
|
#define I40E_QUEUE_STATS_LEN ARRAY_SIZE(i40e_gstrings_queue_stats)
|
|
|
|
enum i40e_ethtool_test_id {
|
|
I40E_ETH_TEST_REG = 0,
|
|
I40E_ETH_TEST_EEPROM,
|
|
I40E_ETH_TEST_INTR,
|
|
I40E_ETH_TEST_LINK,
|
|
};
|
|
|
|
static const char i40e_gstrings_test[][ETH_GSTRING_LEN] = {
|
|
"Register test (offline)",
|
|
"Eeprom test (offline)",
|
|
"Interrupt test (offline)",
|
|
"Link test (on/offline)"
|
|
};
|
|
|
|
#define I40E_TEST_LEN (sizeof(i40e_gstrings_test) / ETH_GSTRING_LEN)
|
|
|
|
struct i40e_priv_flags {
|
|
char flag_string[ETH_GSTRING_LEN];
|
|
u64 flag;
|
|
bool read_only;
|
|
};
|
|
|
|
#define I40E_PRIV_FLAG(_name, _flag, _read_only) { \
|
|
.flag_string = _name, \
|
|
.flag = _flag, \
|
|
.read_only = _read_only, \
|
|
}
|
|
|
|
static const struct i40e_priv_flags i40e_gstrings_priv_flags[] = {
|
|
/* NOTE: MFP setting cannot be changed */
|
|
I40E_PRIV_FLAG("MFP", I40E_FLAG_MFP_ENABLED, 1),
|
|
I40E_PRIV_FLAG("total-port-shutdown",
|
|
I40E_FLAG_TOTAL_PORT_SHUTDOWN_ENABLED, 1),
|
|
I40E_PRIV_FLAG("LinkPolling", I40E_FLAG_LINK_POLLING_ENABLED, 0),
|
|
I40E_PRIV_FLAG("flow-director-atr", I40E_FLAG_FD_ATR_ENABLED, 0),
|
|
I40E_PRIV_FLAG("veb-stats", I40E_FLAG_VEB_STATS_ENABLED, 0),
|
|
I40E_PRIV_FLAG("hw-atr-eviction", I40E_FLAG_HW_ATR_EVICT_ENABLED, 0),
|
|
I40E_PRIV_FLAG("link-down-on-close",
|
|
I40E_FLAG_LINK_DOWN_ON_CLOSE_ENABLED, 0),
|
|
I40E_PRIV_FLAG("legacy-rx", I40E_FLAG_LEGACY_RX, 0),
|
|
I40E_PRIV_FLAG("disable-source-pruning",
|
|
I40E_FLAG_SOURCE_PRUNING_DISABLED, 0),
|
|
I40E_PRIV_FLAG("disable-fw-lldp", I40E_FLAG_DISABLE_FW_LLDP, 0),
|
|
I40E_PRIV_FLAG("rs-fec", I40E_FLAG_RS_FEC, 0),
|
|
I40E_PRIV_FLAG("base-r-fec", I40E_FLAG_BASE_R_FEC, 0),
|
|
};
|
|
|
|
#define I40E_PRIV_FLAGS_STR_LEN ARRAY_SIZE(i40e_gstrings_priv_flags)
|
|
|
|
/* Private flags with a global effect, restricted to PF 0 */
|
|
static const struct i40e_priv_flags i40e_gl_gstrings_priv_flags[] = {
|
|
I40E_PRIV_FLAG("vf-true-promisc-support",
|
|
I40E_FLAG_TRUE_PROMISC_SUPPORT, 0),
|
|
};
|
|
|
|
#define I40E_GL_PRIV_FLAGS_STR_LEN ARRAY_SIZE(i40e_gl_gstrings_priv_flags)
|
|
|
|
/**
|
|
* i40e_partition_setting_complaint - generic complaint for MFP restriction
|
|
* @pf: the PF struct
|
|
**/
|
|
static void i40e_partition_setting_complaint(struct i40e_pf *pf)
|
|
{
|
|
dev_info(&pf->pdev->dev,
|
|
"The link settings are allowed to be changed only from the first partition of a given port. Please switch to the first partition in order to change the setting.\n");
|
|
}
|
|
|
|
/**
|
|
* i40e_phy_type_to_ethtool - convert the phy_types to ethtool link modes
|
|
* @pf: PF struct with phy_types
|
|
* @ks: ethtool link ksettings struct to fill out
|
|
*
|
|
**/
|
|
static void i40e_phy_type_to_ethtool(struct i40e_pf *pf,
|
|
struct ethtool_link_ksettings *ks)
|
|
{
|
|
struct i40e_link_status *hw_link_info = &pf->hw.phy.link_info;
|
|
u64 phy_types = pf->hw.phy.phy_types;
|
|
|
|
ethtool_link_ksettings_zero_link_mode(ks, supported);
|
|
ethtool_link_ksettings_zero_link_mode(ks, advertising);
|
|
|
|
if (phy_types & I40E_CAP_PHY_TYPE_SGMII) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseT_Full);
|
|
if (pf->hw_features & I40E_HW_100M_SGMII_CAPABLE) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
100baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
100baseT_Full);
|
|
}
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_XAUI ||
|
|
phy_types & I40E_CAP_PHY_TYPE_XFI ||
|
|
phy_types & I40E_CAP_PHY_TYPE_SFI ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_SFPP_CU ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_AOC) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_T) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_2_5GBASE_T) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
2500baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_2_5GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
2500baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_5GBASE_T) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
5000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_5GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
5000baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_XLAUI ||
|
|
phy_types & I40E_CAP_PHY_TYPE_XLPPI ||
|
|
phy_types & I40E_CAP_PHY_TYPE_40GBASE_AOC)
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseCR4_Full);
|
|
if (phy_types & I40E_CAP_PHY_TYPE_40GBASE_CR4_CU ||
|
|
phy_types & I40E_CAP_PHY_TYPE_40GBASE_CR4) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseCR4_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_40GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseCR4_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_100BASE_TX) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
100baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_100MB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
100baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_1000BASE_T) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseT_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_40GBASE_SR4) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseSR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseSR4_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_40GBASE_LR4) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseLR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseLR4_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_40GBASE_KR4) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseKR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseKR4_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_20GBASE_KR2) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
20000baseKR2_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_20GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
20000baseKR2_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_KX4) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseKX4_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseKX4_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_KR &&
|
|
!(pf->hw_features & I40E_HW_HAVE_CRT_RETIMER)) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseKR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseKR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_1000BASE_KX &&
|
|
!(pf->hw_features & I40E_HW_HAVE_CRT_RETIMER)) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseKX_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseKX_Full);
|
|
}
|
|
/* need to add 25G PHY types */
|
|
if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_KR) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseKR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseKR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_CR) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseCR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseCR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_SR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_LR) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseSR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseSR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_AOC ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_ACC) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseCR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseCR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_25GBASE_KR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_CR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_SR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_LR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_AOC ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_ACC) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_25GB) {
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_NONE);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_RS);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_BASER);
|
|
}
|
|
}
|
|
/* need to add new 10G PHY types */
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1_CU) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseCR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseCR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_SR) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseSR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseSR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_10GBASE_LR) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseLR_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseLR_Full);
|
|
}
|
|
if (phy_types & I40E_CAP_PHY_TYPE_1000BASE_SX ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_LX ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_T_OPTICAL) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseX_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseX_Full);
|
|
}
|
|
/* Autoneg PHY types */
|
|
if (phy_types & I40E_CAP_PHY_TYPE_SGMII ||
|
|
phy_types & I40E_CAP_PHY_TYPE_40GBASE_KR4 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_40GBASE_CR4_CU ||
|
|
phy_types & I40E_CAP_PHY_TYPE_40GBASE_CR4 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_SR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_LR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_KR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_25GBASE_CR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_20GBASE_KR2 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_SR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_LR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_KX4 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_KR ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1_CU ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_CR1 ||
|
|
phy_types & I40E_CAP_PHY_TYPE_10GBASE_T ||
|
|
phy_types & I40E_CAP_PHY_TYPE_5GBASE_T ||
|
|
phy_types & I40E_CAP_PHY_TYPE_2_5GBASE_T ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_T_OPTICAL ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_T ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_SX ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_LX ||
|
|
phy_types & I40E_CAP_PHY_TYPE_1000BASE_KX ||
|
|
phy_types & I40E_CAP_PHY_TYPE_100BASE_TX) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
Autoneg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_get_settings_link_up_fec - Get the FEC mode encoding from mask
|
|
* @req_fec_info: mask request FEC info
|
|
* @ks: ethtool ksettings to fill in
|
|
**/
|
|
static void i40e_get_settings_link_up_fec(u8 req_fec_info,
|
|
struct ethtool_link_ksettings *ks)
|
|
{
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER);
|
|
|
|
if ((I40E_AQ_SET_FEC_REQUEST_RS & req_fec_info) &&
|
|
(I40E_AQ_SET_FEC_REQUEST_KR & req_fec_info)) {
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_NONE);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_BASER);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
|
|
} else if (I40E_AQ_SET_FEC_REQUEST_RS & req_fec_info) {
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, FEC_RS);
|
|
} else if (I40E_AQ_SET_FEC_REQUEST_KR & req_fec_info) {
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_BASER);
|
|
} else {
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
FEC_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_get_settings_link_up - Get the Link settings for when link is up
|
|
* @hw: hw structure
|
|
* @ks: ethtool ksettings to fill in
|
|
* @netdev: network interface device structure
|
|
* @pf: pointer to physical function struct
|
|
**/
|
|
static void i40e_get_settings_link_up(struct i40e_hw *hw,
|
|
struct ethtool_link_ksettings *ks,
|
|
struct net_device *netdev,
|
|
struct i40e_pf *pf)
|
|
{
|
|
struct i40e_link_status *hw_link_info = &hw->phy.link_info;
|
|
struct ethtool_link_ksettings cap_ksettings;
|
|
u32 link_speed = hw_link_info->link_speed;
|
|
|
|
/* Initialize supported and advertised settings based on phy settings */
|
|
switch (hw_link_info->phy_type) {
|
|
case I40E_PHY_TYPE_40GBASE_CR4:
|
|
case I40E_PHY_TYPE_40GBASE_CR4_CU:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseCR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseCR4_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_XLAUI:
|
|
case I40E_PHY_TYPE_XLPPI:
|
|
case I40E_PHY_TYPE_40GBASE_AOC:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseCR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseCR4_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_40GBASE_SR4:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseSR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseSR4_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_40GBASE_LR4:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseLR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseLR4_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_25GBASE_SR:
|
|
case I40E_PHY_TYPE_25GBASE_LR:
|
|
case I40E_PHY_TYPE_10GBASE_SR:
|
|
case I40E_PHY_TYPE_10GBASE_LR:
|
|
case I40E_PHY_TYPE_1000BASE_SX:
|
|
case I40E_PHY_TYPE_1000BASE_LX:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseSR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseSR_Full);
|
|
i40e_get_settings_link_up_fec(hw_link_info->req_fec_info, ks);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseSR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseSR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseLR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseLR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseX_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseX_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
if (hw_link_info->module_type[2] &
|
|
I40E_MODULE_TYPE_1000BASE_SX ||
|
|
hw_link_info->module_type[2] &
|
|
I40E_MODULE_TYPE_1000BASE_LX) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
if (hw_link_info->requested_speeds &
|
|
I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(
|
|
ks, advertising, 1000baseT_Full);
|
|
}
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_10GBASE_T:
|
|
case I40E_PHY_TYPE_5GBASE_T:
|
|
case I40E_PHY_TYPE_2_5GBASE_T:
|
|
case I40E_PHY_TYPE_1000BASE_T:
|
|
case I40E_PHY_TYPE_100BASE_TX:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
5000baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
2500baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
100baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_5GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
5000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_2_5GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
2500baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_100MB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
100baseT_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_1000BASE_T_OPTICAL:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseT_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_10GBASE_CR1_CU:
|
|
case I40E_PHY_TYPE_10GBASE_CR1:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_XAUI:
|
|
case I40E_PHY_TYPE_XFI:
|
|
case I40E_PHY_TYPE_SFI:
|
|
case I40E_PHY_TYPE_10GBASE_SFPP_CU:
|
|
case I40E_PHY_TYPE_10GBASE_AOC:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_10GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseT_Full);
|
|
break;
|
|
case I40E_PHY_TYPE_SGMII:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseT_Full);
|
|
if (hw_link_info->requested_speeds & I40E_LINK_SPEED_1GB)
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseT_Full);
|
|
if (pf->hw_features & I40E_HW_100M_SGMII_CAPABLE) {
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
100baseT_Full);
|
|
if (hw_link_info->requested_speeds &
|
|
I40E_LINK_SPEED_100MB)
|
|
ethtool_link_ksettings_add_link_mode(
|
|
ks, advertising, 100baseT_Full);
|
|
}
|
|
break;
|
|
case I40E_PHY_TYPE_40GBASE_KR4:
|
|
case I40E_PHY_TYPE_25GBASE_KR:
|
|
case I40E_PHY_TYPE_20GBASE_KR2:
|
|
case I40E_PHY_TYPE_10GBASE_KR:
|
|
case I40E_PHY_TYPE_10GBASE_KX4:
|
|
case I40E_PHY_TYPE_1000BASE_KX:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
40000baseKR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseKR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
20000baseKR2_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseKR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseKX4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
1000baseKX_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
40000baseKR4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseKR_Full);
|
|
i40e_get_settings_link_up_fec(hw_link_info->req_fec_info, ks);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
20000baseKR2_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseKR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseKX4_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
1000baseKX_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
break;
|
|
case I40E_PHY_TYPE_25GBASE_CR:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseCR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseCR_Full);
|
|
i40e_get_settings_link_up_fec(hw_link_info->req_fec_info, ks);
|
|
|
|
break;
|
|
case I40E_PHY_TYPE_25GBASE_AOC:
|
|
case I40E_PHY_TYPE_25GBASE_ACC:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
25000baseCR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
25000baseCR_Full);
|
|
i40e_get_settings_link_up_fec(hw_link_info->req_fec_info, ks);
|
|
|
|
ethtool_link_ksettings_add_link_mode(ks, supported,
|
|
10000baseCR_Full);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
10000baseCR_Full);
|
|
break;
|
|
default:
|
|
/* if we got here and link is up something bad is afoot */
|
|
netdev_info(netdev,
|
|
"WARNING: Link is up but PHY type 0x%x is not recognized.\n",
|
|
hw_link_info->phy_type);
|
|
}
|
|
|
|
/* Now that we've worked out everything that could be supported by the
|
|
* current PHY type, get what is supported by the NVM and intersect
|
|
* them to get what is truly supported
|
|
*/
|
|
memset(&cap_ksettings, 0, sizeof(struct ethtool_link_ksettings));
|
|
i40e_phy_type_to_ethtool(pf, &cap_ksettings);
|
|
ethtool_intersect_link_masks(ks, &cap_ksettings);
|
|
|
|
/* Set speed and duplex */
|
|
switch (link_speed) {
|
|
case I40E_LINK_SPEED_40GB:
|
|
ks->base.speed = SPEED_40000;
|
|
break;
|
|
case I40E_LINK_SPEED_25GB:
|
|
ks->base.speed = SPEED_25000;
|
|
break;
|
|
case I40E_LINK_SPEED_20GB:
|
|
ks->base.speed = SPEED_20000;
|
|
break;
|
|
case I40E_LINK_SPEED_10GB:
|
|
ks->base.speed = SPEED_10000;
|
|
break;
|
|
case I40E_LINK_SPEED_5GB:
|
|
ks->base.speed = SPEED_5000;
|
|
break;
|
|
case I40E_LINK_SPEED_2_5GB:
|
|
ks->base.speed = SPEED_2500;
|
|
break;
|
|
case I40E_LINK_SPEED_1GB:
|
|
ks->base.speed = SPEED_1000;
|
|
break;
|
|
case I40E_LINK_SPEED_100MB:
|
|
ks->base.speed = SPEED_100;
|
|
break;
|
|
default:
|
|
ks->base.speed = SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
ks->base.duplex = DUPLEX_FULL;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_settings_link_down - Get the Link settings for when link is down
|
|
* @hw: hw structure
|
|
* @ks: ethtool ksettings to fill in
|
|
* @pf: pointer to physical function struct
|
|
*
|
|
* Reports link settings that can be determined when link is down
|
|
**/
|
|
static void i40e_get_settings_link_down(struct i40e_hw *hw,
|
|
struct ethtool_link_ksettings *ks,
|
|
struct i40e_pf *pf)
|
|
{
|
|
/* link is down and the driver needs to fall back on
|
|
* supported phy types to figure out what info to display
|
|
*/
|
|
i40e_phy_type_to_ethtool(pf, ks);
|
|
|
|
/* With no link speed and duplex are unknown */
|
|
ks->base.speed = SPEED_UNKNOWN;
|
|
ks->base.duplex = DUPLEX_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_link_ksettings - Get Link Speed and Duplex settings
|
|
* @netdev: network interface device structure
|
|
* @ks: ethtool ksettings
|
|
*
|
|
* Reports speed/duplex settings based on media_type
|
|
**/
|
|
static int i40e_get_link_ksettings(struct net_device *netdev,
|
|
struct ethtool_link_ksettings *ks)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
struct i40e_link_status *hw_link_info = &hw->phy.link_info;
|
|
bool link_up = hw_link_info->link_info & I40E_AQ_LINK_UP;
|
|
|
|
ethtool_link_ksettings_zero_link_mode(ks, supported);
|
|
ethtool_link_ksettings_zero_link_mode(ks, advertising);
|
|
|
|
if (link_up)
|
|
i40e_get_settings_link_up(hw, ks, netdev, pf);
|
|
else
|
|
i40e_get_settings_link_down(hw, ks, pf);
|
|
|
|
/* Now set the settings that don't rely on link being up/down */
|
|
/* Set autoneg settings */
|
|
ks->base.autoneg = ((hw_link_info->an_info & I40E_AQ_AN_COMPLETED) ?
|
|
AUTONEG_ENABLE : AUTONEG_DISABLE);
|
|
|
|
/* Set media type settings */
|
|
switch (hw->phy.media_type) {
|
|
case I40E_MEDIA_TYPE_BACKPLANE:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Backplane);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
Backplane);
|
|
ks->base.port = PORT_NONE;
|
|
break;
|
|
case I40E_MEDIA_TYPE_BASET:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, TP);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, TP);
|
|
ks->base.port = PORT_TP;
|
|
break;
|
|
case I40E_MEDIA_TYPE_DA:
|
|
case I40E_MEDIA_TYPE_CX4:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FIBRE);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, FIBRE);
|
|
ks->base.port = PORT_DA;
|
|
break;
|
|
case I40E_MEDIA_TYPE_FIBER:
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, FIBRE);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, FIBRE);
|
|
ks->base.port = PORT_FIBRE;
|
|
break;
|
|
case I40E_MEDIA_TYPE_UNKNOWN:
|
|
default:
|
|
ks->base.port = PORT_OTHER;
|
|
break;
|
|
}
|
|
|
|
/* Set flow control settings */
|
|
ethtool_link_ksettings_add_link_mode(ks, supported, Pause);
|
|
|
|
switch (hw->fc.requested_mode) {
|
|
case I40E_FC_FULL:
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Pause);
|
|
break;
|
|
case I40E_FC_TX_PAUSE:
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
Asym_Pause);
|
|
break;
|
|
case I40E_FC_RX_PAUSE:
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising, Pause);
|
|
ethtool_link_ksettings_add_link_mode(ks, advertising,
|
|
Asym_Pause);
|
|
break;
|
|
default:
|
|
ethtool_link_ksettings_del_link_mode(ks, advertising, Pause);
|
|
ethtool_link_ksettings_del_link_mode(ks, advertising,
|
|
Asym_Pause);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_link_ksettings - Set Speed and Duplex
|
|
* @netdev: network interface device structure
|
|
* @ks: ethtool ksettings
|
|
*
|
|
* Set speed/duplex per media_types advertised/forced
|
|
**/
|
|
static int i40e_set_link_ksettings(struct net_device *netdev,
|
|
const struct ethtool_link_ksettings *ks)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_aq_get_phy_abilities_resp abilities;
|
|
struct ethtool_link_ksettings safe_ks;
|
|
struct ethtool_link_ksettings copy_ks;
|
|
struct i40e_aq_set_phy_config config;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
bool autoneg_changed = false;
|
|
i40e_status status = 0;
|
|
int timeout = 50;
|
|
int err = 0;
|
|
u8 autoneg;
|
|
|
|
/* Changing port settings is not supported if this isn't the
|
|
* port's controlling PF
|
|
*/
|
|
if (hw->partition_id != 1) {
|
|
i40e_partition_setting_complaint(pf);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (vsi != pf->vsi[pf->lan_vsi])
|
|
return -EOPNOTSUPP;
|
|
if (hw->phy.media_type != I40E_MEDIA_TYPE_BASET &&
|
|
hw->phy.media_type != I40E_MEDIA_TYPE_FIBER &&
|
|
hw->phy.media_type != I40E_MEDIA_TYPE_BACKPLANE &&
|
|
hw->phy.media_type != I40E_MEDIA_TYPE_DA &&
|
|
hw->phy.link_info.link_info & I40E_AQ_LINK_UP)
|
|
return -EOPNOTSUPP;
|
|
if (hw->device_id == I40E_DEV_ID_KX_B ||
|
|
hw->device_id == I40E_DEV_ID_KX_C ||
|
|
hw->device_id == I40E_DEV_ID_20G_KR2 ||
|
|
hw->device_id == I40E_DEV_ID_20G_KR2_A ||
|
|
hw->device_id == I40E_DEV_ID_25G_B ||
|
|
hw->device_id == I40E_DEV_ID_KX_X722) {
|
|
netdev_info(netdev, "Changing settings is not supported on backplane.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* copy the ksettings to copy_ks to avoid modifying the origin */
|
|
memcpy(©_ks, ks, sizeof(struct ethtool_link_ksettings));
|
|
|
|
/* save autoneg out of ksettings */
|
|
autoneg = copy_ks.base.autoneg;
|
|
|
|
/* get our own copy of the bits to check against */
|
|
memset(&safe_ks, 0, sizeof(struct ethtool_link_ksettings));
|
|
safe_ks.base.cmd = copy_ks.base.cmd;
|
|
safe_ks.base.link_mode_masks_nwords =
|
|
copy_ks.base.link_mode_masks_nwords;
|
|
i40e_get_link_ksettings(netdev, &safe_ks);
|
|
|
|
/* Get link modes supported by hardware and check against modes
|
|
* requested by the user. Return an error if unsupported mode was set.
|
|
*/
|
|
if (!bitmap_subset(copy_ks.link_modes.advertising,
|
|
safe_ks.link_modes.supported,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS))
|
|
return -EINVAL;
|
|
|
|
/* set autoneg back to what it currently is */
|
|
copy_ks.base.autoneg = safe_ks.base.autoneg;
|
|
|
|
/* If copy_ks.base and safe_ks.base are not the same now, then they are
|
|
* trying to set something that we do not support.
|
|
*/
|
|
if (memcmp(©_ks.base, &safe_ks.base,
|
|
sizeof(struct ethtool_link_settings)))
|
|
return -EOPNOTSUPP;
|
|
|
|
while (test_and_set_bit(__I40E_CONFIG_BUSY, pf->state)) {
|
|
timeout--;
|
|
if (!timeout)
|
|
return -EBUSY;
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
/* Get the current phy config */
|
|
status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
|
|
NULL);
|
|
if (status) {
|
|
err = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
/* Copy abilities to config in case autoneg is not
|
|
* set below
|
|
*/
|
|
memset(&config, 0, sizeof(struct i40e_aq_set_phy_config));
|
|
config.abilities = abilities.abilities;
|
|
|
|
/* Check autoneg */
|
|
if (autoneg == AUTONEG_ENABLE) {
|
|
/* If autoneg was not already enabled */
|
|
if (!(hw->phy.link_info.an_info & I40E_AQ_AN_COMPLETED)) {
|
|
/* If autoneg is not supported, return error */
|
|
if (!ethtool_link_ksettings_test_link_mode(&safe_ks,
|
|
supported,
|
|
Autoneg)) {
|
|
netdev_info(netdev, "Autoneg not supported on this phy\n");
|
|
err = -EINVAL;
|
|
goto done;
|
|
}
|
|
/* Autoneg is allowed to change */
|
|
config.abilities = abilities.abilities |
|
|
I40E_AQ_PHY_ENABLE_AN;
|
|
autoneg_changed = true;
|
|
}
|
|
} else {
|
|
/* If autoneg is currently enabled */
|
|
if (hw->phy.link_info.an_info & I40E_AQ_AN_COMPLETED) {
|
|
/* If autoneg is supported 10GBASE_T is the only PHY
|
|
* that can disable it, so otherwise return error
|
|
*/
|
|
if (ethtool_link_ksettings_test_link_mode(&safe_ks,
|
|
supported,
|
|
Autoneg) &&
|
|
hw->phy.link_info.phy_type !=
|
|
I40E_PHY_TYPE_10GBASE_T) {
|
|
netdev_info(netdev, "Autoneg cannot be disabled on this phy\n");
|
|
err = -EINVAL;
|
|
goto done;
|
|
}
|
|
/* Autoneg is allowed to change */
|
|
config.abilities = abilities.abilities &
|
|
~I40E_AQ_PHY_ENABLE_AN;
|
|
autoneg_changed = true;
|
|
}
|
|
}
|
|
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
100baseT_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_100MB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
1000baseT_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
1000baseX_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
1000baseKX_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_1GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseT_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseKX4_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseKR_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseCR_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseSR_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
10000baseLR_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_10GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
2500baseT_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_2_5GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
5000baseT_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_5GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
20000baseKR2_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_20GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
25000baseCR_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
25000baseKR_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
25000baseSR_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_25GB;
|
|
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
40000baseKR4_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
40000baseCR4_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
40000baseSR4_Full) ||
|
|
ethtool_link_ksettings_test_link_mode(ks, advertising,
|
|
40000baseLR4_Full))
|
|
config.link_speed |= I40E_LINK_SPEED_40GB;
|
|
|
|
/* If speed didn't get set, set it to what it currently is.
|
|
* This is needed because if advertise is 0 (as it is when autoneg
|
|
* is disabled) then speed won't get set.
|
|
*/
|
|
if (!config.link_speed)
|
|
config.link_speed = abilities.link_speed;
|
|
if (autoneg_changed || abilities.link_speed != config.link_speed) {
|
|
/* copy over the rest of the abilities */
|
|
config.phy_type = abilities.phy_type;
|
|
config.phy_type_ext = abilities.phy_type_ext;
|
|
config.eee_capability = abilities.eee_capability;
|
|
config.eeer = abilities.eeer_val;
|
|
config.low_power_ctrl = abilities.d3_lpan;
|
|
config.fec_config = abilities.fec_cfg_curr_mod_ext_info &
|
|
I40E_AQ_PHY_FEC_CONFIG_MASK;
|
|
|
|
/* save the requested speeds */
|
|
hw->phy.link_info.requested_speeds = config.link_speed;
|
|
/* set link and auto negotiation so changes take effect */
|
|
config.abilities |= I40E_AQ_PHY_ENABLE_ATOMIC_LINK;
|
|
/* If link is up put link down */
|
|
if (hw->phy.link_info.link_info & I40E_AQ_LINK_UP) {
|
|
/* Tell the OS link is going down, the link will go
|
|
* back up when fw says it is ready asynchronously
|
|
*/
|
|
i40e_print_link_message(vsi, false);
|
|
netif_carrier_off(netdev);
|
|
netif_tx_stop_all_queues(netdev);
|
|
}
|
|
|
|
/* make the aq call */
|
|
status = i40e_aq_set_phy_config(hw, &config, NULL);
|
|
if (status) {
|
|
netdev_info(netdev,
|
|
"Set phy config failed, err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
err = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
status = i40e_update_link_info(hw);
|
|
if (status)
|
|
netdev_dbg(netdev,
|
|
"Updating link info failed with err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
|
|
} else {
|
|
netdev_info(netdev, "Nothing changed, exiting without setting anything.\n");
|
|
}
|
|
|
|
done:
|
|
clear_bit(__I40E_CONFIG_BUSY, pf->state);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int i40e_set_fec_cfg(struct net_device *netdev, u8 fec_cfg)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_aq_get_phy_abilities_resp abilities;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
i40e_status status = 0;
|
|
u32 flags = 0;
|
|
int err = 0;
|
|
|
|
flags = READ_ONCE(pf->flags);
|
|
i40e_set_fec_in_flags(fec_cfg, &flags);
|
|
|
|
/* Get the current phy config */
|
|
memset(&abilities, 0, sizeof(abilities));
|
|
status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
|
|
NULL);
|
|
if (status) {
|
|
err = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
if (abilities.fec_cfg_curr_mod_ext_info != fec_cfg) {
|
|
struct i40e_aq_set_phy_config config;
|
|
|
|
memset(&config, 0, sizeof(config));
|
|
config.phy_type = abilities.phy_type;
|
|
config.abilities = abilities.abilities;
|
|
config.phy_type_ext = abilities.phy_type_ext;
|
|
config.link_speed = abilities.link_speed;
|
|
config.eee_capability = abilities.eee_capability;
|
|
config.eeer = abilities.eeer_val;
|
|
config.low_power_ctrl = abilities.d3_lpan;
|
|
config.fec_config = fec_cfg & I40E_AQ_PHY_FEC_CONFIG_MASK;
|
|
status = i40e_aq_set_phy_config(hw, &config, NULL);
|
|
if (status) {
|
|
netdev_info(netdev,
|
|
"Set phy config failed, err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
err = -EAGAIN;
|
|
goto done;
|
|
}
|
|
pf->flags = flags;
|
|
status = i40e_update_link_info(hw);
|
|
if (status)
|
|
/* debug level message only due to relation to the link
|
|
* itself rather than to the FEC settings
|
|
* (e.g. no physical connection etc.)
|
|
*/
|
|
netdev_dbg(netdev,
|
|
"Updating link info failed with err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
}
|
|
|
|
done:
|
|
return err;
|
|
}
|
|
|
|
static int i40e_get_fec_param(struct net_device *netdev,
|
|
struct ethtool_fecparam *fecparam)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_aq_get_phy_abilities_resp abilities;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
i40e_status status = 0;
|
|
int err = 0;
|
|
u8 fec_cfg;
|
|
|
|
/* Get the current phy config */
|
|
memset(&abilities, 0, sizeof(abilities));
|
|
status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
|
|
NULL);
|
|
if (status) {
|
|
err = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
fecparam->fec = 0;
|
|
fec_cfg = abilities.fec_cfg_curr_mod_ext_info;
|
|
if (fec_cfg & I40E_AQ_SET_FEC_AUTO)
|
|
fecparam->fec |= ETHTOOL_FEC_AUTO;
|
|
else if (fec_cfg & (I40E_AQ_SET_FEC_REQUEST_RS |
|
|
I40E_AQ_SET_FEC_ABILITY_RS))
|
|
fecparam->fec |= ETHTOOL_FEC_RS;
|
|
else if (fec_cfg & (I40E_AQ_SET_FEC_REQUEST_KR |
|
|
I40E_AQ_SET_FEC_ABILITY_KR))
|
|
fecparam->fec |= ETHTOOL_FEC_BASER;
|
|
if (fec_cfg == 0)
|
|
fecparam->fec |= ETHTOOL_FEC_OFF;
|
|
|
|
if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_KR_ENA)
|
|
fecparam->active_fec = ETHTOOL_FEC_BASER;
|
|
else if (hw->phy.link_info.fec_info & I40E_AQ_CONFIG_FEC_RS_ENA)
|
|
fecparam->active_fec = ETHTOOL_FEC_RS;
|
|
else
|
|
fecparam->active_fec = ETHTOOL_FEC_OFF;
|
|
done:
|
|
return err;
|
|
}
|
|
|
|
static int i40e_set_fec_param(struct net_device *netdev,
|
|
struct ethtool_fecparam *fecparam)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u8 fec_cfg = 0;
|
|
int err = 0;
|
|
|
|
if (hw->device_id != I40E_DEV_ID_25G_SFP28 &&
|
|
hw->device_id != I40E_DEV_ID_25G_B) {
|
|
err = -EPERM;
|
|
goto done;
|
|
}
|
|
|
|
switch (fecparam->fec) {
|
|
case ETHTOOL_FEC_AUTO:
|
|
fec_cfg = I40E_AQ_SET_FEC_AUTO;
|
|
break;
|
|
case ETHTOOL_FEC_RS:
|
|
fec_cfg = (I40E_AQ_SET_FEC_REQUEST_RS |
|
|
I40E_AQ_SET_FEC_ABILITY_RS);
|
|
break;
|
|
case ETHTOOL_FEC_BASER:
|
|
fec_cfg = (I40E_AQ_SET_FEC_REQUEST_KR |
|
|
I40E_AQ_SET_FEC_ABILITY_KR);
|
|
break;
|
|
case ETHTOOL_FEC_OFF:
|
|
case ETHTOOL_FEC_NONE:
|
|
fec_cfg = 0;
|
|
break;
|
|
default:
|
|
dev_warn(&pf->pdev->dev, "Unsupported FEC mode: %d",
|
|
fecparam->fec);
|
|
err = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
err = i40e_set_fec_cfg(netdev, fec_cfg);
|
|
|
|
done:
|
|
return err;
|
|
}
|
|
|
|
static int i40e_nway_reset(struct net_device *netdev)
|
|
{
|
|
/* restart autonegotiation */
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
bool link_up = hw->phy.link_info.link_info & I40E_AQ_LINK_UP;
|
|
i40e_status ret = 0;
|
|
|
|
ret = i40e_aq_set_link_restart_an(hw, link_up, NULL);
|
|
if (ret) {
|
|
netdev_info(netdev, "link restart failed, err %s aq_err %s\n",
|
|
i40e_stat_str(hw, ret),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_pauseparam - Get Flow Control status
|
|
* @netdev: netdevice structure
|
|
* @pause: buffer to return pause parameters
|
|
*
|
|
* Return tx/rx-pause status
|
|
**/
|
|
static void i40e_get_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
struct i40e_link_status *hw_link_info = &hw->phy.link_info;
|
|
struct i40e_dcbx_config *dcbx_cfg = &hw->local_dcbx_config;
|
|
|
|
pause->autoneg =
|
|
((hw_link_info->an_info & I40E_AQ_AN_COMPLETED) ?
|
|
AUTONEG_ENABLE : AUTONEG_DISABLE);
|
|
|
|
/* PFC enabled so report LFC as off */
|
|
if (dcbx_cfg->pfc.pfcenable) {
|
|
pause->rx_pause = 0;
|
|
pause->tx_pause = 0;
|
|
return;
|
|
}
|
|
|
|
if (hw->fc.current_mode == I40E_FC_RX_PAUSE) {
|
|
pause->rx_pause = 1;
|
|
} else if (hw->fc.current_mode == I40E_FC_TX_PAUSE) {
|
|
pause->tx_pause = 1;
|
|
} else if (hw->fc.current_mode == I40E_FC_FULL) {
|
|
pause->rx_pause = 1;
|
|
pause->tx_pause = 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_set_pauseparam - Set Flow Control parameter
|
|
* @netdev: network interface device structure
|
|
* @pause: return tx/rx flow control status
|
|
**/
|
|
static int i40e_set_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
struct i40e_link_status *hw_link_info = &hw->phy.link_info;
|
|
struct i40e_dcbx_config *dcbx_cfg = &hw->local_dcbx_config;
|
|
bool link_up = hw_link_info->link_info & I40E_AQ_LINK_UP;
|
|
i40e_status status;
|
|
u8 aq_failures;
|
|
int err = 0;
|
|
u32 is_an;
|
|
|
|
/* Changing the port's flow control is not supported if this isn't the
|
|
* port's controlling PF
|
|
*/
|
|
if (hw->partition_id != 1) {
|
|
i40e_partition_setting_complaint(pf);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (vsi != pf->vsi[pf->lan_vsi])
|
|
return -EOPNOTSUPP;
|
|
|
|
is_an = hw_link_info->an_info & I40E_AQ_AN_COMPLETED;
|
|
if (pause->autoneg != is_an) {
|
|
netdev_info(netdev, "To change autoneg please use: ethtool -s <dev> autoneg <on|off>\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* If we have link and don't have autoneg */
|
|
if (!test_bit(__I40E_DOWN, pf->state) && !is_an) {
|
|
/* Send message that it might not necessarily work*/
|
|
netdev_info(netdev, "Autoneg did not complete so changing settings may not result in an actual change.\n");
|
|
}
|
|
|
|
if (dcbx_cfg->pfc.pfcenable) {
|
|
netdev_info(netdev,
|
|
"Priority flow control enabled. Cannot set link flow control.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (pause->rx_pause && pause->tx_pause)
|
|
hw->fc.requested_mode = I40E_FC_FULL;
|
|
else if (pause->rx_pause && !pause->tx_pause)
|
|
hw->fc.requested_mode = I40E_FC_RX_PAUSE;
|
|
else if (!pause->rx_pause && pause->tx_pause)
|
|
hw->fc.requested_mode = I40E_FC_TX_PAUSE;
|
|
else if (!pause->rx_pause && !pause->tx_pause)
|
|
hw->fc.requested_mode = I40E_FC_NONE;
|
|
else
|
|
return -EINVAL;
|
|
|
|
/* Tell the OS link is going down, the link will go back up when fw
|
|
* says it is ready asynchronously
|
|
*/
|
|
i40e_print_link_message(vsi, false);
|
|
netif_carrier_off(netdev);
|
|
netif_tx_stop_all_queues(netdev);
|
|
|
|
/* Set the fc mode and only restart an if link is up*/
|
|
status = i40e_set_fc(hw, &aq_failures, link_up);
|
|
|
|
if (aq_failures & I40E_SET_FC_AQ_FAIL_GET) {
|
|
netdev_info(netdev, "Set fc failed on the get_phy_capabilities call with err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
err = -EAGAIN;
|
|
}
|
|
if (aq_failures & I40E_SET_FC_AQ_FAIL_SET) {
|
|
netdev_info(netdev, "Set fc failed on the set_phy_config call with err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
err = -EAGAIN;
|
|
}
|
|
if (aq_failures & I40E_SET_FC_AQ_FAIL_UPDATE) {
|
|
netdev_info(netdev, "Set fc failed on the get_link_info call with err %s aq_err %s\n",
|
|
i40e_stat_str(hw, status),
|
|
i40e_aq_str(hw, hw->aq.asq_last_status));
|
|
err = -EAGAIN;
|
|
}
|
|
|
|
if (!test_bit(__I40E_DOWN, pf->state) && is_an) {
|
|
/* Give it a little more time to try to come back */
|
|
msleep(75);
|
|
if (!test_bit(__I40E_DOWN, pf->state))
|
|
return i40e_nway_reset(netdev);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static u32 i40e_get_msglevel(struct net_device *netdev)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
u32 debug_mask = pf->hw.debug_mask;
|
|
|
|
if (debug_mask)
|
|
netdev_info(netdev, "i40e debug_mask: 0x%08X\n", debug_mask);
|
|
|
|
return pf->msg_enable;
|
|
}
|
|
|
|
static void i40e_set_msglevel(struct net_device *netdev, u32 data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
|
|
if (I40E_DEBUG_USER & data)
|
|
pf->hw.debug_mask = data;
|
|
else
|
|
pf->msg_enable = data;
|
|
}
|
|
|
|
static int i40e_get_regs_len(struct net_device *netdev)
|
|
{
|
|
int reg_count = 0;
|
|
int i;
|
|
|
|
for (i = 0; i40e_reg_list[i].offset != 0; i++)
|
|
reg_count += i40e_reg_list[i].elements;
|
|
|
|
return reg_count * sizeof(u32);
|
|
}
|
|
|
|
static void i40e_get_regs(struct net_device *netdev, struct ethtool_regs *regs,
|
|
void *p)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u32 *reg_buf = p;
|
|
unsigned int i, j, ri;
|
|
u32 reg;
|
|
|
|
/* Tell ethtool which driver-version-specific regs output we have.
|
|
*
|
|
* At some point, if we have ethtool doing special formatting of
|
|
* this data, it will rely on this version number to know how to
|
|
* interpret things. Hence, this needs to be updated if/when the
|
|
* diags register table is changed.
|
|
*/
|
|
regs->version = 1;
|
|
|
|
/* loop through the diags reg table for what to print */
|
|
ri = 0;
|
|
for (i = 0; i40e_reg_list[i].offset != 0; i++) {
|
|
for (j = 0; j < i40e_reg_list[i].elements; j++) {
|
|
reg = i40e_reg_list[i].offset
|
|
+ (j * i40e_reg_list[i].stride);
|
|
reg_buf[ri++] = rd32(hw, reg);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static int i40e_get_eeprom(struct net_device *netdev,
|
|
struct ethtool_eeprom *eeprom, u8 *bytes)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_hw *hw = &np->vsi->back->hw;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
int ret_val = 0, len, offset;
|
|
u8 *eeprom_buff;
|
|
u16 i, sectors;
|
|
bool last;
|
|
u32 magic;
|
|
|
|
#define I40E_NVM_SECTOR_SIZE 4096
|
|
if (eeprom->len == 0)
|
|
return -EINVAL;
|
|
|
|
/* check for NVMUpdate access method */
|
|
magic = hw->vendor_id | (hw->device_id << 16);
|
|
if (eeprom->magic && eeprom->magic != magic) {
|
|
struct i40e_nvm_access *cmd = (struct i40e_nvm_access *)eeprom;
|
|
int errno = 0;
|
|
|
|
/* make sure it is the right magic for NVMUpdate */
|
|
if ((eeprom->magic >> 16) != hw->device_id)
|
|
errno = -EINVAL;
|
|
else if (test_bit(__I40E_RESET_RECOVERY_PENDING, pf->state) ||
|
|
test_bit(__I40E_RESET_INTR_RECEIVED, pf->state))
|
|
errno = -EBUSY;
|
|
else
|
|
ret_val = i40e_nvmupd_command(hw, cmd, bytes, &errno);
|
|
|
|
if ((errno || ret_val) && (hw->debug_mask & I40E_DEBUG_NVM))
|
|
dev_info(&pf->pdev->dev,
|
|
"NVMUpdate read failed err=%d status=0x%x errno=%d module=%d offset=0x%x size=%d\n",
|
|
ret_val, hw->aq.asq_last_status, errno,
|
|
(u8)(cmd->config & I40E_NVM_MOD_PNT_MASK),
|
|
cmd->offset, cmd->data_size);
|
|
|
|
return errno;
|
|
}
|
|
|
|
/* normal ethtool get_eeprom support */
|
|
eeprom->magic = hw->vendor_id | (hw->device_id << 16);
|
|
|
|
eeprom_buff = kzalloc(eeprom->len, GFP_KERNEL);
|
|
if (!eeprom_buff)
|
|
return -ENOMEM;
|
|
|
|
ret_val = i40e_acquire_nvm(hw, I40E_RESOURCE_READ);
|
|
if (ret_val) {
|
|
dev_info(&pf->pdev->dev,
|
|
"Failed Acquiring NVM resource for read err=%d status=0x%x\n",
|
|
ret_val, hw->aq.asq_last_status);
|
|
goto free_buff;
|
|
}
|
|
|
|
sectors = eeprom->len / I40E_NVM_SECTOR_SIZE;
|
|
sectors += (eeprom->len % I40E_NVM_SECTOR_SIZE) ? 1 : 0;
|
|
len = I40E_NVM_SECTOR_SIZE;
|
|
last = false;
|
|
for (i = 0; i < sectors; i++) {
|
|
if (i == (sectors - 1)) {
|
|
len = eeprom->len - (I40E_NVM_SECTOR_SIZE * i);
|
|
last = true;
|
|
}
|
|
offset = eeprom->offset + (I40E_NVM_SECTOR_SIZE * i),
|
|
ret_val = i40e_aq_read_nvm(hw, 0x0, offset, len,
|
|
(u8 *)eeprom_buff + (I40E_NVM_SECTOR_SIZE * i),
|
|
last, NULL);
|
|
if (ret_val && hw->aq.asq_last_status == I40E_AQ_RC_EPERM) {
|
|
dev_info(&pf->pdev->dev,
|
|
"read NVM failed, invalid offset 0x%x\n",
|
|
offset);
|
|
break;
|
|
} else if (ret_val &&
|
|
hw->aq.asq_last_status == I40E_AQ_RC_EACCES) {
|
|
dev_info(&pf->pdev->dev,
|
|
"read NVM failed, access, offset 0x%x\n",
|
|
offset);
|
|
break;
|
|
} else if (ret_val) {
|
|
dev_info(&pf->pdev->dev,
|
|
"read NVM failed offset %d err=%d status=0x%x\n",
|
|
offset, ret_val, hw->aq.asq_last_status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
i40e_release_nvm(hw);
|
|
memcpy(bytes, (u8 *)eeprom_buff, eeprom->len);
|
|
free_buff:
|
|
kfree(eeprom_buff);
|
|
return ret_val;
|
|
}
|
|
|
|
static int i40e_get_eeprom_len(struct net_device *netdev)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_hw *hw = &np->vsi->back->hw;
|
|
u32 val;
|
|
|
|
#define X722_EEPROM_SCOPE_LIMIT 0x5B9FFF
|
|
if (hw->mac.type == I40E_MAC_X722) {
|
|
val = X722_EEPROM_SCOPE_LIMIT + 1;
|
|
return val;
|
|
}
|
|
val = (rd32(hw, I40E_GLPCI_LBARCTRL)
|
|
& I40E_GLPCI_LBARCTRL_FL_SIZE_MASK)
|
|
>> I40E_GLPCI_LBARCTRL_FL_SIZE_SHIFT;
|
|
/* register returns value in power of 2, 64Kbyte chunks. */
|
|
val = (64 * 1024) * BIT(val);
|
|
return val;
|
|
}
|
|
|
|
static int i40e_set_eeprom(struct net_device *netdev,
|
|
struct ethtool_eeprom *eeprom, u8 *bytes)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_hw *hw = &np->vsi->back->hw;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_nvm_access *cmd = (struct i40e_nvm_access *)eeprom;
|
|
int ret_val = 0;
|
|
int errno = 0;
|
|
u32 magic;
|
|
|
|
/* normal ethtool set_eeprom is not supported */
|
|
magic = hw->vendor_id | (hw->device_id << 16);
|
|
if (eeprom->magic == magic)
|
|
errno = -EOPNOTSUPP;
|
|
/* check for NVMUpdate access method */
|
|
else if (!eeprom->magic || (eeprom->magic >> 16) != hw->device_id)
|
|
errno = -EINVAL;
|
|
else if (test_bit(__I40E_RESET_RECOVERY_PENDING, pf->state) ||
|
|
test_bit(__I40E_RESET_INTR_RECEIVED, pf->state))
|
|
errno = -EBUSY;
|
|
else
|
|
ret_val = i40e_nvmupd_command(hw, cmd, bytes, &errno);
|
|
|
|
if ((errno || ret_val) && (hw->debug_mask & I40E_DEBUG_NVM))
|
|
dev_info(&pf->pdev->dev,
|
|
"NVMUpdate write failed err=%d status=0x%x errno=%d module=%d offset=0x%x size=%d\n",
|
|
ret_val, hw->aq.asq_last_status, errno,
|
|
(u8)(cmd->config & I40E_NVM_MOD_PNT_MASK),
|
|
cmd->offset, cmd->data_size);
|
|
|
|
return errno;
|
|
}
|
|
|
|
static void i40e_get_drvinfo(struct net_device *netdev,
|
|
struct ethtool_drvinfo *drvinfo)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
|
|
strlcpy(drvinfo->driver, i40e_driver_name, sizeof(drvinfo->driver));
|
|
strlcpy(drvinfo->fw_version, i40e_nvm_version_str(&pf->hw),
|
|
sizeof(drvinfo->fw_version));
|
|
strlcpy(drvinfo->bus_info, pci_name(pf->pdev),
|
|
sizeof(drvinfo->bus_info));
|
|
drvinfo->n_priv_flags = I40E_PRIV_FLAGS_STR_LEN;
|
|
if (pf->hw.pf_id == 0)
|
|
drvinfo->n_priv_flags += I40E_GL_PRIV_FLAGS_STR_LEN;
|
|
}
|
|
|
|
static void i40e_get_ringparam(struct net_device *netdev,
|
|
struct ethtool_ringparam *ring)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_vsi *vsi = pf->vsi[pf->lan_vsi];
|
|
|
|
ring->rx_max_pending = I40E_MAX_NUM_DESCRIPTORS;
|
|
ring->tx_max_pending = I40E_MAX_NUM_DESCRIPTORS;
|
|
ring->rx_mini_max_pending = 0;
|
|
ring->rx_jumbo_max_pending = 0;
|
|
ring->rx_pending = vsi->rx_rings[0]->count;
|
|
ring->tx_pending = vsi->tx_rings[0]->count;
|
|
ring->rx_mini_pending = 0;
|
|
ring->rx_jumbo_pending = 0;
|
|
}
|
|
|
|
static bool i40e_active_tx_ring_index(struct i40e_vsi *vsi, u16 index)
|
|
{
|
|
if (i40e_enabled_xdp_vsi(vsi)) {
|
|
return index < vsi->num_queue_pairs ||
|
|
(index >= vsi->alloc_queue_pairs &&
|
|
index < vsi->alloc_queue_pairs + vsi->num_queue_pairs);
|
|
}
|
|
|
|
return index < vsi->num_queue_pairs;
|
|
}
|
|
|
|
static int i40e_set_ringparam(struct net_device *netdev,
|
|
struct ethtool_ringparam *ring)
|
|
{
|
|
struct i40e_ring *tx_rings = NULL, *rx_rings = NULL;
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_hw *hw = &np->vsi->back->hw;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
u32 new_rx_count, new_tx_count;
|
|
u16 tx_alloc_queue_pairs;
|
|
int timeout = 50;
|
|
int i, err = 0;
|
|
|
|
if ((ring->rx_mini_pending) || (ring->rx_jumbo_pending))
|
|
return -EINVAL;
|
|
|
|
if (ring->tx_pending > I40E_MAX_NUM_DESCRIPTORS ||
|
|
ring->tx_pending < I40E_MIN_NUM_DESCRIPTORS ||
|
|
ring->rx_pending > I40E_MAX_NUM_DESCRIPTORS ||
|
|
ring->rx_pending < I40E_MIN_NUM_DESCRIPTORS) {
|
|
netdev_info(netdev,
|
|
"Descriptors requested (Tx: %d / Rx: %d) out of range [%d-%d]\n",
|
|
ring->tx_pending, ring->rx_pending,
|
|
I40E_MIN_NUM_DESCRIPTORS, I40E_MAX_NUM_DESCRIPTORS);
|
|
return -EINVAL;
|
|
}
|
|
|
|
new_tx_count = ALIGN(ring->tx_pending, I40E_REQ_DESCRIPTOR_MULTIPLE);
|
|
new_rx_count = ALIGN(ring->rx_pending, I40E_REQ_DESCRIPTOR_MULTIPLE);
|
|
|
|
/* if nothing to do return success */
|
|
if ((new_tx_count == vsi->tx_rings[0]->count) &&
|
|
(new_rx_count == vsi->rx_rings[0]->count))
|
|
return 0;
|
|
|
|
/* If there is a AF_XDP UMEM attached to any of Rx rings,
|
|
* disallow changing the number of descriptors -- regardless
|
|
* if the netdev is running or not.
|
|
*/
|
|
if (i40e_xsk_any_rx_ring_enabled(vsi))
|
|
return -EBUSY;
|
|
|
|
while (test_and_set_bit(__I40E_CONFIG_BUSY, pf->state)) {
|
|
timeout--;
|
|
if (!timeout)
|
|
return -EBUSY;
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
if (!netif_running(vsi->netdev)) {
|
|
/* simple case - set for the next time the netdev is started */
|
|
for (i = 0; i < vsi->num_queue_pairs; i++) {
|
|
vsi->tx_rings[i]->count = new_tx_count;
|
|
vsi->rx_rings[i]->count = new_rx_count;
|
|
if (i40e_enabled_xdp_vsi(vsi))
|
|
vsi->xdp_rings[i]->count = new_tx_count;
|
|
}
|
|
vsi->num_tx_desc = new_tx_count;
|
|
vsi->num_rx_desc = new_rx_count;
|
|
goto done;
|
|
}
|
|
|
|
/* We can't just free everything and then setup again,
|
|
* because the ISRs in MSI-X mode get passed pointers
|
|
* to the Tx and Rx ring structs.
|
|
*/
|
|
|
|
/* alloc updated Tx and XDP Tx resources */
|
|
tx_alloc_queue_pairs = vsi->alloc_queue_pairs *
|
|
(i40e_enabled_xdp_vsi(vsi) ? 2 : 1);
|
|
if (new_tx_count != vsi->tx_rings[0]->count) {
|
|
netdev_info(netdev,
|
|
"Changing Tx descriptor count from %d to %d.\n",
|
|
vsi->tx_rings[0]->count, new_tx_count);
|
|
tx_rings = kcalloc(tx_alloc_queue_pairs,
|
|
sizeof(struct i40e_ring), GFP_KERNEL);
|
|
if (!tx_rings) {
|
|
err = -ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < tx_alloc_queue_pairs; i++) {
|
|
if (!i40e_active_tx_ring_index(vsi, i))
|
|
continue;
|
|
|
|
tx_rings[i] = *vsi->tx_rings[i];
|
|
tx_rings[i].count = new_tx_count;
|
|
/* the desc and bi pointers will be reallocated in the
|
|
* setup call
|
|
*/
|
|
tx_rings[i].desc = NULL;
|
|
tx_rings[i].rx_bi = NULL;
|
|
err = i40e_setup_tx_descriptors(&tx_rings[i]);
|
|
if (err) {
|
|
while (i) {
|
|
i--;
|
|
if (!i40e_active_tx_ring_index(vsi, i))
|
|
continue;
|
|
i40e_free_tx_resources(&tx_rings[i]);
|
|
}
|
|
kfree(tx_rings);
|
|
tx_rings = NULL;
|
|
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* alloc updated Rx resources */
|
|
if (new_rx_count != vsi->rx_rings[0]->count) {
|
|
netdev_info(netdev,
|
|
"Changing Rx descriptor count from %d to %d\n",
|
|
vsi->rx_rings[0]->count, new_rx_count);
|
|
rx_rings = kcalloc(vsi->alloc_queue_pairs,
|
|
sizeof(struct i40e_ring), GFP_KERNEL);
|
|
if (!rx_rings) {
|
|
err = -ENOMEM;
|
|
goto free_tx;
|
|
}
|
|
|
|
for (i = 0; i < vsi->num_queue_pairs; i++) {
|
|
u16 unused;
|
|
|
|
/* clone ring and setup updated count */
|
|
rx_rings[i] = *vsi->rx_rings[i];
|
|
rx_rings[i].count = new_rx_count;
|
|
/* the desc and bi pointers will be reallocated in the
|
|
* setup call
|
|
*/
|
|
rx_rings[i].desc = NULL;
|
|
rx_rings[i].rx_bi = NULL;
|
|
/* Clear cloned XDP RX-queue info before setup call */
|
|
memset(&rx_rings[i].xdp_rxq, 0, sizeof(rx_rings[i].xdp_rxq));
|
|
/* this is to allow wr32 to have something to write to
|
|
* during early allocation of Rx buffers
|
|
*/
|
|
rx_rings[i].tail = hw->hw_addr + I40E_PRTGEN_STATUS;
|
|
err = i40e_setup_rx_descriptors(&rx_rings[i]);
|
|
if (err)
|
|
goto rx_unwind;
|
|
err = i40e_alloc_rx_bi(&rx_rings[i]);
|
|
if (err)
|
|
goto rx_unwind;
|
|
|
|
/* now allocate the Rx buffers to make sure the OS
|
|
* has enough memory, any failure here means abort
|
|
*/
|
|
unused = I40E_DESC_UNUSED(&rx_rings[i]);
|
|
err = i40e_alloc_rx_buffers(&rx_rings[i], unused);
|
|
rx_unwind:
|
|
if (err) {
|
|
do {
|
|
i40e_free_rx_resources(&rx_rings[i]);
|
|
} while (i--);
|
|
kfree(rx_rings);
|
|
rx_rings = NULL;
|
|
|
|
goto free_tx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Bring interface down, copy in the new ring info,
|
|
* then restore the interface
|
|
*/
|
|
i40e_down(vsi);
|
|
|
|
if (tx_rings) {
|
|
for (i = 0; i < tx_alloc_queue_pairs; i++) {
|
|
if (i40e_active_tx_ring_index(vsi, i)) {
|
|
i40e_free_tx_resources(vsi->tx_rings[i]);
|
|
*vsi->tx_rings[i] = tx_rings[i];
|
|
}
|
|
}
|
|
kfree(tx_rings);
|
|
tx_rings = NULL;
|
|
}
|
|
|
|
if (rx_rings) {
|
|
for (i = 0; i < vsi->num_queue_pairs; i++) {
|
|
i40e_free_rx_resources(vsi->rx_rings[i]);
|
|
/* get the real tail offset */
|
|
rx_rings[i].tail = vsi->rx_rings[i]->tail;
|
|
/* this is to fake out the allocation routine
|
|
* into thinking it has to realloc everything
|
|
* but the recycling logic will let us re-use
|
|
* the buffers allocated above
|
|
*/
|
|
rx_rings[i].next_to_use = 0;
|
|
rx_rings[i].next_to_clean = 0;
|
|
rx_rings[i].next_to_alloc = 0;
|
|
/* do a struct copy */
|
|
*vsi->rx_rings[i] = rx_rings[i];
|
|
}
|
|
kfree(rx_rings);
|
|
rx_rings = NULL;
|
|
}
|
|
|
|
vsi->num_tx_desc = new_tx_count;
|
|
vsi->num_rx_desc = new_rx_count;
|
|
i40e_up(vsi);
|
|
|
|
free_tx:
|
|
/* error cleanup if the Rx allocations failed after getting Tx */
|
|
if (tx_rings) {
|
|
for (i = 0; i < tx_alloc_queue_pairs; i++) {
|
|
if (i40e_active_tx_ring_index(vsi, i))
|
|
i40e_free_tx_resources(vsi->tx_rings[i]);
|
|
}
|
|
kfree(tx_rings);
|
|
tx_rings = NULL;
|
|
}
|
|
|
|
done:
|
|
clear_bit(__I40E_CONFIG_BUSY, pf->state);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_stats_count - return the stats count for a device
|
|
* @netdev: the netdev to return the count for
|
|
*
|
|
* Returns the total number of statistics for this netdev. Note that even
|
|
* though this is a function, it is required that the count for a specific
|
|
* netdev must never change. Basing the count on static values such as the
|
|
* maximum number of queues or the device type is ok. However, the API for
|
|
* obtaining stats is *not* safe against changes based on non-static
|
|
* values such as the *current* number of queues, or runtime flags.
|
|
*
|
|
* If a statistic is not always enabled, return it as part of the count
|
|
* anyways, always return its string, and report its value as zero.
|
|
**/
|
|
static int i40e_get_stats_count(struct net_device *netdev)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
int stats_len;
|
|
|
|
if (vsi == pf->vsi[pf->lan_vsi] && pf->hw.partition_id == 1)
|
|
stats_len = I40E_PF_STATS_LEN;
|
|
else
|
|
stats_len = I40E_VSI_STATS_LEN;
|
|
|
|
/* The number of stats reported for a given net_device must remain
|
|
* constant throughout the life of that device.
|
|
*
|
|
* This is because the API for obtaining the size, strings, and stats
|
|
* is spread out over three separate ethtool ioctls. There is no safe
|
|
* way to lock the number of stats across these calls, so we must
|
|
* assume that they will never change.
|
|
*
|
|
* Due to this, we report the maximum number of queues, even if not
|
|
* every queue is currently configured. Since we always allocate
|
|
* queues in pairs, we'll just use netdev->num_tx_queues * 2. This
|
|
* works because the num_tx_queues is set at device creation and never
|
|
* changes.
|
|
*/
|
|
stats_len += I40E_QUEUE_STATS_LEN * 2 * netdev->num_tx_queues;
|
|
|
|
return stats_len;
|
|
}
|
|
|
|
static int i40e_get_sset_count(struct net_device *netdev, int sset)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
|
|
switch (sset) {
|
|
case ETH_SS_TEST:
|
|
return I40E_TEST_LEN;
|
|
case ETH_SS_STATS:
|
|
return i40e_get_stats_count(netdev);
|
|
case ETH_SS_PRIV_FLAGS:
|
|
return I40E_PRIV_FLAGS_STR_LEN +
|
|
(pf->hw.pf_id == 0 ? I40E_GL_PRIV_FLAGS_STR_LEN : 0);
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_get_pfc_stats - copy HW PFC statistics to formatted structure
|
|
* @pf: the PF device structure
|
|
* @i: the priority value to copy
|
|
*
|
|
* The PFC stats are found as arrays in pf->stats, which is not easy to pass
|
|
* into i40e_add_ethtool_stats. Produce a formatted i40e_pfc_stats structure
|
|
* of the PFC stats for the given priority.
|
|
**/
|
|
static inline struct i40e_pfc_stats
|
|
i40e_get_pfc_stats(struct i40e_pf *pf, unsigned int i)
|
|
{
|
|
#define I40E_GET_PFC_STAT(stat, priority) \
|
|
.stat = pf->stats.stat[priority]
|
|
|
|
struct i40e_pfc_stats pfc = {
|
|
I40E_GET_PFC_STAT(priority_xon_rx, i),
|
|
I40E_GET_PFC_STAT(priority_xoff_rx, i),
|
|
I40E_GET_PFC_STAT(priority_xon_tx, i),
|
|
I40E_GET_PFC_STAT(priority_xoff_tx, i),
|
|
I40E_GET_PFC_STAT(priority_xon_2_xoff, i),
|
|
};
|
|
return pfc;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_ethtool_stats - copy stat values into supplied buffer
|
|
* @netdev: the netdev to collect stats for
|
|
* @stats: ethtool stats command structure
|
|
* @data: ethtool supplied buffer
|
|
*
|
|
* Copy the stats values for this netdev into the buffer. Expects data to be
|
|
* pre-allocated to the size returned by i40e_get_stats_count.. Note that all
|
|
* statistics must be copied in a static order, and the count must not change
|
|
* for a given netdev. See i40e_get_stats_count for more details.
|
|
*
|
|
* If a statistic is not currently valid (such as a disabled queue), this
|
|
* function reports its value as zero.
|
|
**/
|
|
static void i40e_get_ethtool_stats(struct net_device *netdev,
|
|
struct ethtool_stats *stats, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_veb *veb = NULL;
|
|
unsigned int i;
|
|
bool veb_stats;
|
|
u64 *p = data;
|
|
|
|
i40e_update_stats(vsi);
|
|
|
|
i40e_add_ethtool_stats(&data, i40e_get_vsi_stats_struct(vsi),
|
|
i40e_gstrings_net_stats);
|
|
|
|
i40e_add_ethtool_stats(&data, vsi, i40e_gstrings_misc_stats);
|
|
|
|
rcu_read_lock();
|
|
for (i = 0; i < netdev->num_tx_queues; i++) {
|
|
i40e_add_queue_stats(&data, READ_ONCE(vsi->tx_rings[i]));
|
|
i40e_add_queue_stats(&data, READ_ONCE(vsi->rx_rings[i]));
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
if (vsi != pf->vsi[pf->lan_vsi] || pf->hw.partition_id != 1)
|
|
goto check_data_pointer;
|
|
|
|
veb_stats = ((pf->lan_veb != I40E_NO_VEB) &&
|
|
(pf->lan_veb < I40E_MAX_VEB) &&
|
|
(pf->flags & I40E_FLAG_VEB_STATS_ENABLED));
|
|
|
|
if (veb_stats) {
|
|
veb = pf->veb[pf->lan_veb];
|
|
i40e_update_veb_stats(veb);
|
|
}
|
|
|
|
/* If veb stats aren't enabled, pass NULL instead of the veb so that
|
|
* we initialize stats to zero and update the data pointer
|
|
* intelligently
|
|
*/
|
|
i40e_add_ethtool_stats(&data, veb_stats ? veb : NULL,
|
|
i40e_gstrings_veb_stats);
|
|
|
|
for (i = 0; i < I40E_MAX_TRAFFIC_CLASS; i++)
|
|
i40e_add_ethtool_stats(&data, veb_stats ? veb : NULL,
|
|
i40e_gstrings_veb_tc_stats);
|
|
|
|
i40e_add_ethtool_stats(&data, pf, i40e_gstrings_stats);
|
|
|
|
for (i = 0; i < I40E_MAX_USER_PRIORITY; i++) {
|
|
struct i40e_pfc_stats pfc = i40e_get_pfc_stats(pf, i);
|
|
|
|
i40e_add_ethtool_stats(&data, &pfc, i40e_gstrings_pfc_stats);
|
|
}
|
|
|
|
check_data_pointer:
|
|
WARN_ONCE(data - p != i40e_get_stats_count(netdev),
|
|
"ethtool stats count mismatch!");
|
|
}
|
|
|
|
/**
|
|
* i40e_get_stat_strings - copy stat strings into supplied buffer
|
|
* @netdev: the netdev to collect strings for
|
|
* @data: supplied buffer to copy strings into
|
|
*
|
|
* Copy the strings related to stats for this netdev. Expects data to be
|
|
* pre-allocated with the size reported by i40e_get_stats_count. Note that the
|
|
* strings must be copied in a static order and the total count must not
|
|
* change for a given netdev. See i40e_get_stats_count for more details.
|
|
**/
|
|
static void i40e_get_stat_strings(struct net_device *netdev, u8 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
unsigned int i;
|
|
u8 *p = data;
|
|
|
|
i40e_add_stat_strings(&data, i40e_gstrings_net_stats);
|
|
|
|
i40e_add_stat_strings(&data, i40e_gstrings_misc_stats);
|
|
|
|
for (i = 0; i < netdev->num_tx_queues; i++) {
|
|
i40e_add_stat_strings(&data, i40e_gstrings_queue_stats,
|
|
"tx", i);
|
|
i40e_add_stat_strings(&data, i40e_gstrings_queue_stats,
|
|
"rx", i);
|
|
}
|
|
|
|
if (vsi != pf->vsi[pf->lan_vsi] || pf->hw.partition_id != 1)
|
|
goto check_data_pointer;
|
|
|
|
i40e_add_stat_strings(&data, i40e_gstrings_veb_stats);
|
|
|
|
for (i = 0; i < I40E_MAX_TRAFFIC_CLASS; i++)
|
|
i40e_add_stat_strings(&data, i40e_gstrings_veb_tc_stats, i);
|
|
|
|
i40e_add_stat_strings(&data, i40e_gstrings_stats);
|
|
|
|
for (i = 0; i < I40E_MAX_USER_PRIORITY; i++)
|
|
i40e_add_stat_strings(&data, i40e_gstrings_pfc_stats, i);
|
|
|
|
check_data_pointer:
|
|
WARN_ONCE(data - p != i40e_get_stats_count(netdev) * ETH_GSTRING_LEN,
|
|
"stat strings count mismatch!");
|
|
}
|
|
|
|
static void i40e_get_priv_flag_strings(struct net_device *netdev, u8 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
char *p = (char *)data;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < I40E_PRIV_FLAGS_STR_LEN; i++) {
|
|
snprintf(p, ETH_GSTRING_LEN, "%s",
|
|
i40e_gstrings_priv_flags[i].flag_string);
|
|
p += ETH_GSTRING_LEN;
|
|
}
|
|
if (pf->hw.pf_id != 0)
|
|
return;
|
|
for (i = 0; i < I40E_GL_PRIV_FLAGS_STR_LEN; i++) {
|
|
snprintf(p, ETH_GSTRING_LEN, "%s",
|
|
i40e_gl_gstrings_priv_flags[i].flag_string);
|
|
p += ETH_GSTRING_LEN;
|
|
}
|
|
}
|
|
|
|
static void i40e_get_strings(struct net_device *netdev, u32 stringset,
|
|
u8 *data)
|
|
{
|
|
switch (stringset) {
|
|
case ETH_SS_TEST:
|
|
memcpy(data, i40e_gstrings_test,
|
|
I40E_TEST_LEN * ETH_GSTRING_LEN);
|
|
break;
|
|
case ETH_SS_STATS:
|
|
i40e_get_stat_strings(netdev, data);
|
|
break;
|
|
case ETH_SS_PRIV_FLAGS:
|
|
i40e_get_priv_flag_strings(netdev, data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int i40e_get_ts_info(struct net_device *dev,
|
|
struct ethtool_ts_info *info)
|
|
{
|
|
struct i40e_pf *pf = i40e_netdev_to_pf(dev);
|
|
|
|
/* only report HW timestamping if PTP is enabled */
|
|
if (!(pf->flags & I40E_FLAG_PTP))
|
|
return ethtool_op_get_ts_info(dev, info);
|
|
|
|
info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
|
|
SOF_TIMESTAMPING_RX_SOFTWARE |
|
|
SOF_TIMESTAMPING_SOFTWARE |
|
|
SOF_TIMESTAMPING_TX_HARDWARE |
|
|
SOF_TIMESTAMPING_RX_HARDWARE |
|
|
SOF_TIMESTAMPING_RAW_HARDWARE;
|
|
|
|
if (pf->ptp_clock)
|
|
info->phc_index = ptp_clock_index(pf->ptp_clock);
|
|
else
|
|
info->phc_index = -1;
|
|
|
|
info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
|
|
|
|
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ);
|
|
|
|
if (pf->hw_features & I40E_HW_PTP_L4_CAPABLE)
|
|
info->rx_filters |= BIT(HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_SYNC) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
|
|
BIT(HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u64 i40e_link_test(struct net_device *netdev, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
i40e_status status;
|
|
bool link_up = false;
|
|
|
|
netif_info(pf, hw, netdev, "link test\n");
|
|
status = i40e_get_link_status(&pf->hw, &link_up);
|
|
if (status) {
|
|
netif_err(pf, drv, netdev, "link query timed out, please retry test\n");
|
|
*data = 1;
|
|
return *data;
|
|
}
|
|
|
|
if (link_up)
|
|
*data = 0;
|
|
else
|
|
*data = 1;
|
|
|
|
return *data;
|
|
}
|
|
|
|
static u64 i40e_reg_test(struct net_device *netdev, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
|
|
netif_info(pf, hw, netdev, "register test\n");
|
|
*data = i40e_diag_reg_test(&pf->hw);
|
|
|
|
return *data;
|
|
}
|
|
|
|
static u64 i40e_eeprom_test(struct net_device *netdev, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
|
|
netif_info(pf, hw, netdev, "eeprom test\n");
|
|
*data = i40e_diag_eeprom_test(&pf->hw);
|
|
|
|
/* forcebly clear the NVM Update state machine */
|
|
pf->hw.nvmupd_state = I40E_NVMUPD_STATE_INIT;
|
|
|
|
return *data;
|
|
}
|
|
|
|
static u64 i40e_intr_test(struct net_device *netdev, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
u16 swc_old = pf->sw_int_count;
|
|
|
|
netif_info(pf, hw, netdev, "interrupt test\n");
|
|
wr32(&pf->hw, I40E_PFINT_DYN_CTL0,
|
|
(I40E_PFINT_DYN_CTL0_INTENA_MASK |
|
|
I40E_PFINT_DYN_CTL0_SWINT_TRIG_MASK |
|
|
I40E_PFINT_DYN_CTL0_ITR_INDX_MASK |
|
|
I40E_PFINT_DYN_CTL0_SW_ITR_INDX_ENA_MASK |
|
|
I40E_PFINT_DYN_CTL0_SW_ITR_INDX_MASK));
|
|
usleep_range(1000, 2000);
|
|
*data = (swc_old == pf->sw_int_count);
|
|
|
|
return *data;
|
|
}
|
|
|
|
static inline bool i40e_active_vfs(struct i40e_pf *pf)
|
|
{
|
|
struct i40e_vf *vfs = pf->vf;
|
|
int i;
|
|
|
|
for (i = 0; i < pf->num_alloc_vfs; i++)
|
|
if (test_bit(I40E_VF_STATE_ACTIVE, &vfs[i].vf_states))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static inline bool i40e_active_vmdqs(struct i40e_pf *pf)
|
|
{
|
|
return !!i40e_find_vsi_by_type(pf, I40E_VSI_VMDQ2);
|
|
}
|
|
|
|
static void i40e_diag_test(struct net_device *netdev,
|
|
struct ethtool_test *eth_test, u64 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
bool if_running = netif_running(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
|
|
if (eth_test->flags == ETH_TEST_FL_OFFLINE) {
|
|
/* Offline tests */
|
|
netif_info(pf, drv, netdev, "offline testing starting\n");
|
|
|
|
set_bit(__I40E_TESTING, pf->state);
|
|
|
|
if (i40e_active_vfs(pf) || i40e_active_vmdqs(pf)) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Please take active VFs and Netqueues offline and restart the adapter before running NIC diagnostics\n");
|
|
data[I40E_ETH_TEST_REG] = 1;
|
|
data[I40E_ETH_TEST_EEPROM] = 1;
|
|
data[I40E_ETH_TEST_INTR] = 1;
|
|
data[I40E_ETH_TEST_LINK] = 1;
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
clear_bit(__I40E_TESTING, pf->state);
|
|
goto skip_ol_tests;
|
|
}
|
|
|
|
/* If the device is online then take it offline */
|
|
if (if_running)
|
|
/* indicate we're in test mode */
|
|
i40e_close(netdev);
|
|
else
|
|
/* This reset does not affect link - if it is
|
|
* changed to a type of reset that does affect
|
|
* link then the following link test would have
|
|
* to be moved to before the reset
|
|
*/
|
|
i40e_do_reset(pf, BIT(__I40E_PF_RESET_REQUESTED), true);
|
|
|
|
if (i40e_link_test(netdev, &data[I40E_ETH_TEST_LINK]))
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
|
|
if (i40e_eeprom_test(netdev, &data[I40E_ETH_TEST_EEPROM]))
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
|
|
if (i40e_intr_test(netdev, &data[I40E_ETH_TEST_INTR]))
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
|
|
/* run reg test last, a reset is required after it */
|
|
if (i40e_reg_test(netdev, &data[I40E_ETH_TEST_REG]))
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
|
|
clear_bit(__I40E_TESTING, pf->state);
|
|
i40e_do_reset(pf, BIT(__I40E_PF_RESET_REQUESTED), true);
|
|
|
|
if (if_running)
|
|
i40e_open(netdev);
|
|
} else {
|
|
/* Online tests */
|
|
netif_info(pf, drv, netdev, "online testing starting\n");
|
|
|
|
if (i40e_link_test(netdev, &data[I40E_ETH_TEST_LINK]))
|
|
eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
|
|
/* Offline only tests, not run in online; pass by default */
|
|
data[I40E_ETH_TEST_REG] = 0;
|
|
data[I40E_ETH_TEST_EEPROM] = 0;
|
|
data[I40E_ETH_TEST_INTR] = 0;
|
|
}
|
|
|
|
skip_ol_tests:
|
|
|
|
netif_info(pf, drv, netdev, "testing finished\n");
|
|
}
|
|
|
|
static void i40e_get_wol(struct net_device *netdev,
|
|
struct ethtool_wolinfo *wol)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u16 wol_nvm_bits;
|
|
|
|
/* NVM bit on means WoL disabled for the port */
|
|
i40e_read_nvm_word(hw, I40E_SR_NVM_WAKE_ON_LAN, &wol_nvm_bits);
|
|
if ((BIT(hw->port) & wol_nvm_bits) || (hw->partition_id != 1)) {
|
|
wol->supported = 0;
|
|
wol->wolopts = 0;
|
|
} else {
|
|
wol->supported = WAKE_MAGIC;
|
|
wol->wolopts = (pf->wol_en ? WAKE_MAGIC : 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_set_wol - set the WakeOnLAN configuration
|
|
* @netdev: the netdev in question
|
|
* @wol: the ethtool WoL setting data
|
|
**/
|
|
static int i40e_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u16 wol_nvm_bits;
|
|
|
|
/* WoL not supported if this isn't the controlling PF on the port */
|
|
if (hw->partition_id != 1) {
|
|
i40e_partition_setting_complaint(pf);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (vsi != pf->vsi[pf->lan_vsi])
|
|
return -EOPNOTSUPP;
|
|
|
|
/* NVM bit on means WoL disabled for the port */
|
|
i40e_read_nvm_word(hw, I40E_SR_NVM_WAKE_ON_LAN, &wol_nvm_bits);
|
|
if (BIT(hw->port) & wol_nvm_bits)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* only magic packet is supported */
|
|
if (wol->wolopts & ~WAKE_MAGIC)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* is this a new value? */
|
|
if (pf->wol_en != !!wol->wolopts) {
|
|
pf->wol_en = !!wol->wolopts;
|
|
device_set_wakeup_enable(&pf->pdev->dev, pf->wol_en);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i40e_set_phys_id(struct net_device *netdev,
|
|
enum ethtool_phys_id_state state)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
i40e_status ret = 0;
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
int blink_freq = 2;
|
|
u16 temp_status;
|
|
|
|
switch (state) {
|
|
case ETHTOOL_ID_ACTIVE:
|
|
if (!(pf->hw_features & I40E_HW_PHY_CONTROLS_LEDS)) {
|
|
pf->led_status = i40e_led_get(hw);
|
|
} else {
|
|
if (!(hw->flags & I40E_HW_FLAG_AQ_PHY_ACCESS_CAPABLE))
|
|
i40e_aq_set_phy_debug(hw, I40E_PHY_DEBUG_ALL,
|
|
NULL);
|
|
ret = i40e_led_get_phy(hw, &temp_status,
|
|
&pf->phy_led_val);
|
|
pf->led_status = temp_status;
|
|
}
|
|
return blink_freq;
|
|
case ETHTOOL_ID_ON:
|
|
if (!(pf->hw_features & I40E_HW_PHY_CONTROLS_LEDS))
|
|
i40e_led_set(hw, 0xf, false);
|
|
else
|
|
ret = i40e_led_set_phy(hw, true, pf->led_status, 0);
|
|
break;
|
|
case ETHTOOL_ID_OFF:
|
|
if (!(pf->hw_features & I40E_HW_PHY_CONTROLS_LEDS))
|
|
i40e_led_set(hw, 0x0, false);
|
|
else
|
|
ret = i40e_led_set_phy(hw, false, pf->led_status, 0);
|
|
break;
|
|
case ETHTOOL_ID_INACTIVE:
|
|
if (!(pf->hw_features & I40E_HW_PHY_CONTROLS_LEDS)) {
|
|
i40e_led_set(hw, pf->led_status, false);
|
|
} else {
|
|
ret = i40e_led_set_phy(hw, false, pf->led_status,
|
|
(pf->phy_led_val |
|
|
I40E_PHY_LED_MODE_ORIG));
|
|
if (!(hw->flags & I40E_HW_FLAG_AQ_PHY_ACCESS_CAPABLE))
|
|
i40e_aq_set_phy_debug(hw, 0, NULL);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (ret)
|
|
return -ENOENT;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* NOTE: i40e hardware uses a conversion factor of 2 for Interrupt
|
|
* Throttle Rate (ITR) ie. ITR(1) = 2us ITR(10) = 20 us, and also
|
|
* 125us (8000 interrupts per second) == ITR(62)
|
|
*/
|
|
|
|
/**
|
|
* __i40e_get_coalesce - get per-queue coalesce settings
|
|
* @netdev: the netdev to check
|
|
* @ec: ethtool coalesce data structure
|
|
* @queue: which queue to pick
|
|
*
|
|
* Gets the per-queue settings for coalescence. Specifically Rx and Tx usecs
|
|
* are per queue. If queue is <0 then we default to queue 0 as the
|
|
* representative value.
|
|
**/
|
|
static int __i40e_get_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *ec,
|
|
int queue)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_ring *rx_ring, *tx_ring;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
|
|
ec->tx_max_coalesced_frames_irq = vsi->work_limit;
|
|
ec->rx_max_coalesced_frames_irq = vsi->work_limit;
|
|
|
|
/* rx and tx usecs has per queue value. If user doesn't specify the
|
|
* queue, return queue 0's value to represent.
|
|
*/
|
|
if (queue < 0)
|
|
queue = 0;
|
|
else if (queue >= vsi->num_queue_pairs)
|
|
return -EINVAL;
|
|
|
|
rx_ring = vsi->rx_rings[queue];
|
|
tx_ring = vsi->tx_rings[queue];
|
|
|
|
if (ITR_IS_DYNAMIC(rx_ring->itr_setting))
|
|
ec->use_adaptive_rx_coalesce = 1;
|
|
|
|
if (ITR_IS_DYNAMIC(tx_ring->itr_setting))
|
|
ec->use_adaptive_tx_coalesce = 1;
|
|
|
|
ec->rx_coalesce_usecs = rx_ring->itr_setting & ~I40E_ITR_DYNAMIC;
|
|
ec->tx_coalesce_usecs = tx_ring->itr_setting & ~I40E_ITR_DYNAMIC;
|
|
|
|
/* we use the _usecs_high to store/set the interrupt rate limit
|
|
* that the hardware supports, that almost but not quite
|
|
* fits the original intent of the ethtool variable,
|
|
* the rx_coalesce_usecs_high limits total interrupts
|
|
* per second from both tx/rx sources.
|
|
*/
|
|
ec->rx_coalesce_usecs_high = vsi->int_rate_limit;
|
|
ec->tx_coalesce_usecs_high = vsi->int_rate_limit;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_coalesce - get a netdev's coalesce settings
|
|
* @netdev: the netdev to check
|
|
* @ec: ethtool coalesce data structure
|
|
*
|
|
* Gets the coalesce settings for a particular netdev. Note that if user has
|
|
* modified per-queue settings, this only guarantees to represent queue 0. See
|
|
* __i40e_get_coalesce for more details.
|
|
**/
|
|
static int i40e_get_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *ec)
|
|
{
|
|
return __i40e_get_coalesce(netdev, ec, -1);
|
|
}
|
|
|
|
/**
|
|
* i40e_get_per_queue_coalesce - gets coalesce settings for particular queue
|
|
* @netdev: netdev structure
|
|
* @ec: ethtool's coalesce settings
|
|
* @queue: the particular queue to read
|
|
*
|
|
* Will read a specific queue's coalesce settings
|
|
**/
|
|
static int i40e_get_per_queue_coalesce(struct net_device *netdev, u32 queue,
|
|
struct ethtool_coalesce *ec)
|
|
{
|
|
return __i40e_get_coalesce(netdev, ec, queue);
|
|
}
|
|
|
|
/**
|
|
* i40e_set_itr_per_queue - set ITR values for specific queue
|
|
* @vsi: the VSI to set values for
|
|
* @ec: coalesce settings from ethtool
|
|
* @queue: the queue to modify
|
|
*
|
|
* Change the ITR settings for a specific queue.
|
|
**/
|
|
static void i40e_set_itr_per_queue(struct i40e_vsi *vsi,
|
|
struct ethtool_coalesce *ec,
|
|
int queue)
|
|
{
|
|
struct i40e_ring *rx_ring = vsi->rx_rings[queue];
|
|
struct i40e_ring *tx_ring = vsi->tx_rings[queue];
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
struct i40e_q_vector *q_vector;
|
|
u16 intrl;
|
|
|
|
intrl = i40e_intrl_usec_to_reg(vsi->int_rate_limit);
|
|
|
|
rx_ring->itr_setting = ITR_REG_ALIGN(ec->rx_coalesce_usecs);
|
|
tx_ring->itr_setting = ITR_REG_ALIGN(ec->tx_coalesce_usecs);
|
|
|
|
if (ec->use_adaptive_rx_coalesce)
|
|
rx_ring->itr_setting |= I40E_ITR_DYNAMIC;
|
|
else
|
|
rx_ring->itr_setting &= ~I40E_ITR_DYNAMIC;
|
|
|
|
if (ec->use_adaptive_tx_coalesce)
|
|
tx_ring->itr_setting |= I40E_ITR_DYNAMIC;
|
|
else
|
|
tx_ring->itr_setting &= ~I40E_ITR_DYNAMIC;
|
|
|
|
q_vector = rx_ring->q_vector;
|
|
q_vector->rx.target_itr = ITR_TO_REG(rx_ring->itr_setting);
|
|
|
|
q_vector = tx_ring->q_vector;
|
|
q_vector->tx.target_itr = ITR_TO_REG(tx_ring->itr_setting);
|
|
|
|
/* The interrupt handler itself will take care of programming
|
|
* the Tx and Rx ITR values based on the values we have entered
|
|
* into the q_vector, no need to write the values now.
|
|
*/
|
|
|
|
wr32(hw, I40E_PFINT_RATEN(q_vector->reg_idx), intrl);
|
|
i40e_flush(hw);
|
|
}
|
|
|
|
/**
|
|
* __i40e_set_coalesce - set coalesce settings for particular queue
|
|
* @netdev: the netdev to change
|
|
* @ec: ethtool coalesce settings
|
|
* @queue: the queue to change
|
|
*
|
|
* Sets the coalesce settings for a particular queue.
|
|
**/
|
|
static int __i40e_set_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *ec,
|
|
int queue)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
u16 intrl_reg, cur_rx_itr, cur_tx_itr;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
int i;
|
|
|
|
if (ec->tx_max_coalesced_frames_irq || ec->rx_max_coalesced_frames_irq)
|
|
vsi->work_limit = ec->tx_max_coalesced_frames_irq;
|
|
|
|
if (queue < 0) {
|
|
cur_rx_itr = vsi->rx_rings[0]->itr_setting;
|
|
cur_tx_itr = vsi->tx_rings[0]->itr_setting;
|
|
} else if (queue < vsi->num_queue_pairs) {
|
|
cur_rx_itr = vsi->rx_rings[queue]->itr_setting;
|
|
cur_tx_itr = vsi->tx_rings[queue]->itr_setting;
|
|
} else {
|
|
netif_info(pf, drv, netdev, "Invalid queue value, queue range is 0 - %d\n",
|
|
vsi->num_queue_pairs - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cur_tx_itr &= ~I40E_ITR_DYNAMIC;
|
|
cur_rx_itr &= ~I40E_ITR_DYNAMIC;
|
|
|
|
/* tx_coalesce_usecs_high is ignored, use rx-usecs-high instead */
|
|
if (ec->tx_coalesce_usecs_high != vsi->int_rate_limit) {
|
|
netif_info(pf, drv, netdev, "tx-usecs-high is not used, please program rx-usecs-high\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->rx_coalesce_usecs_high > INTRL_REG_TO_USEC(I40E_MAX_INTRL)) {
|
|
netif_info(pf, drv, netdev, "Invalid value, rx-usecs-high range is 0-%lu\n",
|
|
INTRL_REG_TO_USEC(I40E_MAX_INTRL));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->rx_coalesce_usecs != cur_rx_itr &&
|
|
ec->use_adaptive_rx_coalesce) {
|
|
netif_info(pf, drv, netdev, "RX interrupt moderation cannot be changed if adaptive-rx is enabled.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->rx_coalesce_usecs > I40E_MAX_ITR) {
|
|
netif_info(pf, drv, netdev, "Invalid value, rx-usecs range is 0-8160\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->tx_coalesce_usecs != cur_tx_itr &&
|
|
ec->use_adaptive_tx_coalesce) {
|
|
netif_info(pf, drv, netdev, "TX interrupt moderation cannot be changed if adaptive-tx is enabled.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->tx_coalesce_usecs > I40E_MAX_ITR) {
|
|
netif_info(pf, drv, netdev, "Invalid value, tx-usecs range is 0-8160\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ec->use_adaptive_rx_coalesce && !cur_rx_itr)
|
|
ec->rx_coalesce_usecs = I40E_MIN_ITR;
|
|
|
|
if (ec->use_adaptive_tx_coalesce && !cur_tx_itr)
|
|
ec->tx_coalesce_usecs = I40E_MIN_ITR;
|
|
|
|
intrl_reg = i40e_intrl_usec_to_reg(ec->rx_coalesce_usecs_high);
|
|
vsi->int_rate_limit = INTRL_REG_TO_USEC(intrl_reg);
|
|
if (vsi->int_rate_limit != ec->rx_coalesce_usecs_high) {
|
|
netif_info(pf, drv, netdev, "Interrupt rate limit rounded down to %d\n",
|
|
vsi->int_rate_limit);
|
|
}
|
|
|
|
/* rx and tx usecs has per queue value. If user doesn't specify the
|
|
* queue, apply to all queues.
|
|
*/
|
|
if (queue < 0) {
|
|
for (i = 0; i < vsi->num_queue_pairs; i++)
|
|
i40e_set_itr_per_queue(vsi, ec, i);
|
|
} else {
|
|
i40e_set_itr_per_queue(vsi, ec, queue);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_coalesce - set coalesce settings for every queue on the netdev
|
|
* @netdev: the netdev to change
|
|
* @ec: ethtool coalesce settings
|
|
*
|
|
* This will set each queue to the same coalesce settings.
|
|
**/
|
|
static int i40e_set_coalesce(struct net_device *netdev,
|
|
struct ethtool_coalesce *ec)
|
|
{
|
|
return __i40e_set_coalesce(netdev, ec, -1);
|
|
}
|
|
|
|
/**
|
|
* i40e_set_per_queue_coalesce - set specific queue's coalesce settings
|
|
* @netdev: the netdev to change
|
|
* @ec: ethtool's coalesce settings
|
|
* @queue: the queue to change
|
|
*
|
|
* Sets the specified queue's coalesce settings.
|
|
**/
|
|
static int i40e_set_per_queue_coalesce(struct net_device *netdev, u32 queue,
|
|
struct ethtool_coalesce *ec)
|
|
{
|
|
return __i40e_set_coalesce(netdev, ec, queue);
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rss_hash_opts - Get RSS hash Input Set for each flow type
|
|
* @pf: pointer to the physical function struct
|
|
* @cmd: ethtool rxnfc command
|
|
*
|
|
* Returns Success if the flow is supported, else Invalid Input.
|
|
**/
|
|
static int i40e_get_rss_hash_opts(struct i40e_pf *pf, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u8 flow_pctype = 0;
|
|
u64 i_set = 0;
|
|
|
|
cmd->data = 0;
|
|
|
|
switch (cmd->flow_type) {
|
|
case TCP_V4_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV4_TCP;
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV4_UDP;
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV6_TCP;
|
|
break;
|
|
case UDP_V6_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV6_UDP;
|
|
break;
|
|
case SCTP_V4_FLOW:
|
|
case AH_ESP_V4_FLOW:
|
|
case AH_V4_FLOW:
|
|
case ESP_V4_FLOW:
|
|
case IPV4_FLOW:
|
|
case SCTP_V6_FLOW:
|
|
case AH_ESP_V6_FLOW:
|
|
case AH_V6_FLOW:
|
|
case ESP_V6_FLOW:
|
|
case IPV6_FLOW:
|
|
/* Default is src/dest for IP, no matter the L4 hashing */
|
|
cmd->data |= RXH_IP_SRC | RXH_IP_DST;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Read flow based hash input set register */
|
|
if (flow_pctype) {
|
|
i_set = (u64)i40e_read_rx_ctl(hw, I40E_GLQF_HASH_INSET(0,
|
|
flow_pctype)) |
|
|
((u64)i40e_read_rx_ctl(hw, I40E_GLQF_HASH_INSET(1,
|
|
flow_pctype)) << 32);
|
|
}
|
|
|
|
/* Process bits of hash input set */
|
|
if (i_set) {
|
|
if (i_set & I40E_L4_SRC_MASK)
|
|
cmd->data |= RXH_L4_B_0_1;
|
|
if (i_set & I40E_L4_DST_MASK)
|
|
cmd->data |= RXH_L4_B_2_3;
|
|
|
|
if (cmd->flow_type == TCP_V4_FLOW ||
|
|
cmd->flow_type == UDP_V4_FLOW) {
|
|
if (i_set & I40E_L3_SRC_MASK)
|
|
cmd->data |= RXH_IP_SRC;
|
|
if (i_set & I40E_L3_DST_MASK)
|
|
cmd->data |= RXH_IP_DST;
|
|
} else if (cmd->flow_type == TCP_V6_FLOW ||
|
|
cmd->flow_type == UDP_V6_FLOW) {
|
|
if (i_set & I40E_L3_V6_SRC_MASK)
|
|
cmd->data |= RXH_IP_SRC;
|
|
if (i_set & I40E_L3_V6_DST_MASK)
|
|
cmd->data |= RXH_IP_DST;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_check_mask - Check whether a mask field is set
|
|
* @mask: the full mask value
|
|
* @field: mask of the field to check
|
|
*
|
|
* If the given mask is fully set, return positive value. If the mask for the
|
|
* field is fully unset, return zero. Otherwise return a negative error code.
|
|
**/
|
|
static int i40e_check_mask(u64 mask, u64 field)
|
|
{
|
|
u64 value = mask & field;
|
|
|
|
if (value == field)
|
|
return 1;
|
|
else if (!value)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* i40e_parse_rx_flow_user_data - Deconstruct user-defined data
|
|
* @fsp: pointer to rx flow specification
|
|
* @data: pointer to userdef data structure for storage
|
|
*
|
|
* Read the user-defined data and deconstruct the value into a structure. No
|
|
* other code should read the user-defined data, so as to ensure that every
|
|
* place consistently reads the value correctly.
|
|
*
|
|
* The user-defined field is a 64bit Big Endian format value, which we
|
|
* deconstruct by reading bits or bit fields from it. Single bit flags shall
|
|
* be defined starting from the highest bits, while small bit field values
|
|
* shall be defined starting from the lowest bits.
|
|
*
|
|
* Returns 0 if the data is valid, and non-zero if the userdef data is invalid
|
|
* and the filter should be rejected. The data structure will always be
|
|
* modified even if FLOW_EXT is not set.
|
|
*
|
|
**/
|
|
static int i40e_parse_rx_flow_user_data(struct ethtool_rx_flow_spec *fsp,
|
|
struct i40e_rx_flow_userdef *data)
|
|
{
|
|
u64 value, mask;
|
|
int valid;
|
|
|
|
/* Zero memory first so it's always consistent. */
|
|
memset(data, 0, sizeof(*data));
|
|
|
|
if (!(fsp->flow_type & FLOW_EXT))
|
|
return 0;
|
|
|
|
value = be64_to_cpu(*((__be64 *)fsp->h_ext.data));
|
|
mask = be64_to_cpu(*((__be64 *)fsp->m_ext.data));
|
|
|
|
#define I40E_USERDEF_FLEX_WORD GENMASK_ULL(15, 0)
|
|
#define I40E_USERDEF_FLEX_OFFSET GENMASK_ULL(31, 16)
|
|
#define I40E_USERDEF_FLEX_FILTER GENMASK_ULL(31, 0)
|
|
|
|
valid = i40e_check_mask(mask, I40E_USERDEF_FLEX_FILTER);
|
|
if (valid < 0) {
|
|
return -EINVAL;
|
|
} else if (valid) {
|
|
data->flex_word = value & I40E_USERDEF_FLEX_WORD;
|
|
data->flex_offset =
|
|
(value & I40E_USERDEF_FLEX_OFFSET) >> 16;
|
|
data->flex_filter = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_fill_rx_flow_user_data - Fill in user-defined data field
|
|
* @fsp: pointer to rx_flow specification
|
|
* @data: pointer to return userdef data
|
|
*
|
|
* Reads the userdef data structure and properly fills in the user defined
|
|
* fields of the rx_flow_spec.
|
|
**/
|
|
static void i40e_fill_rx_flow_user_data(struct ethtool_rx_flow_spec *fsp,
|
|
struct i40e_rx_flow_userdef *data)
|
|
{
|
|
u64 value = 0, mask = 0;
|
|
|
|
if (data->flex_filter) {
|
|
value |= data->flex_word;
|
|
value |= (u64)data->flex_offset << 16;
|
|
mask |= I40E_USERDEF_FLEX_FILTER;
|
|
}
|
|
|
|
if (value || mask)
|
|
fsp->flow_type |= FLOW_EXT;
|
|
|
|
*((__be64 *)fsp->h_ext.data) = cpu_to_be64(value);
|
|
*((__be64 *)fsp->m_ext.data) = cpu_to_be64(mask);
|
|
}
|
|
|
|
/**
|
|
* i40e_get_ethtool_fdir_all - Populates the rule count of a command
|
|
* @pf: Pointer to the physical function struct
|
|
* @cmd: The command to get or set Rx flow classification rules
|
|
* @rule_locs: Array of used rule locations
|
|
*
|
|
* This function populates both the total and actual rule count of
|
|
* the ethtool flow classification command
|
|
*
|
|
* Returns 0 on success or -EMSGSIZE if entry not found
|
|
**/
|
|
static int i40e_get_ethtool_fdir_all(struct i40e_pf *pf,
|
|
struct ethtool_rxnfc *cmd,
|
|
u32 *rule_locs)
|
|
{
|
|
struct i40e_fdir_filter *rule;
|
|
struct hlist_node *node2;
|
|
int cnt = 0;
|
|
|
|
/* report total rule count */
|
|
cmd->data = i40e_get_fd_cnt_all(pf);
|
|
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&pf->fdir_filter_list, fdir_node) {
|
|
if (cnt == cmd->rule_cnt)
|
|
return -EMSGSIZE;
|
|
|
|
rule_locs[cnt] = rule->fd_id;
|
|
cnt++;
|
|
}
|
|
|
|
cmd->rule_cnt = cnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_ethtool_fdir_entry - Look up a filter based on Rx flow
|
|
* @pf: Pointer to the physical function struct
|
|
* @cmd: The command to get or set Rx flow classification rules
|
|
*
|
|
* This function looks up a filter based on the Rx flow classification
|
|
* command and fills the flow spec info for it if found
|
|
*
|
|
* Returns 0 on success or -EINVAL if filter not found
|
|
**/
|
|
static int i40e_get_ethtool_fdir_entry(struct i40e_pf *pf,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct ethtool_rx_flow_spec *fsp =
|
|
(struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
struct i40e_rx_flow_userdef userdef = {0};
|
|
struct i40e_fdir_filter *rule = NULL;
|
|
struct hlist_node *node2;
|
|
u64 input_set;
|
|
u16 index;
|
|
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&pf->fdir_filter_list, fdir_node) {
|
|
if (fsp->location <= rule->fd_id)
|
|
break;
|
|
}
|
|
|
|
if (!rule || fsp->location != rule->fd_id)
|
|
return -EINVAL;
|
|
|
|
fsp->flow_type = rule->flow_type;
|
|
if (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;
|
|
}
|
|
|
|
/* Reverse the src and dest notion, since the HW views them from
|
|
* Tx perspective where as the user expects it from Rx filter view.
|
|
*/
|
|
fsp->h_u.tcp_ip4_spec.psrc = rule->dst_port;
|
|
fsp->h_u.tcp_ip4_spec.pdst = rule->src_port;
|
|
fsp->h_u.tcp_ip4_spec.ip4src = rule->dst_ip;
|
|
fsp->h_u.tcp_ip4_spec.ip4dst = rule->src_ip;
|
|
|
|
switch (rule->flow_type) {
|
|
case SCTP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_SCTP;
|
|
break;
|
|
case TCP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_TCP;
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_UDP;
|
|
break;
|
|
case IP_USER_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_OTHER;
|
|
break;
|
|
default:
|
|
/* If we have stored a filter with a flow type not listed here
|
|
* it is almost certainly a driver bug. WARN(), and then
|
|
* assign the input_set as if all fields are enabled to avoid
|
|
* reading unassigned memory.
|
|
*/
|
|
WARN(1, "Missing input set index for flow_type %d\n",
|
|
rule->flow_type);
|
|
input_set = 0xFFFFFFFFFFFFFFFFULL;
|
|
goto no_input_set;
|
|
}
|
|
|
|
input_set = i40e_read_fd_input_set(pf, index);
|
|
|
|
no_input_set:
|
|
if (input_set & I40E_L3_SRC_MASK)
|
|
fsp->m_u.tcp_ip4_spec.ip4src = htonl(0xFFFFFFFF);
|
|
|
|
if (input_set & I40E_L3_DST_MASK)
|
|
fsp->m_u.tcp_ip4_spec.ip4dst = htonl(0xFFFFFFFF);
|
|
|
|
if (input_set & I40E_L4_SRC_MASK)
|
|
fsp->m_u.tcp_ip4_spec.psrc = htons(0xFFFF);
|
|
|
|
if (input_set & I40E_L4_DST_MASK)
|
|
fsp->m_u.tcp_ip4_spec.pdst = htons(0xFFFF);
|
|
|
|
if (rule->dest_ctl == I40E_FILTER_PROGRAM_DESC_DEST_DROP_PACKET)
|
|
fsp->ring_cookie = RX_CLS_FLOW_DISC;
|
|
else
|
|
fsp->ring_cookie = rule->q_index;
|
|
|
|
if (rule->dest_vsi != pf->vsi[pf->lan_vsi]->id) {
|
|
struct i40e_vsi *vsi;
|
|
|
|
vsi = i40e_find_vsi_from_id(pf, rule->dest_vsi);
|
|
if (vsi && vsi->type == I40E_VSI_SRIOV) {
|
|
/* VFs are zero-indexed by the driver, but ethtool
|
|
* expects them to be one-indexed, so add one here
|
|
*/
|
|
u64 ring_vf = vsi->vf_id + 1;
|
|
|
|
ring_vf <<= ETHTOOL_RX_FLOW_SPEC_RING_VF_OFF;
|
|
fsp->ring_cookie |= ring_vf;
|
|
}
|
|
}
|
|
|
|
if (rule->flex_filter) {
|
|
userdef.flex_filter = true;
|
|
userdef.flex_word = be16_to_cpu(rule->flex_word);
|
|
userdef.flex_offset = rule->flex_offset;
|
|
}
|
|
|
|
i40e_fill_rx_flow_user_data(fsp, &userdef);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rxnfc - command to get RX flow classification rules
|
|
* @netdev: network interface device structure
|
|
* @cmd: ethtool rxnfc command
|
|
* @rule_locs: pointer to store rule data
|
|
*
|
|
* Returns Success if the command is supported.
|
|
**/
|
|
static int i40e_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
|
|
u32 *rule_locs)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
int ret = -EOPNOTSUPP;
|
|
|
|
switch (cmd->cmd) {
|
|
case ETHTOOL_GRXRINGS:
|
|
cmd->data = vsi->rss_size;
|
|
ret = 0;
|
|
break;
|
|
case ETHTOOL_GRXFH:
|
|
ret = i40e_get_rss_hash_opts(pf, cmd);
|
|
break;
|
|
case ETHTOOL_GRXCLSRLCNT:
|
|
cmd->rule_cnt = pf->fdir_pf_active_filters;
|
|
/* report total rule count */
|
|
cmd->data = i40e_get_fd_cnt_all(pf);
|
|
ret = 0;
|
|
break;
|
|
case ETHTOOL_GRXCLSRULE:
|
|
ret = i40e_get_ethtool_fdir_entry(pf, cmd);
|
|
break;
|
|
case ETHTOOL_GRXCLSRLALL:
|
|
ret = i40e_get_ethtool_fdir_all(pf, cmd, rule_locs);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rss_hash_bits - Read RSS Hash bits from register
|
|
* @nfc: pointer to user request
|
|
* @i_setc: bits currently set
|
|
*
|
|
* Returns value of bits to be set per user request
|
|
**/
|
|
static u64 i40e_get_rss_hash_bits(struct ethtool_rxnfc *nfc, u64 i_setc)
|
|
{
|
|
u64 i_set = i_setc;
|
|
u64 src_l3 = 0, dst_l3 = 0;
|
|
|
|
if (nfc->data & RXH_L4_B_0_1)
|
|
i_set |= I40E_L4_SRC_MASK;
|
|
else
|
|
i_set &= ~I40E_L4_SRC_MASK;
|
|
if (nfc->data & RXH_L4_B_2_3)
|
|
i_set |= I40E_L4_DST_MASK;
|
|
else
|
|
i_set &= ~I40E_L4_DST_MASK;
|
|
|
|
if (nfc->flow_type == TCP_V6_FLOW || nfc->flow_type == UDP_V6_FLOW) {
|
|
src_l3 = I40E_L3_V6_SRC_MASK;
|
|
dst_l3 = I40E_L3_V6_DST_MASK;
|
|
} else if (nfc->flow_type == TCP_V4_FLOW ||
|
|
nfc->flow_type == UDP_V4_FLOW) {
|
|
src_l3 = I40E_L3_SRC_MASK;
|
|
dst_l3 = I40E_L3_DST_MASK;
|
|
} else {
|
|
/* Any other flow type are not supported here */
|
|
return i_set;
|
|
}
|
|
|
|
if (nfc->data & RXH_IP_SRC)
|
|
i_set |= src_l3;
|
|
else
|
|
i_set &= ~src_l3;
|
|
if (nfc->data & RXH_IP_DST)
|
|
i_set |= dst_l3;
|
|
else
|
|
i_set &= ~dst_l3;
|
|
|
|
return i_set;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_rss_hash_opt - Enable/Disable flow types for RSS hash
|
|
* @pf: pointer to the physical function struct
|
|
* @nfc: ethtool rxnfc command
|
|
*
|
|
* Returns Success if the flow input set is supported.
|
|
**/
|
|
static int i40e_set_rss_hash_opt(struct i40e_pf *pf, struct ethtool_rxnfc *nfc)
|
|
{
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u64 hena = (u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(0)) |
|
|
((u64)i40e_read_rx_ctl(hw, I40E_PFQF_HENA(1)) << 32);
|
|
u8 flow_pctype = 0;
|
|
u64 i_set, i_setc;
|
|
|
|
if (pf->flags & I40E_FLAG_MFP_ENABLED) {
|
|
dev_err(&pf->pdev->dev,
|
|
"Change of RSS hash input set is not supported when MFP mode is enabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* 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:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV4_TCP;
|
|
if (pf->hw_features & I40E_HW_MULTIPLE_TCP_UDP_RSS_PCTYPE)
|
|
hena |=
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV4_TCP_SYN_NO_ACK);
|
|
break;
|
|
case TCP_V6_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV6_TCP;
|
|
if (pf->hw_features & I40E_HW_MULTIPLE_TCP_UDP_RSS_PCTYPE)
|
|
hena |=
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV4_TCP_SYN_NO_ACK);
|
|
if (pf->hw_features & I40E_HW_MULTIPLE_TCP_UDP_RSS_PCTYPE)
|
|
hena |=
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV6_TCP_SYN_NO_ACK);
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV4_UDP;
|
|
if (pf->hw_features & I40E_HW_MULTIPLE_TCP_UDP_RSS_PCTYPE)
|
|
hena |=
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_UNICAST_IPV4_UDP) |
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_MULTICAST_IPV4_UDP);
|
|
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_FRAG_IPV4);
|
|
break;
|
|
case UDP_V6_FLOW:
|
|
flow_pctype = I40E_FILTER_PCTYPE_NONF_IPV6_UDP;
|
|
if (pf->hw_features & I40E_HW_MULTIPLE_TCP_UDP_RSS_PCTYPE)
|
|
hena |=
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_UNICAST_IPV6_UDP) |
|
|
BIT_ULL(I40E_FILTER_PCTYPE_NONF_MULTICAST_IPV6_UDP);
|
|
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_FRAG_IPV6);
|
|
break;
|
|
case AH_ESP_V4_FLOW:
|
|
case AH_V4_FLOW:
|
|
case ESP_V4_FLOW:
|
|
case SCTP_V4_FLOW:
|
|
if ((nfc->data & RXH_L4_B_0_1) ||
|
|
(nfc->data & RXH_L4_B_2_3))
|
|
return -EINVAL;
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV4_OTHER);
|
|
break;
|
|
case AH_ESP_V6_FLOW:
|
|
case AH_V6_FLOW:
|
|
case ESP_V6_FLOW:
|
|
case SCTP_V6_FLOW:
|
|
if ((nfc->data & RXH_L4_B_0_1) ||
|
|
(nfc->data & RXH_L4_B_2_3))
|
|
return -EINVAL;
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV6_OTHER);
|
|
break;
|
|
case IPV4_FLOW:
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV4_OTHER) |
|
|
BIT_ULL(I40E_FILTER_PCTYPE_FRAG_IPV4);
|
|
break;
|
|
case IPV6_FLOW:
|
|
hena |= BIT_ULL(I40E_FILTER_PCTYPE_NONF_IPV6_OTHER) |
|
|
BIT_ULL(I40E_FILTER_PCTYPE_FRAG_IPV6);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flow_pctype) {
|
|
i_setc = (u64)i40e_read_rx_ctl(hw, I40E_GLQF_HASH_INSET(0,
|
|
flow_pctype)) |
|
|
((u64)i40e_read_rx_ctl(hw, I40E_GLQF_HASH_INSET(1,
|
|
flow_pctype)) << 32);
|
|
i_set = i40e_get_rss_hash_bits(nfc, i_setc);
|
|
i40e_write_rx_ctl(hw, I40E_GLQF_HASH_INSET(0, flow_pctype),
|
|
(u32)i_set);
|
|
i40e_write_rx_ctl(hw, I40E_GLQF_HASH_INSET(1, flow_pctype),
|
|
(u32)(i_set >> 32));
|
|
hena |= BIT_ULL(flow_pctype);
|
|
}
|
|
|
|
i40e_write_rx_ctl(hw, I40E_PFQF_HENA(0), (u32)hena);
|
|
i40e_write_rx_ctl(hw, I40E_PFQF_HENA(1), (u32)(hena >> 32));
|
|
i40e_flush(hw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_update_ethtool_fdir_entry - Updates the fdir filter entry
|
|
* @vsi: Pointer to the targeted VSI
|
|
* @input: The filter to update or NULL to indicate deletion
|
|
* @sw_idx: Software index to the filter
|
|
* @cmd: The command to get or set Rx flow classification rules
|
|
*
|
|
* This function updates (or deletes) a Flow Director entry from
|
|
* the hlist of the corresponding PF
|
|
*
|
|
* Returns 0 on success
|
|
**/
|
|
static int i40e_update_ethtool_fdir_entry(struct i40e_vsi *vsi,
|
|
struct i40e_fdir_filter *input,
|
|
u16 sw_idx,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct i40e_fdir_filter *rule, *parent;
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct hlist_node *node2;
|
|
int err = -EINVAL;
|
|
|
|
parent = NULL;
|
|
rule = NULL;
|
|
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&pf->fdir_filter_list, fdir_node) {
|
|
/* hash found, or no matching entry */
|
|
if (rule->fd_id >= sw_idx)
|
|
break;
|
|
parent = rule;
|
|
}
|
|
|
|
/* if there is an old rule occupying our place remove it */
|
|
if (rule && (rule->fd_id == sw_idx)) {
|
|
/* Remove this rule, since we're either deleting it, or
|
|
* replacing it.
|
|
*/
|
|
err = i40e_add_del_fdir(vsi, rule, false);
|
|
hlist_del(&rule->fdir_node);
|
|
kfree(rule);
|
|
pf->fdir_pf_active_filters--;
|
|
}
|
|
|
|
/* If we weren't given an input, this is a delete, so just return the
|
|
* error code indicating if there was an entry at the requested slot
|
|
*/
|
|
if (!input)
|
|
return err;
|
|
|
|
/* Otherwise, install the new rule as requested */
|
|
INIT_HLIST_NODE(&input->fdir_node);
|
|
|
|
/* add filter to the list */
|
|
if (parent)
|
|
hlist_add_behind(&input->fdir_node, &parent->fdir_node);
|
|
else
|
|
hlist_add_head(&input->fdir_node,
|
|
&pf->fdir_filter_list);
|
|
|
|
/* update counts */
|
|
pf->fdir_pf_active_filters++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_prune_flex_pit_list - Cleanup unused entries in FLX_PIT table
|
|
* @pf: pointer to PF structure
|
|
*
|
|
* This function searches the list of filters and determines which FLX_PIT
|
|
* entries are still required. It will prune any entries which are no longer
|
|
* in use after the deletion.
|
|
**/
|
|
static void i40e_prune_flex_pit_list(struct i40e_pf *pf)
|
|
{
|
|
struct i40e_flex_pit *entry, *tmp;
|
|
struct i40e_fdir_filter *rule;
|
|
|
|
/* First, we'll check the l3 table */
|
|
list_for_each_entry_safe(entry, tmp, &pf->l3_flex_pit_list, list) {
|
|
bool found = false;
|
|
|
|
hlist_for_each_entry(rule, &pf->fdir_filter_list, fdir_node) {
|
|
if (rule->flow_type != IP_USER_FLOW)
|
|
continue;
|
|
if (rule->flex_filter &&
|
|
rule->flex_offset == entry->src_offset) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we didn't find the filter, then we can prune this entry
|
|
* from the list.
|
|
*/
|
|
if (!found) {
|
|
list_del(&entry->list);
|
|
kfree(entry);
|
|
}
|
|
}
|
|
|
|
/* Followed by the L4 table */
|
|
list_for_each_entry_safe(entry, tmp, &pf->l4_flex_pit_list, list) {
|
|
bool found = false;
|
|
|
|
hlist_for_each_entry(rule, &pf->fdir_filter_list, fdir_node) {
|
|
/* Skip this filter if it's L3, since we already
|
|
* checked those in the above loop
|
|
*/
|
|
if (rule->flow_type == IP_USER_FLOW)
|
|
continue;
|
|
if (rule->flex_filter &&
|
|
rule->flex_offset == entry->src_offset) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we didn't find the filter, then we can prune this entry
|
|
* from the list.
|
|
*/
|
|
if (!found) {
|
|
list_del(&entry->list);
|
|
kfree(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_del_fdir_entry - Deletes a Flow Director filter entry
|
|
* @vsi: Pointer to the targeted VSI
|
|
* @cmd: The command to get or set Rx flow classification rules
|
|
*
|
|
* The function removes a Flow Director filter entry from the
|
|
* hlist of the corresponding PF
|
|
*
|
|
* Returns 0 on success
|
|
*/
|
|
static int i40e_del_fdir_entry(struct i40e_vsi *vsi,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct ethtool_rx_flow_spec *fsp =
|
|
(struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
struct i40e_pf *pf = vsi->back;
|
|
int ret = 0;
|
|
|
|
if (test_bit(__I40E_RESET_RECOVERY_PENDING, pf->state) ||
|
|
test_bit(__I40E_RESET_INTR_RECEIVED, pf->state))
|
|
return -EBUSY;
|
|
|
|
if (test_bit(__I40E_FD_FLUSH_REQUESTED, pf->state))
|
|
return -EBUSY;
|
|
|
|
ret = i40e_update_ethtool_fdir_entry(vsi, NULL, fsp->location, cmd);
|
|
|
|
i40e_prune_flex_pit_list(pf);
|
|
|
|
i40e_fdir_check_and_reenable(pf);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i40e_unused_pit_index - Find an unused PIT index for given list
|
|
* @pf: the PF data structure
|
|
*
|
|
* Find the first unused flexible PIT index entry. We search both the L3 and
|
|
* L4 flexible PIT lists so that the returned index is unique and unused by
|
|
* either currently programmed L3 or L4 filters. We use a bit field as storage
|
|
* to track which indexes are already used.
|
|
**/
|
|
static u8 i40e_unused_pit_index(struct i40e_pf *pf)
|
|
{
|
|
unsigned long available_index = 0xFF;
|
|
struct i40e_flex_pit *entry;
|
|
|
|
/* We need to make sure that the new index isn't in use by either L3
|
|
* or L4 filters so that IP_USER_FLOW filters can program both L3 and
|
|
* L4 to use the same index.
|
|
*/
|
|
|
|
list_for_each_entry(entry, &pf->l4_flex_pit_list, list)
|
|
clear_bit(entry->pit_index, &available_index);
|
|
|
|
list_for_each_entry(entry, &pf->l3_flex_pit_list, list)
|
|
clear_bit(entry->pit_index, &available_index);
|
|
|
|
return find_first_bit(&available_index, 8);
|
|
}
|
|
|
|
/**
|
|
* i40e_find_flex_offset - Find an existing flex src_offset
|
|
* @flex_pit_list: L3 or L4 flex PIT list
|
|
* @src_offset: new src_offset to find
|
|
*
|
|
* Searches the flex_pit_list for an existing offset. If no offset is
|
|
* currently programmed, then this will return an ERR_PTR if there is no space
|
|
* to add a new offset, otherwise it returns NULL.
|
|
**/
|
|
static
|
|
struct i40e_flex_pit *i40e_find_flex_offset(struct list_head *flex_pit_list,
|
|
u16 src_offset)
|
|
{
|
|
struct i40e_flex_pit *entry;
|
|
int size = 0;
|
|
|
|
/* Search for the src_offset first. If we find a matching entry
|
|
* already programmed, we can simply re-use it.
|
|
*/
|
|
list_for_each_entry(entry, flex_pit_list, list) {
|
|
size++;
|
|
if (entry->src_offset == src_offset)
|
|
return entry;
|
|
}
|
|
|
|
/* If we haven't found an entry yet, then the provided src offset has
|
|
* not yet been programmed. We will program the src offset later on,
|
|
* but we need to indicate whether there is enough space to do so
|
|
* here. We'll make use of ERR_PTR for this purpose.
|
|
*/
|
|
if (size >= I40E_FLEX_PIT_TABLE_SIZE)
|
|
return ERR_PTR(-ENOSPC);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* i40e_add_flex_offset - Add src_offset to flex PIT table list
|
|
* @flex_pit_list: L3 or L4 flex PIT list
|
|
* @src_offset: new src_offset to add
|
|
* @pit_index: the PIT index to program
|
|
*
|
|
* This function programs the new src_offset to the list. It is expected that
|
|
* i40e_find_flex_offset has already been tried and returned NULL, indicating
|
|
* that this offset is not programmed, and that the list has enough space to
|
|
* store another offset.
|
|
*
|
|
* Returns 0 on success, and negative value on error.
|
|
**/
|
|
static int i40e_add_flex_offset(struct list_head *flex_pit_list,
|
|
u16 src_offset,
|
|
u8 pit_index)
|
|
{
|
|
struct i40e_flex_pit *new_pit, *entry;
|
|
|
|
new_pit = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
if (!new_pit)
|
|
return -ENOMEM;
|
|
|
|
new_pit->src_offset = src_offset;
|
|
new_pit->pit_index = pit_index;
|
|
|
|
/* We need to insert this item such that the list is sorted by
|
|
* src_offset in ascending order.
|
|
*/
|
|
list_for_each_entry(entry, flex_pit_list, list) {
|
|
if (new_pit->src_offset < entry->src_offset) {
|
|
list_add_tail(&new_pit->list, &entry->list);
|
|
return 0;
|
|
}
|
|
|
|
/* If we found an entry with our offset already programmed we
|
|
* can simply return here, after freeing the memory. However,
|
|
* if the pit_index does not match we need to report an error.
|
|
*/
|
|
if (new_pit->src_offset == entry->src_offset) {
|
|
int err = 0;
|
|
|
|
/* If the PIT index is not the same we can't re-use
|
|
* the entry, so we must report an error.
|
|
*/
|
|
if (new_pit->pit_index != entry->pit_index)
|
|
err = -EINVAL;
|
|
|
|
kfree(new_pit);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* If we reached here, then we haven't yet added the item. This means
|
|
* that we should add the item at the end of the list.
|
|
*/
|
|
list_add_tail(&new_pit->list, flex_pit_list);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* __i40e_reprogram_flex_pit - Re-program specific FLX_PIT table
|
|
* @pf: Pointer to the PF structure
|
|
* @flex_pit_list: list of flexible src offsets in use
|
|
* @flex_pit_start: index to first entry for this section of the table
|
|
*
|
|
* In order to handle flexible data, the hardware uses a table of values
|
|
* called the FLX_PIT table. This table is used to indicate which sections of
|
|
* the input correspond to what PIT index values. Unfortunately, hardware is
|
|
* very restrictive about programming this table. Entries must be ordered by
|
|
* src_offset in ascending order, without duplicates. Additionally, unused
|
|
* entries must be set to the unused index value, and must have valid size and
|
|
* length according to the src_offset ordering.
|
|
*
|
|
* This function will reprogram the FLX_PIT register from a book-keeping
|
|
* structure that we guarantee is already ordered correctly, and has no more
|
|
* than 3 entries.
|
|
*
|
|
* To make things easier, we only support flexible values of one word length,
|
|
* rather than allowing variable length flexible values.
|
|
**/
|
|
static void __i40e_reprogram_flex_pit(struct i40e_pf *pf,
|
|
struct list_head *flex_pit_list,
|
|
int flex_pit_start)
|
|
{
|
|
struct i40e_flex_pit *entry = NULL;
|
|
u16 last_offset = 0;
|
|
int i = 0, j = 0;
|
|
|
|
/* First, loop over the list of flex PIT entries, and reprogram the
|
|
* registers.
|
|
*/
|
|
list_for_each_entry(entry, flex_pit_list, list) {
|
|
/* We have to be careful when programming values for the
|
|
* largest SRC_OFFSET value. It is possible that adding
|
|
* additional empty values at the end would overflow the space
|
|
* for the SRC_OFFSET in the FLX_PIT register. To avoid this,
|
|
* we check here and add the empty values prior to adding the
|
|
* largest value.
|
|
*
|
|
* To determine this, we will use a loop from i+1 to 3, which
|
|
* will determine whether the unused entries would have valid
|
|
* SRC_OFFSET. Note that there cannot be extra entries past
|
|
* this value, because the only valid values would have been
|
|
* larger than I40E_MAX_FLEX_SRC_OFFSET, and thus would not
|
|
* have been added to the list in the first place.
|
|
*/
|
|
for (j = i + 1; j < 3; j++) {
|
|
u16 offset = entry->src_offset + j;
|
|
int index = flex_pit_start + i;
|
|
u32 value = I40E_FLEX_PREP_VAL(I40E_FLEX_DEST_UNUSED,
|
|
1,
|
|
offset - 3);
|
|
|
|
if (offset > I40E_MAX_FLEX_SRC_OFFSET) {
|
|
i40e_write_rx_ctl(&pf->hw,
|
|
I40E_PRTQF_FLX_PIT(index),
|
|
value);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* Now, we can program the actual value into the table */
|
|
i40e_write_rx_ctl(&pf->hw,
|
|
I40E_PRTQF_FLX_PIT(flex_pit_start + i),
|
|
I40E_FLEX_PREP_VAL(entry->pit_index + 50,
|
|
1,
|
|
entry->src_offset));
|
|
i++;
|
|
}
|
|
|
|
/* In order to program the last entries in the table, we need to
|
|
* determine the valid offset. If the list is empty, we'll just start
|
|
* with 0. Otherwise, we'll start with the last item offset and add 1.
|
|
* This ensures that all entries have valid sizes. If we don't do this
|
|
* correctly, the hardware will disable flexible field parsing.
|
|
*/
|
|
if (!list_empty(flex_pit_list))
|
|
last_offset = list_prev_entry(entry, list)->src_offset + 1;
|
|
|
|
for (; i < 3; i++, last_offset++) {
|
|
i40e_write_rx_ctl(&pf->hw,
|
|
I40E_PRTQF_FLX_PIT(flex_pit_start + i),
|
|
I40E_FLEX_PREP_VAL(I40E_FLEX_DEST_UNUSED,
|
|
1,
|
|
last_offset));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_reprogram_flex_pit - Reprogram all FLX_PIT tables after input set change
|
|
* @pf: pointer to the PF structure
|
|
*
|
|
* This function reprograms both the L3 and L4 FLX_PIT tables. See the
|
|
* internal helper function for implementation details.
|
|
**/
|
|
static void i40e_reprogram_flex_pit(struct i40e_pf *pf)
|
|
{
|
|
__i40e_reprogram_flex_pit(pf, &pf->l3_flex_pit_list,
|
|
I40E_FLEX_PIT_IDX_START_L3);
|
|
|
|
__i40e_reprogram_flex_pit(pf, &pf->l4_flex_pit_list,
|
|
I40E_FLEX_PIT_IDX_START_L4);
|
|
|
|
/* We also need to program the L3 and L4 GLQF ORT register */
|
|
i40e_write_rx_ctl(&pf->hw,
|
|
I40E_GLQF_ORT(I40E_L3_GLQF_ORT_IDX),
|
|
I40E_ORT_PREP_VAL(I40E_FLEX_PIT_IDX_START_L3,
|
|
3, 1));
|
|
|
|
i40e_write_rx_ctl(&pf->hw,
|
|
I40E_GLQF_ORT(I40E_L4_GLQF_ORT_IDX),
|
|
I40E_ORT_PREP_VAL(I40E_FLEX_PIT_IDX_START_L4,
|
|
3, 1));
|
|
}
|
|
|
|
/**
|
|
* i40e_flow_str - Converts a flow_type into a human readable string
|
|
* @fsp: the flow specification
|
|
*
|
|
* Currently only flow types we support are included here, and the string
|
|
* value attempts to match what ethtool would use to configure this flow type.
|
|
**/
|
|
static const char *i40e_flow_str(struct ethtool_rx_flow_spec *fsp)
|
|
{
|
|
switch (fsp->flow_type & ~FLOW_EXT) {
|
|
case TCP_V4_FLOW:
|
|
return "tcp4";
|
|
case UDP_V4_FLOW:
|
|
return "udp4";
|
|
case SCTP_V4_FLOW:
|
|
return "sctp4";
|
|
case IP_USER_FLOW:
|
|
return "ip4";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_pit_index_to_mask - Return the FLEX mask for a given PIT index
|
|
* @pit_index: PIT index to convert
|
|
*
|
|
* Returns the mask for a given PIT index. Will return 0 if the pit_index is
|
|
* of range.
|
|
**/
|
|
static u64 i40e_pit_index_to_mask(int pit_index)
|
|
{
|
|
switch (pit_index) {
|
|
case 0:
|
|
return I40E_FLEX_50_MASK;
|
|
case 1:
|
|
return I40E_FLEX_51_MASK;
|
|
case 2:
|
|
return I40E_FLEX_52_MASK;
|
|
case 3:
|
|
return I40E_FLEX_53_MASK;
|
|
case 4:
|
|
return I40E_FLEX_54_MASK;
|
|
case 5:
|
|
return I40E_FLEX_55_MASK;
|
|
case 6:
|
|
return I40E_FLEX_56_MASK;
|
|
case 7:
|
|
return I40E_FLEX_57_MASK;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* i40e_print_input_set - Show changes between two input sets
|
|
* @vsi: the vsi being configured
|
|
* @old: the old input set
|
|
* @new: the new input set
|
|
*
|
|
* Print the difference between old and new input sets by showing which series
|
|
* of words are toggled on or off. Only displays the bits we actually support
|
|
* changing.
|
|
**/
|
|
static void i40e_print_input_set(struct i40e_vsi *vsi, u64 old, u64 new)
|
|
{
|
|
struct i40e_pf *pf = vsi->back;
|
|
bool old_value, new_value;
|
|
int i;
|
|
|
|
old_value = !!(old & I40E_L3_SRC_MASK);
|
|
new_value = !!(new & I40E_L3_SRC_MASK);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "L3 source address: %s -> %s\n",
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
|
|
old_value = !!(old & I40E_L3_DST_MASK);
|
|
new_value = !!(new & I40E_L3_DST_MASK);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "L3 destination address: %s -> %s\n",
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
|
|
old_value = !!(old & I40E_L4_SRC_MASK);
|
|
new_value = !!(new & I40E_L4_SRC_MASK);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "L4 source port: %s -> %s\n",
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
|
|
old_value = !!(old & I40E_L4_DST_MASK);
|
|
new_value = !!(new & I40E_L4_DST_MASK);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "L4 destination port: %s -> %s\n",
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
|
|
old_value = !!(old & I40E_VERIFY_TAG_MASK);
|
|
new_value = !!(new & I40E_VERIFY_TAG_MASK);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "SCTP verification tag: %s -> %s\n",
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
|
|
/* Show change of flexible filter entries */
|
|
for (i = 0; i < I40E_FLEX_INDEX_ENTRIES; i++) {
|
|
u64 flex_mask = i40e_pit_index_to_mask(i);
|
|
|
|
old_value = !!(old & flex_mask);
|
|
new_value = !!(new & flex_mask);
|
|
if (old_value != new_value)
|
|
netif_info(pf, drv, vsi->netdev, "FLEX index %d: %s -> %s\n",
|
|
i,
|
|
old_value ? "ON" : "OFF",
|
|
new_value ? "ON" : "OFF");
|
|
}
|
|
|
|
netif_info(pf, drv, vsi->netdev, " Current input set: %0llx\n",
|
|
old);
|
|
netif_info(pf, drv, vsi->netdev, "Requested input set: %0llx\n",
|
|
new);
|
|
}
|
|
|
|
/**
|
|
* i40e_check_fdir_input_set - Check that a given rx_flow_spec mask is valid
|
|
* @vsi: pointer to the targeted VSI
|
|
* @fsp: pointer to Rx flow specification
|
|
* @userdef: userdefined data from flow specification
|
|
*
|
|
* Ensures that a given ethtool_rx_flow_spec has a valid mask. Some support
|
|
* for partial matches exists with a few limitations. First, hardware only
|
|
* supports masking by word boundary (2 bytes) and not per individual bit.
|
|
* Second, hardware is limited to using one mask for a flow type and cannot
|
|
* use a separate mask for each filter.
|
|
*
|
|
* To support these limitations, if we already have a configured filter for
|
|
* the specified type, this function enforces that new filters of the type
|
|
* match the configured input set. Otherwise, if we do not have a filter of
|
|
* the specified type, we allow the input set to be updated to match the
|
|
* desired filter.
|
|
*
|
|
* To help ensure that administrators understand why filters weren't displayed
|
|
* as supported, we print a diagnostic message displaying how the input set
|
|
* would change and warning to delete the preexisting filters if required.
|
|
*
|
|
* Returns 0 on successful input set match, and a negative return code on
|
|
* failure.
|
|
**/
|
|
static int i40e_check_fdir_input_set(struct i40e_vsi *vsi,
|
|
struct ethtool_rx_flow_spec *fsp,
|
|
struct i40e_rx_flow_userdef *userdef)
|
|
{
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct ethtool_tcpip4_spec *tcp_ip4_spec;
|
|
struct ethtool_usrip4_spec *usr_ip4_spec;
|
|
u64 current_mask, new_mask;
|
|
bool new_flex_offset = false;
|
|
bool flex_l3 = false;
|
|
u16 *fdir_filter_count;
|
|
u16 index, src_offset = 0;
|
|
u8 pit_index = 0;
|
|
int err;
|
|
|
|
switch (fsp->flow_type & ~FLOW_EXT) {
|
|
case SCTP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_SCTP;
|
|
fdir_filter_count = &pf->fd_sctp4_filter_cnt;
|
|
break;
|
|
case TCP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_TCP;
|
|
fdir_filter_count = &pf->fd_tcp4_filter_cnt;
|
|
break;
|
|
case UDP_V4_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_UDP;
|
|
fdir_filter_count = &pf->fd_udp4_filter_cnt;
|
|
break;
|
|
case IP_USER_FLOW:
|
|
index = I40E_FILTER_PCTYPE_NONF_IPV4_OTHER;
|
|
fdir_filter_count = &pf->fd_ip4_filter_cnt;
|
|
flex_l3 = true;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Read the current input set from register memory. */
|
|
current_mask = i40e_read_fd_input_set(pf, index);
|
|
new_mask = current_mask;
|
|
|
|
/* Determine, if any, the required changes to the input set in order
|
|
* to support the provided mask.
|
|
*
|
|
* Hardware only supports masking at word (2 byte) granularity and does
|
|
* not support full bitwise masking. This implementation simplifies
|
|
* even further and only supports fully enabled or fully disabled
|
|
* masks for each field, even though we could split the ip4src and
|
|
* ip4dst fields.
|
|
*/
|
|
switch (fsp->flow_type & ~FLOW_EXT) {
|
|
case SCTP_V4_FLOW:
|
|
new_mask &= ~I40E_VERIFY_TAG_MASK;
|
|
fallthrough;
|
|
case TCP_V4_FLOW:
|
|
case UDP_V4_FLOW:
|
|
tcp_ip4_spec = &fsp->m_u.tcp_ip4_spec;
|
|
|
|
/* IPv4 source address */
|
|
if (tcp_ip4_spec->ip4src == htonl(0xFFFFFFFF))
|
|
new_mask |= I40E_L3_SRC_MASK;
|
|
else if (!tcp_ip4_spec->ip4src)
|
|
new_mask &= ~I40E_L3_SRC_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* IPv4 destination address */
|
|
if (tcp_ip4_spec->ip4dst == htonl(0xFFFFFFFF))
|
|
new_mask |= I40E_L3_DST_MASK;
|
|
else if (!tcp_ip4_spec->ip4dst)
|
|
new_mask &= ~I40E_L3_DST_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* L4 source port */
|
|
if (tcp_ip4_spec->psrc == htons(0xFFFF))
|
|
new_mask |= I40E_L4_SRC_MASK;
|
|
else if (!tcp_ip4_spec->psrc)
|
|
new_mask &= ~I40E_L4_SRC_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* L4 destination port */
|
|
if (tcp_ip4_spec->pdst == htons(0xFFFF))
|
|
new_mask |= I40E_L4_DST_MASK;
|
|
else if (!tcp_ip4_spec->pdst)
|
|
new_mask &= ~I40E_L4_DST_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* Filtering on Type of Service is not supported. */
|
|
if (tcp_ip4_spec->tos)
|
|
return -EOPNOTSUPP;
|
|
|
|
break;
|
|
case IP_USER_FLOW:
|
|
usr_ip4_spec = &fsp->m_u.usr_ip4_spec;
|
|
|
|
/* IPv4 source address */
|
|
if (usr_ip4_spec->ip4src == htonl(0xFFFFFFFF))
|
|
new_mask |= I40E_L3_SRC_MASK;
|
|
else if (!usr_ip4_spec->ip4src)
|
|
new_mask &= ~I40E_L3_SRC_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* IPv4 destination address */
|
|
if (usr_ip4_spec->ip4dst == htonl(0xFFFFFFFF))
|
|
new_mask |= I40E_L3_DST_MASK;
|
|
else if (!usr_ip4_spec->ip4dst)
|
|
new_mask &= ~I40E_L3_DST_MASK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* First 4 bytes of L4 header */
|
|
if (usr_ip4_spec->l4_4_bytes == htonl(0xFFFFFFFF))
|
|
new_mask |= I40E_L4_SRC_MASK | I40E_L4_DST_MASK;
|
|
else if (!usr_ip4_spec->l4_4_bytes)
|
|
new_mask &= ~(I40E_L4_SRC_MASK | I40E_L4_DST_MASK);
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
/* Filtering on Type of Service is not supported. */
|
|
if (usr_ip4_spec->tos)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* Filtering on IP version is not supported */
|
|
if (usr_ip4_spec->ip_ver)
|
|
return -EINVAL;
|
|
|
|
/* Filtering on L4 protocol is not supported */
|
|
if (usr_ip4_spec->proto)
|
|
return -EINVAL;
|
|
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* First, clear all flexible filter entries */
|
|
new_mask &= ~I40E_FLEX_INPUT_MASK;
|
|
|
|
/* If we have a flexible filter, try to add this offset to the correct
|
|
* flexible filter PIT list. Once finished, we can update the mask.
|
|
* If the src_offset changed, we will get a new mask value which will
|
|
* trigger an input set change.
|
|
*/
|
|
if (userdef->flex_filter) {
|
|
struct i40e_flex_pit *l3_flex_pit = NULL, *flex_pit = NULL;
|
|
|
|
/* Flexible offset must be even, since the flexible payload
|
|
* must be aligned on 2-byte boundary.
|
|
*/
|
|
if (userdef->flex_offset & 0x1) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Flexible data offset must be 2-byte aligned\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
src_offset = userdef->flex_offset >> 1;
|
|
|
|
/* FLX_PIT source offset value is only so large */
|
|
if (src_offset > I40E_MAX_FLEX_SRC_OFFSET) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Flexible data must reside within first 64 bytes of the packet payload\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* See if this offset has already been programmed. If we get
|
|
* an ERR_PTR, then the filter is not safe to add. Otherwise,
|
|
* if we get a NULL pointer, this means we will need to add
|
|
* the offset.
|
|
*/
|
|
flex_pit = i40e_find_flex_offset(&pf->l4_flex_pit_list,
|
|
src_offset);
|
|
if (IS_ERR(flex_pit))
|
|
return PTR_ERR(flex_pit);
|
|
|
|
/* IP_USER_FLOW filters match both L4 (ICMP) and L3 (unknown)
|
|
* packet types, and thus we need to program both L3 and L4
|
|
* flexible values. These must have identical flexible index,
|
|
* as otherwise we can't correctly program the input set. So
|
|
* we'll find both an L3 and L4 index and make sure they are
|
|
* the same.
|
|
*/
|
|
if (flex_l3) {
|
|
l3_flex_pit =
|
|
i40e_find_flex_offset(&pf->l3_flex_pit_list,
|
|
src_offset);
|
|
if (IS_ERR(l3_flex_pit))
|
|
return PTR_ERR(l3_flex_pit);
|
|
|
|
if (flex_pit) {
|
|
/* If we already had a matching L4 entry, we
|
|
* need to make sure that the L3 entry we
|
|
* obtained uses the same index.
|
|
*/
|
|
if (l3_flex_pit) {
|
|
if (l3_flex_pit->pit_index !=
|
|
flex_pit->pit_index) {
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
new_flex_offset = true;
|
|
}
|
|
} else {
|
|
flex_pit = l3_flex_pit;
|
|
}
|
|
}
|
|
|
|
/* If we didn't find an existing flex offset, we need to
|
|
* program a new one. However, we don't immediately program it
|
|
* here because we will wait to program until after we check
|
|
* that it is safe to change the input set.
|
|
*/
|
|
if (!flex_pit) {
|
|
new_flex_offset = true;
|
|
pit_index = i40e_unused_pit_index(pf);
|
|
} else {
|
|
pit_index = flex_pit->pit_index;
|
|
}
|
|
|
|
/* Update the mask with the new offset */
|
|
new_mask |= i40e_pit_index_to_mask(pit_index);
|
|
}
|
|
|
|
/* If the mask and flexible filter offsets for this filter match the
|
|
* currently programmed values we don't need any input set change, so
|
|
* this filter is safe to install.
|
|
*/
|
|
if (new_mask == current_mask && !new_flex_offset)
|
|
return 0;
|
|
|
|
netif_info(pf, drv, vsi->netdev, "Input set change requested for %s flows:\n",
|
|
i40e_flow_str(fsp));
|
|
i40e_print_input_set(vsi, current_mask, new_mask);
|
|
if (new_flex_offset) {
|
|
netif_info(pf, drv, vsi->netdev, "FLEX index %d: Offset -> %d",
|
|
pit_index, src_offset);
|
|
}
|
|
|
|
/* Hardware input sets are global across multiple ports, so even the
|
|
* main port cannot change them when in MFP mode as this would impact
|
|
* any filters on the other ports.
|
|
*/
|
|
if (pf->flags & I40E_FLAG_MFP_ENABLED) {
|
|
netif_err(pf, drv, vsi->netdev, "Cannot change Flow Director input sets while MFP is enabled\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* This filter requires us to update the input set. However, hardware
|
|
* only supports one input set per flow type, and does not support
|
|
* separate masks for each filter. This means that we can only support
|
|
* a single mask for all filters of a specific type.
|
|
*
|
|
* If we have preexisting filters, they obviously depend on the
|
|
* current programmed input set. Display a diagnostic message in this
|
|
* case explaining why the filter could not be accepted.
|
|
*/
|
|
if (*fdir_filter_count) {
|
|
netif_err(pf, drv, vsi->netdev, "Cannot change input set for %s flows until %d preexisting filters are removed\n",
|
|
i40e_flow_str(fsp),
|
|
*fdir_filter_count);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
i40e_write_fd_input_set(pf, index, new_mask);
|
|
|
|
/* IP_USER_FLOW filters match both IPv4/Other and IPv4/Fragmented
|
|
* frames. If we're programming the input set for IPv4/Other, we also
|
|
* need to program the IPv4/Fragmented input set. Since we don't have
|
|
* separate support, we'll always assume and enforce that the two flow
|
|
* types must have matching input sets.
|
|
*/
|
|
if (index == I40E_FILTER_PCTYPE_NONF_IPV4_OTHER)
|
|
i40e_write_fd_input_set(pf, I40E_FILTER_PCTYPE_FRAG_IPV4,
|
|
new_mask);
|
|
|
|
/* Add the new offset and update table, if necessary */
|
|
if (new_flex_offset) {
|
|
err = i40e_add_flex_offset(&pf->l4_flex_pit_list, src_offset,
|
|
pit_index);
|
|
if (err)
|
|
return err;
|
|
|
|
if (flex_l3) {
|
|
err = i40e_add_flex_offset(&pf->l3_flex_pit_list,
|
|
src_offset,
|
|
pit_index);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
i40e_reprogram_flex_pit(pf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_match_fdir_filter - Return true of two filters match
|
|
* @a: pointer to filter struct
|
|
* @b: pointer to filter struct
|
|
*
|
|
* Returns true if the two filters match exactly the same criteria. I.e. they
|
|
* match the same flow type and have the same parameters. We don't need to
|
|
* check any input-set since all filters of the same flow type must use the
|
|
* same input set.
|
|
**/
|
|
static bool i40e_match_fdir_filter(struct i40e_fdir_filter *a,
|
|
struct i40e_fdir_filter *b)
|
|
{
|
|
/* The filters do not much if any of these criteria differ. */
|
|
if (a->dst_ip != b->dst_ip ||
|
|
a->src_ip != b->src_ip ||
|
|
a->dst_port != b->dst_port ||
|
|
a->src_port != b->src_port ||
|
|
a->flow_type != b->flow_type ||
|
|
a->ip4_proto != b->ip4_proto)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* i40e_disallow_matching_filters - Check that new filters differ
|
|
* @vsi: pointer to the targeted VSI
|
|
* @input: new filter to check
|
|
*
|
|
* Due to hardware limitations, it is not possible for two filters that match
|
|
* similar criteria to be programmed at the same time. This is true for a few
|
|
* reasons:
|
|
*
|
|
* (a) all filters matching a particular flow type must use the same input
|
|
* set, that is they must match the same criteria.
|
|
* (b) different flow types will never match the same packet, as the flow type
|
|
* is decided by hardware before checking which rules apply.
|
|
* (c) hardware has no way to distinguish which order filters apply in.
|
|
*
|
|
* Due to this, we can't really support using the location data to order
|
|
* filters in the hardware parsing. It is technically possible for the user to
|
|
* request two filters matching the same criteria but which select different
|
|
* queues. In this case, rather than keep both filters in the list, we reject
|
|
* the 2nd filter when the user requests adding it.
|
|
*
|
|
* This avoids needing to track location for programming the filter to
|
|
* hardware, and ensures that we avoid some strange scenarios involving
|
|
* deleting filters which match the same criteria.
|
|
**/
|
|
static int i40e_disallow_matching_filters(struct i40e_vsi *vsi,
|
|
struct i40e_fdir_filter *input)
|
|
{
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_fdir_filter *rule;
|
|
struct hlist_node *node2;
|
|
|
|
/* Loop through every filter, and check that it doesn't match */
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&pf->fdir_filter_list, fdir_node) {
|
|
/* Don't check the filters match if they share the same fd_id,
|
|
* since the new filter is actually just updating the target
|
|
* of the old filter.
|
|
*/
|
|
if (rule->fd_id == input->fd_id)
|
|
continue;
|
|
|
|
/* If any filters match, then print a warning message to the
|
|
* kernel message buffer and bail out.
|
|
*/
|
|
if (i40e_match_fdir_filter(rule, input)) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Existing user defined filter %d already matches this flow.\n",
|
|
rule->fd_id);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_add_fdir_ethtool - Add/Remove Flow Director filters
|
|
* @vsi: pointer to the targeted VSI
|
|
* @cmd: command to get or set RX flow classification rules
|
|
*
|
|
* Add Flow Director filters for a specific flow spec based on their
|
|
* protocol. Returns 0 if the filters were successfully added.
|
|
**/
|
|
static int i40e_add_fdir_ethtool(struct i40e_vsi *vsi,
|
|
struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct i40e_rx_flow_userdef userdef;
|
|
struct ethtool_rx_flow_spec *fsp;
|
|
struct i40e_fdir_filter *input;
|
|
u16 dest_vsi = 0, q_index = 0;
|
|
struct i40e_pf *pf;
|
|
int ret = -EINVAL;
|
|
u8 dest_ctl;
|
|
|
|
if (!vsi)
|
|
return -EINVAL;
|
|
pf = vsi->back;
|
|
|
|
if (!(pf->flags & I40E_FLAG_FD_SB_ENABLED))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (test_bit(__I40E_FD_SB_AUTO_DISABLED, pf->state))
|
|
return -ENOSPC;
|
|
|
|
if (test_bit(__I40E_RESET_RECOVERY_PENDING, pf->state) ||
|
|
test_bit(__I40E_RESET_INTR_RECEIVED, pf->state))
|
|
return -EBUSY;
|
|
|
|
if (test_bit(__I40E_FD_FLUSH_REQUESTED, pf->state))
|
|
return -EBUSY;
|
|
|
|
fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
|
|
|
|
/* Parse the user-defined field */
|
|
if (i40e_parse_rx_flow_user_data(fsp, &userdef))
|
|
return -EINVAL;
|
|
|
|
/* Extended MAC field is not supported */
|
|
if (fsp->flow_type & FLOW_MAC_EXT)
|
|
return -EINVAL;
|
|
|
|
ret = i40e_check_fdir_input_set(vsi, fsp, &userdef);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (fsp->location >= (pf->hw.func_caps.fd_filters_best_effort +
|
|
pf->hw.func_caps.fd_filters_guaranteed)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* ring_cookie is either the drop index, or is a mask of the queue
|
|
* index and VF id we wish to target.
|
|
*/
|
|
if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
|
|
dest_ctl = I40E_FILTER_PROGRAM_DESC_DEST_DROP_PACKET;
|
|
} else {
|
|
u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
|
|
u8 vf = ethtool_get_flow_spec_ring_vf(fsp->ring_cookie);
|
|
|
|
if (!vf) {
|
|
if (ring >= vsi->num_queue_pairs)
|
|
return -EINVAL;
|
|
dest_vsi = vsi->id;
|
|
} else {
|
|
/* VFs are zero-indexed, so we subtract one here */
|
|
vf--;
|
|
|
|
if (vf >= pf->num_alloc_vfs)
|
|
return -EINVAL;
|
|
if (ring >= pf->vf[vf].num_queue_pairs)
|
|
return -EINVAL;
|
|
dest_vsi = pf->vf[vf].lan_vsi_id;
|
|
}
|
|
dest_ctl = I40E_FILTER_PROGRAM_DESC_DEST_DIRECT_PACKET_QINDEX;
|
|
q_index = ring;
|
|
}
|
|
|
|
input = kzalloc(sizeof(*input), GFP_KERNEL);
|
|
|
|
if (!input)
|
|
return -ENOMEM;
|
|
|
|
input->fd_id = fsp->location;
|
|
input->q_index = q_index;
|
|
input->dest_vsi = dest_vsi;
|
|
input->dest_ctl = dest_ctl;
|
|
input->fd_status = I40E_FILTER_PROGRAM_DESC_FD_STATUS_FD_ID;
|
|
input->cnt_index = I40E_FD_SB_STAT_IDX(pf->hw.pf_id);
|
|
input->dst_ip = fsp->h_u.tcp_ip4_spec.ip4src;
|
|
input->src_ip = fsp->h_u.tcp_ip4_spec.ip4dst;
|
|
input->flow_type = fsp->flow_type & ~FLOW_EXT;
|
|
input->ip4_proto = fsp->h_u.usr_ip4_spec.proto;
|
|
|
|
/* Reverse the src and dest notion, since the HW expects them to be from
|
|
* Tx perspective where as the input from user is from Rx filter view.
|
|
*/
|
|
input->dst_port = fsp->h_u.tcp_ip4_spec.psrc;
|
|
input->src_port = fsp->h_u.tcp_ip4_spec.pdst;
|
|
input->dst_ip = fsp->h_u.tcp_ip4_spec.ip4src;
|
|
input->src_ip = fsp->h_u.tcp_ip4_spec.ip4dst;
|
|
|
|
if (userdef.flex_filter) {
|
|
input->flex_filter = true;
|
|
input->flex_word = cpu_to_be16(userdef.flex_word);
|
|
input->flex_offset = userdef.flex_offset;
|
|
}
|
|
|
|
/* Avoid programming two filters with identical match criteria. */
|
|
ret = i40e_disallow_matching_filters(vsi, input);
|
|
if (ret)
|
|
goto free_filter_memory;
|
|
|
|
/* Add the input filter to the fdir_input_list, possibly replacing
|
|
* a previous filter. Do not free the input structure after adding it
|
|
* to the list as this would cause a use-after-free bug.
|
|
*/
|
|
i40e_update_ethtool_fdir_entry(vsi, input, fsp->location, NULL);
|
|
ret = i40e_add_del_fdir(vsi, input, true);
|
|
if (ret)
|
|
goto remove_sw_rule;
|
|
return 0;
|
|
|
|
remove_sw_rule:
|
|
hlist_del(&input->fdir_node);
|
|
pf->fdir_pf_active_filters--;
|
|
free_filter_memory:
|
|
kfree(input);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_rxnfc - command to set RX flow classification rules
|
|
* @netdev: network interface device structure
|
|
* @cmd: ethtool rxnfc command
|
|
*
|
|
* Returns Success if the command is supported.
|
|
**/
|
|
static int i40e_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
int ret = -EOPNOTSUPP;
|
|
|
|
switch (cmd->cmd) {
|
|
case ETHTOOL_SRXFH:
|
|
ret = i40e_set_rss_hash_opt(pf, cmd);
|
|
break;
|
|
case ETHTOOL_SRXCLSRLINS:
|
|
ret = i40e_add_fdir_ethtool(vsi, cmd);
|
|
break;
|
|
case ETHTOOL_SRXCLSRLDEL:
|
|
ret = i40e_del_fdir_entry(vsi, cmd);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i40e_max_channels - get Max number of combined channels supported
|
|
* @vsi: vsi pointer
|
|
**/
|
|
static unsigned int i40e_max_channels(struct i40e_vsi *vsi)
|
|
{
|
|
/* TODO: This code assumes DCB and FD is disabled for now. */
|
|
return vsi->alloc_queue_pairs;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_channels - Get the current channels enabled and max supported etc.
|
|
* @dev: network interface device structure
|
|
* @ch: ethtool channels structure
|
|
*
|
|
* We don't support separate tx and rx queues as channels. The other count
|
|
* represents how many queues are being used for control. max_combined counts
|
|
* how many queue pairs we can support. They may not be mapped 1 to 1 with
|
|
* q_vectors since we support a lot more queue pairs than q_vectors.
|
|
**/
|
|
static void i40e_get_channels(struct net_device *dev,
|
|
struct ethtool_channels *ch)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(dev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
|
|
/* report maximum channels */
|
|
ch->max_combined = i40e_max_channels(vsi);
|
|
|
|
/* report info for other vector */
|
|
ch->other_count = (pf->flags & I40E_FLAG_FD_SB_ENABLED) ? 1 : 0;
|
|
ch->max_other = ch->other_count;
|
|
|
|
/* Note: This code assumes DCB is disabled for now. */
|
|
ch->combined_count = vsi->num_queue_pairs;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_channels - Set the new channels count.
|
|
* @dev: network interface device structure
|
|
* @ch: ethtool channels structure
|
|
*
|
|
* The new channels count may not be the same as requested by the user
|
|
* since it gets rounded down to a power of 2 value.
|
|
**/
|
|
static int i40e_set_channels(struct net_device *dev,
|
|
struct ethtool_channels *ch)
|
|
{
|
|
const u8 drop = I40E_FILTER_PROGRAM_DESC_DEST_DROP_PACKET;
|
|
struct i40e_netdev_priv *np = netdev_priv(dev);
|
|
unsigned int count = ch->combined_count;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_fdir_filter *rule;
|
|
struct hlist_node *node2;
|
|
int new_count;
|
|
int err = 0;
|
|
|
|
/* We do not support setting channels for any other VSI at present */
|
|
if (vsi->type != I40E_VSI_MAIN)
|
|
return -EINVAL;
|
|
|
|
/* We do not support setting channels via ethtool when TCs are
|
|
* configured through mqprio
|
|
*/
|
|
if (pf->flags & I40E_FLAG_TC_MQPRIO)
|
|
return -EINVAL;
|
|
|
|
/* verify they are not requesting separate vectors */
|
|
if (!count || ch->rx_count || ch->tx_count)
|
|
return -EINVAL;
|
|
|
|
/* verify other_count has not changed */
|
|
if (ch->other_count != ((pf->flags & I40E_FLAG_FD_SB_ENABLED) ? 1 : 0))
|
|
return -EINVAL;
|
|
|
|
/* verify the number of channels does not exceed hardware limits */
|
|
if (count > i40e_max_channels(vsi))
|
|
return -EINVAL;
|
|
|
|
/* verify that the number of channels does not invalidate any current
|
|
* flow director rules
|
|
*/
|
|
hlist_for_each_entry_safe(rule, node2,
|
|
&pf->fdir_filter_list, fdir_node) {
|
|
if (rule->dest_ctl != drop && count <= rule->q_index) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Existing user defined filter %d assigns flow to queue %d\n",
|
|
rule->fd_id, rule->q_index);
|
|
err = -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
dev_err(&pf->pdev->dev,
|
|
"Existing filter rules must be deleted to reduce combined channel count to %d\n",
|
|
count);
|
|
return err;
|
|
}
|
|
|
|
/* update feature limits from largest to smallest supported values */
|
|
/* TODO: Flow director limit, DCB etc */
|
|
|
|
/* use rss_reconfig to rebuild with new queue count and update traffic
|
|
* class queue mapping
|
|
*/
|
|
new_count = i40e_reconfig_rss_queues(pf, count);
|
|
if (new_count > 0)
|
|
return 0;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rxfh_key_size - get the RSS hash key size
|
|
* @netdev: network interface device structure
|
|
*
|
|
* Returns the table size.
|
|
**/
|
|
static u32 i40e_get_rxfh_key_size(struct net_device *netdev)
|
|
{
|
|
return I40E_HKEY_ARRAY_SIZE;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rxfh_indir_size - get the rx flow hash indirection table size
|
|
* @netdev: network interface device structure
|
|
*
|
|
* Returns the table size.
|
|
**/
|
|
static u32 i40e_get_rxfh_indir_size(struct net_device *netdev)
|
|
{
|
|
return I40E_HLUT_ARRAY_SIZE;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_rxfh - get the rx flow hash indirection table
|
|
* @netdev: network interface device structure
|
|
* @indir: indirection table
|
|
* @key: hash key
|
|
* @hfunc: hash function
|
|
*
|
|
* Reads the indirection table directly from the hardware. Returns 0 on
|
|
* success.
|
|
**/
|
|
static int i40e_get_rxfh(struct net_device *netdev, u32 *indir, u8 *key,
|
|
u8 *hfunc)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
u8 *lut, *seed = NULL;
|
|
int ret;
|
|
u16 i;
|
|
|
|
if (hfunc)
|
|
*hfunc = ETH_RSS_HASH_TOP;
|
|
|
|
if (!indir)
|
|
return 0;
|
|
|
|
seed = key;
|
|
lut = kzalloc(I40E_HLUT_ARRAY_SIZE, GFP_KERNEL);
|
|
if (!lut)
|
|
return -ENOMEM;
|
|
ret = i40e_get_rss(vsi, seed, lut, I40E_HLUT_ARRAY_SIZE);
|
|
if (ret)
|
|
goto out;
|
|
for (i = 0; i < I40E_HLUT_ARRAY_SIZE; i++)
|
|
indir[i] = (u32)(lut[i]);
|
|
|
|
out:
|
|
kfree(lut);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_rxfh - set the rx flow hash indirection table
|
|
* @netdev: network interface device structure
|
|
* @indir: indirection table
|
|
* @key: hash key
|
|
* @hfunc: hash function to use
|
|
*
|
|
* Returns -EINVAL if the table specifies an invalid queue id, otherwise
|
|
* returns 0 after programming the table.
|
|
**/
|
|
static int i40e_set_rxfh(struct net_device *netdev, const u32 *indir,
|
|
const u8 *key, const u8 hfunc)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
u8 *seed = NULL;
|
|
u16 i;
|
|
|
|
if (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (key) {
|
|
if (!vsi->rss_hkey_user) {
|
|
vsi->rss_hkey_user = kzalloc(I40E_HKEY_ARRAY_SIZE,
|
|
GFP_KERNEL);
|
|
if (!vsi->rss_hkey_user)
|
|
return -ENOMEM;
|
|
}
|
|
memcpy(vsi->rss_hkey_user, key, I40E_HKEY_ARRAY_SIZE);
|
|
seed = vsi->rss_hkey_user;
|
|
}
|
|
if (!vsi->rss_lut_user) {
|
|
vsi->rss_lut_user = kzalloc(I40E_HLUT_ARRAY_SIZE, GFP_KERNEL);
|
|
if (!vsi->rss_lut_user)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Each 32 bits pointed by 'indir' is stored with a lut entry */
|
|
if (indir)
|
|
for (i = 0; i < I40E_HLUT_ARRAY_SIZE; i++)
|
|
vsi->rss_lut_user[i] = (u8)(indir[i]);
|
|
else
|
|
i40e_fill_rss_lut(pf, vsi->rss_lut_user, I40E_HLUT_ARRAY_SIZE,
|
|
vsi->rss_size);
|
|
|
|
return i40e_config_rss(vsi, seed, vsi->rss_lut_user,
|
|
I40E_HLUT_ARRAY_SIZE);
|
|
}
|
|
|
|
/**
|
|
* i40e_get_priv_flags - report device private flags
|
|
* @dev: network interface device structure
|
|
*
|
|
* The get string set count and the string set should be matched for each
|
|
* flag returned. Add new strings for each flag to the i40e_gstrings_priv_flags
|
|
* array.
|
|
*
|
|
* Returns a u32 bitmap of flags.
|
|
**/
|
|
static u32 i40e_get_priv_flags(struct net_device *dev)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(dev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
u32 i, j, ret_flags = 0;
|
|
|
|
for (i = 0; i < I40E_PRIV_FLAGS_STR_LEN; i++) {
|
|
const struct i40e_priv_flags *priv_flags;
|
|
|
|
priv_flags = &i40e_gstrings_priv_flags[i];
|
|
|
|
if (priv_flags->flag & pf->flags)
|
|
ret_flags |= BIT(i);
|
|
}
|
|
|
|
if (pf->hw.pf_id != 0)
|
|
return ret_flags;
|
|
|
|
for (j = 0; j < I40E_GL_PRIV_FLAGS_STR_LEN; j++) {
|
|
const struct i40e_priv_flags *priv_flags;
|
|
|
|
priv_flags = &i40e_gl_gstrings_priv_flags[j];
|
|
|
|
if (priv_flags->flag & pf->flags)
|
|
ret_flags |= BIT(i + j);
|
|
}
|
|
|
|
return ret_flags;
|
|
}
|
|
|
|
/**
|
|
* i40e_set_priv_flags - set private flags
|
|
* @dev: network interface device structure
|
|
* @flags: bit flags to be set
|
|
**/
|
|
static int i40e_set_priv_flags(struct net_device *dev, u32 flags)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(dev);
|
|
u64 orig_flags, new_flags, changed_flags;
|
|
enum i40e_admin_queue_err adq_err;
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
bool is_reset_needed;
|
|
i40e_status status;
|
|
u32 i, j;
|
|
|
|
orig_flags = READ_ONCE(pf->flags);
|
|
new_flags = orig_flags;
|
|
|
|
for (i = 0; i < I40E_PRIV_FLAGS_STR_LEN; i++) {
|
|
const struct i40e_priv_flags *priv_flags;
|
|
|
|
priv_flags = &i40e_gstrings_priv_flags[i];
|
|
|
|
if (flags & BIT(i))
|
|
new_flags |= priv_flags->flag;
|
|
else
|
|
new_flags &= ~(priv_flags->flag);
|
|
|
|
/* If this is a read-only flag, it can't be changed */
|
|
if (priv_flags->read_only &&
|
|
((orig_flags ^ new_flags) & ~BIT(i)))
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (pf->hw.pf_id != 0)
|
|
goto flags_complete;
|
|
|
|
for (j = 0; j < I40E_GL_PRIV_FLAGS_STR_LEN; j++) {
|
|
const struct i40e_priv_flags *priv_flags;
|
|
|
|
priv_flags = &i40e_gl_gstrings_priv_flags[j];
|
|
|
|
if (flags & BIT(i + j))
|
|
new_flags |= priv_flags->flag;
|
|
else
|
|
new_flags &= ~(priv_flags->flag);
|
|
|
|
/* If this is a read-only flag, it can't be changed */
|
|
if (priv_flags->read_only &&
|
|
((orig_flags ^ new_flags) & ~BIT(i)))
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
flags_complete:
|
|
changed_flags = orig_flags ^ new_flags;
|
|
|
|
is_reset_needed = !!(changed_flags & (I40E_FLAG_VEB_STATS_ENABLED |
|
|
I40E_FLAG_LEGACY_RX | I40E_FLAG_SOURCE_PRUNING_DISABLED |
|
|
I40E_FLAG_DISABLE_FW_LLDP));
|
|
|
|
/* Before we finalize any flag changes, we need to perform some
|
|
* checks to ensure that the changes are supported and safe.
|
|
*/
|
|
|
|
/* ATR eviction is not supported on all devices */
|
|
if ((new_flags & I40E_FLAG_HW_ATR_EVICT_ENABLED) &&
|
|
!(pf->hw_features & I40E_HW_ATR_EVICT_CAPABLE))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* If the driver detected FW LLDP was disabled on init, this flag could
|
|
* be set, however we do not support _changing_ the flag:
|
|
* - on XL710 if NPAR is enabled or FW API version < 1.7
|
|
* - on X722 with FW API version < 1.6
|
|
* There are situations where older FW versions/NPAR enabled PFs could
|
|
* disable LLDP, however we _must_ not allow the user to enable/disable
|
|
* LLDP with this flag on unsupported FW versions.
|
|
*/
|
|
if (changed_flags & I40E_FLAG_DISABLE_FW_LLDP) {
|
|
if (!(pf->hw.flags & I40E_HW_FLAG_FW_LLDP_STOPPABLE)) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Device does not support changing FW LLDP\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
if (((changed_flags & I40E_FLAG_RS_FEC) ||
|
|
(changed_flags & I40E_FLAG_BASE_R_FEC)) &&
|
|
pf->hw.device_id != I40E_DEV_ID_25G_SFP28 &&
|
|
pf->hw.device_id != I40E_DEV_ID_25G_B) {
|
|
dev_warn(&pf->pdev->dev,
|
|
"Device does not support changing FEC configuration\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Process any additional changes needed as a result of flag changes.
|
|
* The changed_flags value reflects the list of bits that were
|
|
* changed in the code above.
|
|
*/
|
|
|
|
/* Flush current ATR settings if ATR was disabled */
|
|
if ((changed_flags & I40E_FLAG_FD_ATR_ENABLED) &&
|
|
!(new_flags & I40E_FLAG_FD_ATR_ENABLED)) {
|
|
set_bit(__I40E_FD_ATR_AUTO_DISABLED, pf->state);
|
|
set_bit(__I40E_FD_FLUSH_REQUESTED, pf->state);
|
|
}
|
|
|
|
if (changed_flags & I40E_FLAG_TRUE_PROMISC_SUPPORT) {
|
|
u16 sw_flags = 0, valid_flags = 0;
|
|
int ret;
|
|
|
|
if (!(new_flags & I40E_FLAG_TRUE_PROMISC_SUPPORT))
|
|
sw_flags = I40E_AQ_SET_SWITCH_CFG_PROMISC;
|
|
valid_flags = I40E_AQ_SET_SWITCH_CFG_PROMISC;
|
|
ret = i40e_aq_set_switch_config(&pf->hw, sw_flags, valid_flags,
|
|
0, NULL);
|
|
if (ret && pf->hw.aq.asq_last_status != I40E_AQ_RC_ESRCH) {
|
|
dev_info(&pf->pdev->dev,
|
|
"couldn't set switch config bits, err %s aq_err %s\n",
|
|
i40e_stat_str(&pf->hw, ret),
|
|
i40e_aq_str(&pf->hw,
|
|
pf->hw.aq.asq_last_status));
|
|
/* not a fatal problem, just keep going */
|
|
}
|
|
}
|
|
|
|
if ((changed_flags & I40E_FLAG_RS_FEC) ||
|
|
(changed_flags & I40E_FLAG_BASE_R_FEC)) {
|
|
u8 fec_cfg = 0;
|
|
|
|
if (new_flags & I40E_FLAG_RS_FEC &&
|
|
new_flags & I40E_FLAG_BASE_R_FEC) {
|
|
fec_cfg = I40E_AQ_SET_FEC_AUTO;
|
|
} else if (new_flags & I40E_FLAG_RS_FEC) {
|
|
fec_cfg = (I40E_AQ_SET_FEC_REQUEST_RS |
|
|
I40E_AQ_SET_FEC_ABILITY_RS);
|
|
} else if (new_flags & I40E_FLAG_BASE_R_FEC) {
|
|
fec_cfg = (I40E_AQ_SET_FEC_REQUEST_KR |
|
|
I40E_AQ_SET_FEC_ABILITY_KR);
|
|
}
|
|
if (i40e_set_fec_cfg(dev, fec_cfg))
|
|
dev_warn(&pf->pdev->dev, "Cannot change FEC config\n");
|
|
}
|
|
|
|
if ((changed_flags & I40E_FLAG_LINK_DOWN_ON_CLOSE_ENABLED) &&
|
|
(orig_flags & I40E_FLAG_TOTAL_PORT_SHUTDOWN_ENABLED)) {
|
|
dev_err(&pf->pdev->dev,
|
|
"Setting link-down-on-close not supported on this port (because total-port-shutdown is enabled)\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if ((changed_flags & new_flags &
|
|
I40E_FLAG_LINK_DOWN_ON_CLOSE_ENABLED) &&
|
|
(new_flags & I40E_FLAG_MFP_ENABLED))
|
|
dev_warn(&pf->pdev->dev,
|
|
"Turning on link-down-on-close flag may affect other partitions\n");
|
|
|
|
if (changed_flags & I40E_FLAG_DISABLE_FW_LLDP) {
|
|
if (new_flags & I40E_FLAG_DISABLE_FW_LLDP) {
|
|
struct i40e_dcbx_config *dcbcfg;
|
|
|
|
i40e_aq_stop_lldp(&pf->hw, true, false, NULL);
|
|
i40e_aq_set_dcb_parameters(&pf->hw, true, NULL);
|
|
/* reset local_dcbx_config to default */
|
|
dcbcfg = &pf->hw.local_dcbx_config;
|
|
dcbcfg->etscfg.willing = 1;
|
|
dcbcfg->etscfg.maxtcs = 0;
|
|
dcbcfg->etscfg.tcbwtable[0] = 100;
|
|
for (i = 1; i < I40E_MAX_TRAFFIC_CLASS; i++)
|
|
dcbcfg->etscfg.tcbwtable[i] = 0;
|
|
for (i = 0; i < I40E_MAX_USER_PRIORITY; i++)
|
|
dcbcfg->etscfg.prioritytable[i] = 0;
|
|
dcbcfg->etscfg.tsatable[0] = I40E_IEEE_TSA_ETS;
|
|
dcbcfg->pfc.willing = 1;
|
|
dcbcfg->pfc.pfccap = I40E_MAX_TRAFFIC_CLASS;
|
|
} else {
|
|
status = i40e_aq_start_lldp(&pf->hw, false, NULL);
|
|
if (status) {
|
|
adq_err = pf->hw.aq.asq_last_status;
|
|
switch (adq_err) {
|
|
case I40E_AQ_RC_EEXIST:
|
|
dev_warn(&pf->pdev->dev,
|
|
"FW LLDP agent is already running\n");
|
|
is_reset_needed = false;
|
|
break;
|
|
case I40E_AQ_RC_EPERM:
|
|
dev_warn(&pf->pdev->dev,
|
|
"Device configuration forbids SW from starting the LLDP agent.\n");
|
|
return -EINVAL;
|
|
default:
|
|
dev_warn(&pf->pdev->dev,
|
|
"Starting FW LLDP agent failed: error: %s, %s\n",
|
|
i40e_stat_str(&pf->hw,
|
|
status),
|
|
i40e_aq_str(&pf->hw,
|
|
adq_err));
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now that we've checked to ensure that the new flags are valid, load
|
|
* them into place. Since we only modify flags either (a) during
|
|
* initialization or (b) while holding the RTNL lock, we don't need
|
|
* anything fancy here.
|
|
*/
|
|
pf->flags = new_flags;
|
|
|
|
/* Issue reset to cause things to take effect, as additional bits
|
|
* are added we will need to create a mask of bits requiring reset
|
|
*/
|
|
if (is_reset_needed)
|
|
i40e_do_reset(pf, BIT(__I40E_PF_RESET_REQUESTED), true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_module_info - get (Q)SFP+ module type info
|
|
* @netdev: network interface device structure
|
|
* @modinfo: module EEPROM size and layout information structure
|
|
**/
|
|
static int i40e_get_module_info(struct net_device *netdev,
|
|
struct ethtool_modinfo *modinfo)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
u32 sff8472_comp = 0;
|
|
u32 sff8472_swap = 0;
|
|
u32 sff8636_rev = 0;
|
|
i40e_status status;
|
|
u32 type = 0;
|
|
|
|
/* Check if firmware supports reading module EEPROM. */
|
|
if (!(hw->flags & I40E_HW_FLAG_AQ_PHY_ACCESS_CAPABLE)) {
|
|
netdev_err(vsi->netdev, "Module EEPROM memory read not supported. Please update the NVM image.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
status = i40e_update_link_info(hw);
|
|
if (status)
|
|
return -EIO;
|
|
|
|
if (hw->phy.link_info.phy_type == I40E_PHY_TYPE_EMPTY) {
|
|
netdev_err(vsi->netdev, "Cannot read module EEPROM memory. No module connected.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
type = hw->phy.link_info.module_type[0];
|
|
|
|
switch (type) {
|
|
case I40E_MODULE_TYPE_SFP:
|
|
status = i40e_aq_get_phy_register(hw,
|
|
I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE,
|
|
I40E_I2C_EEPROM_DEV_ADDR, true,
|
|
I40E_MODULE_SFF_8472_COMP,
|
|
&sff8472_comp, NULL);
|
|
if (status)
|
|
return -EIO;
|
|
|
|
status = i40e_aq_get_phy_register(hw,
|
|
I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE,
|
|
I40E_I2C_EEPROM_DEV_ADDR, true,
|
|
I40E_MODULE_SFF_8472_SWAP,
|
|
&sff8472_swap, NULL);
|
|
if (status)
|
|
return -EIO;
|
|
|
|
/* Check if the module requires address swap to access
|
|
* the other EEPROM memory page.
|
|
*/
|
|
if (sff8472_swap & I40E_MODULE_SFF_ADDR_MODE) {
|
|
netdev_warn(vsi->netdev, "Module address swap to access page 0xA2 is not supported.\n");
|
|
modinfo->type = ETH_MODULE_SFF_8079;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
|
|
} else if (sff8472_comp == 0x00) {
|
|
/* Module is not SFF-8472 compliant */
|
|
modinfo->type = ETH_MODULE_SFF_8079;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
|
|
} else if (!(sff8472_swap & I40E_MODULE_SFF_DDM_IMPLEMENTED)) {
|
|
/* Module is SFF-8472 compliant but doesn't implement
|
|
* Digital Diagnostic Monitoring (DDM).
|
|
*/
|
|
modinfo->type = ETH_MODULE_SFF_8079;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
|
|
} else {
|
|
modinfo->type = ETH_MODULE_SFF_8472;
|
|
modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
|
|
}
|
|
break;
|
|
case I40E_MODULE_TYPE_QSFP_PLUS:
|
|
/* Read from memory page 0. */
|
|
status = i40e_aq_get_phy_register(hw,
|
|
I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE,
|
|
0, true,
|
|
I40E_MODULE_REVISION_ADDR,
|
|
&sff8636_rev, NULL);
|
|
if (status)
|
|
return -EIO;
|
|
/* Determine revision compliance byte */
|
|
if (sff8636_rev > 0x02) {
|
|
/* Module is SFF-8636 compliant */
|
|
modinfo->type = ETH_MODULE_SFF_8636;
|
|
modinfo->eeprom_len = I40E_MODULE_QSFP_MAX_LEN;
|
|
} else {
|
|
modinfo->type = ETH_MODULE_SFF_8436;
|
|
modinfo->eeprom_len = I40E_MODULE_QSFP_MAX_LEN;
|
|
}
|
|
break;
|
|
case I40E_MODULE_TYPE_QSFP28:
|
|
modinfo->type = ETH_MODULE_SFF_8636;
|
|
modinfo->eeprom_len = I40E_MODULE_QSFP_MAX_LEN;
|
|
break;
|
|
default:
|
|
netdev_err(vsi->netdev, "Module type unrecognized\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* i40e_get_module_eeprom - fills buffer with (Q)SFP+ module memory contents
|
|
* @netdev: network interface device structure
|
|
* @ee: EEPROM dump request structure
|
|
* @data: buffer to be filled with EEPROM contents
|
|
**/
|
|
static int i40e_get_module_eeprom(struct net_device *netdev,
|
|
struct ethtool_eeprom *ee,
|
|
u8 *data)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_vsi *vsi = np->vsi;
|
|
struct i40e_pf *pf = vsi->back;
|
|
struct i40e_hw *hw = &pf->hw;
|
|
bool is_sfp = false;
|
|
i40e_status status;
|
|
u32 value = 0;
|
|
int i;
|
|
|
|
if (!ee || !ee->len || !data)
|
|
return -EINVAL;
|
|
|
|
if (hw->phy.link_info.module_type[0] == I40E_MODULE_TYPE_SFP)
|
|
is_sfp = true;
|
|
|
|
for (i = 0; i < ee->len; i++) {
|
|
u32 offset = i + ee->offset;
|
|
u32 addr = is_sfp ? I40E_I2C_EEPROM_DEV_ADDR : 0;
|
|
|
|
/* Check if we need to access the other memory page */
|
|
if (is_sfp) {
|
|
if (offset >= ETH_MODULE_SFF_8079_LEN) {
|
|
offset -= ETH_MODULE_SFF_8079_LEN;
|
|
addr = I40E_I2C_EEPROM_DEV_ADDR2;
|
|
}
|
|
} else {
|
|
while (offset >= ETH_MODULE_SFF_8436_LEN) {
|
|
/* Compute memory page number and offset. */
|
|
offset -= ETH_MODULE_SFF_8436_LEN / 2;
|
|
addr++;
|
|
}
|
|
}
|
|
|
|
status = i40e_aq_get_phy_register(hw,
|
|
I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE,
|
|
true, addr, offset, &value, NULL);
|
|
if (status)
|
|
return -EIO;
|
|
data[i] = value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int i40e_get_eee(struct net_device *netdev, struct ethtool_eee *edata)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int i40e_set_eee(struct net_device *netdev, struct ethtool_eee *edata)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static const struct ethtool_ops i40e_ethtool_recovery_mode_ops = {
|
|
.get_drvinfo = i40e_get_drvinfo,
|
|
.set_eeprom = i40e_set_eeprom,
|
|
.get_eeprom_len = i40e_get_eeprom_len,
|
|
.get_eeprom = i40e_get_eeprom,
|
|
};
|
|
|
|
static const struct ethtool_ops i40e_ethtool_ops = {
|
|
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
|
|
ETHTOOL_COALESCE_MAX_FRAMES_IRQ |
|
|
ETHTOOL_COALESCE_USE_ADAPTIVE |
|
|
ETHTOOL_COALESCE_RX_USECS_HIGH |
|
|
ETHTOOL_COALESCE_TX_USECS_HIGH,
|
|
.get_drvinfo = i40e_get_drvinfo,
|
|
.get_regs_len = i40e_get_regs_len,
|
|
.get_regs = i40e_get_regs,
|
|
.nway_reset = i40e_nway_reset,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_wol = i40e_get_wol,
|
|
.set_wol = i40e_set_wol,
|
|
.set_eeprom = i40e_set_eeprom,
|
|
.get_eeprom_len = i40e_get_eeprom_len,
|
|
.get_eeprom = i40e_get_eeprom,
|
|
.get_ringparam = i40e_get_ringparam,
|
|
.set_ringparam = i40e_set_ringparam,
|
|
.get_pauseparam = i40e_get_pauseparam,
|
|
.set_pauseparam = i40e_set_pauseparam,
|
|
.get_msglevel = i40e_get_msglevel,
|
|
.set_msglevel = i40e_set_msglevel,
|
|
.get_rxnfc = i40e_get_rxnfc,
|
|
.set_rxnfc = i40e_set_rxnfc,
|
|
.self_test = i40e_diag_test,
|
|
.get_strings = i40e_get_strings,
|
|
.get_eee = i40e_get_eee,
|
|
.set_eee = i40e_set_eee,
|
|
.set_phys_id = i40e_set_phys_id,
|
|
.get_sset_count = i40e_get_sset_count,
|
|
.get_ethtool_stats = i40e_get_ethtool_stats,
|
|
.get_coalesce = i40e_get_coalesce,
|
|
.set_coalesce = i40e_set_coalesce,
|
|
.get_rxfh_key_size = i40e_get_rxfh_key_size,
|
|
.get_rxfh_indir_size = i40e_get_rxfh_indir_size,
|
|
.get_rxfh = i40e_get_rxfh,
|
|
.set_rxfh = i40e_set_rxfh,
|
|
.get_channels = i40e_get_channels,
|
|
.set_channels = i40e_set_channels,
|
|
.get_module_info = i40e_get_module_info,
|
|
.get_module_eeprom = i40e_get_module_eeprom,
|
|
.get_ts_info = i40e_get_ts_info,
|
|
.get_priv_flags = i40e_get_priv_flags,
|
|
.set_priv_flags = i40e_set_priv_flags,
|
|
.get_per_queue_coalesce = i40e_get_per_queue_coalesce,
|
|
.set_per_queue_coalesce = i40e_set_per_queue_coalesce,
|
|
.get_link_ksettings = i40e_get_link_ksettings,
|
|
.set_link_ksettings = i40e_set_link_ksettings,
|
|
.get_fecparam = i40e_get_fec_param,
|
|
.set_fecparam = i40e_set_fec_param,
|
|
.flash_device = i40e_ddp_flash,
|
|
};
|
|
|
|
void i40e_set_ethtool_ops(struct net_device *netdev)
|
|
{
|
|
struct i40e_netdev_priv *np = netdev_priv(netdev);
|
|
struct i40e_pf *pf = np->vsi->back;
|
|
|
|
if (!test_bit(__I40E_RECOVERY_MODE, pf->state))
|
|
netdev->ethtool_ops = &i40e_ethtool_ops;
|
|
else
|
|
netdev->ethtool_ops = &i40e_ethtool_recovery_mode_ops;
|
|
}
|