Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,101 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Wangxun network device configuration
#
config NET_VENDOR_WANGXUN
bool "Wangxun devices"
default y
help
If you have a network (Ethernet) card from Wangxun(R), say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all
the questions about Wangxun(R) cards. If you say Y, you will
be asked for your specific card in the following questions.
if NET_VENDOR_WANGXUN
config LIBWX
tristate
depends on PTP_1588_CLOCK_OPTIONAL
select PAGE_POOL
select DIMLIB
select PHYLINK
help
Common library for Wangxun(R) Ethernet drivers.
config NGBE
tristate "Wangxun(R) GbE PCI Express adapters support"
depends on PCI
depends on PTP_1588_CLOCK_OPTIONAL
select LIBWX
help
This driver supports Wangxun(R) GbE PCI Express family of
adapters.
More specific information on configuring the driver is in
<file:Documentation/networking/device_drivers/ethernet/wangxun/ngbe.rst>.
To compile this driver as a module, choose M here. The module
will be called ngbe.
config TXGBE
tristate "Wangxun(R) 10/25/40GbE PCI Express adapters support"
depends on PCI
depends on COMMON_CLK
depends on I2C_DESIGNWARE_PLATFORM
depends on PTP_1588_CLOCK_OPTIONAL
select MARVELL_10G_PHY
select REGMAP
select HWMON if TXGBE=y
select SFP
select GPIOLIB
select GPIOLIB_IRQCHIP
select PCS_XPCS
select LIBWX
help
This driver supports Wangxun(R) 10/25/40GbE PCI Express family of
adapters.
More specific information on configuring the driver is in
<file:Documentation/networking/device_drivers/ethernet/wangxun/txgbe.rst>.
To compile this driver as a module, choose M here. The module
will be called txgbe.
config TXGBEVF
tristate "Wangxun(R) 10/25/40G Virtual Function Ethernet support"
depends on PCI
depends on PCI_MSI
depends on PTP_1588_CLOCK_OPTIONAL
select LIBWX
help
This driver supports virtual functions for SP1000A, WX1820AL,
WX5XXX, WX5XXXAL.
This driver was formerly named txgbevf.
More specific information on configuring the driver is in
<file:Documentation/networking/device_drivers/ethernet/wangxun/txgbevf.rst>.
To compile this driver as a module, choose M here. MSI-X interrupt
support is required for this driver to work correctly.
config NGBEVF
tristate "Wangxun(R) GbE Virtual Function Ethernet support"
depends on PCI_MSI
depends on PTP_1588_CLOCK_OPTIONAL
select LIBWX
help
This driver supports virtual functions for WX1860, WX1860AL.
This driver was formerly named ngbevf.
More specific information on configuring the driver is in
<file:Documentation/networking/device_drivers/ethernet/wangxun/ngbevf.rst>.
To compile this driver as a module, choose M here. MSI-X interrupt
support is required for this driver to work correctly.
endif # NET_VENDOR_WANGXUN
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Wangxun network device drivers.
#
obj-$(CONFIG_LIBWX) += libwx/
obj-$(CONFIG_TXGBE) += txgbe/
obj-$(CONFIG_TXGBEVF) += txgbevf/
obj-$(CONFIG_NGBE) += ngbe/
obj-$(CONFIG_NGBEVF) += ngbevf/
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd.
#
obj-$(CONFIG_LIBWX) += libwx.o
libwx-objs := wx_hw.o wx_lib.o wx_ethtool.o wx_ptp.o wx_mbx.o wx_sriov.o
libwx-objs += wx_vf.o wx_vf_lib.o wx_vf_common.o
@@ -0,0 +1,795 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/pci.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include "wx_type.h"
#include "wx_ethtool.h"
#include "wx_hw.h"
#include "wx_lib.h"
struct wx_stats {
char stat_string[ETH_GSTRING_LEN];
size_t sizeof_stat;
off_t stat_offset;
};
#define WX_STAT(str, m) { \
.stat_string = str, \
.sizeof_stat = sizeof(((struct wx *)0)->m), \
.stat_offset = offsetof(struct wx, m) }
static const struct wx_stats wx_gstrings_stats[] = {
WX_STAT("rx_dma_pkts", stats.gprc),
WX_STAT("tx_dma_pkts", stats.gptc),
WX_STAT("rx_dma_bytes", stats.gorc),
WX_STAT("tx_dma_bytes", stats.gotc),
WX_STAT("rx_total_pkts", stats.tpr),
WX_STAT("tx_total_pkts", stats.tpt),
WX_STAT("rx_long_length_count", stats.roc),
WX_STAT("rx_short_length_count", stats.ruc),
WX_STAT("os2bmc_rx_by_bmc", stats.o2bgptc),
WX_STAT("os2bmc_tx_by_bmc", stats.b2ospc),
WX_STAT("os2bmc_tx_by_host", stats.o2bspc),
WX_STAT("os2bmc_rx_by_host", stats.b2ogprc),
WX_STAT("rx_no_dma_resources", stats.rdmdrop),
WX_STAT("tx_busy", tx_busy),
WX_STAT("non_eop_descs", non_eop_descs),
WX_STAT("tx_restart_queue", restart_queue),
WX_STAT("rx_csum_offload_good_count", hw_csum_rx_good),
WX_STAT("rx_csum_offload_errors", hw_csum_rx_error),
WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed),
WX_STAT("tx_hwtstamp_timeouts", tx_hwtstamp_timeouts),
WX_STAT("tx_hwtstamp_skipped", tx_hwtstamp_skipped),
WX_STAT("rx_hwtstamp_cleared", rx_hwtstamp_cleared),
};
static const struct wx_stats wx_gstrings_fdir_stats[] = {
WX_STAT("fdir_match", stats.fdirmatch),
WX_STAT("fdir_miss", stats.fdirmiss),
};
static const struct wx_stats wx_gstrings_rsc_stats[] = {
WX_STAT("rsc_aggregated", rsc_count),
WX_STAT("rsc_flushed", rsc_flush),
};
/* drivers allocates num_tx_queues and num_rx_queues symmetrically so
* we set the num_rx_queues to evaluate to num_tx_queues. This is
* used because we do not have a good way to get the max number of
* rx queues with CONFIG_RPS disabled.
*/
#define WX_NUM_RX_QUEUES netdev->num_tx_queues
#define WX_NUM_TX_QUEUES netdev->num_tx_queues
#define WX_QUEUE_STATS_LEN ( \
(WX_NUM_TX_QUEUES + WX_NUM_RX_QUEUES) * \
(sizeof(struct wx_queue_stats) / sizeof(u64)))
#define WX_GLOBAL_STATS_LEN ARRAY_SIZE(wx_gstrings_stats)
#define WX_FDIR_STATS_LEN ARRAY_SIZE(wx_gstrings_fdir_stats)
#define WX_RSC_STATS_LEN ARRAY_SIZE(wx_gstrings_rsc_stats)
#define WX_STATS_LEN (WX_GLOBAL_STATS_LEN + WX_QUEUE_STATS_LEN)
int wx_get_sset_count(struct net_device *netdev, int sset)
{
struct wx *wx = netdev_priv(netdev);
int len = WX_STATS_LEN;
switch (sset) {
case ETH_SS_STATS:
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
len += WX_FDIR_STATS_LEN;
if (test_bit(WX_FLAG_RSC_CAPABLE, wx->flags))
len += WX_RSC_STATS_LEN;
return len;
default:
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL(wx_get_sset_count);
void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
{
struct wx *wx = netdev_priv(netdev);
u8 *p = data;
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < WX_GLOBAL_STATS_LEN; i++)
ethtool_puts(&p, wx_gstrings_stats[i].stat_string);
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)) {
for (i = 0; i < WX_FDIR_STATS_LEN; i++)
ethtool_puts(&p, wx_gstrings_fdir_stats[i].stat_string);
}
if (test_bit(WX_FLAG_RSC_CAPABLE, wx->flags)) {
for (i = 0; i < WX_RSC_STATS_LEN; i++)
ethtool_puts(&p, wx_gstrings_rsc_stats[i].stat_string);
}
for (i = 0; i < netdev->num_tx_queues; i++) {
ethtool_sprintf(&p, "tx_queue_%u_packets", i);
ethtool_sprintf(&p, "tx_queue_%u_bytes", i);
}
for (i = 0; i < WX_NUM_RX_QUEUES; i++) {
ethtool_sprintf(&p, "rx_queue_%u_packets", i);
ethtool_sprintf(&p, "rx_queue_%u_bytes", i);
}
break;
}
}
EXPORT_SYMBOL(wx_get_strings);
void wx_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats, u64 *data)
{
struct wx *wx = netdev_priv(netdev);
struct wx_ring *ring;
unsigned int start;
int i, j, k;
char *p;
wx_update_stats(wx);
for (i = 0; i < WX_GLOBAL_STATS_LEN; i++) {
p = (char *)wx + wx_gstrings_stats[i].stat_offset;
data[i] = (wx_gstrings_stats[i].sizeof_stat ==
sizeof(u64)) ? *(u64 *)p : *(u32 *)p;
}
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)) {
for (k = 0; k < WX_FDIR_STATS_LEN; k++) {
p = (char *)wx + wx_gstrings_fdir_stats[k].stat_offset;
data[i++] = *(u64 *)p;
}
}
if (test_bit(WX_FLAG_RSC_CAPABLE, wx->flags)) {
for (k = 0; k < WX_RSC_STATS_LEN; k++) {
p = (char *)wx + wx_gstrings_rsc_stats[k].stat_offset;
data[i++] = *(u64 *)p;
}
}
for (j = 0; j < netdev->num_tx_queues; j++) {
ring = wx->tx_ring[j];
if (!ring) {
data[i++] = 0;
data[i++] = 0;
continue;
}
do {
start = u64_stats_fetch_begin(&ring->syncp);
data[i] = ring->stats.packets;
data[i + 1] = ring->stats.bytes;
} while (u64_stats_fetch_retry(&ring->syncp, start));
i += 2;
}
for (j = 0; j < WX_NUM_RX_QUEUES; j++) {
ring = wx->rx_ring[j];
if (!ring) {
data[i++] = 0;
data[i++] = 0;
continue;
}
do {
start = u64_stats_fetch_begin(&ring->syncp);
data[i] = ring->stats.packets;
data[i + 1] = ring->stats.bytes;
} while (u64_stats_fetch_retry(&ring->syncp, start));
i += 2;
}
}
EXPORT_SYMBOL(wx_get_ethtool_stats);
void wx_get_mac_stats(struct net_device *netdev,
struct ethtool_eth_mac_stats *mac_stats)
{
struct wx *wx = netdev_priv(netdev);
struct wx_hw_stats *hwstats;
wx_update_stats(wx);
hwstats = &wx->stats;
mac_stats->MulticastFramesXmittedOK = hwstats->mptc;
mac_stats->BroadcastFramesXmittedOK = hwstats->bptc;
mac_stats->MulticastFramesReceivedOK = hwstats->mprc;
mac_stats->BroadcastFramesReceivedOK = hwstats->bprc;
}
EXPORT_SYMBOL(wx_get_mac_stats);
void wx_get_pause_stats(struct net_device *netdev,
struct ethtool_pause_stats *stats)
{
struct wx *wx = netdev_priv(netdev);
struct wx_hw_stats *hwstats;
wx_update_stats(wx);
hwstats = &wx->stats;
stats->tx_pause_frames = hwstats->lxontxc + hwstats->lxofftxc;
stats->rx_pause_frames = hwstats->lxonrxc + hwstats->lxoffrxc;
}
EXPORT_SYMBOL(wx_get_pause_stats);
void wx_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info)
{
unsigned int stats_len = WX_STATS_LEN;
struct wx *wx = netdev_priv(netdev);
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
stats_len += WX_FDIR_STATS_LEN;
strscpy(info->driver, wx->driver_name, sizeof(info->driver));
strscpy(info->fw_version, wx->eeprom_id, sizeof(info->fw_version));
strscpy(info->bus_info, pci_name(wx->pdev), sizeof(info->bus_info));
if (wx->num_tx_queues <= WX_NUM_TX_QUEUES) {
info->n_stats = stats_len -
(WX_NUM_TX_QUEUES - wx->num_tx_queues) *
(sizeof(struct wx_queue_stats) / sizeof(u64)) * 2;
} else {
info->n_stats = stats_len;
}
}
EXPORT_SYMBOL(wx_get_drvinfo);
int wx_nway_reset(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
return phylink_ethtool_nway_reset(wx->phylink);
}
EXPORT_SYMBOL(wx_nway_reset);
int wx_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct wx *wx = netdev_priv(netdev);
return phylink_ethtool_ksettings_get(wx->phylink, cmd);
}
EXPORT_SYMBOL(wx_get_link_ksettings);
int wx_set_link_ksettings(struct net_device *netdev,
const struct ethtool_link_ksettings *cmd)
{
struct wx *wx = netdev_priv(netdev);
return phylink_ethtool_ksettings_set(wx->phylink, cmd);
}
EXPORT_SYMBOL(wx_set_link_ksettings);
void wx_get_wol(struct net_device *netdev,
struct ethtool_wolinfo *wol)
{
struct wx *wx = netdev_priv(netdev);
if (!wx->wol_hw_supported)
return;
wol->supported = WAKE_MAGIC;
wol->wolopts = 0;
if (wx->wol & WX_PSR_WKUP_CTL_MAG)
wol->wolopts |= WAKE_MAGIC;
}
EXPORT_SYMBOL(wx_get_wol);
int wx_set_wol(struct net_device *netdev,
struct ethtool_wolinfo *wol)
{
struct wx *wx = netdev_priv(netdev);
struct pci_dev *pdev = wx->pdev;
if (!wx->wol_hw_supported)
return -EOPNOTSUPP;
wx->wol = 0;
if (wol->wolopts & WAKE_MAGIC)
wx->wol = WX_PSR_WKUP_CTL_MAG;
wr32(wx, WX_PSR_WKUP_CTL, wx->wol);
device_set_wakeup_enable(&pdev->dev, !!(wx->wol));
return 0;
}
EXPORT_SYMBOL(wx_set_wol);
void wx_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct wx *wx = netdev_priv(netdev);
phylink_ethtool_get_pauseparam(wx->phylink, pause);
}
EXPORT_SYMBOL(wx_get_pauseparam);
int wx_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct wx *wx = netdev_priv(netdev);
return phylink_ethtool_set_pauseparam(wx->phylink, pause);
}
EXPORT_SYMBOL(wx_set_pauseparam);
void wx_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
ring->rx_max_pending = WX_MAX_RXD;
ring->tx_max_pending = WX_MAX_TXD;
ring->rx_mini_max_pending = 0;
ring->rx_jumbo_max_pending = 0;
ring->rx_pending = wx->rx_ring_count;
ring->tx_pending = wx->tx_ring_count;
ring->rx_mini_pending = 0;
ring->rx_jumbo_pending = 0;
}
EXPORT_SYMBOL(wx_get_ringparam);
int wx_get_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
ec->tx_max_coalesced_frames_irq = wx->tx_work_limit;
/* only valid if in constant ITR mode */
if (wx->rx_itr_setting <= 1)
ec->rx_coalesce_usecs = wx->rx_itr_setting;
else
ec->rx_coalesce_usecs = wx->rx_itr_setting >> 2;
if (wx->adaptive_itr) {
ec->use_adaptive_rx_coalesce = 1;
ec->use_adaptive_tx_coalesce = 1;
}
/* if in mixed tx/rx queues per vector mode, report only rx settings */
if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count)
return 0;
/* only valid if in constant ITR mode */
if (wx->tx_itr_setting <= 1)
ec->tx_coalesce_usecs = wx->tx_itr_setting;
else
ec->tx_coalesce_usecs = wx->tx_itr_setting >> 2;
return 0;
}
EXPORT_SYMBOL(wx_get_coalesce);
static void wx_update_rsc(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
bool need_reset = false;
/* nothing to do if LRO or RSC are not enabled */
if (!test_bit(WX_FLAG_RSC_CAPABLE, wx->flags) ||
!(netdev->features & NETIF_F_LRO))
return;
/* check the feature flag value and enable RSC if necessary */
if (wx->rx_itr_setting == 1 ||
wx->rx_itr_setting > WX_MIN_RSC_ITR) {
if (!test_bit(WX_FLAG_RSC_ENABLED, wx->flags)) {
set_bit(WX_FLAG_RSC_ENABLED, wx->flags);
dev_info(&wx->pdev->dev,
"rx-usecs value high enough to re-enable RSC\n");
need_reset = true;
}
/* if interrupt rate is too high then disable RSC */
} else if (test_bit(WX_FLAG_RSC_ENABLED, wx->flags)) {
clear_bit(WX_FLAG_RSC_ENABLED, wx->flags);
dev_info(&wx->pdev->dev,
"rx-usecs set too low, disabling RSC\n");
need_reset = true;
}
/* reset the device to apply the new RSC setting */
if (need_reset && wx->do_reset)
wx->do_reset(netdev);
}
int wx_set_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
u16 tx_itr_param, rx_itr_param;
struct wx_q_vector *q_vector;
u16 max_eitr;
int i;
if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count) {
/* reject Tx specific changes in case of mixed RxTx vectors */
if (ec->tx_coalesce_usecs)
return -EOPNOTSUPP;
}
if (ec->tx_max_coalesced_frames_irq > U16_MAX ||
!ec->tx_max_coalesced_frames_irq)
return -EINVAL;
wx->tx_work_limit = ec->tx_max_coalesced_frames_irq;
switch (wx->mac.type) {
case wx_mac_sp:
max_eitr = WX_SP_MAX_EITR;
rx_itr_param = WX_20K_ITR;
tx_itr_param = WX_12K_ITR;
break;
case wx_mac_aml:
case wx_mac_aml40:
max_eitr = WX_AML_MAX_EITR;
rx_itr_param = WX_20K_ITR;
tx_itr_param = WX_12K_ITR;
break;
default:
max_eitr = WX_EM_MAX_EITR;
rx_itr_param = WX_7K_ITR;
tx_itr_param = WX_7K_ITR;
break;
}
if ((ec->rx_coalesce_usecs > (max_eitr >> 2)) ||
(ec->tx_coalesce_usecs > (max_eitr >> 2)))
return -EINVAL;
if (ec->use_adaptive_rx_coalesce) {
wx->adaptive_itr = true;
wx->rx_itr_setting = 1;
wx->tx_itr_setting = 1;
return 0;
}
if (ec->rx_coalesce_usecs > 1)
wx->rx_itr_setting = ec->rx_coalesce_usecs << 2;
else
wx->rx_itr_setting = ec->rx_coalesce_usecs;
if (ec->tx_coalesce_usecs > 1)
wx->tx_itr_setting = ec->tx_coalesce_usecs << 2;
else
wx->tx_itr_setting = ec->tx_coalesce_usecs;
if (wx->adaptive_itr) {
wx->adaptive_itr = false;
wx->rx_itr_setting = rx_itr_param;
wx->tx_itr_setting = tx_itr_param;
} else if (wx->rx_itr_setting == 1 || wx->tx_itr_setting == 1) {
wx->adaptive_itr = true;
}
if (wx->rx_itr_setting != 1)
rx_itr_param = wx->rx_itr_setting;
if (wx->tx_itr_setting != 1)
tx_itr_param = wx->tx_itr_setting;
/* mixed Rx/Tx */
if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count)
wx->tx_itr_setting = wx->rx_itr_setting;
for (i = 0; i < wx->num_q_vectors; i++) {
q_vector = wx->q_vector[i];
if (q_vector->tx.count && !q_vector->rx.count)
/* tx only */
q_vector->itr = tx_itr_param;
else
/* rx only or mixed */
q_vector->itr = rx_itr_param;
wx_write_eitr(q_vector);
}
wx_update_rsc(wx);
return 0;
}
EXPORT_SYMBOL(wx_set_coalesce);
static unsigned int wx_max_channels(struct wx *wx)
{
unsigned int max_combined;
if (!wx->msix_q_entries) {
/* We only support one q_vector without MSI-X */
max_combined = 1;
} else {
/* support up to max allowed queues with RSS */
if (test_bit(WX_FLAG_MULTI_64_FUNC, wx->flags))
max_combined = 63;
else
max_combined = 8;
}
return max_combined;
}
void wx_get_channels(struct net_device *dev,
struct ethtool_channels *ch)
{
struct wx *wx = netdev_priv(dev);
/* report maximum channels */
ch->max_combined = wx_max_channels(wx);
/* report info for other vector */
if (wx->msix_q_entries) {
ch->max_other = 1;
ch->other_count = 1;
}
/* record RSS queues */
ch->combined_count = wx->ring_feature[RING_F_RSS].indices;
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
ch->combined_count = wx->ring_feature[RING_F_FDIR].indices;
}
EXPORT_SYMBOL(wx_get_channels);
int wx_set_channels(struct net_device *dev,
struct ethtool_channels *ch)
{
unsigned int count = ch->combined_count;
struct wx *wx = netdev_priv(dev);
/* verify other_count has not changed */
if (ch->other_count != 1)
return -EINVAL;
/* verify the number of channels does not exceed hardware limits */
if (count > wx_max_channels(wx))
return -EINVAL;
if (test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags))
wx->ring_feature[RING_F_FDIR].limit = count;
wx->ring_feature[RING_F_RSS].limit = count;
return wx->setup_tc(dev, netdev_get_num_tc(dev));
}
EXPORT_SYMBOL(wx_set_channels);
u32 wx_rss_indir_size(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
return wx_rss_indir_tbl_entries(wx);
}
EXPORT_SYMBOL(wx_rss_indir_size);
u32 wx_get_rxfh_key_size(struct net_device *netdev)
{
return WX_RSS_KEY_SIZE;
}
EXPORT_SYMBOL(wx_get_rxfh_key_size);
static void wx_get_reta(struct wx *wx, u32 *indir)
{
u32 reta_size = wx_rss_indir_tbl_entries(wx);
u16 rss_m = wx->ring_feature[RING_F_RSS].mask;
if (test_bit(WX_FLAG_SRIOV_ENABLED, wx->flags))
rss_m = wx->ring_feature[RING_F_RSS].indices - 1;
for (u32 i = 0; i < reta_size; i++)
indir[i] = wx->rss_indir_tbl[i] & rss_m;
}
int wx_get_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh)
{
struct wx *wx = netdev_priv(netdev);
rxfh->hfunc = ETH_RSS_HASH_TOP;
if (rxfh->indir)
wx_get_reta(wx, rxfh->indir);
if (rxfh->key)
memcpy(rxfh->key, wx->rss_key, WX_RSS_KEY_SIZE);
return 0;
}
EXPORT_SYMBOL(wx_get_rxfh);
int wx_set_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
u32 reta_entries, i;
if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
rxfh->hfunc != ETH_RSS_HASH_TOP)
return -EOPNOTSUPP;
reta_entries = wx_rss_indir_tbl_entries(wx);
/* Fill out the redirection table */
if (rxfh->indir) {
for (i = 0; i < reta_entries; i++)
wx->rss_indir_tbl[i] = rxfh->indir[i];
wx_store_reta(wx);
}
/* Fill out the rss hash key */
if (rxfh->key) {
memcpy(wx->rss_key, rxfh->key, WX_RSS_KEY_SIZE);
wx_store_rsskey(wx);
}
return 0;
}
EXPORT_SYMBOL(wx_set_rxfh);
static const struct wx_rss_flow_map rss_flow_table[] = {
{ TCP_V4_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV4_TCP },
{ TCP_V6_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV6_TCP },
{ UDP_V4_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV4_UDP },
{ UDP_V6_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV6_UDP },
{ SCTP_V4_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV4_SCTP },
{ SCTP_V6_FLOW, RXH_L4_B_0_1 | RXH_L4_B_2_3, WX_RSS_FIELD_IPV6_SCTP },
};
int wx_get_rxfh_fields(struct net_device *dev,
struct ethtool_rxfh_fields *nfc)
{
struct wx *wx = netdev_priv(dev);
nfc->data = RXH_IP_SRC | RXH_IP_DST;
for (u32 i = 0; i < ARRAY_SIZE(rss_flow_table); i++) {
const struct wx_rss_flow_map *entry = &rss_flow_table[i];
if (entry->flow_type == nfc->flow_type) {
if (wx->rss_flags & entry->flag)
nfc->data |= entry->data;
break;
}
}
return 0;
}
EXPORT_SYMBOL(wx_get_rxfh_fields);
int wx_set_rxfh_fields(struct net_device *dev,
const struct ethtool_rxfh_fields *nfc,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(dev);
u8 flags = wx->rss_flags;
if (!(nfc->data & RXH_IP_SRC) ||
!(nfc->data & RXH_IP_DST))
return -EINVAL;
for (u32 i = 0; i < ARRAY_SIZE(rss_flow_table); i++) {
const struct wx_rss_flow_map *entry = &rss_flow_table[i];
if (entry->flow_type == nfc->flow_type) {
if (nfc->data & entry->data)
flags |= entry->flag;
else
flags &= ~entry->flag;
if (flags != wx->rss_flags) {
wx->rss_flags = flags;
wx_config_rss_field(wx);
}
return 0;
}
}
return -EINVAL;
}
EXPORT_SYMBOL(wx_set_rxfh_fields);
u32 wx_get_msglevel(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
return wx->msg_enable;
}
EXPORT_SYMBOL(wx_get_msglevel);
void wx_set_msglevel(struct net_device *netdev, u32 data)
{
struct wx *wx = netdev_priv(netdev);
wx->msg_enable = data;
}
EXPORT_SYMBOL(wx_set_msglevel);
int wx_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info)
{
struct wx *wx = netdev_priv(dev);
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
BIT(HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
BIT(HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
BIT(HWTSTAMP_FILTER_PTP_V2_SYNC) |
BIT(HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
BIT(HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
BIT(HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
BIT(HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
BIT(HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
if (wx->ptp_clock)
info->phc_index = ptp_clock_index(wx->ptp_clock);
else
info->phc_index = -1;
info->tx_types = BIT(HWTSTAMP_TX_OFF) |
BIT(HWTSTAMP_TX_ON);
return 0;
}
EXPORT_SYMBOL(wx_get_ts_info);
void wx_get_ptp_stats(struct net_device *dev,
struct ethtool_ts_stats *ts_stats)
{
struct wx *wx = netdev_priv(dev);
if (wx->ptp_clock) {
ts_stats->pkts = wx->tx_hwtstamp_pkts;
ts_stats->lost = wx->tx_hwtstamp_timeouts +
wx->tx_hwtstamp_skipped +
wx->rx_hwtstamp_cleared;
ts_stats->err = wx->tx_hwtstamp_errors;
}
}
EXPORT_SYMBOL(wx_get_ptp_stats);
static int wx_get_link_ksettings_vf(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct wx *wx = netdev_priv(netdev);
ethtool_link_ksettings_zero_link_mode(cmd, supported);
cmd->base.autoneg = AUTONEG_DISABLE;
cmd->base.port = PORT_NONE;
cmd->base.duplex = DUPLEX_FULL;
cmd->base.speed = wx->speed;
return 0;
}
static const struct ethtool_ops wx_ethtool_ops_vf = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ |
ETHTOOL_COALESCE_USE_ADAPTIVE,
.get_drvinfo = wx_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_ringparam = wx_get_ringparam,
.get_msglevel = wx_get_msglevel,
.get_coalesce = wx_get_coalesce,
.get_ts_info = ethtool_op_get_ts_info,
.get_link_ksettings = wx_get_link_ksettings_vf,
};
void wx_set_ethtool_ops_vf(struct net_device *netdev)
{
netdev->ethtool_ops = &wx_ethtool_ops_vf;
}
EXPORT_SYMBOL(wx_set_ethtool_ops_vf);
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_ETHTOOL_H_
#define _WX_ETHTOOL_H_
int wx_get_sset_count(struct net_device *netdev, int sset);
void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data);
void wx_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *stats, u64 *data);
void wx_get_mac_stats(struct net_device *netdev,
struct ethtool_eth_mac_stats *mac_stats);
void wx_get_pause_stats(struct net_device *netdev,
struct ethtool_pause_stats *stats);
void wx_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info);
int wx_nway_reset(struct net_device *netdev);
int wx_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd);
int wx_set_link_ksettings(struct net_device *netdev,
const struct ethtool_link_ksettings *cmd);
void wx_get_wol(struct net_device *netdev,
struct ethtool_wolinfo *wol);
int wx_set_wol(struct net_device *netdev,
struct ethtool_wolinfo *wol);
void wx_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause);
int wx_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause);
void wx_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack);
int wx_get_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack);
int wx_set_coalesce(struct net_device *netdev,
struct ethtool_coalesce *ec,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack);
void wx_get_channels(struct net_device *dev,
struct ethtool_channels *ch);
int wx_set_channels(struct net_device *dev,
struct ethtool_channels *ch);
u32 wx_rss_indir_size(struct net_device *netdev);
u32 wx_get_rxfh_key_size(struct net_device *netdev);
int wx_get_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh);
int wx_set_rxfh(struct net_device *netdev,
struct ethtool_rxfh_param *rxfh,
struct netlink_ext_ack *extack);
int wx_get_rxfh_fields(struct net_device *dev,
struct ethtool_rxfh_fields *cmd);
int wx_set_rxfh_fields(struct net_device *dev,
const struct ethtool_rxfh_fields *nfc,
struct netlink_ext_ack *extack);
u32 wx_get_msglevel(struct net_device *netdev);
void wx_set_msglevel(struct net_device *netdev, u32 data);
int wx_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info);
void wx_get_ptp_stats(struct net_device *dev,
struct ethtool_ts_stats *ts_stats);
void wx_set_ethtool_ops_vf(struct net_device *netdev);
#endif /* _WX_ETHTOOL_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_HW_H_
#define _WX_HW_H_
#include <linux/phy.h>
int wx_phy_read_reg_mdi_c22(struct mii_bus *bus, int phy_addr, int regnum);
int wx_phy_write_reg_mdi_c22(struct mii_bus *bus, int phy_addr, int regnum, u16 value);
int wx_phy_read_reg_mdi_c45(struct mii_bus *bus, int phy_addr, int devnum, int regnum);
int wx_phy_write_reg_mdi_c45(struct mii_bus *bus, int phy_addr,
int devnum, int regnum, u16 value);
void wx_intr_enable(struct wx *wx, u64 qmask);
void wx_irq_disable(struct wx *wx);
int wx_check_flash_load(struct wx *wx, u32 check_bit);
void wx_control_hw(struct wx *wx, bool drv);
int wx_mng_present(struct wx *wx);
int wx_host_interface_command(struct wx *wx, u32 *buffer,
u32 length, u32 timeout, bool return_data);
int wx_set_pps(struct wx *wx, bool enable, u64 nsec, u64 cycles);
int wx_read_ee_hostif(struct wx *wx, u16 offset, u16 *data);
int wx_read_ee_hostif_buffer(struct wx *wx,
u16 offset, u16 words, u16 *data);
void wx_init_eeprom_params(struct wx *wx);
void wx_get_mac_addr(struct wx *wx, u8 *mac_addr);
void wx_init_rx_addrs(struct wx *wx);
void wx_mac_set_default_filter(struct wx *wx, u8 *addr);
int wx_add_mac_filter(struct wx *wx, u8 *addr, u16 pool);
int wx_del_mac_filter(struct wx *wx, u8 *addr, u16 pool);
void wx_flush_sw_mac_table(struct wx *wx);
u32 wx_mta_vector(struct wx *wx, u8 *mc_addr);
int wx_set_mac(struct net_device *netdev, void *p);
void wx_disable_rx(struct wx *wx);
int wx_set_vf_spoofchk(struct net_device *netdev, int vf, bool setting);
int wx_disable_sec_rx_path(struct wx *wx);
void wx_enable_sec_rx_path(struct wx *wx);
void wx_set_rx_mode(struct net_device *netdev);
int wx_change_mtu(struct net_device *netdev, int new_mtu);
void wx_disable_rx_queue(struct wx *wx, struct wx_ring *ring);
void wx_enable_rx_queue(struct wx *wx, struct wx_ring *ring);
u32 wx_rss_indir_tbl_entries(struct wx *wx);
void wx_store_reta(struct wx *wx);
void wx_store_rsskey(struct wx *wx);
void wx_config_rss_field(struct wx *wx);
void wx_enable_rss(struct wx *wx, bool enable);
void wx_configure_rx(struct wx *wx);
void wx_configure(struct wx *wx);
void wx_start_hw(struct wx *wx);
int wx_disable_pcie_master(struct wx *wx);
int wx_stop_adapter(struct wx *wx);
void wx_reset_mac(struct wx *wx);
void wx_reset_misc(struct wx *wx);
int wx_get_pcie_msix_counts(struct wx *wx, u16 *msix_count, u16 max_msix_count);
int wx_sw_init(struct wx *wx);
int wx_set_vfta(struct wx *wx, u32 vlan, u32 vind, bool vlan_on);
int wx_vlan_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid);
int wx_vlan_rx_kill_vid(struct net_device *netdev, __be16 proto, u16 vid);
int wx_fc_enable(struct wx *wx, bool tx_pause, bool rx_pause);
void wx_update_stats(struct wx *wx);
void wx_clear_hw_cntrs(struct wx *wx);
#endif /* _WX_HW_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* WangXun Gigabit PCI Express Linux driver
* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd.
*/
#ifndef _WX_LIB_H_
#define _WX_LIB_H_
struct wx_dec_ptype wx_decode_ptype(const u8 ptype);
void wx_alloc_rx_buffers(struct wx_ring *rx_ring, u16 cleaned_count);
u16 wx_desc_unused(struct wx_ring *ring);
netdev_tx_t wx_xmit_frame(struct sk_buff *skb,
struct net_device *netdev);
void wx_napi_enable_all(struct wx *wx);
void wx_napi_disable_all(struct wx *wx);
void wx_reset_interrupt_capability(struct wx *wx);
void wx_clear_interrupt_scheme(struct wx *wx);
int wx_init_interrupt_scheme(struct wx *wx);
irqreturn_t wx_msix_clean_rings(int __always_unused irq, void *data);
void wx_free_irq(struct wx *wx);
int wx_setup_isb_resources(struct wx *wx);
void wx_free_isb_resources(struct wx *wx);
u32 wx_misc_isb(struct wx *wx, enum wx_isb_idx idx);
void wx_write_eitr(struct wx_q_vector *q_vector);
void wx_configure_vectors(struct wx *wx);
void wx_clean_all_rx_rings(struct wx *wx);
void wx_clean_all_tx_rings(struct wx *wx);
void wx_free_resources(struct wx *wx);
int wx_setup_resources(struct wx *wx);
void wx_get_stats64(struct net_device *netdev,
struct rtnl_link_stats64 *stats);
int wx_set_features(struct net_device *netdev, netdev_features_t features);
netdev_features_t wx_fix_features(struct net_device *netdev,
netdev_features_t features);
netdev_features_t wx_features_check(struct sk_buff *skb,
struct net_device *netdev,
netdev_features_t features);
void wx_set_ring(struct wx *wx, u32 new_tx_count,
u32 new_rx_count, struct wx_ring *temp_ring);
void wx_service_event_schedule(struct wx *wx);
void wx_service_event_complete(struct wx *wx);
void wx_service_timer(struct timer_list *t);
#endif /* _WX_LIB_H_ */
@@ -0,0 +1,418 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_mbx.h"
/**
* wx_obtain_mbx_lock_pf - obtain mailbox lock
* @wx: pointer to the HW structure
* @vf: the VF index
*
* Return: return 0 on success and -EBUSY on failure
**/
static int wx_obtain_mbx_lock_pf(struct wx *wx, u16 vf)
{
int count = 5;
u32 mailbox;
while (count--) {
/* Take ownership of the buffer */
wr32(wx, WX_PXMAILBOX(vf), WX_PXMAILBOX_PFU);
/* reserve mailbox for vf use */
mailbox = rd32(wx, WX_PXMAILBOX(vf));
if (mailbox & WX_PXMAILBOX_PFU)
return 0;
else if (count)
udelay(10);
}
wx_err(wx, "Failed to obtain mailbox lock for PF%d", vf);
return -EBUSY;
}
static int wx_check_for_bit_pf(struct wx *wx, u32 mask, int index)
{
u32 mbvficr = rd32(wx, WX_MBVFICR(index));
if (!(mbvficr & mask))
return -EBUSY;
wr32(wx, WX_MBVFICR(index), mask);
return 0;
}
/**
* wx_check_for_ack_pf - checks to see if the VF has acked
* @wx: pointer to the HW structure
* @vf: the VF index
*
* Return: return 0 if the VF has set the status bit or else -EBUSY
**/
int wx_check_for_ack_pf(struct wx *wx, u16 vf)
{
u32 index = vf / 16, vf_bit = vf % 16;
return wx_check_for_bit_pf(wx,
FIELD_PREP(WX_MBVFICR_VFACK_MASK,
BIT(vf_bit)),
index);
}
/**
* wx_check_for_msg_pf - checks to see if the VF has sent mail
* @wx: pointer to the HW structure
* @vf: the VF index
*
* Return: return 0 if the VF has got req bit or else -EBUSY
**/
int wx_check_for_msg_pf(struct wx *wx, u16 vf)
{
u32 index = vf / 16, vf_bit = vf % 16;
return wx_check_for_bit_pf(wx,
FIELD_PREP(WX_MBVFICR_VFREQ_MASK,
BIT(vf_bit)),
index);
}
/**
* wx_write_mbx_pf - Places a message in the mailbox
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
* @vf: the VF index
*
* Return: return 0 on success and -EINVAL/-EBUSY on failure
**/
int wx_write_mbx_pf(struct wx *wx, u32 *msg, u16 size, u16 vf)
{
struct wx_mbx_info *mbx = &wx->mbx;
int ret, i;
/* mbx->size is up to 15 */
if (size > mbx->size) {
wx_err(wx, "Invalid mailbox message size %d", size);
return -EINVAL;
}
/* lock the mailbox to prevent pf/vf race condition */
ret = wx_obtain_mbx_lock_pf(wx, vf);
if (ret)
return ret;
/* flush msg and acks as we are overwriting the message buffer */
wx_check_for_msg_pf(wx, vf);
wx_check_for_ack_pf(wx, vf);
/* copy the caller specified message to the mailbox memory buffer */
for (i = 0; i < size; i++)
wr32a(wx, WX_PXMBMEM(vf), i, msg[i]);
/* Interrupt VF to tell it a message has been sent and release buffer */
/* set mirrored mailbox flags */
wr32a(wx, WX_PXMBMEM(vf), WX_VXMAILBOX_SIZE, WX_PXMAILBOX_STS);
wr32(wx, WX_PXMAILBOX(vf), WX_PXMAILBOX_STS);
return 0;
}
/**
* wx_read_mbx_pf - Read a message from the mailbox
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
* @vf: the VF index
*
* Return: return 0 on success and -EBUSY on failure
**/
int wx_read_mbx_pf(struct wx *wx, u32 *msg, u16 size, u16 vf)
{
struct wx_mbx_info *mbx = &wx->mbx;
int ret;
u16 i;
/* limit read to size of mailbox and mbx->size is up to 15 */
if (size > mbx->size)
size = mbx->size;
/* lock the mailbox to prevent pf/vf race condition */
ret = wx_obtain_mbx_lock_pf(wx, vf);
if (ret)
return ret;
for (i = 0; i < size; i++)
msg[i] = rd32a(wx, WX_PXMBMEM(vf), i);
/* Acknowledge the message and release buffer */
/* set mirrored mailbox flags */
wr32a(wx, WX_PXMBMEM(vf), WX_VXMAILBOX_SIZE, WX_PXMAILBOX_ACK);
wr32(wx, WX_PXMAILBOX(vf), WX_PXMAILBOX_ACK);
return 0;
}
/**
* wx_check_for_rst_pf - checks to see if the VF has reset
* @wx: pointer to the HW structure
* @vf: the VF index
*
* Return: return 0 on success and -EBUSY on failure
**/
int wx_check_for_rst_pf(struct wx *wx, u16 vf)
{
u32 reg_offset = WX_VF_REG_OFFSET(vf);
u32 vf_shift = WX_VF_IND_SHIFT(vf);
u32 vflre = 0;
vflre = rd32(wx, WX_VFLRE(reg_offset));
if (!(vflre & BIT(vf_shift)))
return -EBUSY;
wr32(wx, WX_VFLREC(reg_offset), BIT(vf_shift));
return 0;
}
static u32 wx_read_v2p_mailbox(struct wx *wx)
{
u32 mailbox = rd32(wx, WX_VXMAILBOX);
mailbox |= wx->mbx.mailbox;
wx->mbx.mailbox |= mailbox & WX_VXMAILBOX_R2C_BITS;
return mailbox;
}
static u32 wx_mailbox_get_lock_vf(struct wx *wx)
{
wr32(wx, WX_VXMAILBOX, WX_VXMAILBOX_VFU);
return wx_read_v2p_mailbox(wx);
}
/**
* wx_obtain_mbx_lock_vf - obtain mailbox lock
* @wx: pointer to the HW structure
*
* Return: return 0 on success and -EBUSY on failure
**/
static int wx_obtain_mbx_lock_vf(struct wx *wx)
{
int count = 5, ret;
u32 mailbox;
ret = readx_poll_timeout_atomic(wx_mailbox_get_lock_vf, wx, mailbox,
(mailbox & WX_VXMAILBOX_VFU),
1, count);
if (ret)
wx_err(wx, "Failed to obtain mailbox lock for VF.\n");
return ret;
}
static int wx_check_for_bit_vf(struct wx *wx, u32 mask)
{
u32 mailbox = wx_read_v2p_mailbox(wx);
wx->mbx.mailbox &= ~mask;
return (mailbox & mask ? 0 : -EBUSY);
}
/**
* wx_check_for_ack_vf - checks to see if the PF has ACK'd
* @wx: pointer to the HW structure
*
* Return: return 0 if the PF has set the status bit or else -EBUSY
**/
static int wx_check_for_ack_vf(struct wx *wx)
{
/* read clear the pf ack bit */
return wx_check_for_bit_vf(wx, WX_VXMAILBOX_PFACK);
}
/**
* wx_check_for_msg_vf - checks to see if the PF has sent mail
* @wx: pointer to the HW structure
*
* Return: return 0 if the PF has got req bit or else -EBUSY
**/
int wx_check_for_msg_vf(struct wx *wx)
{
/* read clear the pf sts bit */
return wx_check_for_bit_vf(wx, WX_VXMAILBOX_PFSTS);
}
/**
* wx_check_for_rst_vf - checks to see if the PF has reset
* @wx: pointer to the HW structure
*
* Return: return 0 if the PF has set the reset done and -EBUSY on failure
**/
int wx_check_for_rst_vf(struct wx *wx)
{
/* read clear the pf reset done bit */
return wx_check_for_bit_vf(wx,
WX_VXMAILBOX_RSTD |
WX_VXMAILBOX_RSTI);
}
/**
* wx_poll_for_msg - Wait for message notification
* @wx: pointer to the HW structure
*
* Return: return 0 if the VF has successfully received a message notification
**/
static int wx_poll_for_msg(struct wx *wx)
{
struct wx_mbx_info *mbx = &wx->mbx;
u32 val;
return readx_poll_timeout_atomic(wx_check_for_msg_vf, wx, val,
(val == 0), mbx->udelay, mbx->timeout);
}
/**
* wx_poll_for_ack - Wait for message acknowledgment
* @wx: pointer to the HW structure
*
* Return: return 0 if the VF has successfully received a message ack
**/
static int wx_poll_for_ack(struct wx *wx)
{
struct wx_mbx_info *mbx = &wx->mbx;
u32 val;
return readx_poll_timeout_atomic(wx_check_for_ack_vf, wx, val,
(val == 0), mbx->udelay, mbx->timeout);
}
/**
* wx_read_posted_mbx - Wait for message notification and receive message
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
*
* Return: returns 0 if it successfully received a message notification and
* copied it into the receive buffer.
**/
int wx_read_posted_mbx(struct wx *wx, u32 *msg, u16 size)
{
int ret;
ret = wx_poll_for_msg(wx);
/* if ack received read message, otherwise we timed out */
if (ret)
return ret;
return wx_read_mbx_vf(wx, msg, size);
}
/**
* wx_write_posted_mbx - Write a message to the mailbox, wait for ack
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
*
* Return: returns 0 if it successfully copied message into the buffer and
* received an ack to that message within delay * timeout period
**/
int wx_write_posted_mbx(struct wx *wx, u32 *msg, u16 size)
{
int ret;
/* send msg */
ret = wx_write_mbx_vf(wx, msg, size);
/* if msg sent wait until we receive an ack */
if (ret)
return ret;
return wx_poll_for_ack(wx);
}
/**
* wx_write_mbx_vf - Write a message to the mailbox
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
*
* Return: returns 0 if it successfully copied message into the buffer
**/
int wx_write_mbx_vf(struct wx *wx, u32 *msg, u16 size)
{
struct wx_mbx_info *mbx = &wx->mbx;
int ret, i;
/* mbx->size is up to 15 */
if (size > mbx->size) {
wx_err(wx, "Invalid mailbox message size %d", size);
return -EINVAL;
}
/* lock the mailbox to prevent pf/vf race condition */
ret = wx_obtain_mbx_lock_vf(wx);
if (ret)
return ret;
/* flush msg and acks as we are overwriting the message buffer */
wx_check_for_msg_vf(wx);
wx_check_for_ack_vf(wx);
/* copy the caller specified message to the mailbox memory buffer */
for (i = 0; i < size; i++)
wr32a(wx, WX_VXMBMEM, i, msg[i]);
/* Drop VFU and interrupt the PF to tell it a message has been sent */
wr32(wx, WX_VXMAILBOX, WX_VXMAILBOX_REQ);
return 0;
}
/**
* wx_read_mbx_vf - Reads a message from the inbox intended for vf
* @wx: pointer to the HW structure
* @msg: The message buffer
* @size: Length of buffer
*
* Return: returns 0 if it successfully copied message into the buffer
**/
int wx_read_mbx_vf(struct wx *wx, u32 *msg, u16 size)
{
struct wx_mbx_info *mbx = &wx->mbx;
int ret, i;
/* limit read to size of mailbox and mbx->size is up to 15 */
if (size > mbx->size)
size = mbx->size;
/* lock the mailbox to prevent pf/vf race condition */
ret = wx_obtain_mbx_lock_vf(wx);
if (ret)
return ret;
/* copy the message from the mailbox memory buffer */
for (i = 0; i < size; i++)
msg[i] = rd32a(wx, WX_VXMBMEM, i);
/* Acknowledge receipt and release mailbox, then we're done */
wr32(wx, WX_VXMAILBOX, WX_VXMAILBOX_ACK);
return 0;
}
int wx_init_mbx_params_vf(struct wx *wx)
{
wx->vfinfo = kzalloc_obj(struct vf_data_storage);
if (!wx->vfinfo)
return -ENOMEM;
/* Initialize mailbox parameters */
wx->mbx.size = WX_VXMAILBOX_SIZE;
wx->mbx.mailbox = WX_VXMAILBOX;
wx->mbx.udelay = 10;
wx->mbx.timeout = 1000;
return 0;
}
EXPORT_SYMBOL(wx_init_mbx_params_vf);
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_MBX_H_
#define _WX_MBX_H_
#define WX_VXMAILBOX_SIZE 15
/* PF Registers */
#define WX_PXMAILBOX(i) (0x600 + (4 * (i))) /* i=[0,63] */
#define WX_PXMAILBOX_STS BIT(0) /* Initiate message send to VF */
#define WX_PXMAILBOX_ACK BIT(1) /* Ack message recv'd from VF */
#define WX_PXMAILBOX_PFU BIT(3) /* PF owns the mailbox buffer */
/* VF Registers */
#define WX_VXMAILBOX 0x600
#define WX_VXMAILBOX_REQ BIT(0) /* Request for PF Ready bit */
#define WX_VXMAILBOX_ACK BIT(1) /* Ack PF message received */
#define WX_VXMAILBOX_VFU BIT(2) /* VF owns the mailbox buffer */
#define WX_VXMAILBOX_PFU BIT(3) /* PF owns the mailbox buffer */
#define WX_VXMAILBOX_PFSTS BIT(4) /* PF wrote a message in the MB */
#define WX_VXMAILBOX_PFACK BIT(5) /* PF ack the previous VF msg */
#define WX_VXMAILBOX_RSTI BIT(6) /* PF has reset indication */
#define WX_VXMAILBOX_RSTD BIT(7) /* PF has indicated reset done */
#define WX_VXMAILBOX_R2C_BITS (WX_VXMAILBOX_RSTD | \
WX_VXMAILBOX_PFSTS | WX_VXMAILBOX_PFACK)
#define WX_VXMBMEM 0x00C00 /* 16*4B */
#define WX_PXMBMEM(i) (0x5000 + (64 * (i))) /* i=[0,63] */
#define WX_VFLRE(i) (0x4A0 + (4 * (i))) /* i=[0,1] */
#define WX_VFLREC(i) (0x4A8 + (4 * (i))) /* i=[0,1] */
/* SR-IOV specific macros */
#define WX_MBVFICR(i) (0x480 + (4 * (i))) /* i=[0,3] */
#define WX_MBVFICR_VFREQ_MASK GENMASK(15, 0)
#define WX_MBVFICR_VFACK_MASK GENMASK(31, 16)
#define WX_VT_MSGTYPE_ACK BIT(31)
#define WX_VT_MSGTYPE_NACK BIT(30)
#define WX_VT_MSGTYPE_CTS BIT(29)
#define WX_VT_MSGINFO_SHIFT 16
#define WX_VT_MSGINFO_MASK GENMASK(23, 16)
enum wx_pfvf_api_rev {
wx_mbox_api_null,
wx_mbox_api_13 = 4, /* API version 1.3 */
wx_mbox_api_unknown, /* indicates that API version is not known */
};
/* mailbox API */
#define WX_VF_RESET 0x01 /* VF requests reset */
#define WX_VF_SET_MAC_ADDR 0x02 /* VF requests PF to set MAC addr */
#define WX_VF_SET_MULTICAST 0x03 /* VF requests PF to set MC addr */
#define WX_VF_SET_VLAN 0x04 /* VF requests PF to set VLAN */
#define WX_VF_SET_LPE 0x05 /* VF requests PF to set VMOLR.LPE */
#define WX_VF_SET_MACVLAN 0x06 /* VF requests PF unicast filter */
#define WX_VF_API_NEGOTIATE 0x08 /* negotiate API version */
#define WX_VF_GET_QUEUES 0x09 /* get queue configuration */
#define WX_VF_GET_RETA 0x0a /* VF request for RETA */
#define WX_VF_GET_RSS_KEY 0x0b /* get RSS key */
#define WX_VF_UPDATE_XCAST_MODE 0x0c
#define WX_VF_GET_LINK_STATE 0x10 /* get vf link state */
#define WX_VF_GET_FW_VERSION 0x11 /* get fw version */
#define WX_VF_BACKUP 0x8001 /* VF requests backup */
#define WX_PF_CONTROL_MSG BIT(8) /* PF control message */
#define WX_PF_NOFITY_VF_LINK_STATUS 0x1
#define WX_PF_NOFITY_VF_NET_NOT_RUNNING BIT(31)
#define WX_VF_TX_QUEUES 1 /* number of Tx queues supported */
#define WX_VF_RX_QUEUES 2 /* number of Rx queues supported */
#define WX_VF_TRANS_VLAN 3 /* Indication of port vlan */
#define WX_VF_DEF_QUEUE 4 /* Default queue offset */
#define WX_VF_PERMADDR_MSG_LEN 4
enum wxvf_xcast_modes {
WXVF_XCAST_MODE_NONE = 0,
WXVF_XCAST_MODE_MULTI,
WXVF_XCAST_MODE_ALLMULTI,
WXVF_XCAST_MODE_PROMISC,
};
int wx_write_mbx_pf(struct wx *wx, u32 *msg, u16 size, u16 vf);
int wx_read_mbx_pf(struct wx *wx, u32 *msg, u16 size, u16 vf);
int wx_check_for_rst_pf(struct wx *wx, u16 mbx_id);
int wx_check_for_msg_pf(struct wx *wx, u16 mbx_id);
int wx_check_for_ack_pf(struct wx *wx, u16 mbx_id);
int wx_read_posted_mbx(struct wx *wx, u32 *msg, u16 size);
int wx_write_posted_mbx(struct wx *wx, u32 *msg, u16 size);
int wx_check_for_rst_vf(struct wx *wx);
int wx_check_for_msg_vf(struct wx *wx);
int wx_read_mbx_vf(struct wx *wx, u32 *msg, u16 size);
int wx_write_mbx_vf(struct wx *wx, u32 *msg, u16 size);
int wx_init_mbx_params_vf(struct wx *wx);
#endif /* _WX_MBX_H_ */
@@ -0,0 +1,905 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
/* Copyright (c) 1999 - 2025 Intel Corporation. */
#include <linux/ptp_classify.h>
#include <linux/clocksource.h>
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_ptp.h"
#include "wx_hw.h"
#define WX_INCVAL_10GB 0xCCCCCC
#define WX_INCVAL_1GB 0x800000
#define WX_INCVAL_100 0xA00000
#define WX_INCVAL_10 0xC7F380
#define WX_INCVAL_EM 0x2000000
#define WX_INCVAL_AML 0xA00000
#define WX_INCVAL_SHIFT_10GB 20
#define WX_INCVAL_SHIFT_1GB 18
#define WX_INCVAL_SHIFT_100 15
#define WX_INCVAL_SHIFT_10 12
#define WX_INCVAL_SHIFT_EM 22
#define WX_INCVAL_SHIFT_AML 21
#define WX_OVERFLOW_PERIOD (HZ * 30)
#define WX_PTP_TX_TIMEOUT (HZ)
#define WX_1588_PPS_WIDTH_EM 120
#define WX_NS_PER_SEC 1000000000ULL
static u64 wx_ptp_timecounter_cyc2time(struct wx *wx, u64 timestamp)
{
unsigned int seq;
u64 ns;
do {
seq = read_seqbegin(&wx->hw_tc_lock);
ns = timecounter_cyc2time(&wx->hw_tc, timestamp);
} while (read_seqretry(&wx->hw_tc_lock, seq));
return ns;
}
static u64 wx_ptp_readtime(struct wx *wx, struct ptp_system_timestamp *sts)
{
u32 timeh1, timeh2, timel;
timeh1 = rd32ptp(wx, WX_TSC_1588_SYSTIMH);
ptp_read_system_prets(sts);
timel = rd32ptp(wx, WX_TSC_1588_SYSTIML);
ptp_read_system_postts(sts);
timeh2 = rd32ptp(wx, WX_TSC_1588_SYSTIMH);
if (timeh1 != timeh2) {
ptp_read_system_prets(sts);
timel = rd32ptp(wx, WX_TSC_1588_SYSTIML);
ptp_read_system_prets(sts);
}
return (u64)timel | (u64)timeh2 << 32;
}
static int wx_ptp_adjfine(struct ptp_clock_info *ptp, long ppb)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
u64 incval, mask;
smp_mb(); /* Force any pending update before accessing. */
incval = READ_ONCE(wx->base_incval);
incval = adjust_by_scaled_ppm(incval, ppb);
mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
incval &= mask;
if (wx->mac.type != wx_mac_em)
incval |= 2 << 24;
wr32ptp(wx, WX_TSC_1588_INC, incval);
return 0;
}
static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
unsigned long flags;
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
timecounter_adjtime(&wx->hw_tc, delta);
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
return 0;
}
static int wx_ptp_gettimex64(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
u64 ns, stamp;
stamp = wx_ptp_readtime(wx, sts);
ns = wx_ptp_timecounter_cyc2time(wx, stamp);
*ts = ns_to_timespec64(ns);
return 0;
}
static int wx_ptp_settime64(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
unsigned long flags;
u64 ns;
ns = timespec64_to_ns(ts);
/* reset the timecounter */
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
return 0;
}
/**
* wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state
* @wx: the private board structure
*
* This function should be called whenever the state related to a Tx timestamp
* needs to be cleared. This helps ensure that all related bits are reset for
* the next Tx timestamp event.
*/
static void wx_ptp_clear_tx_timestamp(struct wx *wx)
{
rd32ptp(wx, WX_TSC_1588_STMPH);
if (wx->ptp_tx_skb) {
dev_kfree_skb_any(wx->ptp_tx_skb);
wx->ptp_tx_skb = NULL;
}
clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
}
/**
* wx_ptp_convert_to_hwtstamp - convert register value to hw timestamp
* @wx: private board structure
* @hwtstamp: stack timestamp structure
* @timestamp: unsigned 64bit system time value
*
* We need to convert the adapter's RX/TXSTMP registers into a hwtstamp value
* which can be used by the stack's ptp functions.
*
* The lock is used to protect consistency of the cyclecounter and the SYSTIME
* registers. However, it does not need to protect against the Rx or Tx
* timestamp registers, as there can't be a new timestamp until the old one is
* unlatched by reading.
*
* In addition to the timestamp in hardware, some controllers need a software
* overflow cyclecounter, and this function takes this into account as well.
**/
static void wx_ptp_convert_to_hwtstamp(struct wx *wx,
struct skb_shared_hwtstamps *hwtstamp,
u64 timestamp)
{
u64 ns;
ns = wx_ptp_timecounter_cyc2time(wx, timestamp);
hwtstamp->hwtstamp = ns_to_ktime(ns);
}
/**
* wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp
* @wx: the private board struct
*
* if the timestamp is valid, we convert it into the timecounter ns
* value, then store that result into the shhwtstamps structure which
* is passed up the network stack
*/
static void wx_ptp_tx_hwtstamp(struct wx *wx)
{
struct skb_shared_hwtstamps shhwtstamps;
struct sk_buff *skb = wx->ptp_tx_skb;
u64 regval = 0;
regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL);
regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32;
wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval);
wx->ptp_tx_skb = NULL;
clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state);
skb_tstamp_tx(skb, &shhwtstamps);
dev_kfree_skb_any(skb);
wx->tx_hwtstamp_pkts++;
}
static int wx_ptp_tx_hwtstamp_work(struct wx *wx)
{
u32 tsynctxctl;
/* we have to have a valid skb to poll for a timestamp */
if (!wx->ptp_tx_skb) {
wx_ptp_clear_tx_timestamp(wx);
return 0;
}
/* stop polling once we have a valid timestamp */
tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL);
if (tsynctxctl & WX_TSC_1588_CTL_VALID) {
wx_ptp_tx_hwtstamp(wx);
return 0;
}
return -1;
}
/**
* wx_ptp_overflow_check - watchdog task to detect SYSTIME overflow
* @wx: pointer to wx struct
*
* this watchdog task periodically reads the timecounter
* in order to prevent missing when the system time registers wrap
* around. This needs to be run approximately twice a minute for the fastest
* overflowing hardware. We run it for all hardware since it shouldn't have a
* large impact.
*/
static void wx_ptp_overflow_check(struct wx *wx)
{
bool timeout = time_is_before_jiffies(wx->last_overflow_check +
WX_OVERFLOW_PERIOD);
unsigned long flags;
if (timeout) {
/* Update the timecounter */
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
timecounter_read(&wx->hw_tc);
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
wx->last_overflow_check = jiffies;
}
}
/**
* wx_ptp_rx_hang - detect error case when Rx timestamp registers latched
* @wx: pointer to wx struct
*
* this watchdog task is scheduled to detect error case where hardware has
* dropped an Rx packet that was timestamped when the ring is full. The
* particular error is rare but leaves the device in a state unable to
* timestamp any future packets.
*/
static void wx_ptp_rx_hang(struct wx *wx)
{
struct wx_ring *rx_ring;
unsigned long rx_event;
u32 tsyncrxctl;
int n;
tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
/* if we don't have a valid timestamp in the registers, just update the
* timeout counter and exit
*/
if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID)) {
wx->last_rx_ptp_check = jiffies;
return;
}
/* determine the most recent watchdog or rx_timestamp event */
rx_event = wx->last_rx_ptp_check;
for (n = 0; n < wx->num_rx_queues; n++) {
rx_ring = wx->rx_ring[n];
if (time_after(rx_ring->last_rx_timestamp, rx_event))
rx_event = rx_ring->last_rx_timestamp;
}
/* only need to read the high RXSTMP register to clear the lock */
if (time_is_before_jiffies(rx_event + 5 * HZ)) {
rd32(wx, WX_PSR_1588_STMPH);
wx->last_rx_ptp_check = jiffies;
wx->rx_hwtstamp_cleared++;
dev_warn(&wx->pdev->dev, "clearing RX Timestamp hang");
}
}
/**
* wx_ptp_tx_hang - detect error case where Tx timestamp never finishes
* @wx: private network wx structure
*/
static void wx_ptp_tx_hang(struct wx *wx)
{
bool timeout = time_is_before_jiffies(wx->ptp_tx_start +
WX_PTP_TX_TIMEOUT);
if (!wx->ptp_tx_skb)
return;
if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state))
return;
/* If we haven't received a timestamp within the timeout, it is
* reasonable to assume that it will never occur, so we can unlock the
* timestamp bit when this occurs.
*/
if (timeout) {
wx_ptp_clear_tx_timestamp(wx);
wx->tx_hwtstamp_timeouts++;
dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n");
}
}
static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
int ts_done;
ts_done = wx_ptp_tx_hwtstamp_work(wx);
wx_ptp_overflow_check(wx);
if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
wx->flags)))
wx_ptp_rx_hang(wx);
wx_ptp_tx_hang(wx);
return ts_done ? 1 : HZ;
}
static u64 wx_ptp_trigger_calc(struct wx *wx)
{
struct cyclecounter *cc = &wx->hw_cc;
unsigned long flags;
u64 ns = 0;
u32 rem;
/* Read the current clock time, and save the cycle counter value */
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
ns = timecounter_read(&wx->hw_tc);
wx->pps_edge_start = wx->hw_tc.cycle_last;
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
wx->pps_edge_end = wx->pps_edge_start;
/* Figure out how far past the next second we are */
div_u64_rem(ns, WX_NS_PER_SEC, &rem);
/* Figure out how many nanoseconds to add to round the clock edge up
* to the next full second
*/
rem = (WX_NS_PER_SEC - rem);
/* Adjust the clock edge to align with the next full second. */
wx->pps_edge_start += div_u64(((u64)rem << cc->shift), cc->mult);
wx->pps_edge_end += div_u64(((u64)(rem + wx->pps_width) <<
cc->shift), cc->mult);
return (ns + rem);
}
static int wx_ptp_setup_sdp(struct wx *wx)
{
struct cyclecounter *cc = &wx->hw_cc;
u32 tsauxc;
u64 nsec;
if (wx->pps_width >= WX_NS_PER_SEC) {
wx_err(wx, "PTP pps width cannot be longer than 1s!\n");
return -EINVAL;
}
/* disable the pin first */
wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
WX_WRITE_FLUSH(wx);
if (!test_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags)) {
if (wx->pps_enabled) {
wx->pps_enabled = false;
wx_set_pps(wx, false, 0, 0);
}
return 0;
}
wx->pps_enabled = true;
nsec = wx_ptp_trigger_calc(wx);
wx_set_pps(wx, wx->pps_enabled, nsec, wx->pps_edge_start);
tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
wr32ptp(wx, WX_TSC_1588_SDP(0),
WX_TSC_1588_SDP_FUN_SEL_TT0 | WX_TSC_1588_SDP_OUT_LEVEL_H);
wr32ptp(wx, WX_TSC_1588_SDP(1), WX_TSC_1588_SDP_FUN_SEL_TS0);
wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
wr32ptp(wx, WX_TSC_1588_INT_EN, WX_TSC_1588_INT_EN_TT1);
WX_WRITE_FLUSH(wx);
/* Adjust the clock edge to align with the next full second. */
wx->sec_to_cc = div_u64(((u64)WX_NS_PER_SEC << cc->shift), cc->mult);
return 0;
}
static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
/**
* When PPS is enabled, unmask the interrupt for the ClockOut
* feature, so that the interrupt handler can send the PPS
* event when the clock SDP triggers. Clear mask when PPS is
* disabled
*/
if (rq->type != PTP_CLK_REQ_PEROUT || !wx->ptp_setup_sdp)
return -EOPNOTSUPP;
/* Reject requests with unsupported flags */
if (rq->perout.flags & ~(PTP_PEROUT_DUTY_CYCLE |
PTP_PEROUT_PHASE))
return -EOPNOTSUPP;
if (rq->perout.phase.sec || rq->perout.phase.nsec) {
wx_err(wx, "Absolute start time not supported.\n");
return -EINVAL;
}
if (rq->perout.period.sec != 1 || rq->perout.period.nsec) {
wx_err(wx, "Only 1pps is supported.\n");
return -EINVAL;
}
if (rq->perout.flags & PTP_PEROUT_DUTY_CYCLE) {
struct timespec64 ts_on;
ts_on.tv_sec = rq->perout.on.sec;
ts_on.tv_nsec = rq->perout.on.nsec;
wx->pps_width = timespec64_to_ns(&ts_on);
} else {
wx->pps_width = 120000000;
}
if (on)
set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
else
clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
return wx->ptp_setup_sdp(wx);
}
void wx_ptp_check_pps_event(struct wx *wx)
{
u32 tsauxc, int_status;
/* this check is necessary in case the interrupt was enabled via some
* alternative means (ex. debug_fs). Better to check here than
* everywhere that calls this function.
*/
if (!wx->ptp_clock)
return;
int_status = rd32ptp(wx, WX_TSC_1588_INT_ST);
if (int_status & WX_TSC_1588_INT_ST_TT1) {
/* disable the pin first */
wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
WX_WRITE_FLUSH(wx);
wx_ptp_trigger_calc(wx);
tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
WX_WRITE_FLUSH(wx);
}
}
EXPORT_SYMBOL(wx_ptp_check_pps_event);
static long wx_ptp_create_clock(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
long err;
/* do nothing if we already have a clock device */
if (!IS_ERR_OR_NULL(wx->ptp_clock))
return 0;
snprintf(wx->ptp_caps.name, sizeof(wx->ptp_caps.name),
"%s", netdev->name);
wx->ptp_caps.owner = THIS_MODULE;
wx->ptp_caps.n_alarm = 0;
wx->ptp_caps.n_ext_ts = 0;
wx->ptp_caps.pps = 0;
wx->ptp_caps.adjfine = wx_ptp_adjfine;
wx->ptp_caps.adjtime = wx_ptp_adjtime;
wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
wx->ptp_caps.settime64 = wx_ptp_settime64;
wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
switch (wx->mac.type) {
case wx_mac_aml:
case wx_mac_aml40:
wx->ptp_caps.max_adj = 250000000;
wx->ptp_caps.n_per_out = 1;
wx->ptp_setup_sdp = wx_ptp_setup_sdp;
wx->ptp_caps.enable = wx_ptp_feature_enable;
break;
case wx_mac_sp:
wx->ptp_caps.max_adj = 250000000;
wx->ptp_caps.n_per_out = 0;
wx->ptp_setup_sdp = NULL;
break;
case wx_mac_em:
wx->ptp_caps.max_adj = 500000000;
wx->ptp_caps.n_per_out = 1;
wx->ptp_setup_sdp = wx_ptp_setup_sdp;
wx->ptp_caps.enable = wx_ptp_feature_enable;
break;
default:
return -EOPNOTSUPP;
}
wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
if (IS_ERR(wx->ptp_clock)) {
err = PTR_ERR(wx->ptp_clock);
wx->ptp_clock = NULL;
wx_err(wx, "ptp clock register failed\n");
return err;
} else if (wx->ptp_clock) {
dev_info(&wx->pdev->dev, "registered PHC device on %s\n",
netdev->name);
}
/* Set the default timestamp mode to disabled here. We do this in
* create_clock instead of initialization, because we don't want to
* override the previous settings during a suspend/resume cycle.
*/
wx->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
wx->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
return 0;
}
static int wx_ptp_set_timestamp_mode(struct wx *wx,
struct kernel_hwtstamp_config *config)
{
u32 tsync_tx_ctl = WX_TSC_1588_CTL_ENABLED;
u32 tsync_rx_ctl = WX_PSR_1588_CTL_ENABLED;
DECLARE_BITMAP(flags, WX_PF_FLAGS_NBITS);
u32 tsync_rx_mtrl = PTP_EV_PORT << 16;
bool is_l2 = false;
u32 regval;
memcpy(flags, wx->flags, sizeof(wx->flags));
switch (config->tx_type) {
case HWTSTAMP_TX_OFF:
tsync_tx_ctl = 0;
break;
case HWTSTAMP_TX_ON:
break;
default:
return -ERANGE;
}
switch (config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
tsync_rx_ctl = 0;
tsync_rx_mtrl = 0;
clear_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
clear_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_SYNC;
set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
break;
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_L4_V1;
tsync_rx_mtrl |= WX_PSR_1588_MSG_V1_DELAY_REQ;
set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
break;
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
tsync_rx_ctl |= WX_PSR_1588_CTL_TYPE_EVENT_V2;
is_l2 = true;
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
set_bit(WX_FLAG_RX_HWTSTAMP_ENABLED, flags);
set_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER, flags);
break;
default:
/* register PSR_1588_MSG must be set in order to do V1 packets,
* therefore it is not possible to time stamp both V1 Sync and
* Delay_Req messages unless hardware supports timestamping all
* packets => return error
*/
config->rx_filter = HWTSTAMP_FILTER_NONE;
return -ERANGE;
}
/* define ethertype filter for timestamping L2 packets */
if (is_l2)
wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588),
(WX_PSR_ETYPE_SWC_FILTER_EN | /* enable filter */
WX_PSR_ETYPE_SWC_1588 | /* enable timestamping */
ETH_P_1588)); /* 1588 eth protocol type */
else
wr32(wx, WX_PSR_ETYPE_SWC(WX_PSR_ETYPE_SWC_FILTER_1588), 0);
/* enable/disable TX */
regval = rd32ptp(wx, WX_TSC_1588_CTL);
regval &= ~WX_TSC_1588_CTL_ENABLED;
regval |= tsync_tx_ctl;
wr32ptp(wx, WX_TSC_1588_CTL, regval);
/* enable/disable RX */
regval = rd32(wx, WX_PSR_1588_CTL);
regval &= ~(WX_PSR_1588_CTL_ENABLED | WX_PSR_1588_CTL_TYPE_MASK);
regval |= tsync_rx_ctl;
wr32(wx, WX_PSR_1588_CTL, regval);
/* define which PTP packets are time stamped */
wr32(wx, WX_PSR_1588_MSG, tsync_rx_mtrl);
WX_WRITE_FLUSH(wx);
/* configure adapter flags only when HW is actually configured */
memcpy(wx->flags, flags, sizeof(wx->flags));
/* clear TX/RX timestamp state, just to be sure */
wx_ptp_clear_tx_timestamp(wx);
rd32(wx, WX_PSR_1588_STMPH);
return 0;
}
static u64 wx_ptp_read(struct cyclecounter *hw_cc)
{
struct wx *wx = container_of(hw_cc, struct wx, hw_cc);
return wx_ptp_readtime(wx, NULL);
}
static void wx_ptp_link_speed_adjust(struct wx *wx, u32 *shift, u32 *incval)
{
switch (wx->mac.type) {
case wx_mac_aml:
case wx_mac_aml40:
*shift = WX_INCVAL_SHIFT_AML;
*incval = WX_INCVAL_AML;
return;
case wx_mac_em:
*shift = WX_INCVAL_SHIFT_EM;
*incval = WX_INCVAL_EM;
return;
default:
break;
}
switch (wx->speed) {
case SPEED_10:
*shift = WX_INCVAL_SHIFT_10;
*incval = WX_INCVAL_10;
break;
case SPEED_100:
*shift = WX_INCVAL_SHIFT_100;
*incval = WX_INCVAL_100;
break;
case SPEED_1000:
*shift = WX_INCVAL_SHIFT_1GB;
*incval = WX_INCVAL_1GB;
break;
case SPEED_10000:
default:
*shift = WX_INCVAL_SHIFT_10GB;
*incval = WX_INCVAL_10GB;
break;
}
}
/**
* wx_ptp_reset_cyclecounter - create the cycle counter from hw
* @wx: pointer to the wx structure
*
* This function should be called to set the proper values for the TSC_1588_INC
* register and tell the cyclecounter structure what the tick rate of SYSTIME
* is. It does not directly modify SYSTIME registers or the timecounter
* structure. It should be called whenever a new TSC_1588_INC value is
* necessary, such as during initialization or when the link speed changes.
*/
void wx_ptp_reset_cyclecounter(struct wx *wx)
{
u32 incval = 0, mask = 0;
struct cyclecounter cc;
unsigned long flags;
/* For some of the boards below this mask is technically incorrect.
* The timestamp mask overflows at approximately 61bits. However the
* particular hardware does not overflow on an even bitmask value.
* Instead, it overflows due to conversion of upper 32bits billions of
* cycles. Timecounters are not really intended for this purpose so
* they do not properly function if the overflow point isn't 2^N-1.
* However, the actual SYSTIME values in question take ~138 years to
* overflow. In practice this means they won't actually overflow. A
* proper fix to this problem would require modification of the
* timecounter delta calculations.
*/
cc.mask = CLOCKSOURCE_MASK(64);
cc.mult = 1;
cc.shift = 0;
cc.read = wx_ptp_read;
wx_ptp_link_speed_adjust(wx, &cc.shift, &incval);
/* update the base incval used to calculate frequency adjustment */
WRITE_ONCE(wx->base_incval, incval);
mask = (wx->mac.type == wx_mac_em) ? 0x7FFFFFF : 0xFFFFFF;
incval &= mask;
if (wx->mac.type != wx_mac_em)
incval |= 2 << 24;
wr32ptp(wx, WX_TSC_1588_INC, incval);
smp_mb(); /* Force the above update. */
/* need lock to prevent incorrect read while modifying cyclecounter */
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
memcpy(&wx->hw_cc, &cc, sizeof(wx->hw_cc));
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
}
EXPORT_SYMBOL(wx_ptp_reset_cyclecounter);
void wx_ptp_reset(struct wx *wx)
{
unsigned long flags;
/* reset the hardware timestamping mode */
wx_ptp_set_timestamp_mode(wx, &wx->tstamp_config);
wx_ptp_reset_cyclecounter(wx);
wr32ptp(wx, WX_TSC_1588_SYSTIML, 0);
wr32ptp(wx, WX_TSC_1588_SYSTIMH, 0);
WX_WRITE_FLUSH(wx);
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
timecounter_init(&wx->hw_tc, &wx->hw_cc,
ktime_to_ns(ktime_get_real()));
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
wx->last_overflow_check = jiffies;
ptp_schedule_worker(wx->ptp_clock, HZ);
/* Now that the shift has been calculated and the systime
* registers reset, (re-)enable the Clock out feature
*/
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
}
EXPORT_SYMBOL(wx_ptp_reset);
void wx_ptp_init(struct wx *wx)
{
/* Initialize the seqlock_t first, since the user might call the clock
* functions any time after we've initialized the ptp clock device.
*/
seqlock_init(&wx->hw_tc_lock);
/* obtain a ptp clock device, or re-use an existing device */
if (wx_ptp_create_clock(wx))
return;
wx->tx_hwtstamp_pkts = 0;
wx->tx_hwtstamp_timeouts = 0;
wx->tx_hwtstamp_skipped = 0;
wx->tx_hwtstamp_errors = 0;
wx->rx_hwtstamp_cleared = 0;
/* reset the ptp related hardware bits */
wx_ptp_reset(wx);
/* enter the WX_STATE_PTP_RUNNING state */
set_bit(WX_STATE_PTP_RUNNING, wx->state);
}
EXPORT_SYMBOL(wx_ptp_init);
/**
* wx_ptp_suspend - stop ptp work items
* @wx: pointer to wx struct
*
* This function suspends ptp activity, and prevents more work from being
* generated, but does not destroy the clock device.
*/
void wx_ptp_suspend(struct wx *wx)
{
/* leave the WX_STATE_PTP_RUNNING STATE */
if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
return;
clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
wx_ptp_clear_tx_timestamp(wx);
}
EXPORT_SYMBOL(wx_ptp_suspend);
/**
* wx_ptp_stop - destroy the ptp_clock device
* @wx: pointer to wx struct
*
* Completely destroy the ptp_clock device, and disable all PTP related
* features. Intended to be run when the device is being closed.
*/
void wx_ptp_stop(struct wx *wx)
{
/* first, suspend ptp activity */
wx_ptp_suspend(wx);
/* now destroy the ptp clock device */
if (wx->ptp_clock) {
ptp_clock_unregister(wx->ptp_clock);
wx->ptp_clock = NULL;
dev_info(&wx->pdev->dev, "removed PHC on %s\n", wx->netdev->name);
}
}
EXPORT_SYMBOL(wx_ptp_stop);
/**
* wx_ptp_rx_hwtstamp - utility function which checks for RX time stamp
* @wx: pointer to wx struct
* @skb: particular skb to send timestamp with
*
* if the timestamp is valid, we convert it into the timecounter ns
* value, then store that result into the shhwtstamps structure which
* is passed up the network stack
*/
void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb)
{
u64 regval = 0;
u32 tsyncrxctl;
/* Read the tsyncrxctl register afterwards in order to prevent taking an
* I/O hit on every packet.
*/
tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID))
return;
regval |= (u64)rd32(wx, WX_PSR_1588_STMPL);
regval |= (u64)rd32(wx, WX_PSR_1588_STMPH) << 32;
wx_ptp_convert_to_hwtstamp(wx, skb_hwtstamps(skb), regval);
}
int wx_hwtstamp_get(struct net_device *dev,
struct kernel_hwtstamp_config *cfg)
{
struct wx *wx = netdev_priv(dev);
if (!netif_running(dev))
return -EINVAL;
*cfg = wx->tstamp_config;
return 0;
}
EXPORT_SYMBOL(wx_hwtstamp_get);
int wx_hwtstamp_set(struct net_device *dev,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(dev);
int err;
if (!netif_running(dev))
return -EINVAL;
err = wx_ptp_set_timestamp_mode(wx, cfg);
if (err)
return err;
/* save these settings for future reference */
memcpy(&wx->tstamp_config, cfg, sizeof(wx->tstamp_config));
return 0;
}
EXPORT_SYMBOL(wx_hwtstamp_set);
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2019 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_PTP_H_
#define _WX_PTP_H_
void wx_ptp_check_pps_event(struct wx *wx);
void wx_ptp_reset_cyclecounter(struct wx *wx);
void wx_ptp_reset(struct wx *wx);
void wx_ptp_init(struct wx *wx);
void wx_ptp_suspend(struct wx *wx);
void wx_ptp_stop(struct wx *wx);
void wx_ptp_rx_hwtstamp(struct wx *wx, struct sk_buff *skb);
int wx_hwtstamp_get(struct net_device *dev,
struct kernel_hwtstamp_config *cfg);
int wx_hwtstamp_set(struct net_device *dev,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack);
#endif /* _WX_PTP_H_ */
@@ -0,0 +1,927 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_hw.h"
#include "wx_mbx.h"
#include "wx_sriov.h"
static void wx_vf_configuration(struct pci_dev *pdev, int event_mask)
{
bool enable = !!WX_VF_ENABLE_CHECK(event_mask);
struct wx *wx = pci_get_drvdata(pdev);
u32 vfn = WX_VF_NUM_GET(event_mask);
if (enable)
eth_zero_addr(wx->vfinfo[vfn].vf_mac_addr);
}
static int wx_alloc_vf_macvlans(struct wx *wx, u8 num_vfs)
{
struct vf_macvlans *mv_list;
int num_vf_macvlans, i;
/* Initialize list of VF macvlans */
INIT_LIST_HEAD(&wx->vf_mvs.mvlist);
num_vf_macvlans = wx->mac.num_rar_entries -
(WX_MAX_PF_MACVLANS + 1 + num_vfs);
if (!num_vf_macvlans)
return -EINVAL;
mv_list = kzalloc_objs(struct vf_macvlans, num_vf_macvlans);
if (!mv_list)
return -ENOMEM;
for (i = 0; i < num_vf_macvlans; i++) {
mv_list[i].vf = -1;
mv_list[i].free = true;
list_add(&mv_list[i].mvlist, &wx->vf_mvs.mvlist);
}
wx->mv_list = mv_list;
return 0;
}
static void wx_sriov_clear_data(struct wx *wx)
{
/* set num VFs to 0 to prevent access to vfinfo */
wx->num_vfs = 0;
/* free VF control structures */
kfree(wx->vfinfo);
wx->vfinfo = NULL;
/* free macvlan list */
kfree(wx->mv_list);
wx->mv_list = NULL;
/* set default pool back to 0 */
wr32m(wx, WX_PSR_VM_CTL, WX_PSR_VM_CTL_POOL_MASK, 0);
wx->ring_feature[RING_F_VMDQ].offset = 0;
clear_bit(WX_FLAG_IRQ_VECTOR_SHARED, wx->flags);
clear_bit(WX_FLAG_SRIOV_ENABLED, wx->flags);
/* Disable VMDq flag so device will be set in NM mode */
if (wx->ring_feature[RING_F_VMDQ].limit == 1)
clear_bit(WX_FLAG_VMDQ_ENABLED, wx->flags);
}
static int __wx_enable_sriov(struct wx *wx, u8 num_vfs)
{
int i, ret = 0;
u32 value = 0;
set_bit(WX_FLAG_SRIOV_ENABLED, wx->flags);
dev_info(&wx->pdev->dev, "SR-IOV enabled with %d VFs\n", num_vfs);
if (num_vfs == 7 && wx->mac.type == wx_mac_em)
set_bit(WX_FLAG_IRQ_VECTOR_SHARED, wx->flags);
/* Enable VMDq flag so device will be set in VM mode */
set_bit(WX_FLAG_VMDQ_ENABLED, wx->flags);
if (!wx->ring_feature[RING_F_VMDQ].limit)
wx->ring_feature[RING_F_VMDQ].limit = 1;
wx->ring_feature[RING_F_VMDQ].offset = num_vfs;
wx->vfinfo = kzalloc_objs(struct vf_data_storage, num_vfs);
if (!wx->vfinfo)
return -ENOMEM;
ret = wx_alloc_vf_macvlans(wx, num_vfs);
if (ret)
return ret;
/* Initialize default switching mode VEB */
wr32m(wx, WX_PSR_CTL, WX_PSR_CTL_SW_EN, WX_PSR_CTL_SW_EN);
for (i = 0; i < num_vfs; i++) {
/* enable spoof checking for all VFs */
wx->vfinfo[i].spoofchk_enabled = true;
wx->vfinfo[i].link_enable = true;
/* untrust all VFs */
wx->vfinfo[i].trusted = false;
/* set the default xcast mode */
wx->vfinfo[i].xcast_mode = WXVF_XCAST_MODE_NONE;
}
if (!test_bit(WX_FLAG_MULTI_64_FUNC, wx->flags)) {
value = WX_CFG_PORT_CTL_NUM_VT_8;
} else {
if (num_vfs < 32)
value = WX_CFG_PORT_CTL_NUM_VT_32;
else
value = WX_CFG_PORT_CTL_NUM_VT_64;
}
wr32m(wx, WX_CFG_PORT_CTL,
WX_CFG_PORT_CTL_NUM_VT_MASK,
value);
/* Disable RSC when in SR-IOV mode */
clear_bit(WX_FLAG_RSC_CAPABLE, wx->flags);
clear_bit(WX_FLAG_RSC_ENABLED, wx->flags);
return ret;
}
static void wx_sriov_reinit(struct wx *wx)
{
rtnl_lock();
wx->setup_tc(wx->netdev, netdev_get_num_tc(wx->netdev));
rtnl_unlock();
}
void wx_disable_sriov(struct wx *wx)
{
if (!pci_vfs_assigned(wx->pdev))
pci_disable_sriov(wx->pdev);
else
wx_err(wx, "Unloading driver while VFs are assigned.\n");
/* clear flags and free allloced data */
wx_sriov_clear_data(wx);
}
EXPORT_SYMBOL(wx_disable_sriov);
static int wx_pci_sriov_enable(struct pci_dev *dev,
int num_vfs)
{
struct wx *wx = pci_get_drvdata(dev);
int err = 0, i;
if (netif_is_rxfh_configured(wx->netdev)) {
wx_err(wx, "Cannot enable SR-IOV while RXFH is configured\n");
wx_err(wx, "Run 'ethtool -X <if> default' to reset RSS table\n");
return -EBUSY;
}
err = __wx_enable_sriov(wx, num_vfs);
if (err)
return err;
wx->num_vfs = num_vfs;
for (i = 0; i < wx->num_vfs; i++)
wx_vf_configuration(dev, (i | WX_VF_ENABLE));
/* reset before enabling SRIOV to avoid mailbox issues */
wx_sriov_reinit(wx);
err = pci_enable_sriov(dev, num_vfs);
if (err) {
wx_err(wx, "Failed to enable PCI sriov: %d\n", err);
goto err_out;
}
return num_vfs;
err_out:
wx_sriov_clear_data(wx);
return err;
}
static int wx_pci_sriov_disable(struct pci_dev *dev)
{
struct wx *wx = pci_get_drvdata(dev);
if (netif_is_rxfh_configured(wx->netdev)) {
wx_err(wx, "Cannot disable SR-IOV while RXFH is configured\n");
wx_err(wx, "Run 'ethtool -X <if> default' to reset RSS table\n");
return -EBUSY;
}
wx_disable_sriov(wx);
wx_sriov_reinit(wx);
return 0;
}
int wx_pci_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
struct wx *wx = pci_get_drvdata(pdev);
int err;
if (!num_vfs) {
if (!pci_vfs_assigned(pdev))
return wx_pci_sriov_disable(pdev);
wx_err(wx, "can't free VFs because some are assigned to VMs.\n");
return -EBUSY;
}
err = wx_pci_sriov_enable(pdev, num_vfs);
if (err)
return err;
return num_vfs;
}
EXPORT_SYMBOL(wx_pci_sriov_configure);
static int wx_set_vf_mac(struct wx *wx, u16 vf, const u8 *mac_addr)
{
u8 hw_addr[ETH_ALEN];
int ret = 0;
ether_addr_copy(hw_addr, mac_addr);
wx_del_mac_filter(wx, wx->vfinfo[vf].vf_mac_addr, vf);
ret = wx_add_mac_filter(wx, hw_addr, vf);
if (ret >= 0)
ether_addr_copy(wx->vfinfo[vf].vf_mac_addr, mac_addr);
else
eth_zero_addr(wx->vfinfo[vf].vf_mac_addr);
return ret;
}
static void wx_set_vmolr(struct wx *wx, u16 vf, bool aupe)
{
u32 vmolr = rd32(wx, WX_PSR_VM_L2CTL(vf));
vmolr |= WX_PSR_VM_L2CTL_BAM;
if (aupe)
vmolr |= WX_PSR_VM_L2CTL_AUPE;
else
vmolr &= ~WX_PSR_VM_L2CTL_AUPE;
wr32(wx, WX_PSR_VM_L2CTL(vf), vmolr);
}
static void wx_set_vmvir(struct wx *wx, u16 vid, u16 qos, u16 vf)
{
u32 vmvir = vid | (qos << VLAN_PRIO_SHIFT) |
WX_TDM_VLAN_INS_VLANA_DEFAULT;
wr32(wx, WX_TDM_VLAN_INS(vf), vmvir);
}
static int wx_set_vf_vlan(struct wx *wx, int add, int vid, u16 vf)
{
if (!vid && !add)
return 0;
return wx_set_vfta(wx, vid, vf, (bool)add);
}
static void wx_set_vlan_anti_spoofing(struct wx *wx, bool enable, int vf)
{
u32 index = WX_VF_REG_OFFSET(vf), vf_bit = WX_VF_IND_SHIFT(vf);
u32 pfvfspoof;
pfvfspoof = rd32(wx, WX_TDM_VLAN_AS(index));
if (enable)
pfvfspoof |= BIT(vf_bit);
else
pfvfspoof &= ~BIT(vf_bit);
wr32(wx, WX_TDM_VLAN_AS(index), pfvfspoof);
}
static void wx_write_qde(struct wx *wx, u32 vf, u32 qde)
{
struct wx_ring_feature *vmdq = &wx->ring_feature[RING_F_VMDQ];
u32 q_per_pool = __ALIGN_MASK(1, ~vmdq->mask);
u32 reg = 0, n = vf * q_per_pool / 32;
u32 i = vf * q_per_pool;
reg = rd32(wx, WX_RDM_PF_QDE(n));
for (i = (vf * q_per_pool - n * 32);
i < ((vf + 1) * q_per_pool - n * 32);
i++) {
if (qde == 1)
reg |= qde << i;
else
reg &= qde << i;
}
wr32(wx, WX_RDM_PF_QDE(n), reg);
}
static void wx_clear_vmvir(struct wx *wx, u32 vf)
{
wr32(wx, WX_TDM_VLAN_INS(vf), 0);
}
static void wx_ping_vf(struct wx *wx, int vf)
{
u32 ping = WX_PF_CONTROL_MSG;
if (wx->vfinfo[vf].clear_to_send)
ping |= WX_VT_MSGTYPE_CTS;
wx_write_mbx_pf(wx, &ping, 1, vf);
}
static void wx_set_vf_rx_tx(struct wx *wx, int vf)
{
u32 index = WX_VF_REG_OFFSET(vf), vf_bit = WX_VF_IND_SHIFT(vf);
u32 reg_cur_tx, reg_cur_rx, reg_req_tx, reg_req_rx;
reg_cur_tx = rd32(wx, WX_TDM_VF_TE(index));
reg_cur_rx = rd32(wx, WX_RDM_VF_RE(index));
if (wx->vfinfo[vf].link_enable) {
reg_req_tx = reg_cur_tx | BIT(vf_bit);
reg_req_rx = reg_cur_rx | BIT(vf_bit);
/* Enable particular VF */
if (reg_cur_tx != reg_req_tx)
wr32(wx, WX_TDM_VF_TE(index), reg_req_tx);
if (reg_cur_rx != reg_req_rx)
wr32(wx, WX_RDM_VF_RE(index), reg_req_rx);
} else {
reg_req_tx = BIT(vf_bit);
reg_req_rx = BIT(vf_bit);
/* Disable particular VF */
if (reg_cur_tx & reg_req_tx)
wr32(wx, WX_TDM_VFTE_CLR(index), reg_req_tx);
if (reg_cur_rx & reg_req_rx)
wr32(wx, WX_RDM_VFRE_CLR(index), reg_req_rx);
}
}
static int wx_get_vf_queues(struct wx *wx, u32 *msgbuf, u32 vf)
{
struct wx_ring_feature *vmdq = &wx->ring_feature[RING_F_VMDQ];
unsigned int default_tc = 0;
msgbuf[WX_VF_TX_QUEUES] = __ALIGN_MASK(1, ~vmdq->mask);
msgbuf[WX_VF_RX_QUEUES] = __ALIGN_MASK(1, ~vmdq->mask);
if (wx->vfinfo[vf].pf_vlan || wx->vfinfo[vf].pf_qos)
msgbuf[WX_VF_TRANS_VLAN] = 1;
else
msgbuf[WX_VF_TRANS_VLAN] = 0;
/* notify VF of default queue */
msgbuf[WX_VF_DEF_QUEUE] = default_tc;
return 0;
}
static void wx_vf_reset_event(struct wx *wx, u16 vf)
{
struct vf_data_storage *vfinfo = &wx->vfinfo[vf];
u8 num_tcs = netdev_get_num_tc(wx->netdev);
/* add PF assigned VLAN */
wx_set_vf_vlan(wx, true, vfinfo->pf_vlan, vf);
/* reset offloads to defaults */
wx_set_vmolr(wx, vf, !vfinfo->pf_vlan);
/* set outgoing tags for VFs */
if (!vfinfo->pf_vlan && !vfinfo->pf_qos && !num_tcs) {
wx_clear_vmvir(wx, vf);
} else {
if (vfinfo->pf_qos || !num_tcs)
wx_set_vmvir(wx, vfinfo->pf_vlan,
vfinfo->pf_qos, vf);
else
wx_set_vmvir(wx, vfinfo->pf_vlan,
wx->default_up, vf);
}
/* reset multicast table array for vf */
wx->vfinfo[vf].num_vf_mc_hashes = 0;
/* Flush and reset the mta with the new values */
wx_set_rx_mode(wx->netdev);
wx_del_mac_filter(wx, wx->vfinfo[vf].vf_mac_addr, vf);
/* reset VF api back to unknown */
wx->vfinfo[vf].vf_api = wx_mbox_api_null;
}
static void wx_vf_reset_msg(struct wx *wx, u16 vf)
{
const u8 *vf_mac = wx->vfinfo[vf].vf_mac_addr;
struct net_device *dev = wx->netdev;
u32 msgbuf[5] = {0, 0, 0, 0, 0};
u8 *addr = (u8 *)(&msgbuf[1]);
u32 reg = 0, index, vf_bit;
int pf_max_frame;
/* reset the filters for the device */
wx_vf_reset_event(wx, vf);
/* set vf mac address */
if (!is_zero_ether_addr(vf_mac))
wx_set_vf_mac(wx, vf, vf_mac);
index = WX_VF_REG_OFFSET(vf);
vf_bit = WX_VF_IND_SHIFT(vf);
/* force drop enable for all VF Rx queues */
wx_write_qde(wx, vf, 1);
/* set transmit and receive for vf */
wx_set_vf_rx_tx(wx, vf);
pf_max_frame = dev->mtu + ETH_HLEN;
if (pf_max_frame > ETH_FRAME_LEN)
reg = BIT(vf_bit);
wr32(wx, WX_RDM_VFRE_CLR(index), reg);
/* enable VF mailbox for further messages */
wx->vfinfo[vf].clear_to_send = true;
/* reply to reset with ack and vf mac address */
msgbuf[0] = WX_VF_RESET;
if (!is_zero_ether_addr(vf_mac)) {
msgbuf[0] |= WX_VT_MSGTYPE_ACK;
memcpy(addr, vf_mac, ETH_ALEN);
} else {
msgbuf[0] |= WX_VT_MSGTYPE_NACK;
wx_err(wx, "VF %d has no MAC address assigned", vf);
}
msgbuf[3] = wx->mac.mc_filter_type;
wx_write_mbx_pf(wx, msgbuf, WX_VF_PERMADDR_MSG_LEN, vf);
}
static int wx_set_vf_mac_addr(struct wx *wx, u32 *msgbuf, u16 vf)
{
const u8 *new_mac = ((u8 *)(&msgbuf[1]));
int ret;
if (!is_valid_ether_addr(new_mac)) {
wx_err(wx, "VF %d attempted to set invalid mac\n", vf);
return -EINVAL;
}
if (wx->vfinfo[vf].pf_set_mac &&
memcmp(wx->vfinfo[vf].vf_mac_addr, new_mac, ETH_ALEN)) {
wx_err(wx,
"VF %d attempt to set a MAC but it already had a MAC.",
vf);
return -EBUSY;
}
ret = wx_set_vf_mac(wx, vf, new_mac);
if (ret < 0)
return ret;
return 0;
}
static void wx_set_vf_multicasts(struct wx *wx, u32 *msgbuf, u32 vf)
{
struct vf_data_storage *vfinfo = &wx->vfinfo[vf];
u16 entries = (msgbuf[0] & WX_VT_MSGINFO_MASK)
>> WX_VT_MSGINFO_SHIFT;
u32 vmolr = rd32(wx, WX_PSR_VM_L2CTL(vf));
u32 vector_bit, vector_reg, mta_reg, i;
u16 *hash_list = (u16 *)&msgbuf[1];
/* only so many hash values supported */
entries = min_t(u16, entries, WX_MAX_VF_MC_ENTRIES);
vfinfo->num_vf_mc_hashes = entries;
for (i = 0; i < entries; i++)
vfinfo->vf_mc_hashes[i] = hash_list[i];
for (i = 0; i < vfinfo->num_vf_mc_hashes; i++) {
vector_reg = WX_PSR_MC_TBL_REG(vfinfo->vf_mc_hashes[i]);
vector_bit = WX_PSR_MC_TBL_BIT(vfinfo->vf_mc_hashes[i]);
mta_reg = wx->mac.mta_shadow[vector_reg];
mta_reg |= BIT(vector_bit);
wx->mac.mta_shadow[vector_reg] = mta_reg;
wr32(wx, WX_PSR_MC_TBL(vector_reg), mta_reg);
}
vmolr |= WX_PSR_VM_L2CTL_ROMPE;
wr32(wx, WX_PSR_VM_L2CTL(vf), vmolr);
}
static void wx_set_vf_lpe(struct wx *wx, u32 max_frame, u32 vf)
{
u32 index, vf_bit, vfre;
u32 max_frs, reg_val;
/* determine VF receive enable location */
index = WX_VF_REG_OFFSET(vf);
vf_bit = WX_VF_IND_SHIFT(vf);
vfre = rd32(wx, WX_RDM_VF_RE(index));
vfre |= BIT(vf_bit);
wr32(wx, WX_RDM_VF_RE(index), vfre);
/* pull current max frame size from hardware */
max_frs = DIV_ROUND_UP(max_frame, 1024);
reg_val = rd32(wx, WX_MAC_WDG_TIMEOUT) & WX_MAC_WDG_TIMEOUT_WTO_MASK;
if (max_frs > (reg_val + WX_MAC_WDG_TIMEOUT_WTO_DELTA))
wr32(wx, WX_MAC_WDG_TIMEOUT,
max_frs - WX_MAC_WDG_TIMEOUT_WTO_DELTA);
}
static int wx_find_vlvf_entry(struct wx *wx, u32 vlan)
{
int regindex;
u32 vlvf;
/* short cut the special case */
if (vlan == 0)
return 0;
/* Search for the vlan id in the VLVF entries */
for (regindex = 1; regindex < WX_PSR_VLAN_SWC_ENTRIES; regindex++) {
wr32(wx, WX_PSR_VLAN_SWC_IDX, regindex);
vlvf = rd32(wx, WX_PSR_VLAN_SWC);
if ((vlvf & VLAN_VID_MASK) == vlan)
break;
}
/* Return a negative value if not found */
if (regindex >= WX_PSR_VLAN_SWC_ENTRIES)
regindex = -EINVAL;
return regindex;
}
static int wx_set_vf_macvlan(struct wx *wx,
u16 vf, int index, unsigned char *mac_addr)
{
struct vf_macvlans *entry;
struct list_head *pos;
int retval = 0;
if (index <= 1) {
list_for_each(pos, &wx->vf_mvs.mvlist) {
entry = list_entry(pos, struct vf_macvlans, mvlist);
if (entry->vf == vf) {
entry->vf = -1;
entry->free = true;
entry->is_macvlan = false;
wx_del_mac_filter(wx, entry->vf_macvlan, vf);
}
}
}
if (!index)
return 0;
entry = NULL;
list_for_each(pos, &wx->vf_mvs.mvlist) {
entry = list_entry(pos, struct vf_macvlans, mvlist);
if (entry->free)
break;
}
if (!entry || !entry->free)
return -ENOSPC;
retval = wx_add_mac_filter(wx, mac_addr, vf);
if (retval >= 0) {
entry->free = false;
entry->is_macvlan = true;
entry->vf = vf;
memcpy(entry->vf_macvlan, mac_addr, ETH_ALEN);
}
return retval;
}
static int wx_set_vf_vlan_msg(struct wx *wx, u32 *msgbuf, u16 vf)
{
int add = (msgbuf[0] & WX_VT_MSGINFO_MASK) >> WX_VT_MSGINFO_SHIFT;
int vid = (msgbuf[1] & WX_PSR_VLAN_SWC_VLANID_MASK);
int ret;
if (add)
wx->vfinfo[vf].vlan_count++;
else if (wx->vfinfo[vf].vlan_count)
wx->vfinfo[vf].vlan_count--;
/* in case of promiscuous mode any VLAN filter set for a VF must
* also have the PF pool added to it.
*/
if (add && wx->netdev->flags & IFF_PROMISC)
wx_set_vf_vlan(wx, add, vid, VMDQ_P(0));
ret = wx_set_vf_vlan(wx, add, vid, vf);
if (!ret && wx->vfinfo[vf].spoofchk_enabled)
wx_set_vlan_anti_spoofing(wx, true, vf);
/* Go through all the checks to see if the VLAN filter should
* be wiped completely.
*/
if (!add && wx->netdev->flags & IFF_PROMISC) {
u32 bits = 0, vlvf;
int reg_ndx;
reg_ndx = wx_find_vlvf_entry(wx, vid);
if (reg_ndx < 0)
return -ENOSPC;
wr32(wx, WX_PSR_VLAN_SWC_IDX, reg_ndx);
vlvf = rd32(wx, WX_PSR_VLAN_SWC);
/* See if any other pools are set for this VLAN filter
* entry other than the PF.
*/
if (VMDQ_P(0) < 32) {
bits = rd32(wx, WX_PSR_VLAN_SWC_VM_L);
bits &= ~BIT(VMDQ_P(0));
if (test_bit(WX_FLAG_MULTI_64_FUNC, wx->flags))
bits |= rd32(wx, WX_PSR_VLAN_SWC_VM_H);
} else {
if (test_bit(WX_FLAG_MULTI_64_FUNC, wx->flags))
bits = rd32(wx, WX_PSR_VLAN_SWC_VM_H);
bits &= ~BIT(VMDQ_P(0) % 32);
bits |= rd32(wx, WX_PSR_VLAN_SWC_VM_L);
}
/* If the filter was removed then ensure PF pool bit
* is cleared if the PF only added itself to the pool
* because the PF is in promiscuous mode.
*/
if ((vlvf & VLAN_VID_MASK) == vid && !bits)
wx_set_vf_vlan(wx, add, vid, VMDQ_P(0));
}
return 0;
}
static int wx_set_vf_macvlan_msg(struct wx *wx, u32 *msgbuf, u16 vf)
{
int index = (msgbuf[0] & WX_VT_MSGINFO_MASK) >>
WX_VT_MSGINFO_SHIFT;
u8 *new_mac = ((u8 *)(&msgbuf[1]));
int err;
if (wx->vfinfo[vf].pf_set_mac && index > 0) {
wx_err(wx, "VF %d request MACVLAN filter but is denied\n", vf);
return -EINVAL;
}
/* An non-zero index indicates the VF is setting a filter */
if (index) {
if (!is_valid_ether_addr(new_mac)) {
wx_err(wx, "VF %d attempted to set invalid mac\n", vf);
return -EINVAL;
}
/* If the VF is allowed to set MAC filters then turn off
* anti-spoofing to avoid false positives.
*/
if (wx->vfinfo[vf].spoofchk_enabled)
wx_set_vf_spoofchk(wx->netdev, vf, false);
}
err = wx_set_vf_macvlan(wx, vf, index, new_mac);
if (err == -ENOSPC)
wx_err(wx,
"VF %d request MACVLAN filter but there is no space\n",
vf);
if (err < 0)
return err;
return 0;
}
static int wx_negotiate_vf_api(struct wx *wx, u32 *msgbuf, u32 vf)
{
int api = msgbuf[1];
switch (api) {
case wx_mbox_api_13:
wx->vfinfo[vf].vf_api = api;
return 0;
default:
wx_err(wx, "VF %d requested invalid api version %u\n", vf, api);
return -EINVAL;
}
}
static int wx_get_vf_link_state(struct wx *wx, u32 *msgbuf, u32 vf)
{
msgbuf[1] = wx->vfinfo[vf].link_enable;
return 0;
}
static int wx_get_fw_version(struct wx *wx, u32 *msgbuf, u32 vf)
{
unsigned long fw_version = 0ULL;
int ret = 0;
ret = kstrtoul(wx->eeprom_id, 16, &fw_version);
if (ret)
return -EOPNOTSUPP;
msgbuf[1] = fw_version;
return 0;
}
static int wx_update_vf_xcast_mode(struct wx *wx, u32 *msgbuf, u32 vf)
{
int xcast_mode = msgbuf[1];
u32 vmolr, disable, enable;
if (wx->vfinfo[vf].xcast_mode == xcast_mode)
return 0;
switch (xcast_mode) {
case WXVF_XCAST_MODE_NONE:
disable = WX_PSR_VM_L2CTL_BAM | WX_PSR_VM_L2CTL_ROMPE |
WX_PSR_VM_L2CTL_MPE | WX_PSR_VM_L2CTL_UPE |
WX_PSR_VM_L2CTL_VPE;
enable = 0;
break;
case WXVF_XCAST_MODE_MULTI:
disable = WX_PSR_VM_L2CTL_MPE | WX_PSR_VM_L2CTL_UPE |
WX_PSR_VM_L2CTL_VPE;
enable = WX_PSR_VM_L2CTL_BAM | WX_PSR_VM_L2CTL_ROMPE;
break;
case WXVF_XCAST_MODE_ALLMULTI:
disable = WX_PSR_VM_L2CTL_UPE | WX_PSR_VM_L2CTL_VPE;
enable = WX_PSR_VM_L2CTL_BAM | WX_PSR_VM_L2CTL_ROMPE |
WX_PSR_VM_L2CTL_MPE;
break;
case WXVF_XCAST_MODE_PROMISC:
disable = 0;
enable = WX_PSR_VM_L2CTL_BAM | WX_PSR_VM_L2CTL_ROMPE |
WX_PSR_VM_L2CTL_MPE | WX_PSR_VM_L2CTL_UPE |
WX_PSR_VM_L2CTL_VPE;
break;
default:
return -EOPNOTSUPP;
}
vmolr = rd32(wx, WX_PSR_VM_L2CTL(vf));
vmolr &= ~disable;
vmolr |= enable;
wr32(wx, WX_PSR_VM_L2CTL(vf), vmolr);
wx->vfinfo[vf].xcast_mode = xcast_mode;
msgbuf[1] = xcast_mode;
return 0;
}
static void wx_rcv_msg_from_vf(struct wx *wx, u16 vf)
{
u16 mbx_size = WX_VXMAILBOX_SIZE;
u32 msgbuf[WX_VXMAILBOX_SIZE];
int retval;
retval = wx_read_mbx_pf(wx, msgbuf, mbx_size, vf);
if (retval) {
wx_err(wx, "Error receiving message from VF\n");
return;
}
/* this is a message we already processed, do nothing */
if (msgbuf[0] & (WX_VT_MSGTYPE_ACK | WX_VT_MSGTYPE_NACK))
return;
if (msgbuf[0] == WX_VF_RESET) {
wx_vf_reset_msg(wx, vf);
return;
}
/* until the vf completes a virtual function reset it should not be
* allowed to start any configuration.
*/
if (!wx->vfinfo[vf].clear_to_send) {
msgbuf[0] |= WX_VT_MSGTYPE_NACK;
wx_write_mbx_pf(wx, msgbuf, 1, vf);
return;
}
switch ((msgbuf[0] & U16_MAX)) {
case WX_VF_SET_MAC_ADDR:
retval = wx_set_vf_mac_addr(wx, msgbuf, vf);
break;
case WX_VF_SET_MULTICAST:
wx_set_vf_multicasts(wx, msgbuf, vf);
retval = 0;
break;
case WX_VF_SET_VLAN:
retval = wx_set_vf_vlan_msg(wx, msgbuf, vf);
break;
case WX_VF_SET_LPE:
wx_set_vf_lpe(wx, msgbuf[1], vf);
retval = 0;
break;
case WX_VF_SET_MACVLAN:
retval = wx_set_vf_macvlan_msg(wx, msgbuf, vf);
break;
case WX_VF_API_NEGOTIATE:
retval = wx_negotiate_vf_api(wx, msgbuf, vf);
break;
case WX_VF_GET_QUEUES:
retval = wx_get_vf_queues(wx, msgbuf, vf);
break;
case WX_VF_GET_LINK_STATE:
retval = wx_get_vf_link_state(wx, msgbuf, vf);
break;
case WX_VF_GET_FW_VERSION:
retval = wx_get_fw_version(wx, msgbuf, vf);
break;
case WX_VF_UPDATE_XCAST_MODE:
retval = wx_update_vf_xcast_mode(wx, msgbuf, vf);
break;
case WX_VF_BACKUP:
break;
default:
wx_err(wx, "Unhandled Msg %8.8x\n", msgbuf[0]);
break;
}
/* notify the VF of the results of what it sent us */
if (retval)
msgbuf[0] |= WX_VT_MSGTYPE_NACK;
else
msgbuf[0] |= WX_VT_MSGTYPE_ACK;
msgbuf[0] |= WX_VT_MSGTYPE_CTS;
wx_write_mbx_pf(wx, msgbuf, mbx_size, vf);
}
static void wx_rcv_ack_from_vf(struct wx *wx, u16 vf)
{
u32 msg = WX_VT_MSGTYPE_NACK;
/* if device isn't clear to send it shouldn't be reading either */
if (!wx->vfinfo[vf].clear_to_send)
wx_write_mbx_pf(wx, &msg, 1, vf);
}
void wx_msg_task(struct wx *wx)
{
u16 vf;
for (vf = 0; vf < wx->num_vfs; vf++) {
/* process any reset requests */
if (!wx_check_for_rst_pf(wx, vf))
wx_vf_reset_event(wx, vf);
/* process any messages pending */
if (!wx_check_for_msg_pf(wx, vf))
wx_rcv_msg_from_vf(wx, vf);
/* process any acks */
if (!wx_check_for_ack_pf(wx, vf))
wx_rcv_ack_from_vf(wx, vf);
}
}
EXPORT_SYMBOL(wx_msg_task);
void wx_disable_vf_rx_tx(struct wx *wx)
{
wr32(wx, WX_TDM_VFTE_CLR(0), U32_MAX);
wr32(wx, WX_RDM_VFRE_CLR(0), U32_MAX);
if (test_bit(WX_FLAG_MULTI_64_FUNC, wx->flags)) {
wr32(wx, WX_TDM_VFTE_CLR(1), U32_MAX);
wr32(wx, WX_RDM_VFRE_CLR(1), U32_MAX);
}
}
EXPORT_SYMBOL(wx_disable_vf_rx_tx);
void wx_ping_all_vfs_with_link_status(struct wx *wx, bool link_up)
{
u32 msgbuf[2] = {0, 0};
u16 i;
if (!wx->num_vfs)
return;
msgbuf[0] = WX_PF_NOFITY_VF_LINK_STATUS | WX_PF_CONTROL_MSG;
if (link_up)
msgbuf[1] = FIELD_PREP(GENMASK(31, 1), wx->speed) | link_up;
if (wx->notify_down)
msgbuf[1] |= WX_PF_NOFITY_VF_NET_NOT_RUNNING;
for (i = 0; i < wx->num_vfs; i++) {
if (wx->vfinfo[i].clear_to_send)
msgbuf[0] |= WX_VT_MSGTYPE_CTS;
wx_write_mbx_pf(wx, msgbuf, 2, i);
}
}
EXPORT_SYMBOL(wx_ping_all_vfs_with_link_status);
static void wx_set_vf_link_state(struct wx *wx, int vf, int state)
{
wx->vfinfo[vf].link_state = state;
switch (state) {
case IFLA_VF_LINK_STATE_AUTO:
if (!test_bit(WX_STATE_DOWN, wx->state))
wx->vfinfo[vf].link_enable = true;
else
wx->vfinfo[vf].link_enable = false;
break;
case IFLA_VF_LINK_STATE_ENABLE:
wx->vfinfo[vf].link_enable = true;
break;
case IFLA_VF_LINK_STATE_DISABLE:
wx->vfinfo[vf].link_enable = false;
break;
}
/* restart the VF */
wx->vfinfo[vf].clear_to_send = false;
wx_ping_vf(wx, vf);
wx_set_vf_rx_tx(wx, vf);
}
void wx_set_all_vfs(struct wx *wx)
{
int i;
for (i = 0; i < wx->num_vfs; i++)
wx_set_vf_link_state(wx, i, wx->vfinfo[i].link_state);
}
EXPORT_SYMBOL(wx_set_all_vfs);
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_SRIOV_H_
#define _WX_SRIOV_H_
#define WX_VF_ENABLE_CHECK(_m) FIELD_GET(BIT(31), (_m))
#define WX_VF_NUM_GET(_m) FIELD_GET(GENMASK(5, 0), (_m))
#define WX_VF_ENABLE BIT(31)
void wx_disable_sriov(struct wx *wx);
int wx_pci_sriov_configure(struct pci_dev *pdev, int num_vfs);
void wx_msg_task(struct wx *wx);
void wx_disable_vf_rx_tx(struct wx *wx);
void wx_ping_all_vfs_with_link_status(struct wx *wx, bool link_up);
void wx_set_all_vfs(struct wx *wx);
#endif /* _WX_SRIOV_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,599 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_hw.h"
#include "wx_mbx.h"
#include "wx_vf.h"
static void wx_virt_clr_reg(struct wx *wx)
{
u32 vfsrrctl, i;
/* VRSRRCTL default values (BSIZEPACKET = 2048, BSIZEHEADER = 256) */
vfsrrctl = WX_VXRXDCTL_HDRSZ(wx_hdr_sz(WX_RX_HDR_SIZE));
vfsrrctl |= WX_VXRXDCTL_BUFSZ(wx_buf_sz(WX_RX_BUF_SIZE));
/* clear all rxd ctl */
for (i = 0; i < WX_VF_MAX_RING_NUMS; i++)
wr32m(wx, WX_VXRXDCTL(i),
WX_VXRXDCTL_HDRSZ_MASK | WX_VXRXDCTL_BUFSZ_MASK,
vfsrrctl);
rd32(wx, WX_VXSTATUS);
}
/**
* wx_init_hw_vf - virtual function hardware initialization
* @wx: pointer to hardware structure
*
* Initialize the mac address
**/
void wx_init_hw_vf(struct wx *wx)
{
wx_get_mac_addr_vf(wx, wx->mac.addr);
}
EXPORT_SYMBOL(wx_init_hw_vf);
static int wx_mbx_write_and_read_reply(struct wx *wx, u32 *req_buf,
u32 *resp_buf, u16 size)
{
int ret;
ret = wx_write_posted_mbx(wx, req_buf, size);
if (ret)
return ret;
return wx_read_posted_mbx(wx, resp_buf, size);
}
/**
* wx_reset_hw_vf - Performs hardware reset
* @wx: pointer to hardware structure
*
* Resets the hardware by resetting the transmit and receive units, masks and
* clears all interrupts.
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_reset_hw_vf(struct wx *wx)
{
struct wx_mbx_info *mbx = &wx->mbx;
u32 msgbuf[4] = {WX_VF_RESET};
u8 *addr = (u8 *)(&msgbuf[1]);
u32 b4_buf[16] = {0};
u32 timeout = 200;
int ret;
u32 i;
/* Call wx stop to disable tx/rx and clear interrupts */
wx_stop_adapter_vf(wx);
/* reset the api version */
wx->vfinfo->vf_api = wx_mbox_api_null;
/* backup msix vectors */
if (wx->b4_addr) {
for (i = 0; i < 16; i++)
b4_buf[i] = readl(wx->b4_addr + i * 4);
}
wr32m(wx, WX_VXCTRL, WX_VXCTRL_RST, WX_VXCTRL_RST);
rd32(wx, WX_VXSTATUS);
/* we cannot reset while the RSTI / RSTD bits are asserted */
while (!wx_check_for_rst_vf(wx) && timeout) {
timeout--;
udelay(5);
}
/* restore msix vectors */
if (wx->b4_addr) {
for (i = 0; i < 16; i++)
writel(b4_buf[i], wx->b4_addr + i * 4);
}
/* amlite: bme */
if (wx->mac.type == wx_mac_aml || wx->mac.type == wx_mac_aml40)
wr32(wx, WX_VX_PF_BME, WX_VF_BME_ENABLE);
if (!timeout)
return -EBUSY;
/* Reset VF registers to initial values */
wx_virt_clr_reg(wx);
/* mailbox timeout can now become active */
mbx->timeout = 2000;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
if (msgbuf[0] != (WX_VF_RESET | WX_VT_MSGTYPE_ACK) &&
msgbuf[0] != (WX_VF_RESET | WX_VT_MSGTYPE_NACK))
return -EINVAL;
if (msgbuf[0] == (WX_VF_RESET | WX_VT_MSGTYPE_ACK))
ether_addr_copy(wx->mac.perm_addr, addr);
wx->mac.mc_filter_type = msgbuf[3];
return 0;
}
EXPORT_SYMBOL(wx_reset_hw_vf);
/**
* wx_stop_adapter_vf - Generic stop Tx/Rx units
* @wx: pointer to hardware structure
*
* Clears interrupts, disables transmit and receive units.
**/
void wx_stop_adapter_vf(struct wx *wx)
{
u32 reg_val;
u16 i;
/* Clear interrupt mask to stop from interrupts being generated */
wr32(wx, WX_VXIMS, WX_VF_IRQ_CLEAR_MASK);
/* Clear any pending interrupts, flush previous writes */
wr32(wx, WX_VXICR, U32_MAX);
/* Disable the transmit unit. Each queue must be disabled. */
for (i = 0; i < wx->mac.max_tx_queues; i++)
wr32(wx, WX_VXTXDCTL(i), WX_VXTXDCTL_FLUSH);
/* Disable the receive unit by stopping each queue */
for (i = 0; i < wx->mac.max_rx_queues; i++) {
reg_val = rd32(wx, WX_VXRXDCTL(i));
reg_val &= ~WX_VXRXDCTL_ENABLE;
wr32(wx, WX_VXRXDCTL(i), reg_val);
}
/* Clear packet split and pool config */
wr32(wx, WX_VXMRQC, 0);
/* flush all queues disables */
rd32(wx, WX_VXSTATUS);
}
EXPORT_SYMBOL(wx_stop_adapter_vf);
/**
* wx_set_rar_vf - set device MAC address
* @wx: pointer to hardware structure
* @index: Receive address register to write
* @addr: Address to put into receive address register
* @enable_addr: set flag that address is active
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_set_rar_vf(struct wx *wx, u32 index, u8 *addr, u32 enable_addr)
{
u32 msgbuf[3] = {WX_VF_SET_MAC_ADDR};
u8 *msg_addr = (u8 *)(&msgbuf[1]);
int ret;
memcpy(msg_addr, addr, ETH_ALEN);
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
msgbuf[0] &= ~WX_VT_MSGTYPE_CTS;
/* if nacked the address was rejected, use "perm_addr" */
if (msgbuf[0] == (WX_VF_SET_MAC_ADDR | WX_VT_MSGTYPE_NACK)) {
wx_get_mac_addr_vf(wx, wx->mac.addr);
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(wx_set_rar_vf);
/**
* wx_update_mc_addr_list_vf - Update Multicast addresses
* @wx: pointer to the HW structure
* @netdev: pointer to the net device structure
*
* Updates the Multicast Table Array.
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_update_mc_addr_list_vf(struct wx *wx, struct net_device *netdev)
{
u32 msgbuf[WX_VXMAILBOX_SIZE] = {WX_VF_SET_MULTICAST};
u16 *vector_l = (u16 *)&msgbuf[1];
struct netdev_hw_addr *ha;
u32 cnt, i;
cnt = netdev_mc_count(netdev);
if (cnt > 28)
cnt = 28;
msgbuf[0] |= cnt << WX_VT_MSGINFO_SHIFT;
i = 0;
netdev_for_each_mc_addr(ha, netdev) {
if (i == cnt)
break;
if (is_link_local_ether_addr(ha->addr))
continue;
vector_l[i++] = wx_mta_vector(wx, ha->addr);
}
return wx_write_posted_mbx(wx, msgbuf, ARRAY_SIZE(msgbuf));
}
EXPORT_SYMBOL(wx_update_mc_addr_list_vf);
/**
* wx_update_xcast_mode_vf - Update Multicast mode
* @wx: pointer to the HW structure
* @xcast_mode: new multicast mode
*
* Updates the Multicast Mode of VF.
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_update_xcast_mode_vf(struct wx *wx, int xcast_mode)
{
u32 msgbuf[2] = {WX_VF_UPDATE_XCAST_MODE, xcast_mode};
int ret = 0;
if (wx->vfinfo->vf_api < wx_mbox_api_13)
return -EINVAL;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
msgbuf[0] &= ~WX_VT_MSGTYPE_CTS;
if (msgbuf[0] == (WX_VF_UPDATE_XCAST_MODE | WX_VT_MSGTYPE_NACK))
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(wx_update_xcast_mode_vf);
/**
* wx_get_link_state_vf - Get VF link state from PF
* @wx: pointer to the HW structure
* @link_state: link state storage
*
* Return: return state of the operation error or success.
**/
int wx_get_link_state_vf(struct wx *wx, u16 *link_state)
{
u32 msgbuf[2] = {WX_VF_GET_LINK_STATE};
int ret;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
if (msgbuf[0] & WX_VT_MSGTYPE_NACK)
return -EINVAL;
*link_state = msgbuf[1];
return 0;
}
EXPORT_SYMBOL(wx_get_link_state_vf);
/**
* wx_set_vfta_vf - Set/Unset vlan filter table address
* @wx: pointer to the HW structure
* @vlan: 12 bit VLAN ID
* @vind: unused by VF drivers
* @vlan_on: if true then set bit, else clear bit
* @vlvf_bypass: boolean flag indicating updating default pool is okay
*
* Turn on/off specified VLAN in the VLAN filter table.
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_set_vfta_vf(struct wx *wx, u32 vlan, u32 vind, bool vlan_on,
bool vlvf_bypass)
{
u32 msgbuf[2] = {WX_VF_SET_VLAN, vlan};
bool vlan_offload = false;
int ret;
/* Setting the 8 bit field MSG INFO to TRUE indicates "add" */
msgbuf[0] |= vlan_on << WX_VT_MSGINFO_SHIFT;
/* if vf vlan offload is disabled, allow to create vlan under pf port vlan */
msgbuf[0] |= BIT(vlan_offload);
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
if (msgbuf[0] & WX_VT_MSGTYPE_ACK)
return 0;
return msgbuf[0] & WX_VT_MSGTYPE_NACK;
}
EXPORT_SYMBOL(wx_set_vfta_vf);
void wx_get_mac_addr_vf(struct wx *wx, u8 *mac_addr)
{
ether_addr_copy(mac_addr, wx->mac.perm_addr);
}
EXPORT_SYMBOL(wx_get_mac_addr_vf);
int wx_get_fw_version_vf(struct wx *wx)
{
u32 msgbuf[2] = {WX_VF_GET_FW_VERSION};
int ret;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
if (msgbuf[0] & WX_VT_MSGTYPE_NACK)
return -EINVAL;
snprintf(wx->eeprom_id, 32, "0x%08x", msgbuf[1]);
return 0;
}
EXPORT_SYMBOL(wx_get_fw_version_vf);
int wx_set_uc_addr_vf(struct wx *wx, u32 index, u8 *addr)
{
u32 msgbuf[3] = {WX_VF_SET_MACVLAN};
u8 *msg_addr = (u8 *)(&msgbuf[1]);
int ret;
/* If index is one then this is the start of a new list and needs
* indication to the PF so it can do it's own list management.
* If it is zero then that tells the PF to just clear all of
* this VF's macvlans and there is no new list.
*/
msgbuf[0] |= index << WX_VT_MSGINFO_SHIFT;
if (addr)
memcpy(msg_addr, addr, 6);
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
msgbuf[0] &= ~WX_VT_MSGTYPE_CTS;
if (msgbuf[0] == (WX_VF_SET_MACVLAN | WX_VT_MSGTYPE_NACK))
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(wx_set_uc_addr_vf);
/**
* wx_rlpml_set_vf - Set the maximum receive packet length
* @wx: pointer to the HW structure
* @max_size: value to assign to max frame size
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_rlpml_set_vf(struct wx *wx, u16 max_size)
{
u32 msgbuf[2] = {WX_VF_SET_LPE, max_size};
int ret;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
if ((msgbuf[0] & WX_VF_SET_LPE) &&
(msgbuf[0] & WX_VT_MSGTYPE_NACK))
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(wx_rlpml_set_vf);
/**
* wx_negotiate_api_version - Negotiate supported API version
* @wx: pointer to the HW structure
* @api: integer containing requested API version
*
* Return: returns 0 on success, negative error code on failure
**/
int wx_negotiate_api_version(struct wx *wx, int api)
{
u32 msgbuf[2] = {WX_VF_API_NEGOTIATE, api};
int ret;
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
msgbuf[0] &= ~WX_VT_MSGTYPE_CTS;
/* Store value and return 0 on success */
if (msgbuf[0] == (WX_VF_API_NEGOTIATE | WX_VT_MSGTYPE_NACK))
return -EINVAL;
wx->vfinfo->vf_api = api;
return 0;
}
EXPORT_SYMBOL(wx_negotiate_api_version);
int wx_get_queues_vf(struct wx *wx, u32 *num_tcs, u32 *default_tc)
{
u32 msgbuf[5] = {WX_VF_GET_QUEUES};
int ret;
/* do nothing if API doesn't support wx_get_queues */
if (wx->vfinfo->vf_api < wx_mbox_api_13)
return -EINVAL;
/* Fetch queue configuration from the PF */
ret = wx_mbx_write_and_read_reply(wx, msgbuf, msgbuf,
ARRAY_SIZE(msgbuf));
if (ret)
return ret;
msgbuf[0] &= ~WX_VT_MSGTYPE_CTS;
/* if we didn't get an ACK there must have been
* some sort of mailbox error so we should treat it
* as such
*/
if (msgbuf[0] != (WX_VF_GET_QUEUES | WX_VT_MSGTYPE_ACK))
return -EINVAL;
/* record and validate values from message */
wx->mac.max_tx_queues = msgbuf[WX_VF_TX_QUEUES];
if (wx->mac.max_tx_queues == 0 ||
wx->mac.max_tx_queues > WX_VF_MAX_TX_QUEUES)
wx->mac.max_tx_queues = WX_VF_MAX_TX_QUEUES;
wx->mac.max_rx_queues = msgbuf[WX_VF_RX_QUEUES];
if (wx->mac.max_rx_queues == 0 ||
wx->mac.max_rx_queues > WX_VF_MAX_RX_QUEUES)
wx->mac.max_rx_queues = WX_VF_MAX_RX_QUEUES;
*num_tcs = msgbuf[WX_VF_TRANS_VLAN];
/* in case of unknown state assume we cannot tag frames */
if (*num_tcs > wx->mac.max_rx_queues)
*num_tcs = 1;
*default_tc = msgbuf[WX_VF_DEF_QUEUE];
/* default to queue 0 on out-of-bounds queue number */
if (*default_tc >= wx->mac.max_tx_queues)
*default_tc = 0;
return 0;
}
EXPORT_SYMBOL(wx_get_queues_vf);
static int wx_get_link_status_from_pf(struct wx *wx, u32 *msgbuf)
{
u32 links_reg = msgbuf[1];
if (msgbuf[1] & WX_PF_NOFITY_VF_NET_NOT_RUNNING)
wx->notify_down = true;
else
wx->notify_down = false;
if (wx->notify_down) {
wx->link = false;
wx->speed = SPEED_UNKNOWN;
return 0;
}
wx->link = WX_PFLINK_STATUS(links_reg);
wx->speed = WX_PFLINK_SPEED(links_reg);
return 0;
}
static int wx_pf_ping_vf(struct wx *wx, u32 *msgbuf)
{
if (!(msgbuf[0] & WX_VT_MSGTYPE_CTS))
/* msg is not CTS, we need to do reset */
return -EINVAL;
return 0;
}
static struct wx_link_reg_fields wx_speed_lookup_vf[] = {
{wx_mac_unknown},
{wx_mac_sp, SPEED_10000, SPEED_1000, SPEED_100, SPEED_UNKNOWN, SPEED_UNKNOWN},
{wx_mac_em, SPEED_1000, SPEED_100, SPEED_10, SPEED_UNKNOWN, SPEED_UNKNOWN},
{wx_mac_aml, SPEED_40000, SPEED_25000, SPEED_10000, SPEED_1000, SPEED_UNKNOWN},
{wx_mac_aml40, SPEED_40000, SPEED_25000, SPEED_10000, SPEED_1000, SPEED_UNKNOWN},
};
static void wx_check_physical_link(struct wx *wx)
{
u32 val, link_val;
int ret;
/* get link status from hw status reg
* for SFP+ modules and DA cables, it can take up to 500usecs
* before the link status is correct
*/
if (wx->mac.type == wx_mac_em)
ret = read_poll_timeout_atomic(rd32, val, val & GENMASK(4, 1),
100, 500, false, wx, WX_VXSTATUS);
else
ret = read_poll_timeout_atomic(rd32, val, val & BIT(0), 100,
500, false, wx, WX_VXSTATUS);
if (ret) {
wx->speed = SPEED_UNKNOWN;
wx->link = false;
return;
}
wx->link = true;
link_val = WX_VXSTATUS_SPEED(val);
if (link_val & BIT(0))
wx->speed = wx_speed_lookup_vf[wx->mac.type].bit0_f;
else if (link_val & BIT(1))
wx->speed = wx_speed_lookup_vf[wx->mac.type].bit1_f;
else if (link_val & BIT(2))
wx->speed = wx_speed_lookup_vf[wx->mac.type].bit2_f;
else if (link_val & BIT(3))
wx->speed = wx_speed_lookup_vf[wx->mac.type].bit3_f;
else
wx->speed = SPEED_UNKNOWN;
}
int wx_check_mac_link_vf(struct wx *wx)
{
struct wx_mbx_info *mbx = &wx->mbx;
u32 msgbuf[2] = {0};
int ret = 0;
if (!mbx->timeout)
goto out;
wx_check_for_rst_vf(wx);
if (!wx_check_for_msg_vf(wx))
ret = wx_read_mbx_vf(wx, msgbuf, 2);
if (ret)
goto out;
switch (msgbuf[0] & GENMASK(8, 0)) {
case WX_PF_NOFITY_VF_LINK_STATUS | WX_PF_CONTROL_MSG:
ret = wx_get_link_status_from_pf(wx, msgbuf);
goto out;
case WX_PF_CONTROL_MSG:
ret = wx_pf_ping_vf(wx, msgbuf);
goto out;
case 0:
if (msgbuf[0] & WX_VT_MSGTYPE_NACK) {
/* msg is NACK, we must have lost CTS status */
ret = -EBUSY;
goto out;
}
/* no message, check link status */
wx_check_physical_link(wx);
goto out;
default:
break;
}
if (!(msgbuf[0] & WX_VT_MSGTYPE_CTS)) {
/* msg is not CTS and is NACK we must have lost CTS status */
if (msgbuf[0] & WX_VT_MSGTYPE_NACK)
ret = -EBUSY;
goto out;
}
/* the pf is talking, if we timed out in the past we reinit */
if (!mbx->timeout) {
ret = -EBUSY;
goto out;
}
out:
return ret;
}
@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_VF_H_
#define _WX_VF_H_
/* Control registers */
#define WX_VF_MAX_RING_NUMS 8
#define WX_VX_PF_BME 0x4B8
#define WX_VF_BME_ENABLE BIT(0)
#define WX_VXSTATUS 0x4
#define WX_VXCTRL 0x8
#define WX_VXCTRL_RST BIT(0)
#define WX_VXMRQC 0x78
#define WX_VXMRQC_PSR_L4HDR BIT(0)
#define WX_VXMRQC_PSR_L3HDR BIT(1)
#define WX_VXMRQC_PSR_L2HDR BIT(2)
#define WX_VXMRQC_PSR_TUNHDR BIT(3)
#define WX_VXMRQC_PSR_TUNMAC BIT(4)
#define WX_VXMRQC_PSR_MASK GENMASK(5, 1)
#define WX_VXMRQC_PSR(f) FIELD_PREP(GENMASK(5, 1), f)
#define WX_VXMRQC_RSS_HASH(f) FIELD_PREP(GENMASK(15, 13), f)
#define WX_VXMRQC_RSS_MASK GENMASK(31, 16)
#define WX_VXMRQC_RSS(f) FIELD_PREP(GENMASK(31, 16), f)
#define WX_VXMRQC_RSS_ALG_IPV4_TCP BIT(0)
#define WX_VXMRQC_RSS_ALG_IPV4 BIT(1)
#define WX_VXMRQC_RSS_ALG_IPV6 BIT(4)
#define WX_VXMRQC_RSS_ALG_IPV6_TCP BIT(5)
#define WX_VXMRQC_RSS_EN BIT(8)
#define WX_VXRSSRK(i) (0x80 + ((i) * 4)) /* i=[0,9] */
#define WX_VXRETA(i) (0xC0 + ((i) * 4)) /* i=[0,15] */
/* Interrupt registers */
#define WX_VXICR 0x100
#define WX_VXIMS 0x108
#define WX_VXIMC 0x10C
#define WX_VF_IRQ_CLEAR_MASK 7
#define WX_VF_MAX_TX_QUEUES 4
#define WX_VF_MAX_RX_QUEUES 4
#define WX_VXITR(i) (0x200 + (4 * (i))) /* i=[0,1] */
#define WX_VXITR_MASK GENMASK(8, 0)
#define WX_VXITR_CNT_WDIS BIT(31)
#define WX_VXIVAR_MISC 0x260
#define WX_VXIVAR(i) (0x240 + (4 * (i))) /* i=[0,3] */
#define wx_conf_size(v, mwidth, uwidth) ({ \
typeof(v) _v = (v); \
(_v == 2 << (mwidth) ? 0 : _v >> (uwidth)); \
})
#define wx_buf_len(v) wx_conf_size(v, 13, 7)
#define wx_hdr_sz(v) wx_conf_size(v, 10, 6)
#define wx_buf_sz(v) wx_conf_size(v, 14, 10)
#define wx_pkt_thresh(v) wx_conf_size(v, 4, 0)
#define WX_RX_HDR_SIZE 256
#define WX_RX_BUF_SIZE 2048
#define WX_RXBUFFER_2048 (2048)
#define WX_RXBUFFER_3072 3072
/* Receive Path */
#define WX_VXRDBAL(r) (0x1000 + (0x40 * (r)))
#define WX_VXRDBAH(r) (0x1004 + (0x40 * (r)))
#define WX_VXRDT(r) (0x1008 + (0x40 * (r)))
#define WX_VXRDH(r) (0x100C + (0x40 * (r)))
#define WX_VXRXDCTL(r) (0x1010 + (0x40 * (r)))
#define WX_VXRXDCTL_ENABLE BIT(0)
#define WX_VXRXDCTL_BUFLEN_MASK GENMASK(6, 1)
#define WX_VXRXDCTL_BUFLEN(f) FIELD_PREP(GENMASK(6, 1), f)
#define WX_VXRXDCTL_BUFSZ_MASK GENMASK(11, 8)
#define WX_VXRXDCTL_BUFSZ(f) FIELD_PREP(GENMASK(11, 8), f)
#define WX_VXRXDCTL_HDRSZ_MASK GENMASK(15, 12)
#define WX_VXRXDCTL_HDRSZ(f) FIELD_PREP(GENMASK(15, 12), f)
#define WX_VXRXDCTL_DESC_MERGE BIT(19)
#define WX_VXRXDCTL_RSCMAX_MASK GENMASK(24, 23)
#define WX_VXRXDCTL_RSCMAX(f) FIELD_PREP(GENMASK(24, 23), f)
#define WX_VXRXDCTL_RSCEN BIT(29)
#define WX_VXRXDCTL_DROP BIT(30)
#define WX_VXRXDCTL_VLAN BIT(31)
/* Transimit Path */
#define WX_VXTDBAL(r) (0x3000 + (0x40 * (r)))
#define WX_VXTDBAH(r) (0x3004 + (0x40 * (r)))
#define WX_VXTDT(r) (0x3008 + (0x40 * (r)))
#define WX_VXTDH(r) (0x300C + (0x40 * (r)))
#define WX_VXTXDCTL(r) (0x3010 + (0x40 * (r)))
#define WX_VXTXDCTL_ENABLE BIT(0)
#define WX_VXTXDCTL_BUFLEN(f) FIELD_PREP(GENMASK(6, 1), f)
#define WX_VXTXDCTL_PTHRESH(f) FIELD_PREP(GENMASK(11, 8), f)
#define WX_VXTXDCTL_WTHRESH(f) FIELD_PREP(GENMASK(22, 16), f)
#define WX_VXTXDCTL_FLUSH BIT(26)
#define WX_VXTXDCTL_HEAD_WB BIT(27)
#define WX_VXTXD_HEAD_ADDRL(r) (0x3028 + (0x40 * (r)))
#define WX_VXTXD_HEAD_ADDRH(r) (0x302C + (0x40 * (r)))
#define WX_PFLINK_STATUS(g) FIELD_GET(BIT(0), g)
#define WX_PFLINK_SPEED(g) FIELD_GET(GENMASK(31, 1), g)
#define WX_VXSTATUS_SPEED(g) FIELD_GET(GENMASK(4, 1), g)
struct wx_link_reg_fields {
u32 mac_type;
u32 bit0_f;
u32 bit1_f;
u32 bit2_f;
u32 bit3_f;
u32 bit4_f;
};
void wx_init_hw_vf(struct wx *wx);
int wx_reset_hw_vf(struct wx *wx);
void wx_get_mac_addr_vf(struct wx *wx, u8 *mac_addr);
void wx_stop_adapter_vf(struct wx *wx);
int wx_get_fw_version_vf(struct wx *wx);
int wx_set_rar_vf(struct wx *wx, u32 index, u8 *addr, u32 enable_addr);
int wx_update_mc_addr_list_vf(struct wx *wx, struct net_device *netdev);
int wx_set_uc_addr_vf(struct wx *wx, u32 index, u8 *addr);
int wx_rlpml_set_vf(struct wx *wx, u16 max_size);
int wx_negotiate_api_version(struct wx *wx, int api);
int wx_get_queues_vf(struct wx *wx, u32 *num_tcs, u32 *default_tc);
int wx_update_xcast_mode_vf(struct wx *wx, int xcast_mode);
int wx_get_link_state_vf(struct wx *wx, u16 *link_state);
int wx_set_vfta_vf(struct wx *wx, u32 vlan, u32 vind, bool vlan_on,
bool vlvf_bypass);
int wx_check_mac_link_vf(struct wx *wx);
#endif /* _WX_VF_H_ */
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_mbx.h"
#include "wx_lib.h"
#include "wx_vf.h"
#include "wx_vf_lib.h"
#include "wx_vf_common.h"
int wxvf_suspend(struct device *dev_d)
{
struct pci_dev *pdev = to_pci_dev(dev_d);
struct wx *wx = pci_get_drvdata(pdev);
netif_device_detach(wx->netdev);
wx_clear_interrupt_scheme(wx);
pci_disable_device(pdev);
return 0;
}
EXPORT_SYMBOL(wxvf_suspend);
void wxvf_shutdown(struct pci_dev *pdev)
{
wxvf_suspend(&pdev->dev);
}
EXPORT_SYMBOL(wxvf_shutdown);
int wxvf_resume(struct device *dev_d)
{
struct pci_dev *pdev = to_pci_dev(dev_d);
struct wx *wx = pci_get_drvdata(pdev);
pci_set_master(pdev);
wx_init_interrupt_scheme(wx);
netif_device_attach(wx->netdev);
return 0;
}
EXPORT_SYMBOL(wxvf_resume);
void wxvf_remove(struct pci_dev *pdev)
{
struct wx *wx = pci_get_drvdata(pdev);
struct net_device *netdev;
netdev = wx->netdev;
unregister_netdev(netdev);
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
kfree(wx->vfinfo);
kfree(wx->rss_key);
kfree(wx->mac_table);
wx_clear_interrupt_scheme(wx);
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
pci_disable_device(pdev);
}
EXPORT_SYMBOL(wxvf_remove);
static irqreturn_t wx_msix_misc_vf(int __always_unused irq, void *data)
{
struct wx *wx = data;
set_bit(WX_FLAG_NEED_UPDATE_LINK, wx->flags);
/* Clear the interrupt */
if (!test_bit(WX_STATE_DOWN, wx->state))
wr32(wx, WX_VXIMC, wx->eims_other);
return IRQ_HANDLED;
}
int wx_request_msix_irqs_vf(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
int vector, err;
for (vector = 0; vector < wx->num_q_vectors; vector++) {
struct wx_q_vector *q_vector = wx->q_vector[vector];
struct msix_entry *entry = &wx->msix_q_entries[vector];
if (q_vector->tx.ring && q_vector->rx.ring)
snprintf(q_vector->name, sizeof(q_vector->name) - 1,
"%s-TxRx-%d", netdev->name, entry->entry);
else
/* skip this unused q_vector */
continue;
err = request_irq(entry->vector, wx_msix_clean_rings, 0,
q_vector->name, q_vector);
if (err) {
wx_err(wx, "request_irq failed for MSIX interrupt %s Error: %d\n",
q_vector->name, err);
goto free_queue_irqs;
}
}
err = request_irq(wx->msix_entry->vector, wx_msix_misc_vf,
0, netdev->name, wx);
if (err) {
wx_err(wx, "request_irq for msix_other failed: %d\n", err);
goto free_queue_irqs;
}
return 0;
free_queue_irqs:
while (vector) {
vector--;
free_irq(wx->msix_q_entries[vector].vector,
wx->q_vector[vector]);
}
wx_reset_interrupt_capability(wx);
return err;
}
EXPORT_SYMBOL(wx_request_msix_irqs_vf);
void wx_negotiate_api_vf(struct wx *wx)
{
int api[] = {
wx_mbox_api_13,
wx_mbox_api_null};
int err = 0, idx = 0;
spin_lock_bh(&wx->mbx.mbx_lock);
while (api[idx] != wx_mbox_api_null) {
err = wx_negotiate_api_version(wx, api[idx]);
if (!err)
break;
idx++;
}
spin_unlock_bh(&wx->mbx.mbx_lock);
}
EXPORT_SYMBOL(wx_negotiate_api_vf);
void wx_reset_vf(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
int ret = 0;
ret = wx_reset_hw_vf(wx);
if (!ret)
wx_init_hw_vf(wx);
wx_negotiate_api_vf(wx);
if (is_valid_ether_addr(wx->mac.addr)) {
eth_hw_addr_set(netdev, wx->mac.addr);
ether_addr_copy(netdev->perm_addr, wx->mac.addr);
}
}
EXPORT_SYMBOL(wx_reset_vf);
void wx_set_rx_mode_vf(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
unsigned int flags = netdev->flags;
int xcast_mode;
xcast_mode = (flags & IFF_ALLMULTI) ? WXVF_XCAST_MODE_ALLMULTI :
(flags & (IFF_BROADCAST | IFF_MULTICAST)) ?
WXVF_XCAST_MODE_MULTI : WXVF_XCAST_MODE_NONE;
/* request the most inclusive mode we need */
if (flags & IFF_PROMISC)
xcast_mode = WXVF_XCAST_MODE_PROMISC;
else if (flags & IFF_ALLMULTI)
xcast_mode = WXVF_XCAST_MODE_ALLMULTI;
else if (flags & (IFF_BROADCAST | IFF_MULTICAST))
xcast_mode = WXVF_XCAST_MODE_MULTI;
else
xcast_mode = WXVF_XCAST_MODE_NONE;
spin_lock_bh(&wx->mbx.mbx_lock);
wx_update_xcast_mode_vf(wx, xcast_mode);
wx_update_mc_addr_list_vf(wx, netdev);
wx_write_uc_addr_list_vf(netdev);
spin_unlock_bh(&wx->mbx.mbx_lock);
}
EXPORT_SYMBOL(wx_set_rx_mode_vf);
/**
* wx_configure_rx_vf - Configure Receive Unit after Reset
* @wx: board private structure
*
* Configure the Rx unit of the MAC after a reset.
**/
static void wx_configure_rx_vf(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
int i, ret;
wx_setup_psrtype_vf(wx);
wx_setup_vfmrqc_vf(wx);
spin_lock_bh(&wx->mbx.mbx_lock);
ret = wx_rlpml_set_vf(wx,
netdev->mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN);
spin_unlock_bh(&wx->mbx.mbx_lock);
if (ret)
wx_dbg(wx, "Failed to set MTU at %d\n", netdev->mtu);
/* Setup the HW Rx Head and Tail Descriptor Pointers and
* the Base and Length of the Rx Descriptor Ring
*/
for (i = 0; i < wx->num_rx_queues; i++) {
struct wx_ring *rx_ring = wx->rx_ring[i];
#ifdef HAVE_SWIOTLB_SKIP_CPU_SYNC
wx_set_rx_buffer_len_vf(wx, rx_ring);
#endif
wx_configure_rx_ring_vf(wx, rx_ring);
}
}
void wx_configure_vf(struct wx *wx)
{
wx_set_rx_mode_vf(wx->netdev);
wx_configure_tx_vf(wx);
wx_configure_rx_vf(wx);
}
EXPORT_SYMBOL(wx_configure_vf);
int wx_set_mac_vf(struct net_device *netdev, void *p)
{
struct wx *wx = netdev_priv(netdev);
struct sockaddr *addr = p;
int ret;
ret = eth_prepare_mac_addr_change(netdev, addr);
if (ret)
return ret;
spin_lock_bh(&wx->mbx.mbx_lock);
ret = wx_set_rar_vf(wx, 1, (u8 *)addr->sa_data, 1);
spin_unlock_bh(&wx->mbx.mbx_lock);
if (ret)
return -EPERM;
memcpy(wx->mac.addr, addr->sa_data, netdev->addr_len);
memcpy(wx->mac.perm_addr, addr->sa_data, netdev->addr_len);
eth_hw_addr_set(netdev, addr->sa_data);
return 0;
}
EXPORT_SYMBOL(wx_set_mac_vf);
void wxvf_watchdog_update_link(struct wx *wx)
{
int err;
if (!test_bit(WX_FLAG_NEED_UPDATE_LINK, wx->flags))
return;
spin_lock_bh(&wx->mbx.mbx_lock);
err = wx_check_mac_link_vf(wx);
spin_unlock_bh(&wx->mbx.mbx_lock);
if (err) {
wx->link = false;
set_bit(WX_FLAG_NEED_DO_RESET, wx->flags);
}
clear_bit(WX_FLAG_NEED_UPDATE_LINK, wx->flags);
}
EXPORT_SYMBOL(wxvf_watchdog_update_link);
static void wxvf_irq_enable(struct wx *wx)
{
wr32(wx, WX_VXIMC, wx->eims_enable_mask);
}
static void wxvf_up_complete(struct wx *wx)
{
/* Always set the carrier off */
netif_carrier_off(wx->netdev);
mod_timer(&wx->service_timer, jiffies + HZ);
set_bit(WX_FLAG_NEED_UPDATE_LINK, wx->flags);
wx_configure_msix_vf(wx);
smp_mb__before_atomic();
clear_bit(WX_STATE_DOWN, wx->state);
wx_napi_enable_all(wx);
/* clear any pending interrupts, may auto mask */
wr32(wx, WX_VXICR, U32_MAX);
wxvf_irq_enable(wx);
/* enable transmits */
netif_tx_start_all_queues(wx->netdev);
}
int wxvf_open(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
int err;
err = wx_setup_resources(wx);
if (err)
goto err_reset;
wx_configure_vf(wx);
err = wx_request_msix_irqs_vf(wx);
if (err)
goto err_free_resources;
/* Notify the stack of the actual queue counts. */
err = netif_set_real_num_tx_queues(netdev, wx->num_tx_queues);
if (err)
goto err_free_irq;
err = netif_set_real_num_rx_queues(netdev, wx->num_rx_queues);
if (err)
goto err_free_irq;
wxvf_up_complete(wx);
return 0;
err_free_irq:
wx_free_irq(wx);
err_free_resources:
wx_free_resources(wx);
err_reset:
wx_reset_vf(wx);
return err;
}
EXPORT_SYMBOL(wxvf_open);
static void wxvf_down(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
if (test_and_set_bit(WX_STATE_DOWN, wx->state))
return;
timer_delete_sync(&wx->service_timer);
netif_tx_stop_all_queues(netdev);
netif_tx_disable(netdev);
netif_carrier_off(netdev);
wx_napi_disable_all(wx);
wx_reset_vf(wx);
wx_clean_all_tx_rings(wx);
wx_clean_all_rx_rings(wx);
}
static void wxvf_reinit_locked(struct wx *wx)
{
mutex_lock(&wx->reset_lock);
set_bit(WX_STATE_RESETTING, wx->state);
wxvf_down(wx);
wx_free_irq(wx);
wx_configure_vf(wx);
wx_request_msix_irqs_vf(wx);
wxvf_up_complete(wx);
clear_bit(WX_STATE_RESETTING, wx->state);
mutex_unlock(&wx->reset_lock);
}
static void wxvf_reset_subtask(struct wx *wx)
{
if (!test_bit(WX_FLAG_NEED_DO_RESET, wx->flags))
return;
clear_bit(WX_FLAG_NEED_DO_RESET, wx->flags);
rtnl_lock();
if (test_bit(WX_STATE_RESETTING, wx->state) ||
test_bit(WX_STATE_DOWN, wx->state)) {
rtnl_unlock();
return;
}
wxvf_reinit_locked(wx);
rtnl_unlock();
}
int wxvf_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
wxvf_down(wx);
wx_free_irq(wx);
wx_free_resources(wx);
return 0;
}
EXPORT_SYMBOL(wxvf_close);
static void wxvf_link_config_subtask(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
wxvf_watchdog_update_link(wx);
if (wx->link) {
if (netif_carrier_ok(netdev))
return;
netif_carrier_on(netdev);
netdev_info(netdev, "Link is Up - %s\n",
phy_speed_to_str(wx->speed));
} else {
if (!netif_carrier_ok(netdev))
return;
netif_carrier_off(netdev);
netdev_info(netdev, "Link is Down\n");
}
}
static void wxvf_service_task(struct work_struct *work)
{
struct wx *wx = container_of(work, struct wx, service_task);
wxvf_link_config_subtask(wx);
wxvf_reset_subtask(wx);
wx_service_event_complete(wx);
}
void wxvf_init_service(struct wx *wx)
{
timer_setup(&wx->service_timer, wx_service_timer, 0);
INIT_WORK(&wx->service_task, wxvf_service_task);
clear_bit(WX_STATE_SERVICE_SCHED, wx->state);
}
EXPORT_SYMBOL(wxvf_init_service);
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_VF_COMMON_H_
#define _WX_VF_COMMON_H_
int wxvf_suspend(struct device *dev_d);
void wxvf_shutdown(struct pci_dev *pdev);
int wxvf_resume(struct device *dev_d);
void wxvf_remove(struct pci_dev *pdev);
int wx_request_msix_irqs_vf(struct wx *wx);
void wx_negotiate_api_vf(struct wx *wx);
void wx_reset_vf(struct wx *wx);
void wx_set_rx_mode_vf(struct net_device *netdev);
void wx_configure_vf(struct wx *wx);
int wx_set_mac_vf(struct net_device *netdev, void *p);
void wxvf_watchdog_update_link(struct wx *wx);
int wxvf_open(struct net_device *netdev);
int wxvf_close(struct net_device *netdev);
void wxvf_init_service(struct wx *wx);
#endif /* _WX_VF_COMMON_H_ */
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/pci.h>
#include "wx_type.h"
#include "wx_hw.h"
#include "wx_lib.h"
#include "wx_vf.h"
#include "wx_vf_lib.h"
void wx_write_eitr_vf(struct wx_q_vector *q_vector)
{
struct wx *wx = q_vector->wx;
int v_idx = q_vector->v_idx;
u32 itr_reg;
itr_reg = q_vector->itr & WX_VXITR_MASK;
/* set the WDIS bit to not clear the timer bits and cause an
* immediate assertion of the interrupt
*/
itr_reg |= WX_VXITR_CNT_WDIS;
wr32(wx, WX_VXITR(v_idx), itr_reg);
}
static void wx_set_ivar_vf(struct wx *wx, s8 direction, u8 queue,
u8 msix_vector)
{
u32 ivar, index;
if (direction == -1) {
/* other causes */
msix_vector |= WX_PX_IVAR_ALLOC_VAL;
ivar = rd32(wx, WX_VXIVAR_MISC);
ivar &= ~0xFF;
ivar |= msix_vector;
wr32(wx, WX_VXIVAR_MISC, ivar);
} else {
/* tx or rx causes */
msix_vector |= WX_PX_IVAR_ALLOC_VAL;
index = ((16 * (queue & 1)) + (8 * direction));
ivar = rd32(wx, WX_VXIVAR(queue >> 1));
ivar &= ~(0xFF << index);
ivar |= (msix_vector << index);
wr32(wx, WX_VXIVAR(queue >> 1), ivar);
}
}
void wx_configure_msix_vf(struct wx *wx)
{
int v_idx;
wx->eims_enable_mask = 0;
for (v_idx = 0; v_idx < wx->num_q_vectors; v_idx++) {
struct wx_q_vector *q_vector = wx->q_vector[v_idx];
struct wx_ring *ring;
wx_for_each_ring(ring, q_vector->rx)
wx_set_ivar_vf(wx, 0, ring->reg_idx, v_idx);
wx_for_each_ring(ring, q_vector->tx)
wx_set_ivar_vf(wx, 1, ring->reg_idx, v_idx);
/* add q_vector eims value to global eims_enable_mask */
wx->eims_enable_mask |= BIT(v_idx);
wx_write_eitr_vf(q_vector);
}
wx_set_ivar_vf(wx, -1, 1, v_idx);
/* setup eims_other and add value to global eims_enable_mask */
wx->eims_other = BIT(v_idx);
wx->eims_enable_mask |= wx->eims_other;
}
int wx_write_uc_addr_list_vf(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
int count = 0;
if (!netdev_uc_empty(netdev)) {
struct netdev_hw_addr *ha;
netdev_for_each_uc_addr(ha, netdev)
wx_set_uc_addr_vf(wx, ++count, ha->addr);
} else {
/*
* If the list is empty then send message to PF driver to
* clear all macvlans on this VF.
*/
wx_set_uc_addr_vf(wx, 0, NULL);
}
return count;
}
/**
* wx_configure_tx_ring_vf - Configure Tx ring after Reset
* @wx: board private structure
* @ring: structure containing ring specific data
*
* Configure the Tx descriptor ring after a reset.
**/
static void wx_configure_tx_ring_vf(struct wx *wx, struct wx_ring *ring)
{
u8 reg_idx = ring->reg_idx;
u64 tdba = ring->dma;
u32 txdctl = 0;
int ret;
/* disable queue to avoid issues while updating state */
wr32(wx, WX_VXTXDCTL(reg_idx), WX_VXTXDCTL_FLUSH);
wr32(wx, WX_VXTDBAL(reg_idx), tdba & DMA_BIT_MASK(32));
wr32(wx, WX_VXTDBAH(reg_idx), tdba >> 32);
/* enable relaxed ordering */
pcie_capability_clear_and_set_word(wx->pdev, PCI_EXP_DEVCTL,
0, PCI_EXP_DEVCTL_RELAX_EN);
/* reset head and tail pointers */
wr32(wx, WX_VXTDH(reg_idx), 0);
wr32(wx, WX_VXTDT(reg_idx), 0);
ring->tail = wx->hw_addr + WX_VXTDT(reg_idx);
/* reset ntu and ntc to place SW in sync with hardwdare */
ring->next_to_clean = 0;
ring->next_to_use = 0;
txdctl |= WX_VXTXDCTL_BUFLEN(wx_buf_len(ring->count));
txdctl |= WX_VXTXDCTL_ENABLE;
if (ring->headwb_mem) {
wr32(wx, WX_VXTXD_HEAD_ADDRL(reg_idx),
ring->headwb_dma & DMA_BIT_MASK(32));
wr32(wx, WX_VXTXD_HEAD_ADDRH(reg_idx),
upper_32_bits(ring->headwb_dma));
txdctl |= WX_VXTXDCTL_HEAD_WB;
}
/* reinitialize tx_buffer_info */
memset(ring->tx_buffer_info, 0,
sizeof(struct wx_tx_buffer) * ring->count);
wr32(wx, WX_VXTXDCTL(reg_idx), txdctl);
/* poll to verify queue is enabled */
ret = read_poll_timeout(rd32, txdctl, txdctl & WX_VXTXDCTL_ENABLE,
1000, 10000, true, wx, WX_VXTXDCTL(reg_idx));
if (ret == -ETIMEDOUT)
wx_err(wx, "Could not enable Tx Queue %d\n", reg_idx);
}
/**
* wx_configure_tx_vf - Configure Transmit Unit after Reset
* @wx: board private structure
*
* Configure the Tx unit of the MAC after a reset.
**/
void wx_configure_tx_vf(struct wx *wx)
{
u32 i;
/* Setup the HW Tx Head and Tail descriptor pointers */
for (i = 0; i < wx->num_tx_queues; i++)
wx_configure_tx_ring_vf(wx, wx->tx_ring[i]);
}
static void wx_configure_srrctl_vf(struct wx *wx, struct wx_ring *ring,
int index)
{
u32 srrctl;
srrctl = rd32m(wx, WX_VXRXDCTL(index),
(u32)~(WX_VXRXDCTL_HDRSZ_MASK | WX_VXRXDCTL_BUFSZ_MASK));
srrctl |= WX_VXRXDCTL_DROP;
srrctl |= WX_VXRXDCTL_HDRSZ(wx_hdr_sz(WX_RX_HDR_SIZE));
srrctl |= WX_VXRXDCTL_BUFSZ(wx_buf_sz(WX_RX_BUF_SIZE));
wr32(wx, WX_VXRXDCTL(index), srrctl);
}
void wx_setup_psrtype_vf(struct wx *wx)
{
/* PSRTYPE must be initialized */
u32 psrtype = WX_VXMRQC_PSR_L2HDR |
WX_VXMRQC_PSR_L3HDR |
WX_VXMRQC_PSR_L4HDR |
WX_VXMRQC_PSR_TUNHDR |
WX_VXMRQC_PSR_TUNMAC;
wr32m(wx, WX_VXMRQC, WX_VXMRQC_PSR_MASK, WX_VXMRQC_PSR(psrtype));
}
void wx_setup_vfmrqc_vf(struct wx *wx)
{
u16 rss_i = wx->num_rx_queues;
u32 vfmrqc = 0, vfreta = 0;
u8 i, j;
/* Fill out hash function seeds */
netdev_rss_key_fill(wx->rss_key, WX_RSS_KEY_SIZE);
for (i = 0; i < WX_RSS_KEY_SIZE / 4; i++)
wr32(wx, WX_VXRSSRK(i), wx->rss_key[i]);
for (i = 0, j = 0; i < WX_MAX_RETA_ENTRIES; i++, j++) {
if (j == rss_i)
j = 0;
wx->rss_indir_tbl[i] = j;
vfreta |= j << (i & 0x3) * 8;
if ((i & 3) == 3) {
wr32(wx, WX_VXRETA(i >> 2), vfreta);
vfreta = 0;
}
}
/* Perform hash on these packet types */
vfmrqc |= WX_VXMRQC_RSS_ALG_IPV4 |
WX_VXMRQC_RSS_ALG_IPV4_TCP |
WX_VXMRQC_RSS_ALG_IPV6 |
WX_VXMRQC_RSS_ALG_IPV6_TCP;
vfmrqc |= WX_VXMRQC_RSS_EN;
if (wx->num_rx_queues > 3)
vfmrqc |= WX_VXMRQC_RSS_HASH(2);
else if (wx->num_rx_queues > 1)
vfmrqc |= WX_VXMRQC_RSS_HASH(1);
wr32m(wx, WX_VXMRQC, WX_VXMRQC_RSS_MASK, WX_VXMRQC_RSS(vfmrqc));
}
void wx_configure_rx_ring_vf(struct wx *wx, struct wx_ring *ring)
{
u8 reg_idx = ring->reg_idx;
union wx_rx_desc *rx_desc;
u64 rdba = ring->dma;
u32 rxdctl;
/* disable queue to avoid issues while updating state */
rxdctl = rd32(wx, WX_VXRXDCTL(reg_idx));
wx_disable_rx_queue(wx, ring);
wr32(wx, WX_VXRDBAL(reg_idx), rdba & DMA_BIT_MASK(32));
wr32(wx, WX_VXRDBAH(reg_idx), rdba >> 32);
/* enable relaxed ordering */
pcie_capability_clear_and_set_word(wx->pdev, PCI_EXP_DEVCTL,
0, PCI_EXP_DEVCTL_RELAX_EN);
/* reset head and tail pointers */
wr32(wx, WX_VXRDH(reg_idx), 0);
wr32(wx, WX_VXRDT(reg_idx), 0);
ring->tail = wx->hw_addr + WX_VXRDT(reg_idx);
/* initialize rx_buffer_info */
memset(ring->rx_buffer_info, 0,
sizeof(struct wx_rx_buffer) * ring->count);
/* initialize Rx descriptor 0 */
rx_desc = WX_RX_DESC(ring, 0);
rx_desc->wb.upper.length = 0;
/* reset ntu and ntc to place SW in sync with hardwdare */
ring->next_to_clean = 0;
ring->next_to_use = 0;
ring->next_to_alloc = 0;
wx_configure_srrctl_vf(wx, ring, reg_idx);
/* allow any size packet since we can handle overflow */
rxdctl &= ~WX_VXRXDCTL_BUFLEN_MASK;
rxdctl |= WX_VXRXDCTL_BUFLEN(wx_buf_len(ring->count));
rxdctl |= WX_VXRXDCTL_ENABLE | WX_VXRXDCTL_VLAN;
/* enable RSC */
rxdctl &= ~WX_VXRXDCTL_RSCMAX_MASK;
rxdctl |= WX_VXRXDCTL_RSCMAX(0);
rxdctl |= WX_VXRXDCTL_RSCEN;
if (test_bit(WX_FLAG_RX_MERGE_ENABLED, wx->flags))
rxdctl |= WX_VXRXDCTL_DESC_MERGE;
wr32(wx, WX_VXRXDCTL(reg_idx), rxdctl);
/* pf/vf reuse */
wx_enable_rx_queue(wx, ring);
wx_alloc_rx_buffers(ring, wx_desc_unused(ring));
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _WX_VF_LIB_H_
#define _WX_VF_LIB_H_
void wx_write_eitr_vf(struct wx_q_vector *q_vector);
void wx_configure_msix_vf(struct wx *wx);
int wx_write_uc_addr_list_vf(struct net_device *netdev);
void wx_setup_psrtype_vf(struct wx *wx);
void wx_setup_vfmrqc_vf(struct wx *wx);
void wx_configure_tx_vf(struct wx *wx);
void wx_configure_rx_ring_vf(struct wx *wx, struct wx_ring *ring);
#endif /* _WX_VF_LIB_H_ */
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd.
#
# Makefile for the Wangxun(R) GbE PCI Express ethernet driver
#
obj-$(CONFIG_NGBE) += ngbe.o
ngbe-objs := ngbe_main.o ngbe_hw.o ngbe_mdio.o ngbe_ethtool.o
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/pci.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
#include "../libwx/wx_ethtool.h"
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_hw.h"
#include "ngbe_ethtool.h"
#include "ngbe_type.h"
static int ngbe_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
u32 new_rx_count, new_tx_count;
struct wx_ring *temp_ring;
int i, err = 0;
new_tx_count = clamp_t(u32, ring->tx_pending, WX_MIN_TXD, WX_MAX_TXD);
new_tx_count = ALIGN(new_tx_count, WX_REQ_TX_DESCRIPTOR_MULTIPLE);
new_rx_count = clamp_t(u32, ring->rx_pending, WX_MIN_RXD, WX_MAX_RXD);
new_rx_count = ALIGN(new_rx_count, WX_REQ_RX_DESCRIPTOR_MULTIPLE);
if (new_tx_count == wx->tx_ring_count &&
new_rx_count == wx->rx_ring_count)
return 0;
mutex_lock(&wx->reset_lock);
set_bit(WX_STATE_RESETTING, wx->state);
if (!netif_running(wx->netdev)) {
for (i = 0; i < wx->num_tx_queues; i++)
wx->tx_ring[i]->count = new_tx_count;
for (i = 0; i < wx->num_rx_queues; i++)
wx->rx_ring[i]->count = new_rx_count;
wx->tx_ring_count = new_tx_count;
wx->rx_ring_count = new_rx_count;
goto clear_reset;
}
/* allocate temporary buffer to store rings in */
i = max_t(int, wx->num_tx_queues, wx->num_rx_queues);
temp_ring = kvmalloc_objs(struct wx_ring, i);
if (!temp_ring) {
err = -ENOMEM;
goto clear_reset;
}
ngbe_down(wx);
wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring);
kvfree(temp_ring);
wx_configure(wx);
ngbe_up(wx);
clear_reset:
clear_bit(WX_STATE_RESETTING, wx->state);
mutex_unlock(&wx->reset_lock);
return err;
}
static const struct ethtool_ops ngbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ |
ETHTOOL_COALESCE_USE_ADAPTIVE,
.get_drvinfo = wx_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_link_ksettings = wx_get_link_ksettings,
.set_link_ksettings = wx_set_link_ksettings,
.nway_reset = wx_nway_reset,
.get_wol = wx_get_wol,
.set_wol = wx_set_wol,
.get_sset_count = wx_get_sset_count,
.get_strings = wx_get_strings,
.get_ethtool_stats = wx_get_ethtool_stats,
.get_eth_mac_stats = wx_get_mac_stats,
.get_pause_stats = wx_get_pause_stats,
.get_pauseparam = wx_get_pauseparam,
.set_pauseparam = wx_set_pauseparam,
.get_ringparam = wx_get_ringparam,
.set_ringparam = ngbe_set_ringparam,
.get_coalesce = wx_get_coalesce,
.set_coalesce = wx_set_coalesce,
.get_channels = wx_get_channels,
.set_channels = wx_set_channels,
.get_rxfh_fields = wx_get_rxfh_fields,
.set_rxfh_fields = wx_set_rxfh_fields,
.get_rxfh_indir_size = wx_rss_indir_size,
.get_rxfh_key_size = wx_get_rxfh_key_size,
.get_rxfh = wx_get_rxfh,
.set_rxfh = wx_set_rxfh,
.get_msglevel = wx_get_msglevel,
.set_msglevel = wx_set_msglevel,
.get_ts_info = wx_get_ts_info,
.get_ts_stats = wx_get_ptp_stats,
};
void ngbe_set_ethtool_ops(struct net_device *netdev)
{
netdev->ethtool_ops = &ngbe_ethtool_ops;
}
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#ifndef _NGBE_ETHTOOL_H_
#define _NGBE_ETHTOOL_H_
void ngbe_set_ethtool_ops(struct net_device *netdev);
#endif /* _NGBE_ETHTOOL_H_ */
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/iopoll.h>
#include <linux/pci.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "ngbe_type.h"
#include "ngbe_hw.h"
int ngbe_eeprom_chksum_hostif(struct wx *wx)
{
struct wx_hic_read_shadow_ram buffer;
int status;
int tmp;
buffer.hdr.req.cmd = NGBE_FW_EEPROM_CHECKSUM_CMD;
buffer.hdr.req.buf_lenh = 0;
buffer.hdr.req.buf_lenl = 0;
buffer.hdr.req.checksum = NGBE_FW_CMD_DEFAULT_CHECKSUM;
/* convert offset from words to bytes */
buffer.address = 0;
/* one word */
buffer.length = 0;
status = wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer),
WX_HI_COMMAND_TIMEOUT, false);
if (status < 0)
return status;
tmp = rd32a(wx, WX_MNG_MBOX, 1);
if (tmp == NGBE_FW_CMD_ST_PASS)
return 0;
return -EIO;
}
static int ngbe_reset_misc(struct wx *wx)
{
wx_reset_misc(wx);
if (wx->gpio_ctrl) {
/* gpio0 is used to power on/off control*/
wr32(wx, NGBE_GPIO_DDR, 0x1);
ngbe_sfp_modules_txrx_powerctl(wx, false);
}
return 0;
}
void ngbe_sfp_modules_txrx_powerctl(struct wx *wx, bool swi)
{
/* gpio0 is used to power on control . 0 is on */
wr32(wx, NGBE_GPIO_DR, swi ? 0 : NGBE_GPIO_DR_0);
}
/**
* ngbe_reset_hw - Perform hardware reset
* @wx: pointer to hardware structure
*
* Resets the hardware by resetting the transmit and receive units, masks
* and clears all interrupts, perform a PHY reset, and perform a link (MAC)
* reset.
**/
int ngbe_reset_hw(struct wx *wx)
{
u32 val = 0;
int ret = 0;
/* Call wx stop to disable tx/rx and clear interrupts */
ret = wx_stop_adapter(wx);
if (ret != 0)
return ret;
if (wx->mac_type != em_mac_type_mdi) {
val = WX_MIS_RST_LAN_RST(wx->bus.func);
wr32(wx, WX_MIS_RST, val | rd32(wx, WX_MIS_RST));
ret = read_poll_timeout(rd32, val,
!(val & (BIT(9) << wx->bus.func)), 1000,
100000, false, wx, 0x10028);
if (ret) {
wx_err(wx, "Lan reset exceed s maximum times.\n");
return ret;
}
}
ngbe_reset_misc(wx);
wx_clear_hw_cntrs(wx);
/* Store the permanent mac address */
wx_get_mac_addr(wx, wx->mac.perm_addr);
/* reset num_rar_entries to 128 */
wx->mac.num_rar_entries = NGBE_RAR_ENTRIES;
wx_init_rx_addrs(wx);
pci_set_master(wx->pdev);
return 0;
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* WangXun Gigabit PCI Express Linux driver
* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd.
*/
#ifndef _NGBE_HW_H_
#define _NGBE_HW_H_
int ngbe_eeprom_chksum_hostif(struct wx *wx);
void ngbe_sfp_modules_txrx_powerctl(struct wx *wx, bool swi);
int ngbe_reset_hw(struct wx *wx);
#endif /* _NGBE_HW_H_ */
@@ -0,0 +1,926 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd. */
#include <linux/types.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/etherdevice.h>
#include <net/ip.h>
#include <linux/phy.h>
#include <linux/if_vlan.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_sriov.h"
#include "ngbe_type.h"
#include "ngbe_mdio.h"
#include "ngbe_hw.h"
#include "ngbe_ethtool.h"
char ngbe_driver_name[] = "ngbe";
/* ngbe_pci_tbl - PCI Device ID Table
*
* { Vendor ID, Device ID, SubVendor ID, SubDevice ID,
* Class, Class Mask, private data (not used) }
*/
static const struct pci_device_id ngbe_pci_tbl[] = {
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860AL_W) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A2) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A2S) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A4) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A4S) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860AL2) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860AL2S) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860AL4) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860AL4S) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860LC) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A1) },
{ PCI_VDEVICE(WANGXUN, NGBE_DEV_ID_EM_WX1860A1L) },
/* required last entry */
{ }
};
/**
* ngbe_init_type_code - Initialize the shared code
* @wx: pointer to hardware structure
**/
static void ngbe_init_type_code(struct wx *wx)
{
int wol_mask = 0, ncsi_mask = 0;
u16 type_mask = 0, val;
wx->mac.type = wx_mac_em;
type_mask = (u16)(wx->subsystem_device_id & NGBE_OEM_MASK);
ncsi_mask = wx->subsystem_device_id & WX_NCSI_MASK;
wol_mask = wx->subsystem_device_id & WX_WOL_MASK;
val = rd32(wx, WX_CFG_PORT_ST);
wx->mac_type = (val & BIT(7)) >> 7 ?
em_mac_type_rgmii :
em_mac_type_mdi;
wx->wol_hw_supported = (wol_mask == WX_WOL_SUP) ? 1 : 0;
wx->ncsi_enabled = (ncsi_mask == WX_NCSI_SUP ||
type_mask == NGBE_SUBID_OCP_CARD) ? 1 : 0;
switch (type_mask) {
case NGBE_SUBID_LY_YT8521S_SFP:
case NGBE_SUBID_LY_M88E1512_SFP:
case NGBE_SUBID_YT8521S_SFP_GPIO:
case NGBE_SUBID_INTERNAL_YT8521S_SFP_GPIO:
wx->gpio_ctrl = 1;
break;
default:
wx->gpio_ctrl = 0;
break;
}
}
/**
* ngbe_sw_init - Initialize general software structures
* @wx: board private structure to initialize
**/
static int ngbe_sw_init(struct wx *wx)
{
struct pci_dev *pdev = wx->pdev;
u16 msix_count = 0;
int err = 0;
wx->mac.num_rar_entries = NGBE_RAR_ENTRIES;
wx->mac.max_rx_queues = NGBE_MAX_RX_QUEUES;
wx->mac.max_tx_queues = NGBE_MAX_TX_QUEUES;
wx->mac.mcft_size = NGBE_MC_TBL_SIZE;
wx->mac.vft_size = NGBE_SP_VFT_TBL_SIZE;
wx->mac.rx_pb_size = NGBE_RX_PB_SIZE;
wx->mac.tx_pb_size = NGBE_TDB_PB_SZ;
/* PCI config space info */
err = wx_sw_init(wx);
if (err < 0)
return err;
/* mac type, phy type , oem type */
ngbe_init_type_code(wx);
/* Set common capability flags and settings */
wx->max_q_vectors = NGBE_MAX_MSIX_VECTORS;
err = wx_get_pcie_msix_counts(wx, &msix_count, NGBE_MAX_MSIX_VECTORS);
if (err)
dev_err(&pdev->dev, "Do not support MSI-X\n");
wx->mac.max_msix_vectors = msix_count;
wx->ring_feature[RING_F_RSS].limit = min_t(int, NGBE_MAX_RSS_INDICES,
num_online_cpus());
wx->rss_enabled = true;
wx->adaptive_itr = false;
wx->rx_itr_setting = WX_7K_ITR;
wx->tx_itr_setting = WX_7K_ITR;
/* set default ring sizes */
wx->tx_ring_count = NGBE_DEFAULT_TXD;
wx->rx_ring_count = NGBE_DEFAULT_RXD;
/* set default work limits */
wx->tx_work_limit = NGBE_DEFAULT_TX_WORK;
wx->rx_work_limit = NGBE_DEFAULT_RX_WORK;
wx->mbx.size = WX_VXMAILBOX_SIZE;
wx->setup_tc = ngbe_setup_tc;
set_bit(0, &wx->fwd_bitmask);
return 0;
}
/**
* ngbe_service_task - manages and runs subtasks
* @work: pointer to work_struct containing our data
**/
static void ngbe_service_task(struct work_struct *work)
{
struct wx *wx = container_of(work, struct wx, service_task);
wx_update_stats(wx);
wx_service_event_complete(wx);
}
static void ngbe_init_service(struct wx *wx)
{
timer_setup(&wx->service_timer, wx_service_timer, 0);
INIT_WORK(&wx->service_task, ngbe_service_task);
clear_bit(WX_STATE_SERVICE_SCHED, wx->state);
}
/**
* ngbe_irq_enable - Enable default interrupt generation settings
* @wx: board private structure
* @queues: enable all queues interrupts
**/
static void ngbe_irq_enable(struct wx *wx, bool queues)
{
u32 mask;
/* enable misc interrupt */
mask = NGBE_PX_MISC_IEN_MASK;
wr32(wx, WX_GPIO_DDR, WX_GPIO_DDR_0);
wr32(wx, WX_GPIO_INTEN, WX_GPIO_INTEN_0 | WX_GPIO_INTEN_1);
wr32(wx, WX_GPIO_INTTYPE_LEVEL, 0x0);
wr32(wx, WX_GPIO_POLARITY, wx->gpio_ctrl ? 0 : 0x3);
wr32(wx, WX_PX_MISC_IEN, mask);
/* mask interrupt */
if (queues)
wx_intr_enable(wx, NGBE_INTR_ALL);
else if (wx->pdev->msix_enabled)
wx_intr_enable(wx, NGBE_INTR_MISC(wx));
else
wx_intr_enable(wx, BIT(0));
}
/**
* ngbe_intr - msi/legacy mode Interrupt Handler
* @irq: interrupt number
* @data: pointer to a network interface device structure
**/
static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
{
struct wx_q_vector *q_vector;
struct wx *wx = data;
struct pci_dev *pdev;
u32 eicr, eicr_misc;
q_vector = wx->q_vector[0];
pdev = wx->pdev;
eicr = wx_misc_isb(wx, WX_ISB_VEC0);
if (!eicr) {
/* shared interrupt alert!
* the interrupt that we masked before the EICR read.
*/
if (!test_bit(WX_STATE_DOWN, wx->state))
ngbe_irq_enable(wx, true);
return IRQ_NONE; /* Not our interrupt */
}
wx->isb_mem[WX_ISB_VEC0] = 0;
if (!(pdev->msi_enabled))
wr32(wx, WX_PX_INTA, 1);
eicr_misc = wx_misc_isb(wx, WX_ISB_MISC);
if (unlikely(eicr_misc & NGBE_PX_MISC_IC_TIMESYNC))
wx_ptp_check_pps_event(wx);
wx->isb_mem[WX_ISB_MISC] = 0;
/* would disable interrupts here but it is auto disabled */
napi_schedule_irqoff(&q_vector->napi);
if (!test_bit(WX_STATE_DOWN, wx->state))
ngbe_irq_enable(wx, false);
return IRQ_HANDLED;
}
static irqreturn_t __ngbe_msix_misc(struct wx *wx, u32 eicr)
{
if (eicr & NGBE_PX_MISC_IC_VF_MBOX)
wx_msg_task(wx);
if (unlikely(eicr & NGBE_PX_MISC_IC_TIMESYNC))
wx_ptp_check_pps_event(wx);
/* re-enable the original interrupt state, no lsc, no queues */
if (!test_bit(WX_STATE_DOWN, wx->state))
ngbe_irq_enable(wx, false);
return IRQ_HANDLED;
}
static irqreturn_t ngbe_msix_misc(int __always_unused irq, void *data)
{
struct wx *wx = data;
u32 eicr;
eicr = wx_misc_isb(wx, WX_ISB_MISC);
return __ngbe_msix_misc(wx, eicr);
}
static irqreturn_t ngbe_misc_and_queue(int __always_unused irq, void *data)
{
struct wx_q_vector *q_vector;
struct wx *wx = data;
u32 eicr;
eicr = wx_misc_isb(wx, WX_ISB_MISC);
if (!eicr) {
/* queue */
q_vector = wx->q_vector[0];
napi_schedule_irqoff(&q_vector->napi);
if (!test_bit(WX_STATE_DOWN, wx->state))
ngbe_irq_enable(wx, true);
return IRQ_HANDLED;
}
return __ngbe_msix_misc(wx, eicr);
}
/**
* ngbe_request_msix_irqs - Initialize MSI-X interrupts
* @wx: board private structure
*
* ngbe_request_msix_irqs allocates MSI-X vectors and requests
* interrupts from the kernel.
**/
static int ngbe_request_msix_irqs(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
int vector, err;
for (vector = 0; vector < wx->num_q_vectors; vector++) {
struct wx_q_vector *q_vector = wx->q_vector[vector];
struct msix_entry *entry = &wx->msix_q_entries[vector];
if (q_vector->tx.ring && q_vector->rx.ring)
snprintf(q_vector->name, sizeof(q_vector->name) - 1,
"%s-TxRx-%d", netdev->name, entry->entry);
else
/* skip this unused q_vector */
continue;
err = request_irq(entry->vector, wx_msix_clean_rings, 0,
q_vector->name, q_vector);
if (err) {
wx_err(wx, "request_irq failed for MSIX interrupt %s Error: %d\n",
q_vector->name, err);
goto free_queue_irqs;
}
}
/* Due to hardware design, when num_vfs < 7, pf can use 0 for misc and 1
* for queue. But when num_vfs == 7, vector[1] is assigned to vf6.
* Misc and queue should reuse interrupt vector[0].
*/
if (test_bit(WX_FLAG_IRQ_VECTOR_SHARED, wx->flags))
err = request_irq(wx->msix_entry->vector,
ngbe_misc_and_queue, 0, netdev->name, wx);
else
err = request_irq(wx->msix_entry->vector,
ngbe_msix_misc, 0, netdev->name, wx);
if (err) {
wx_err(wx, "request_irq for msix_other failed: %d\n", err);
goto free_queue_irqs;
}
return 0;
free_queue_irqs:
while (vector) {
vector--;
free_irq(wx->msix_q_entries[vector].vector,
wx->q_vector[vector]);
}
wx_reset_interrupt_capability(wx);
return err;
}
/**
* ngbe_request_irq - initialize interrupts
* @wx: board private structure
*
* Attempts to configure interrupts using the best available
* capabilities of the hardware and kernel.
**/
static int ngbe_request_irq(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
struct pci_dev *pdev = wx->pdev;
int err;
if (pdev->msix_enabled)
err = ngbe_request_msix_irqs(wx);
else if (pdev->msi_enabled)
err = request_irq(pdev->irq, ngbe_intr, 0,
netdev->name, wx);
else
err = request_irq(pdev->irq, ngbe_intr, IRQF_SHARED,
netdev->name, wx);
if (err)
wx_err(wx, "request_irq failed, Error %d\n", err);
return err;
}
static void ngbe_disable_device(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
u32 i;
if (test_and_set_bit(WX_STATE_DOWN, wx->state))
return;
if (wx->num_vfs) {
/* Clear EITR Select mapping */
wr32(wx, WX_PX_ITRSEL, 0);
/* Mark all the VFs as inactive */
for (i = 0; i < wx->num_vfs; i++)
wx->vfinfo[i].clear_to_send = 0;
wx->notify_down = true;
/* ping all the active vfs to let them know we are going down */
wx_ping_all_vfs_with_link_status(wx, false);
wx->notify_down = false;
/* Disable all VFTE/VFRE TX/RX */
wx_disable_vf_rx_tx(wx);
}
/* disable all enabled rx queues */
for (i = 0; i < wx->num_rx_queues; i++)
/* this call also flushes the previous write */
wx_disable_rx_queue(wx, wx->rx_ring[i]);
/* disable receives */
wx_disable_rx(wx);
wx_napi_disable_all(wx);
netif_tx_stop_all_queues(netdev);
netif_tx_disable(netdev);
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
if (wx->gpio_ctrl)
ngbe_sfp_modules_txrx_powerctl(wx, false);
wx_irq_disable(wx);
/* disable transmits in the hardware now that interrupts are off */
for (i = 0; i < wx->num_tx_queues; i++) {
u8 reg_idx = wx->tx_ring[i]->reg_idx;
wr32(wx, WX_PX_TR_CFG(reg_idx), WX_PX_TR_CFG_SWFLSH);
}
}
static void ngbe_reset(struct wx *wx)
{
wx_flush_sw_mac_table(wx);
wx_mac_set_default_filter(wx, wx->mac.addr);
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset(wx);
}
void ngbe_down(struct wx *wx)
{
phylink_stop(wx->phylink);
ngbe_disable_device(wx);
ngbe_reset(wx);
wx_clean_all_tx_rings(wx);
wx_clean_all_rx_rings(wx);
}
void ngbe_up(struct wx *wx)
{
wx_configure_vectors(wx);
/* make sure to complete pre-operations */
smp_mb__before_atomic();
clear_bit(WX_STATE_DOWN, wx->state);
wx_napi_enable_all(wx);
/* enable transmits */
netif_tx_start_all_queues(wx->netdev);
mod_timer(&wx->service_timer, jiffies);
/* clear any pending interrupts, may auto mask */
rd32(wx, WX_PX_IC(0));
rd32(wx, WX_PX_MISC_IC);
ngbe_irq_enable(wx, true);
if (wx->gpio_ctrl)
ngbe_sfp_modules_txrx_powerctl(wx, true);
phylink_start(wx->phylink);
/* Set PF Reset Done bit so PF/VF Mail Ops can work */
wr32m(wx, WX_CFG_PORT_CTL,
WX_CFG_PORT_CTL_PFRSTD, WX_CFG_PORT_CTL_PFRSTD);
if (wx->num_vfs)
wx_ping_all_vfs_with_link_status(wx, false);
}
/**
* ngbe_open - Called when a network interface is made active
* @netdev: network interface device structure
*
* Returns 0 on success, negative value on failure
*
* The open entry point is called when a network interface is made
* active by the system (IFF_UP).
**/
static int ngbe_open(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
int err;
wx_control_hw(wx, true);
err = wx_setup_resources(wx);
if (err)
return err;
wx_configure(wx);
err = ngbe_request_irq(wx);
if (err)
goto err_free_resources;
err = phylink_connect_phy(wx->phylink, wx->phydev);
if (err)
goto err_free_irq;
err = netif_set_real_num_tx_queues(netdev, wx->num_tx_queues);
if (err)
goto err_dis_phy;
err = netif_set_real_num_rx_queues(netdev, wx->num_rx_queues);
if (err)
goto err_dis_phy;
wx_ptp_init(wx);
ngbe_up(wx);
return 0;
err_dis_phy:
phylink_disconnect_phy(wx->phylink);
err_free_irq:
wx_free_irq(wx);
err_free_resources:
wx_free_isb_resources(wx);
wx_free_resources(wx);
return err;
}
/**
* ngbe_close - Disables a network interface
* @netdev: network interface device structure
*
* Returns 0, this is not allowed to fail
*
* The close entry point is called when an interface is de-activated
* by the OS. The hardware is still under the drivers control, but
* needs to be disabled. A global MAC reset is issued to stop the
* hardware, and all transmit and receive resources are freed.
**/
static int ngbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
wx_ptp_stop(wx);
ngbe_down(wx);
wx_free_irq(wx);
wx_free_isb_resources(wx);
wx_free_resources(wx);
phylink_disconnect_phy(wx->phylink);
wx_control_hw(wx, false);
return 0;
}
static void ngbe_dev_shutdown(struct pci_dev *pdev, bool *enable_wake)
{
struct wx *wx = pci_get_drvdata(pdev);
struct net_device *netdev;
u32 wufc = wx->wol;
netdev = wx->netdev;
rtnl_lock();
netif_device_detach(netdev);
if (netif_running(netdev))
ngbe_close(netdev);
wx_clear_interrupt_scheme(wx);
rtnl_unlock();
if (wufc) {
wx_set_rx_mode(netdev);
wx_configure_rx(wx);
wr32(wx, WX_PSR_WKUP_CTL, wufc);
} else {
wr32(wx, WX_PSR_WKUP_CTL, 0);
}
pci_wake_from_d3(pdev, !!wufc);
*enable_wake = !!wufc;
wx_control_hw(wx, false);
pci_disable_device(pdev);
}
static void ngbe_shutdown(struct pci_dev *pdev)
{
struct wx *wx = pci_get_drvdata(pdev);
bool wake;
wake = !!wx->wol;
ngbe_dev_shutdown(pdev, &wake);
if (system_state == SYSTEM_POWER_OFF) {
pci_wake_from_d3(pdev, wake);
pci_set_power_state(pdev, PCI_D3hot);
}
}
/**
* ngbe_setup_tc - routine to configure net_device for multiple traffic
* classes.
*
* @dev: net device to configure
* @tc: number of traffic classes to enable
*/
int ngbe_setup_tc(struct net_device *dev, u8 tc)
{
struct wx *wx = netdev_priv(dev);
/* Hardware has to reinitialize queues and interrupts to
* match packet buffer alignment. Unfortunately, the
* hardware is not flexible enough to do this dynamically.
*/
if (netif_running(dev))
ngbe_close(dev);
wx_clear_interrupt_scheme(wx);
if (tc)
netdev_set_num_tc(dev, tc);
else
netdev_reset_tc(dev);
wx_init_interrupt_scheme(wx);
if (netif_running(dev))
ngbe_open(dev);
return 0;
}
static const struct net_device_ops ngbe_netdev_ops = {
.ndo_open = ngbe_open,
.ndo_stop = ngbe_close,
.ndo_change_mtu = wx_change_mtu,
.ndo_start_xmit = wx_xmit_frame,
.ndo_set_rx_mode = wx_set_rx_mode,
.ndo_set_features = wx_set_features,
.ndo_fix_features = wx_fix_features,
.ndo_features_check = wx_features_check,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = wx_set_mac,
.ndo_get_stats64 = wx_get_stats64,
.ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
.ndo_hwtstamp_set = wx_hwtstamp_set,
.ndo_hwtstamp_get = wx_hwtstamp_get,
};
/**
* ngbe_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in ngbe_pci_tbl
*
* Returns 0 on success, negative on failure
*
* ngbe_probe initializes an wx identified by a pci_dev structure.
* The OS initialization, configuring of the wx private structure,
* and a hardware reset occur.
**/
static int ngbe_probe(struct pci_dev *pdev,
const struct pci_device_id __always_unused *ent)
{
struct net_device *netdev;
u32 e2rom_cksum_cap = 0;
struct wx *wx = NULL;
static int func_nums;
u16 e2rom_ver = 0;
u32 etrack_id = 0;
u32 saved_ver = 0;
int err;
err = pci_enable_device_mem(pdev);
if (err)
return err;
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(&pdev->dev,
"No usable DMA configuration, aborting\n");
goto err_pci_disable_dev;
}
err = pci_request_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM),
ngbe_driver_name);
if (err) {
dev_err(&pdev->dev,
"pci_request_selected_regions failed %d\n", err);
goto err_pci_disable_dev;
}
pci_set_master(pdev);
netdev = devm_alloc_etherdev_mqs(&pdev->dev,
sizeof(struct wx),
NGBE_MAX_TX_QUEUES,
NGBE_MAX_RX_QUEUES);
if (!netdev) {
err = -ENOMEM;
goto err_pci_release_regions;
}
SET_NETDEV_DEV(netdev, &pdev->dev);
wx = netdev_priv(netdev);
wx->netdev = netdev;
wx->pdev = pdev;
wx->msg_enable = BIT(3) - 1;
wx->hw_addr = devm_ioremap(&pdev->dev,
pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (!wx->hw_addr) {
err = -EIO;
goto err_pci_release_regions;
}
/* The emerald supports up to 8 VFs per pf, but physical
* function also need one pool for basic networking.
*/
pci_sriov_set_totalvfs(pdev, NGBE_MAX_VFS_DRV_LIMIT);
wx->driver_name = ngbe_driver_name;
ngbe_set_ethtool_ops(netdev);
netdev->netdev_ops = &ngbe_netdev_ops;
netdev->features = NETIF_F_SG | NETIF_F_IP_CSUM |
NETIF_F_TSO | NETIF_F_TSO6 |
NETIF_F_RXHASH | NETIF_F_RXCSUM;
netdev->features |= NETIF_F_SCTP_CRC | NETIF_F_TSO_MANGLEID;
netdev->vlan_features |= netdev->features;
netdev->features |= NETIF_F_IPV6_CSUM | NETIF_F_VLAN_FEATURES;
/* copy netdev features into list of user selectable features */
netdev->hw_features |= netdev->features | NETIF_F_RXALL;
netdev->hw_features |= NETIF_F_NTUPLE | NETIF_F_HW_TC;
netdev->features |= NETIF_F_HIGHDMA;
netdev->hw_features |= NETIF_F_GRO;
netdev->features |= NETIF_F_GRO;
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
netdev->min_mtu = ETH_MIN_MTU;
netdev->max_mtu = WX_MAX_JUMBO_FRAME_SIZE -
(ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN);
wx->bd_number = func_nums;
/* setup the private structure */
err = ngbe_sw_init(wx);
if (err)
goto err_pci_release_regions;
/* check if flash load is done after hw power up */
err = wx_check_flash_load(wx, NGBE_SPI_ILDR_STATUS_PERST);
if (err)
goto err_free_mac_table;
err = wx_check_flash_load(wx, NGBE_SPI_ILDR_STATUS_PWRRST);
if (err)
goto err_free_mac_table;
err = wx_mng_present(wx);
if (err) {
dev_err(&pdev->dev, "Management capability is not present\n");
goto err_free_mac_table;
}
err = ngbe_reset_hw(wx);
if (err) {
dev_err(&pdev->dev, "HW Init failed: %d\n", err);
goto err_free_mac_table;
}
if (wx->bus.func == 0) {
wr32(wx, NGBE_CALSUM_CAP_STATUS, 0x0);
wr32(wx, NGBE_EEPROM_VERSION_STORE_REG, 0x0);
} else {
e2rom_cksum_cap = rd32(wx, NGBE_CALSUM_CAP_STATUS);
saved_ver = rd32(wx, NGBE_EEPROM_VERSION_STORE_REG);
}
wx_init_eeprom_params(wx);
if (wx->bus.func == 0 || e2rom_cksum_cap == 0) {
/* make sure the EEPROM is ready */
err = ngbe_eeprom_chksum_hostif(wx);
if (err) {
dev_err(&pdev->dev, "The EEPROM Checksum Is Not Valid\n");
err = -EIO;
goto err_free_mac_table;
}
}
wx->wol = 0;
if (wx->wol_hw_supported)
wx->wol = WX_PSR_WKUP_CTL_MAG;
netdev->ethtool->wol_enabled = !!(wx->wol);
wr32(wx, WX_PSR_WKUP_CTL, wx->wol);
device_set_wakeup_enable(&pdev->dev, wx->wol);
/* Save off EEPROM version number and Option Rom version which
* together make a unique identify for the eeprom
*/
if (saved_ver) {
etrack_id = saved_ver;
} else {
wx_read_ee_hostif(wx,
wx->eeprom.sw_region_offset + NGBE_EEPROM_VERSION_H,
&e2rom_ver);
etrack_id = e2rom_ver << 16;
wx_read_ee_hostif(wx,
wx->eeprom.sw_region_offset + NGBE_EEPROM_VERSION_L,
&e2rom_ver);
etrack_id |= e2rom_ver;
wr32(wx, NGBE_EEPROM_VERSION_STORE_REG, etrack_id);
}
snprintf(wx->eeprom_id, sizeof(wx->eeprom_id),
"0x%08x", etrack_id);
eth_hw_addr_set(netdev, wx->mac.perm_addr);
wx_mac_set_default_filter(wx, wx->mac.perm_addr);
ngbe_init_service(wx);
err = wx_init_interrupt_scheme(wx);
if (err)
goto err_cancel_service;
/* phy Interface Configuration */
err = ngbe_mdio_init(wx);
if (err)
goto err_clear_interrupt_scheme;
err = register_netdev(netdev);
if (err)
goto err_register;
pci_set_drvdata(pdev, wx);
return 0;
err_register:
phylink_destroy(wx->phylink);
wx_control_hw(wx, false);
err_clear_interrupt_scheme:
wx_clear_interrupt_scheme(wx);
err_cancel_service:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
err_free_mac_table:
kfree(wx->rss_key);
kfree(wx->mac_table);
err_pci_release_regions:
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_disable_dev:
pci_disable_device(pdev);
return err;
}
/**
* ngbe_remove - Device Removal Routine
* @pdev: PCI device information struct
*
* ngbe_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device. The could be caused by a
* Hot-Plug event, or because the driver is going to be removed from
* memory.
**/
static void ngbe_remove(struct pci_dev *pdev)
{
struct wx *wx = pci_get_drvdata(pdev);
struct net_device *netdev;
netdev = wx->netdev;
wx_disable_sriov(wx);
unregister_netdev(netdev);
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
phylink_destroy(wx->phylink);
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
kfree(wx->rss_key);
kfree(wx->mac_table);
wx_clear_interrupt_scheme(wx);
pci_disable_device(pdev);
}
static int ngbe_suspend(struct pci_dev *pdev, pm_message_t state)
{
bool wake;
ngbe_dev_shutdown(pdev, &wake);
device_set_wakeup_enable(&pdev->dev, wake);
return 0;
}
static int ngbe_resume(struct pci_dev *pdev)
{
struct net_device *netdev;
struct wx *wx;
u32 err;
wx = pci_get_drvdata(pdev);
netdev = wx->netdev;
err = pci_enable_device_mem(pdev);
if (err) {
wx_err(wx, "Cannot enable PCI device from suspend\n");
return err;
}
pci_set_master(pdev);
device_wakeup_disable(&pdev->dev);
ngbe_reset_hw(wx);
rtnl_lock();
err = wx_init_interrupt_scheme(wx);
if (!err && netif_running(netdev))
err = ngbe_open(netdev);
if (!err)
netif_device_attach(netdev);
rtnl_unlock();
return 0;
}
static struct pci_driver ngbe_driver = {
.name = ngbe_driver_name,
.id_table = ngbe_pci_tbl,
.probe = ngbe_probe,
.remove = ngbe_remove,
.suspend = ngbe_suspend,
.resume = ngbe_resume,
.shutdown = ngbe_shutdown,
.sriov_configure = wx_pci_sriov_configure,
};
module_pci_driver(ngbe_driver);
MODULE_DEVICE_TABLE(pci, ngbe_pci_tbl);
MODULE_AUTHOR("Beijing WangXun Technology Co., Ltd, <software@net-swift.com>");
MODULE_DESCRIPTION("WangXun(R) Gigabit PCI Express Network Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd. */
#include <linux/ethtool.h>
#include <linux/iopoll.h>
#include <linux/pci.h>
#include <linux/phy.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_sriov.h"
#include "ngbe_type.h"
#include "ngbe_mdio.h"
static int ngbe_phy_read_reg_internal(struct mii_bus *bus, int phy_addr, int regnum)
{
struct wx *wx = bus->priv;
if (phy_addr != 0)
return 0xffff;
return (u16)rd32(wx, NGBE_PHY_CONFIG(regnum));
}
static int ngbe_phy_write_reg_internal(struct mii_bus *bus, int phy_addr, int regnum, u16 value)
{
struct wx *wx = bus->priv;
if (phy_addr == 0)
wr32(wx, NGBE_PHY_CONFIG(regnum), value);
return 0;
}
static int ngbe_phy_read_reg_c22(struct mii_bus *bus, int phy_addr, int regnum)
{
struct wx *wx = bus->priv;
u16 phy_data;
if (wx->mac_type == em_mac_type_mdi)
phy_data = ngbe_phy_read_reg_internal(bus, phy_addr, regnum);
else
phy_data = wx_phy_read_reg_mdi_c22(bus, phy_addr, regnum);
return phy_data;
}
static int ngbe_phy_write_reg_c22(struct mii_bus *bus, int phy_addr,
int regnum, u16 value)
{
struct wx *wx = bus->priv;
int ret;
if (wx->mac_type == em_mac_type_mdi)
ret = ngbe_phy_write_reg_internal(bus, phy_addr, regnum, value);
else
ret = wx_phy_write_reg_mdi_c22(bus, phy_addr, regnum, value);
return ret;
}
static void ngbe_mac_config(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
}
static void ngbe_mac_link_down(struct phylink_config *config,
unsigned int mode, phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
wx->speed = SPEED_UNKNOWN;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going down */
wx_ping_all_vfs_with_link_status(wx, false);
}
static void ngbe_mac_link_up(struct phylink_config *config,
struct phy_device *phy,
unsigned int mode, phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct wx *wx = phylink_to_wx(config);
u32 lan_speed, reg;
wx_fc_enable(wx, tx_pause, rx_pause);
switch (speed) {
case SPEED_10:
lan_speed = 0;
break;
case SPEED_100:
lan_speed = 1;
break;
case SPEED_1000:
default:
lan_speed = 2;
break;
}
wr32m(wx, NGBE_CFG_LAN_SPEED, 0x3, lan_speed);
reg = rd32(wx, WX_MAC_TX_CFG);
reg &= ~WX_MAC_TX_CFG_SPEED_MASK;
reg |= WX_MAC_TX_CFG_SPEED_1G | WX_MAC_TX_CFG_TE;
wr32(wx, WX_MAC_TX_CFG, reg);
/* Re configure MAC Rx */
reg = rd32(wx, WX_MAC_RX_CFG);
wr32(wx, WX_MAC_RX_CFG, reg);
wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
reg = rd32(wx, WX_MAC_WDG_TIMEOUT);
wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
wx->speed = speed;
wx->last_rx_ptp_check = jiffies;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going up */
wx_ping_all_vfs_with_link_status(wx, true);
}
static const struct phylink_mac_ops ngbe_mac_ops = {
.mac_config = ngbe_mac_config,
.mac_link_down = ngbe_mac_link_down,
.mac_link_up = ngbe_mac_link_up,
};
static int ngbe_phylink_init(struct wx *wx)
{
struct phylink_config *config;
phy_interface_t phy_mode;
struct phylink *phylink;
config = &wx->phylink_config;
config->dev = &wx->netdev->dev;
config->type = PHYLINK_NETDEV;
config->mac_capabilities = MAC_1000FD | MAC_100FD | MAC_10FD |
MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
config->mac_managed_pm = true;
/* The MAC only has add the Tx delay and it can not be modified.
* So just disable TX delay in PHY, and it is does not matter to
* internal phy.
*/
phy_mode = PHY_INTERFACE_MODE_RGMII_RXID;
__set_bit(PHY_INTERFACE_MODE_RGMII_RXID, config->supported_interfaces);
phylink = phylink_create(config, NULL, phy_mode, &ngbe_mac_ops);
if (IS_ERR(phylink))
return PTR_ERR(phylink);
wx->phylink = phylink;
return 0;
}
int ngbe_mdio_init(struct wx *wx)
{
struct pci_dev *pdev = wx->pdev;
struct mii_bus *mii_bus;
int ret;
mii_bus = devm_mdiobus_alloc(&pdev->dev);
if (!mii_bus)
return -ENOMEM;
mii_bus->name = "ngbe_mii_bus";
mii_bus->read = ngbe_phy_read_reg_c22;
mii_bus->write = ngbe_phy_write_reg_c22;
mii_bus->phy_mask = GENMASK(31, 4);
mii_bus->parent = &pdev->dev;
mii_bus->priv = wx;
if (wx->mac_type == em_mac_type_rgmii) {
mii_bus->read_c45 = wx_phy_read_reg_mdi_c45;
mii_bus->write_c45 = wx_phy_write_reg_mdi_c45;
}
snprintf(mii_bus->id, MII_BUS_ID_SIZE, "ngbe-%x", pci_dev_id(pdev));
ret = devm_mdiobus_register(&pdev->dev, mii_bus);
if (ret)
return ret;
wx->phydev = phy_find_first(mii_bus);
if (!wx->phydev)
return -ENODEV;
phy_attached_info(wx->phydev);
wx->link = 0;
wx->speed = 0;
wx->duplex = 0;
ret = ngbe_phylink_init(wx);
if (ret) {
wx_err(wx, "failed to init phylink: %d\n", ret);
return ret;
}
return 0;
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* WangXun Gigabit PCI Express Linux driver
* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd.
*/
#ifndef _NGBE_MDIO_H_
#define _NGBE_MDIO_H_
int ngbe_mdio_init(struct wx *wx);
#endif /* _NGBE_MDIO_H_ */
@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2019 - 2022 Beijing WangXun Technology Co., Ltd. */
#ifndef _NGBE_TYPE_H_
#define _NGBE_TYPE_H_
#include <linux/types.h>
#include <linux/netdevice.h>
/************ NGBE_register.h ************/
/* Device IDs */
#define NGBE_DEV_ID_EM_WX1860AL_W 0x0100
#define NGBE_DEV_ID_EM_WX1860A2 0x0101
#define NGBE_DEV_ID_EM_WX1860A2S 0x0102
#define NGBE_DEV_ID_EM_WX1860A4 0x0103
#define NGBE_DEV_ID_EM_WX1860A4S 0x0104
#define NGBE_DEV_ID_EM_WX1860AL2 0x0105
#define NGBE_DEV_ID_EM_WX1860AL2S 0x0106
#define NGBE_DEV_ID_EM_WX1860AL4 0x0107
#define NGBE_DEV_ID_EM_WX1860AL4S 0x0108
#define NGBE_DEV_ID_EM_WX1860LC 0x0109
#define NGBE_DEV_ID_EM_WX1860A1 0x010a
#define NGBE_DEV_ID_EM_WX1860A1L 0x010b
/* Subsystem ID */
#define NGBE_SUBID_M88E1512_SFP 0x0003
#define NGBE_SUBID_OCP_CARD 0x0040
#define NGBE_SUBID_LY_M88E1512_SFP 0x0050
#define NGBE_SUBID_M88E1512_RJ45 0x0051
#define NGBE_SUBID_M88E1512_MIX 0x0052
#define NGBE_SUBID_YT8521S_SFP 0x0060
#define NGBE_SUBID_INTERNAL_YT8521S_SFP 0x0061
#define NGBE_SUBID_YT8521S_SFP_GPIO 0x0062
#define NGBE_SUBID_INTERNAL_YT8521S_SFP_GPIO 0x0064
#define NGBE_SUBID_LY_YT8521S_SFP 0x0070
#define NGBE_SUBID_RGMII_FPGA 0x0080
#define NGBE_OEM_MASK 0x00FF
/**************** EM Registers ****************************/
/* chip control Registers */
#define NGBE_MIS_PRB_CTL 0x10010
/* FMGR Registers */
#define NGBE_SPI_ILDR_STATUS 0x10120
#define NGBE_SPI_ILDR_STATUS_PERST BIT(0) /* PCIE_PERST is done */
#define NGBE_SPI_ILDR_STATUS_PWRRST BIT(1) /* Power on reset is done */
/* Checksum and EEPROM pointers */
#define NGBE_CALSUM_COMMAND 0xE9
#define NGBE_CALSUM_CAP_STATUS 0x10224
#define NGBE_EEPROM_VERSION_STORE_REG 0x1022C
#define NGBE_SAN_MAC_ADDR_PTR 0x18
#define NGBE_DEVICE_CAPS 0x1C
#define NGBE_EEPROM_VERSION_L 0x1D
#define NGBE_EEPROM_VERSION_H 0x1E
/* GPIO Registers */
#define NGBE_GPIO_DR 0x14800
#define NGBE_GPIO_DDR 0x14804
/*GPIO bit */
#define NGBE_GPIO_DR_0 BIT(0) /* SDP0 Data Value */
#define NGBE_GPIO_DR_1 BIT(1) /* SDP1 Data Value */
#define NGBE_GPIO_DDR_0 BIT(0) /* SDP0 IO direction */
#define NGBE_GPIO_DDR_1 BIT(1) /* SDP1 IO direction */
/* Extended Interrupt Enable Set */
#define NGBE_PX_MISC_IEN_DEV_RST BIT(10)
#define NGBE_PX_MISC_IEN_TIMESYNC BIT(11)
#define NGBE_PX_MISC_IEN_ETH_LK BIT(18)
#define NGBE_PX_MISC_IEN_INT_ERR BIT(20)
#define NGBE_PX_MISC_IC_VF_MBOX BIT(23)
#define NGBE_PX_MISC_IEN_GPIO BIT(26)
#define NGBE_PX_MISC_IEN_MASK ( \
NGBE_PX_MISC_IEN_DEV_RST | \
NGBE_PX_MISC_IEN_TIMESYNC | \
NGBE_PX_MISC_IEN_ETH_LK | \
NGBE_PX_MISC_IEN_INT_ERR | \
NGBE_PX_MISC_IC_VF_MBOX | \
NGBE_PX_MISC_IEN_GPIO)
/* Extended Interrupt Cause Read */
#define NGBE_PX_MISC_IC_TIMESYNC BIT(11) /* time sync */
#define NGBE_INTR_ALL 0x1FF
#define NGBE_INTR_MISC(A) BIT((A)->msix_entry->entry)
#define NGBE_PHY_CONFIG(reg_offset) (0x14000 + ((reg_offset) * 4))
#define NGBE_CFG_LAN_SPEED 0x14440
#define NGBE_CFG_PORT_ST 0x14404
#define NGBE_FW_EEPROM_CHECKSUM_CMD 0xE9
#define NGBE_FW_NVM_DATA_OFFSET 3
#define NGBE_FW_CMD_DEFAULT_CHECKSUM 0xFF /* checksum always 0xFF */
#define NGBE_FW_CMD_ST_PASS 0x80658383
#define NGBE_FW_CMD_ST_FAIL 0x70657376
#define NGBE_MAX_FDIR_INDICES 7
#define NGBE_MAX_RSS_INDICES 8
#define NGBE_MAX_RX_QUEUES (NGBE_MAX_FDIR_INDICES + 1)
#define NGBE_MAX_TX_QUEUES (NGBE_MAX_FDIR_INDICES + 1)
#define NGBE_ETH_LENGTH_OF_ADDRESS 6
#define NGBE_MAX_MSIX_VECTORS 0x09
#define NGBE_RAR_ENTRIES 32
#define NGBE_RX_PB_SIZE 42
#define NGBE_MC_TBL_SIZE 128
#define NGBE_SP_VFT_TBL_SIZE 128
#define NGBE_TDB_PB_SZ (20 * 1024) /* 160KB Packet Buffer */
/* TX/RX descriptor defines */
#define NGBE_DEFAULT_TXD 512 /* default ring size */
#define NGBE_DEFAULT_TX_WORK 256
#define NGBE_MAX_TXD 8192
#define NGBE_MIN_TXD 128
#define NGBE_DEFAULT_RXD 512 /* default ring size */
#define NGBE_DEFAULT_RX_WORK 256
#define NGBE_MAX_RXD 8192
#define NGBE_MIN_RXD 128
#define NGBE_MAX_VFS_DRV_LIMIT 7
extern char ngbe_driver_name[];
void ngbe_down(struct wx *wx);
void ngbe_up(struct wx *wx);
int ngbe_setup_tc(struct net_device *dev, u8 tc);
#endif /* _NGBE_TYPE_H_ */
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd.
#
# Makefile for the Wangxun(R) 1GbE virtual functions driver
#
obj-$(CONFIG_NGBE) += ngbevf.o
ngbevf-objs := ngbevf_main.o
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/types.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/etherdevice.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_vf.h"
#include "../libwx/wx_vf_common.h"
#include "../libwx/wx_ethtool.h"
#include "ngbevf_type.h"
/* ngbevf_pci_tbl - PCI Device ID Table
*
* Wildcard entries (PCI_ANY_ID) should come last
* Last entry must be all 0s
*
* { Vendor ID, Device ID, SubVendor ID, SubDevice ID,
* Class, Class Mask, private data (not used) }
*/
static const struct pci_device_id ngbevf_pci_tbl[] = {
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL_W) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860A2) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860A2S) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860A4) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860A4S) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL2) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL2S) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL4) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL4S) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860NCSI) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860A1) },
{ PCI_VDEVICE(WANGXUN, NGBEVF_DEV_ID_EM_WX1860AL1) },
/* required last entry */
{ }
};
static const struct net_device_ops ngbevf_netdev_ops = {
.ndo_open = wxvf_open,
.ndo_stop = wxvf_close,
.ndo_start_xmit = wx_xmit_frame,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = wx_set_mac_vf,
};
static void ngbevf_set_num_queues(struct wx *wx)
{
/* Start with base case */
wx->num_rx_queues = 1;
wx->num_tx_queues = 1;
}
static int ngbevf_sw_init(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
struct pci_dev *pdev = wx->pdev;
int err;
/* Initialize pcie info and common capability flags */
err = wx_sw_init(wx);
if (err < 0)
goto err_wx_sw_init;
/* Initialize the mailbox */
err = wx_init_mbx_params_vf(wx);
if (err)
goto err_init_mbx_params;
/* Initialize the device type */
wx->mac.type = wx_mac_em;
wx->mac.max_msix_vectors = NGBEVF_MAX_MSIX_VECTORS;
/* lock to protect mailbox accesses */
spin_lock_init(&wx->mbx.mbx_lock);
err = wx_reset_hw_vf(wx);
if (err) {
wx_err(wx, "PF still in reset state. Is the PF interface up?\n");
goto err_reset_hw;
}
wx_init_hw_vf(wx);
wx_negotiate_api_vf(wx);
if (is_zero_ether_addr(wx->mac.addr))
dev_info(&pdev->dev,
"MAC address not assigned by administrator.\n");
eth_hw_addr_set(netdev, wx->mac.addr);
if (!is_valid_ether_addr(netdev->dev_addr)) {
dev_info(&pdev->dev, "Assigning random MAC address\n");
eth_hw_addr_random(netdev);
ether_addr_copy(wx->mac.addr, netdev->dev_addr);
ether_addr_copy(wx->mac.perm_addr, netdev->dev_addr);
}
wx->mac.max_tx_queues = NGBEVF_MAX_TX_QUEUES;
wx->mac.max_rx_queues = NGBEVF_MAX_RX_QUEUES;
/* Enable dynamic interrupt throttling rates */
wx->adaptive_itr = true;
wx->rx_itr_setting = 1;
wx->tx_itr_setting = 1;
/* set default ring sizes */
wx->tx_ring_count = NGBEVF_DEFAULT_TXD;
wx->rx_ring_count = NGBEVF_DEFAULT_RXD;
/* set default work limits */
wx->tx_work_limit = NGBEVF_DEFAULT_TX_WORK;
wx->rx_work_limit = NGBEVF_DEFAULT_RX_WORK;
wx->set_num_queues = ngbevf_set_num_queues;
return 0;
err_reset_hw:
kfree(wx->vfinfo);
err_init_mbx_params:
kfree(wx->rss_key);
kfree(wx->mac_table);
err_wx_sw_init:
return err;
}
/**
* ngbevf_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in ngbevf_pci_tbl
*
* Return: return 0 on success, negative on failure
*
* ngbevf_probe initializes an adapter identified by a pci_dev structure.
* The OS initialization, configuring of the adapter private structure,
* and a hardware reset occur.
**/
static int ngbevf_probe(struct pci_dev *pdev,
const struct pci_device_id __always_unused *ent)
{
struct net_device *netdev;
struct wx *wx = NULL;
int err;
err = pci_enable_device_mem(pdev);
if (err)
return err;
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(&pdev->dev,
"No usable DMA configuration, aborting\n");
goto err_pci_disable_dev;
}
err = pci_request_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM),
dev_driver_string(&pdev->dev));
if (err) {
dev_err(&pdev->dev,
"pci_request_selected_regions failed 0x%x\n", err);
goto err_pci_disable_dev;
}
pci_set_master(pdev);
netdev = devm_alloc_etherdev_mqs(&pdev->dev,
sizeof(struct wx),
NGBEVF_MAX_TX_QUEUES,
NGBEVF_MAX_RX_QUEUES);
if (!netdev) {
err = -ENOMEM;
goto err_pci_release_regions;
}
SET_NETDEV_DEV(netdev, &pdev->dev);
wx = netdev_priv(netdev);
wx->netdev = netdev;
wx->pdev = pdev;
wx->msg_enable = netif_msg_init(-1, NETIF_MSG_DRV |
NETIF_MSG_PROBE | NETIF_MSG_LINK);
wx->hw_addr = devm_ioremap(&pdev->dev,
pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (!wx->hw_addr) {
err = -EIO;
goto err_pci_release_regions;
}
wx->driver_name = KBUILD_MODNAME;
wx_set_ethtool_ops_vf(netdev);
netdev->netdev_ops = &ngbevf_netdev_ops;
/* setup the private structure */
err = ngbevf_sw_init(wx);
if (err)
goto err_pci_release_regions;
netdev->features |= NETIF_F_HIGHDMA;
eth_hw_addr_set(netdev, wx->mac.perm_addr);
ether_addr_copy(netdev->perm_addr, wx->mac.addr);
wxvf_init_service(wx);
err = wx_init_interrupt_scheme(wx);
if (err)
goto err_free_sw_init;
wx_get_fw_version_vf(wx);
err = register_netdev(netdev);
if (err)
goto err_register;
pci_set_drvdata(pdev, wx);
netif_tx_stop_all_queues(netdev);
return 0;
err_register:
wx_clear_interrupt_scheme(wx);
err_free_sw_init:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
kfree(wx->vfinfo);
kfree(wx->rss_key);
kfree(wx->mac_table);
err_pci_release_regions:
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_disable_dev:
pci_disable_device(pdev);
return err;
}
/**
* ngbevf_remove - Device Removal Routine
* @pdev: PCI device information struct
*
* ngbevf_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device. The could be caused by a
* Hot-Plug event, or because the driver is going to be removed from
* memory.
**/
static void ngbevf_remove(struct pci_dev *pdev)
{
wxvf_remove(pdev);
}
static DEFINE_SIMPLE_DEV_PM_OPS(ngbevf_pm_ops, wxvf_suspend, wxvf_resume);
static struct pci_driver ngbevf_driver = {
.name = KBUILD_MODNAME,
.id_table = ngbevf_pci_tbl,
.probe = ngbevf_probe,
.remove = ngbevf_remove,
.shutdown = wxvf_shutdown,
/* Power Management Hooks */
.driver.pm = pm_sleep_ptr(&ngbevf_pm_ops)
};
module_pci_driver(ngbevf_driver);
MODULE_DEVICE_TABLE(pci, ngbevf_pci_tbl);
MODULE_AUTHOR("Beijing WangXun Technology Co., Ltd, <software@trustnetic.com>");
MODULE_DESCRIPTION("WangXun(R) Gigabit PCI Express Network Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _NGBEVF_TYPE_H_
#define _NGBEVF_TYPE_H_
/* Device IDs */
#define NGBEVF_DEV_ID_EM_WX1860AL_W 0x0110
#define NGBEVF_DEV_ID_EM_WX1860A2 0x0111
#define NGBEVF_DEV_ID_EM_WX1860A2S 0x0112
#define NGBEVF_DEV_ID_EM_WX1860A4 0x0113
#define NGBEVF_DEV_ID_EM_WX1860A4S 0x0114
#define NGBEVF_DEV_ID_EM_WX1860AL2 0x0115
#define NGBEVF_DEV_ID_EM_WX1860AL2S 0x0116
#define NGBEVF_DEV_ID_EM_WX1860AL4 0x0117
#define NGBEVF_DEV_ID_EM_WX1860AL4S 0x0118
#define NGBEVF_DEV_ID_EM_WX1860NCSI 0x0119
#define NGBEVF_DEV_ID_EM_WX1860A1 0x011a
#define NGBEVF_DEV_ID_EM_WX1860AL1 0x011b
#define NGBEVF_MAX_MSIX_VECTORS 1
#define NGBEVF_MAX_RX_QUEUES 1
#define NGBEVF_MAX_TX_QUEUES 1
#define NGBEVF_DEFAULT_TXD 128
#define NGBEVF_DEFAULT_RXD 128
#define NGBEVF_DEFAULT_TX_WORK 256
#define NGBEVF_DEFAULT_RX_WORK 256
#endif /* _NGBEVF_TYPE_H_ */
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd.
#
# Makefile for the Wangxun(R) 10GbE PCI Express ethernet driver
#
obj-$(CONFIG_TXGBE) += txgbe.o
txgbe-objs := txgbe_main.o \
txgbe_hw.o \
txgbe_phy.o \
txgbe_irq.o \
txgbe_fdir.o \
txgbe_ethtool.o \
txgbe_aml.o
@@ -0,0 +1,527 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/phylink.h>
#include <linux/iopoll.h>
#include <linux/pci.h>
#include <linux/phy.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_sriov.h"
#include "txgbe_type.h"
#include "txgbe_aml.h"
#include "txgbe_hw.h"
void txgbe_gpio_init_aml(struct wx *wx)
{
u32 status, mod_rst;
if (wx->mac.type == wx_mac_aml40)
mod_rst = TXGBE_GPIOBIT_4;
else
mod_rst = TXGBE_GPIOBIT_2;
wr32(wx, WX_GPIO_INTTYPE_LEVEL, mod_rst);
wr32(wx, WX_GPIO_INTEN, mod_rst);
status = rd32(wx, WX_GPIO_INTSTATUS);
for (int i = 0; i < 6; i++) {
if (status & BIT(i))
wr32(wx, WX_GPIO_EOI, BIT(i));
}
}
irqreturn_t txgbe_gpio_irq_handler_aml(int irq, void *data)
{
struct txgbe *txgbe = data;
struct wx *wx = txgbe->wx;
u32 status, mod_rst;
if (wx->mac.type == wx_mac_aml40)
mod_rst = TXGBE_GPIOBIT_4;
else
mod_rst = TXGBE_GPIOBIT_2;
wr32(wx, WX_GPIO_INTMASK, 0xFF);
status = rd32(wx, WX_GPIO_INTSTATUS);
if (status & mod_rst) {
set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags);
wr32(wx, WX_GPIO_EOI, mod_rst);
wx_service_event_schedule(wx);
}
wr32(wx, WX_GPIO_INTMASK, 0);
return IRQ_HANDLED;
}
int txgbe_test_hostif(struct wx *wx)
{
struct txgbe_hic_ephy_getlink buffer;
if (wx->mac.type == wx_mac_sp)
return 0;
buffer.hdr.cmd = FW_PHY_GET_LINK_CMD;
buffer.hdr.buf_len = sizeof(struct txgbe_hic_ephy_getlink) -
sizeof(struct wx_hic_hdr);
buffer.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer),
WX_HI_COMMAND_TIMEOUT, false);
}
int txgbe_read_eeprom_hostif(struct wx *wx,
struct txgbe_hic_i2c_read *buffer,
u32 length, u8 *data)
{
u32 dword_len, offset, value, i;
int err;
buffer->hdr.cmd = FW_READ_EEPROM_CMD;
buffer->hdr.buf_len = sizeof(struct txgbe_hic_i2c_read) -
sizeof(struct wx_hic_hdr);
buffer->hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
err = wx_host_interface_command(wx, (u32 *)buffer,
sizeof(struct txgbe_hic_i2c_read),
WX_HI_COMMAND_TIMEOUT, false);
if (err != 0)
return err;
/* buffer length offset to read return data */
offset = sizeof(struct txgbe_hic_i2c_read) >> 2;
dword_len = round_up(length, 4) >> 2;
for (i = 0; i < dword_len; i++) {
u32 copy_len = min_t(u32, 4, length - i * 4);
value = rd32a(wx, WX_FW2SW_MBOX, i + offset);
le32_to_cpus(&value);
memcpy(data, &value, copy_len);
data += copy_len;
}
return 0;
}
static int txgbe_identify_module_hostif(struct wx *wx,
struct txgbe_hic_get_module_info *buffer)
{
buffer->hdr.cmd = FW_GET_MODULE_INFO_CMD;
buffer->hdr.buf_len = sizeof(struct txgbe_hic_get_module_info) -
sizeof(struct wx_hic_hdr);
buffer->hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
return wx_host_interface_command(wx, (u32 *)buffer,
sizeof(struct txgbe_hic_get_module_info),
WX_HI_COMMAND_TIMEOUT, true);
}
static int txgbe_set_phy_link_hostif(struct wx *wx, int speed, int autoneg, int duplex)
{
struct txgbe_hic_ephy_setlink buffer;
buffer.hdr.cmd = FW_PHY_SET_LINK_CMD;
buffer.hdr.buf_len = sizeof(struct txgbe_hic_ephy_setlink) -
sizeof(struct wx_hic_hdr);
buffer.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
switch (speed) {
case SPEED_40000:
buffer.speed = TXGBE_LINK_SPEED_40GB_FULL;
break;
case SPEED_25000:
buffer.speed = TXGBE_LINK_SPEED_25GB_FULL;
break;
case SPEED_10000:
buffer.speed = TXGBE_LINK_SPEED_10GB_FULL;
break;
default:
buffer.speed = TXGBE_LINK_SPEED_UNKNOWN;
break;
}
buffer.fec_mode = TXGBE_PHY_FEC_AUTO;
buffer.autoneg = autoneg;
buffer.duplex = duplex;
return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer),
WX_HI_COMMAND_TIMEOUT, false);
}
static void txgbe_get_link_capabilities(struct wx *wx, int *speed,
int *autoneg, int *duplex)
{
struct txgbe *txgbe = wx->priv;
if (test_bit(PHY_INTERFACE_MODE_XLGMII, txgbe->link_interfaces))
*speed = SPEED_40000;
else if (test_bit(PHY_INTERFACE_MODE_25GBASER, txgbe->link_interfaces))
*speed = SPEED_25000;
else if (test_bit(PHY_INTERFACE_MODE_10GBASER, txgbe->link_interfaces))
*speed = SPEED_10000;
else
*speed = SPEED_UNKNOWN;
*autoneg = phylink_test(txgbe->advertising, Autoneg);
*duplex = *speed == SPEED_UNKNOWN ? DUPLEX_HALF : DUPLEX_FULL;
}
static void txgbe_get_mac_link(struct wx *wx, int *speed)
{
u32 status;
status = rd32(wx, TXGBE_CFG_PORT_ST);
if (!(status & TXGBE_CFG_PORT_ST_LINK_UP))
*speed = SPEED_UNKNOWN;
else if (status & TXGBE_CFG_PORT_ST_LINK_AML_40G)
*speed = SPEED_40000;
else if (status & TXGBE_CFG_PORT_ST_LINK_AML_25G)
*speed = SPEED_25000;
else if (status & TXGBE_CFG_PORT_ST_LINK_AML_10G)
*speed = SPEED_10000;
else
*speed = SPEED_UNKNOWN;
}
void txgbe_set_phy_link(struct wx *wx)
{
int speed, autoneg, duplex, err;
txgbe_get_link_capabilities(wx, &speed, &autoneg, &duplex);
err = txgbe_set_phy_link_hostif(wx, speed, autoneg, duplex);
if (err)
wx_err(wx, "Failed to setup link\n");
}
static int txgbe_sfp_to_linkmodes(struct wx *wx, struct txgbe_sff_id *id)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, };
DECLARE_PHY_INTERFACE_MASK_ZERO(interfaces);
struct txgbe *txgbe = wx->priv;
if (id->cable_tech & TXGBE_SFF_DA_PASSIVE_CABLE) {
txgbe->link_port = PORT_DA;
phylink_set(modes, Autoneg);
if (id->com_25g_code == TXGBE_SFF_25GBASECR_91FEC ||
id->com_25g_code == TXGBE_SFF_25GBASECR_74FEC ||
id->com_25g_code == TXGBE_SFF_25GBASECR_NOFEC) {
phylink_set(modes, 25000baseCR_Full);
phylink_set(modes, 10000baseCR_Full);
__set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
} else {
phylink_set(modes, 10000baseCR_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
} else if (id->cable_tech & TXGBE_SFF_DA_ACTIVE_CABLE) {
txgbe->link_port = PORT_DA;
phylink_set(modes, Autoneg);
phylink_set(modes, 25000baseCR_Full);
__set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
} else {
if (id->com_25g_code == TXGBE_SFF_25GBASESR_CAPABLE ||
id->com_25g_code == TXGBE_SFF_25GBASEER_CAPABLE ||
id->com_25g_code == TXGBE_SFF_25GBASELR_CAPABLE) {
txgbe->link_port = PORT_FIBRE;
phylink_set(modes, 25000baseSR_Full);
__set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
}
if (id->com_10g_code & TXGBE_SFF_10GBASESR_CAPABLE) {
txgbe->link_port = PORT_FIBRE;
phylink_set(modes, 10000baseSR_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
if (id->com_10g_code & TXGBE_SFF_10GBASELR_CAPABLE) {
txgbe->link_port = PORT_FIBRE;
phylink_set(modes, 10000baseLR_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
}
if (phy_interface_empty(interfaces)) {
wx_err(wx, "unsupported SFP module\n");
return -EINVAL;
}
phylink_set(modes, Pause);
phylink_set(modes, Asym_Pause);
phylink_set(modes, FIBRE);
if (!linkmode_equal(txgbe->link_support, modes)) {
linkmode_copy(txgbe->link_support, modes);
phy_interface_and(txgbe->link_interfaces,
wx->phylink_config.supported_interfaces,
interfaces);
linkmode_copy(txgbe->advertising, modes);
set_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags);
}
return 0;
}
static int txgbe_qsfp_to_linkmodes(struct wx *wx, struct txgbe_sff_id *id)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, };
DECLARE_PHY_INTERFACE_MASK_ZERO(interfaces);
struct txgbe *txgbe = wx->priv;
if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_CR4) {
txgbe->link_port = PORT_DA;
phylink_set(modes, Autoneg);
phylink_set(modes, 40000baseCR4_Full);
phylink_set(modes, 10000baseCR_Full);
__set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_SR4) {
txgbe->link_port = PORT_FIBRE;
phylink_set(modes, 40000baseSR4_Full);
__set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces);
}
if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_LR4) {
txgbe->link_port = PORT_FIBRE;
phylink_set(modes, 40000baseLR4_Full);
__set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces);
}
if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_ACTIVE) {
txgbe->link_port = PORT_DA;
phylink_set(modes, Autoneg);
phylink_set(modes, 40000baseCR4_Full);
__set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces);
}
if (id->transceiver_type & TXGBE_SFF_ETHERNET_RSRVD) {
if (id->sff_opt1 & TXGBE_SFF_ETHERNET_100G_CR4) {
txgbe->link_port = PORT_DA;
phylink_set(modes, Autoneg);
phylink_set(modes, 40000baseCR4_Full);
phylink_set(modes, 25000baseCR_Full);
phylink_set(modes, 10000baseCR_Full);
__set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces);
__set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
}
if (phy_interface_empty(interfaces)) {
wx_err(wx, "unsupported QSFP module\n");
return -EINVAL;
}
phylink_set(modes, Pause);
phylink_set(modes, Asym_Pause);
phylink_set(modes, FIBRE);
if (!linkmode_equal(txgbe->link_support, modes)) {
linkmode_copy(txgbe->link_support, modes);
phy_interface_and(txgbe->link_interfaces,
wx->phylink_config.supported_interfaces,
interfaces);
linkmode_copy(txgbe->advertising, modes);
set_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags);
}
return 0;
}
int txgbe_identify_module(struct wx *wx)
{
struct txgbe_hic_get_module_info buffer = { 0 };
struct txgbe_sff_id *id;
int err = 0;
u32 mod_abs;
u32 gpio;
if (wx->mac.type == wx_mac_aml40)
mod_abs = TXGBE_GPIOBIT_4;
else
mod_abs = TXGBE_GPIOBIT_2;
gpio = rd32(wx, WX_GPIO_EXT);
if (gpio & mod_abs)
return -ENODEV;
err = txgbe_identify_module_hostif(wx, &buffer);
if (err) {
wx_err(wx, "Failed to identify module\n");
return err;
}
id = &buffer.id;
if (id->identifier == TXGBE_SFF_IDENTIFIER_SFP)
return txgbe_sfp_to_linkmodes(wx, id);
if (id->identifier == TXGBE_SFF_IDENTIFIER_QSFP ||
id->identifier == TXGBE_SFF_IDENTIFIER_QSFP_PLUS ||
id->identifier == TXGBE_SFF_IDENTIFIER_QSFP28)
return txgbe_qsfp_to_linkmodes(wx, id);
wx_err(wx, "Invalid module\n");
return -EINVAL;
}
void txgbe_setup_link(struct wx *wx)
{
struct txgbe *txgbe = wx->priv;
phy_interface_zero(txgbe->link_interfaces);
linkmode_zero(txgbe->link_support);
set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags);
wx_service_event_schedule(wx);
}
static void txgbe_get_link_state(struct phylink_config *config,
struct phylink_link_state *state)
{
struct wx *wx = phylink_to_wx(config);
int speed;
txgbe_get_mac_link(wx, &speed);
state->link = speed != SPEED_UNKNOWN;
state->speed = speed;
state->duplex = state->link ? DUPLEX_FULL : DUPLEX_UNKNOWN;
}
static void txgbe_reconfig_mac(struct wx *wx)
{
u32 wdg, fc;
wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
fc = rd32(wx, WX_MAC_RX_FLOW_CTRL);
wr32(wx, WX_MIS_RST, TXGBE_MIS_RST_MAC_RST(wx->bus.func));
/* wait for MAC reset complete */
usleep_range(1000, 1500);
wr32m(wx, TXGBE_MAC_MISC_CTL, TXGBE_MAC_MISC_CTL_LINK_STS_MOD,
TXGBE_MAC_MISC_CTL_LINK_BOTH);
wx_reset_mac(wx);
wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
wr32(wx, WX_MAC_RX_FLOW_CTRL, fc);
}
static void txgbe_mac_link_up_aml(struct phylink_config *config,
struct phy_device *phy,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct wx *wx = phylink_to_wx(config);
u32 txcfg;
wx_fc_enable(wx, tx_pause, rx_pause);
txgbe_reconfig_mac(wx);
txgbe_enable_sec_tx_path(wx);
txcfg = rd32(wx, TXGBE_AML_MAC_TX_CFG);
txcfg &= ~TXGBE_AML_MAC_TX_CFG_SPEED_MASK;
switch (speed) {
case SPEED_40000:
txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_40G;
break;
case SPEED_25000:
txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_25G;
break;
case SPEED_10000:
txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_10G;
break;
default:
break;
}
wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, WX_MAC_RX_CFG_RE);
wr32(wx, TXGBE_AML_MAC_TX_CFG, txcfg | TXGBE_AML_MAC_TX_CFG_TE);
wx->speed = speed;
wx->last_rx_ptp_check = jiffies;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going up */
wx_ping_all_vfs_with_link_status(wx, true);
}
static void txgbe_mac_link_down_aml(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
wr32m(wx, TXGBE_AML_MAC_TX_CFG, TXGBE_AML_MAC_TX_CFG_TE, 0);
wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, 0);
wx->speed = SPEED_UNKNOWN;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going down */
wx_ping_all_vfs_with_link_status(wx, false);
}
static void txgbe_mac_config_aml(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
}
static const struct phylink_mac_ops txgbe_mac_ops_aml = {
.mac_config = txgbe_mac_config_aml,
.mac_link_down = txgbe_mac_link_down_aml,
.mac_link_up = txgbe_mac_link_up_aml,
};
int txgbe_phylink_init_aml(struct txgbe *txgbe)
{
struct phylink_link_state state;
struct phylink_config *config;
struct wx *wx = txgbe->wx;
phy_interface_t phy_mode;
struct phylink *phylink;
int err;
config = &wx->phylink_config;
config->dev = &wx->netdev->dev;
config->type = PHYLINK_NETDEV;
config->mac_capabilities = MAC_25000FD | MAC_10000FD |
MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
config->get_fixed_state = txgbe_get_link_state;
if (wx->mac.type == wx_mac_aml40) {
config->mac_capabilities |= MAC_40000FD;
phy_mode = PHY_INTERFACE_MODE_XLGMII;
__set_bit(PHY_INTERFACE_MODE_XLGMII, config->supported_interfaces);
state.speed = SPEED_40000;
state.duplex = DUPLEX_FULL;
} else {
phy_mode = PHY_INTERFACE_MODE_25GBASER;
state.speed = SPEED_25000;
state.duplex = DUPLEX_FULL;
}
__set_bit(PHY_INTERFACE_MODE_25GBASER, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
phylink = phylink_create(config, NULL, phy_mode, &txgbe_mac_ops_aml);
if (IS_ERR(phylink))
return PTR_ERR(phylink);
err = phylink_set_fixed_link(phylink, &state);
if (err) {
wx_err(wx, "Failed to set fixed link\n");
phylink_destroy(phylink);
return err;
}
wx->phylink = phylink;
return 0;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_AML_H_
#define _TXGBE_AML_H_
void txgbe_gpio_init_aml(struct wx *wx);
irqreturn_t txgbe_gpio_irq_handler_aml(int irq, void *data);
int txgbe_test_hostif(struct wx *wx);
int txgbe_read_eeprom_hostif(struct wx *wx,
struct txgbe_hic_i2c_read *buffer,
u32 length, u8 *data);
void txgbe_set_phy_link(struct wx *wx);
int txgbe_identify_module(struct wx *wx);
void txgbe_setup_link(struct wx *wx);
int txgbe_phylink_init_aml(struct txgbe *txgbe);
#endif /* _TXGBE_AML_H_ */
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/pci.h>
#include <linux/phylink.h>
#include <linux/netdevice.h>
#include "../libwx/wx_ethtool.h"
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "txgbe_type.h"
#include "txgbe_fdir.h"
#include "txgbe_aml.h"
#include "txgbe_ethtool.h"
int txgbe_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct wx *wx = netdev_priv(netdev);
struct txgbe *txgbe = wx->priv;
int err;
err = wx_get_link_ksettings(netdev, cmd);
if (err)
return err;
if (wx->mac.type == wx_mac_sp)
return 0;
cmd->base.port = txgbe->link_port;
cmd->base.autoneg = phylink_test(txgbe->advertising, Autoneg) ?
AUTONEG_ENABLE : AUTONEG_DISABLE;
linkmode_copy(cmd->link_modes.supported, txgbe->link_support);
linkmode_copy(cmd->link_modes.advertising, txgbe->advertising);
return 0;
}
static int txgbe_set_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
u32 new_rx_count, new_tx_count;
struct wx_ring *temp_ring;
int i, err = 0;
new_tx_count = clamp_t(u32, ring->tx_pending, WX_MIN_TXD, WX_MAX_TXD);
new_tx_count = ALIGN(new_tx_count, WX_REQ_TX_DESCRIPTOR_MULTIPLE);
new_rx_count = clamp_t(u32, ring->rx_pending, WX_MIN_RXD, WX_MAX_RXD);
new_rx_count = ALIGN(new_rx_count, WX_REQ_RX_DESCRIPTOR_MULTIPLE);
if (new_tx_count == wx->tx_ring_count &&
new_rx_count == wx->rx_ring_count)
return 0;
mutex_lock(&wx->reset_lock);
set_bit(WX_STATE_RESETTING, wx->state);
if (!netif_running(wx->netdev)) {
for (i = 0; i < wx->num_tx_queues; i++)
wx->tx_ring[i]->count = new_tx_count;
for (i = 0; i < wx->num_rx_queues; i++)
wx->rx_ring[i]->count = new_rx_count;
wx->tx_ring_count = new_tx_count;
wx->rx_ring_count = new_rx_count;
goto clear_reset;
}
/* allocate temporary buffer to store rings in */
i = max_t(int, wx->num_tx_queues, wx->num_rx_queues);
temp_ring = kvmalloc_objs(struct wx_ring, i);
if (!temp_ring) {
err = -ENOMEM;
goto clear_reset;
}
txgbe_down(wx);
wx_set_ring(wx, new_tx_count, new_rx_count, temp_ring);
kvfree(temp_ring);
txgbe_up(wx);
clear_reset:
clear_bit(WX_STATE_RESETTING, wx->state);
mutex_unlock(&wx->reset_lock);
return err;
}
static int txgbe_get_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
union txgbe_atr_input *mask = &txgbe->fdir_mask;
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (fsp->location <= rule->sw_idx)
break;
}
if (!rule || fsp->location != rule->sw_idx)
return -EINVAL;
/* set flow type field */
switch (rule->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
fsp->flow_type = TCP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
fsp->flow_type = UDP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
fsp->flow_type = SCTP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
fsp->flow_type = IP_USER_FLOW;
fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
fsp->h_u.usr_ip4_spec.proto = 0;
fsp->m_u.usr_ip4_spec.proto = 0;
break;
default:
return -EINVAL;
}
fsp->h_u.tcp_ip4_spec.psrc = rule->filter.formatted.src_port;
fsp->m_u.tcp_ip4_spec.psrc = mask->formatted.src_port;
fsp->h_u.tcp_ip4_spec.pdst = rule->filter.formatted.dst_port;
fsp->m_u.tcp_ip4_spec.pdst = mask->formatted.dst_port;
fsp->h_u.tcp_ip4_spec.ip4src = rule->filter.formatted.src_ip[0];
fsp->m_u.tcp_ip4_spec.ip4src = mask->formatted.src_ip[0];
fsp->h_u.tcp_ip4_spec.ip4dst = rule->filter.formatted.dst_ip[0];
fsp->m_u.tcp_ip4_spec.ip4dst = mask->formatted.dst_ip[0];
fsp->h_ext.vlan_etype = rule->filter.formatted.flex_bytes;
fsp->m_ext.vlan_etype = mask->formatted.flex_bytes;
fsp->h_ext.data[1] = htonl(rule->filter.formatted.vm_pool);
fsp->m_ext.data[1] = htonl(mask->formatted.vm_pool);
fsp->flow_type |= FLOW_EXT;
/* record action */
if (rule->action == TXGBE_RDB_FDIR_DROP_QUEUE)
fsp->ring_cookie = RX_CLS_FLOW_DISC;
else
fsp->ring_cookie = rule->action;
return 0;
}
static int txgbe_get_ethtool_fdir_all(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct txgbe_fdir_filter *rule;
struct hlist_node *node;
int cnt = 0;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (cnt == cmd->rule_cnt)
return -EMSGSIZE;
rule_locs[cnt] = rule->sw_idx;
cnt++;
}
cmd->rule_cnt = cnt;
return 0;
}
static u32 txgbe_get_rx_ring_count(struct net_device *dev)
{
struct wx *wx = netdev_priv(dev);
return wx->num_rx_queues;
}
static int txgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = txgbe->fdir_filter_count;
ret = 0;
break;
case ETHTOOL_GRXCLSRULE:
ret = txgbe_get_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_GRXCLSRLALL:
ret = txgbe_get_ethtool_fdir_all(txgbe, cmd, (u32 *)rule_locs);
break;
default:
break;
}
return ret;
}
static int txgbe_flowspec_to_flow_type(struct ethtool_rx_flow_spec *fsp,
u8 *flow_type)
{
switch (fsp->flow_type & ~FLOW_EXT) {
case TCP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case UDP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case SCTP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case IP_USER_FLOW:
switch (fsp->h_u.usr_ip4_spec.proto) {
case IPPROTO_TCP:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case IPPROTO_UDP:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case IPPROTO_SCTP:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case 0:
if (!fsp->m_u.usr_ip4_spec.proto) {
*flow_type = TXGBE_ATR_FLOW_TYPE_IPV4;
break;
}
fallthrough;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
return 0;
}
static bool txgbe_match_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input)
{
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node2;
hlist_for_each_entry_safe(rule, node2, &txgbe->fdir_filter_list,
fdir_node) {
if (rule->filter.formatted.bkt_hash ==
input->filter.formatted.bkt_hash &&
rule->action == input->action) {
wx_dbg(txgbe->wx, "FDIR entry already exist\n");
return true;
}
}
return false;
}
static int txgbe_update_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input,
u16 sw_idx)
{
struct hlist_node *node = NULL, *parent = NULL;
struct txgbe_fdir_filter *rule;
struct wx *wx = txgbe->wx;
bool deleted = false;
int err;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
/* hash found, or no matching entry */
if (rule->sw_idx >= sw_idx)
break;
parent = node;
}
/* if there is an old rule occupying our place remove it */
if (rule && rule->sw_idx == sw_idx) {
/* hardware filters are only configured when interface is up,
* and we should not issue filter commands while the interface
* is down
*/
if (netif_running(wx->netdev) &&
(!input || rule->filter.formatted.bkt_hash !=
input->filter.formatted.bkt_hash)) {
err = txgbe_fdir_erase_perfect_filter(wx,
&rule->filter,
sw_idx);
if (err)
return -EINVAL;
}
hlist_del(&rule->fdir_node);
kfree(rule);
txgbe->fdir_filter_count--;
deleted = true;
}
/* If we weren't given an input, then this was a request to delete a
* filter. We should return -EINVAL if the filter wasn't found, but
* return 0 if the rule was successfully deleted.
*/
if (!input)
return deleted ? 0 : -EINVAL;
/* initialize node and set software index */
INIT_HLIST_NODE(&input->fdir_node);
/* add filter to the list */
if (parent)
hlist_add_behind(&input->fdir_node, parent);
else
hlist_add_head(&input->fdir_node,
&txgbe->fdir_filter_list);
/* update counts */
txgbe->fdir_filter_count++;
return 0;
}
static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
struct txgbe_fdir_filter *input;
union txgbe_atr_input mask;
struct wx *wx = txgbe->wx;
int err = -EINVAL;
u16 ptype = 0;
u8 queue;
if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
return -EOPNOTSUPP;
/* ring_cookie is a masked into a set of queues and txgbe pools or
* we use drop index
*/
if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
queue = TXGBE_RDB_FDIR_DROP_QUEUE;
} else {
u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
u8 vf = ethtool_get_flow_spec_ring_vf(fsp->ring_cookie);
if (!vf && ring >= wx->num_rx_queues)
return -EINVAL;
else if (vf && (vf > wx->num_vfs ||
ring >= wx->num_rx_queues_per_pool))
return -EINVAL;
/* Map the ring onto the absolute queue index */
if (!vf)
queue = wx->rx_ring[ring]->reg_idx;
else
queue = ((vf - 1) * wx->num_rx_queues_per_pool) + ring;
}
/* Don't allow indexes to exist outside of available space */
if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
wx_err(wx, "Location out of range\n");
return -EINVAL;
}
input = kzalloc_obj(*input, GFP_ATOMIC);
if (!input)
return -ENOMEM;
memset(&mask, 0, sizeof(union txgbe_atr_input));
/* set SW index */
input->sw_idx = fsp->location;
/* record flow type */
if (txgbe_flowspec_to_flow_type(fsp,
&input->filter.formatted.flow_type)) {
wx_err(wx, "Unrecognized flow type\n");
goto err_out;
}
mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
TXGBE_ATR_L4TYPE_MASK;
if (input->filter.formatted.flow_type == TXGBE_ATR_FLOW_TYPE_IPV4)
mask.formatted.flow_type &= TXGBE_ATR_L4TYPE_IPV6_MASK;
/* Copy input into formatted structures */
input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
if (fsp->flow_type & FLOW_EXT) {
input->filter.formatted.vm_pool =
(unsigned char)ntohl(fsp->h_ext.data[1]);
mask.formatted.vm_pool =
(unsigned char)ntohl(fsp->m_ext.data[1]);
input->filter.formatted.flex_bytes =
fsp->h_ext.vlan_etype;
mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
}
switch (input->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
ptype = WX_PTYPE_L2_IPV4_TCP;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
ptype = WX_PTYPE_L2_IPV4_UDP;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
ptype = WX_PTYPE_L2_IPV4_SCTP;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
ptype = WX_PTYPE_L2_IPV4;
break;
default:
break;
}
input->filter.formatted.vlan_id = htons(ptype);
if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
mask.formatted.vlan_id = htons(0xFFFF);
else
mask.formatted.vlan_id = htons(0xFFF8);
/* determine if we need to drop or route the packet */
if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
else
input->action = fsp->ring_cookie;
spin_lock(&txgbe->fdir_perfect_lock);
if (hlist_empty(&txgbe->fdir_filter_list)) {
/* save mask and program input mask into HW */
memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
err = txgbe_fdir_set_input_mask(wx, &mask);
if (err)
goto err_unlock;
} else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
wx_err(wx, "Hardware only supports one mask per port. To change the mask you must first delete all the rules.\n");
goto err_unlock;
}
/* apply mask and compute/store hash */
txgbe_atr_compute_perfect_hash(&input->filter, &mask);
/* check if new entry does not exist on filter list */
if (txgbe_match_ethtool_fdir_entry(txgbe, input))
goto err_unlock;
/* only program filters to hardware if the net device is running, as
* we store the filters in the Rx buffer which is not allocated when
* the device is down
*/
if (netif_running(wx->netdev)) {
err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
input->sw_idx, queue);
if (err)
goto err_unlock;
}
txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
spin_unlock(&txgbe->fdir_perfect_lock);
return 0;
err_unlock:
spin_unlock(&txgbe->fdir_perfect_lock);
err_out:
kfree(input);
return err;
}
static int txgbe_del_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
int err = 0;
spin_lock(&txgbe->fdir_perfect_lock);
err = txgbe_update_ethtool_fdir_entry(txgbe, NULL, fsp->location);
spin_unlock(&txgbe->fdir_perfect_lock);
return err;
}
static int txgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
ret = txgbe_add_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_SRXCLSRLDEL:
ret = txgbe_del_ethtool_fdir_entry(txgbe, cmd);
break;
default:
break;
}
return ret;
}
static int
txgbe_get_module_eeprom_by_page(struct net_device *netdev,
const struct ethtool_module_eeprom *page_data,
struct netlink_ext_ack *extack)
{
struct wx *wx = netdev_priv(netdev);
struct txgbe_hic_i2c_read buffer;
int err;
if (!test_bit(WX_FLAG_SWFW_RING, wx->flags))
return -EOPNOTSUPP;
buffer.length = cpu_to_be32(page_data->length);
buffer.offset = cpu_to_be32(page_data->offset);
buffer.page = page_data->page;
buffer.bank = page_data->bank;
buffer.i2c_address = page_data->i2c_address;
err = txgbe_read_eeprom_hostif(wx, &buffer, page_data->length,
page_data->data);
if (err) {
wx_err(wx, "Failed to read module EEPROM\n");
return err;
}
return page_data->length;
}
static const struct ethtool_ops txgbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ |
ETHTOOL_COALESCE_USE_ADAPTIVE,
.get_drvinfo = wx_get_drvinfo,
.nway_reset = wx_nway_reset,
.get_link = ethtool_op_get_link,
.get_link_ksettings = txgbe_get_link_ksettings,
.set_link_ksettings = wx_set_link_ksettings,
.get_sset_count = wx_get_sset_count,
.get_strings = wx_get_strings,
.get_ethtool_stats = wx_get_ethtool_stats,
.get_eth_mac_stats = wx_get_mac_stats,
.get_pause_stats = wx_get_pause_stats,
.get_pauseparam = wx_get_pauseparam,
.set_pauseparam = wx_set_pauseparam,
.get_ringparam = wx_get_ringparam,
.set_ringparam = txgbe_set_ringparam,
.get_coalesce = wx_get_coalesce,
.set_coalesce = wx_set_coalesce,
.get_channels = wx_get_channels,
.set_channels = wx_set_channels,
.get_rxnfc = txgbe_get_rxnfc,
.set_rxnfc = txgbe_set_rxnfc,
.get_rx_ring_count = txgbe_get_rx_ring_count,
.get_rxfh_fields = wx_get_rxfh_fields,
.set_rxfh_fields = wx_set_rxfh_fields,
.get_rxfh_indir_size = wx_rss_indir_size,
.get_rxfh_key_size = wx_get_rxfh_key_size,
.get_rxfh = wx_get_rxfh,
.set_rxfh = wx_set_rxfh,
.get_msglevel = wx_get_msglevel,
.set_msglevel = wx_set_msglevel,
.get_ts_info = wx_get_ts_info,
.get_ts_stats = wx_get_ptp_stats,
.get_module_eeprom_by_page = txgbe_get_module_eeprom_by_page,
};
void txgbe_set_ethtool_ops(struct net_device *netdev)
{
netdev->ethtool_ops = &txgbe_ethtool_ops;
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_ETHTOOL_H_
#define _TXGBE_ETHTOOL_H_
int txgbe_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *cmd);
void txgbe_set_ethtool_ops(struct net_device *netdev);
#endif /* _TXGBE_ETHTOOL_H_ */
@@ -0,0 +1,648 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
#include <linux/string.h>
#include <linux/types.h>
#include <linux/pci.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_fdir.h"
/* These defines allow us to quickly generate all of the necessary instructions
* in the function below by simply calling out TXGBE_COMPUTE_SIG_HASH_ITERATION
* for values 0 through 15
*/
#define TXGBE_ATR_COMMON_HASH_KEY \
(TXGBE_ATR_BUCKET_HASH_KEY & TXGBE_ATR_SIGNATURE_HASH_KEY)
#define TXGBE_COMPUTE_SIG_HASH_ITERATION(_n) \
do { \
u32 n = (_n); \
if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << n)) \
common_hash ^= lo_hash_dword >> n; \
else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
bucket_hash ^= lo_hash_dword >> n; \
else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << n)) \
sig_hash ^= lo_hash_dword << (16 - n); \
if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << (n + 16))) \
common_hash ^= hi_hash_dword >> n; \
else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
bucket_hash ^= hi_hash_dword >> n; \
else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << (n + 16))) \
sig_hash ^= hi_hash_dword << (16 - n); \
} while (0)
/**
* txgbe_atr_compute_sig_hash - Compute the signature hash
* @input: input bitstream to compute the hash on
* @common: compressed common input dword
* @hash: pointer to the computed hash
*
* This function is almost identical to the function above but contains
* several optimizations such as unwinding all of the loops, letting the
* compiler work out all of the conditional ifs since the keys are static
* defines, and computing two keys at once since the hashed dword stream
* will be the same for both keys.
**/
static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input,
union txgbe_atr_hash_dword common,
u32 *hash)
{
u32 sig_hash = 0, bucket_hash = 0, common_hash = 0;
u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
u32 i;
/* record the flow_vm_vlan bits as they are a key part to the hash */
flow_vm_vlan = ntohl(input.dword);
/* generate common hash dword */
hi_hash_dword = ntohl(common.dword);
/* low dword is word swapped version of common */
lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
/* apply flow ID/VM pool/VLAN ID bits to hash words */
hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
/* Process bits 0 and 16 */
TXGBE_COMPUTE_SIG_HASH_ITERATION(0);
/* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
* delay this because bit 0 of the stream should not be processed
* so we do not add the VLAN until after bit 0 was processed
*/
lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
/* Process remaining 30 bit of the key */
for (i = 1; i <= 15; i++)
TXGBE_COMPUTE_SIG_HASH_ITERATION(i);
/* combine common_hash result with signature and bucket hashes */
bucket_hash ^= common_hash;
bucket_hash &= TXGBE_ATR_HASH_MASK;
sig_hash ^= common_hash << 16;
sig_hash &= TXGBE_ATR_HASH_MASK << 16;
/* return completed signature hash */
*hash = sig_hash ^ bucket_hash;
}
#define TXGBE_COMPUTE_BKT_HASH_ITERATION(_n) \
do { \
u32 n = (_n); \
if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
bucket_hash ^= lo_hash_dword >> n; \
if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
bucket_hash ^= hi_hash_dword >> n; \
} while (0)
/**
* txgbe_atr_compute_perfect_hash - Compute the perfect filter hash
* @input: input bitstream to compute the hash on
* @input_mask: mask for the input bitstream
*
* This function serves two main purposes. First it applies the input_mask
* to the atr_input resulting in a cleaned up atr_input data stream.
* Secondly it computes the hash and stores it in the bkt_hash field at
* the end of the input byte stream. This way it will be available for
* future use without needing to recompute the hash.
**/
void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
union txgbe_atr_input *input_mask)
{
u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
u32 bucket_hash = 0;
__be32 hi_dword = 0;
u32 i = 0;
/* Apply masks to input data */
for (i = 0; i < 11; i++)
input->dword_stream[i] &= input_mask->dword_stream[i];
/* record the flow_vm_vlan bits as they are a key part to the hash */
flow_vm_vlan = ntohl(input->dword_stream[0]);
/* generate common hash dword */
for (i = 1; i <= 10; i++)
hi_dword ^= input->dword_stream[i];
hi_hash_dword = ntohl(hi_dword);
/* low dword is word swapped version of common */
lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
/* apply flow ID/VM pool/VLAN ID bits to hash words */
hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
/* Process bits 0 and 16 */
TXGBE_COMPUTE_BKT_HASH_ITERATION(0);
/* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
* delay this because bit 0 of the stream should not be processed
* so we do not add the VLAN until after bit 0 was processed
*/
lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
/* Process remaining 30 bit of the key */
for (i = 1; i <= 15; i++)
TXGBE_COMPUTE_BKT_HASH_ITERATION(i);
/* Limit hash to 13 bits since max bucket count is 8K.
* Store result at the end of the input stream.
*/
input->formatted.bkt_hash = (__force __be16)(bucket_hash & 0x1FFF);
}
static int txgbe_fdir_check_cmd_complete(struct wx *wx)
{
u32 val;
return read_poll_timeout_atomic(rd32, val,
!(val & TXGBE_RDB_FDIR_CMD_CMD_MASK),
10, 100, false,
wx, TXGBE_RDB_FDIR_CMD);
}
/**
* txgbe_fdir_add_signature_filter - Adds a signature hash filter
* @wx: pointer to hardware structure
* @input: unique input dword
* @common: compressed common input dword
* @queue: queue index to direct traffic to
*
* @return: 0 on success and negative on failure
**/
static int txgbe_fdir_add_signature_filter(struct wx *wx,
union txgbe_atr_hash_dword input,
union txgbe_atr_hash_dword common,
u8 queue)
{
u32 fdirhashcmd, fdircmd;
u8 flow_type;
int err;
/* Get the flow_type in order to program FDIRCMD properly
* lowest 2 bits are FDIRCMD.L4TYPE, third lowest bit is FDIRCMD.IPV6
* fifth is FDIRCMD.TUNNEL_FILTER
*/
flow_type = input.formatted.flow_type;
switch (flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
case TXGBE_ATR_FLOW_TYPE_UDPV4:
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
case TXGBE_ATR_FLOW_TYPE_TCPV6:
case TXGBE_ATR_FLOW_TYPE_UDPV6:
case TXGBE_ATR_FLOW_TYPE_SCTPV6:
break;
default:
wx_err(wx, "Error on flow type input\n");
return -EINVAL;
}
/* configure FDIRCMD register */
fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
TXGBE_RDB_FDIR_CMD_LAST | TXGBE_RDB_FDIR_CMD_QUEUE_EN;
fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(flow_type);
fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
txgbe_atr_compute_sig_hash(input, common, &fdirhashcmd);
fdirhashcmd |= TXGBE_RDB_FDIR_HASH_BUCKET_VALID;
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhashcmd);
wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
wx_dbg(wx, "Tx Queue=%x hash=%x\n", queue, (u32)fdirhashcmd);
err = txgbe_fdir_check_cmd_complete(wx);
if (err)
wx_err(wx, "Flow Director command did not complete!\n");
return err;
}
void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype)
{
union txgbe_atr_hash_dword common = { .dword = 0 };
union txgbe_atr_hash_dword input = { .dword = 0 };
struct wx_q_vector *q_vector = ring->q_vector;
struct wx_dec_ptype dptype;
union network_header {
struct ipv6hdr *ipv6;
struct iphdr *ipv4;
void *raw;
} hdr;
struct tcphdr *th;
/* if ring doesn't have a interrupt vector, cannot perform ATR */
if (!q_vector)
return;
ring->atr_count++;
dptype = wx_decode_ptype(ptype);
if (dptype.etype) {
if (WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
return;
hdr.raw = (void *)skb_inner_network_header(first->skb);
th = inner_tcp_hdr(first->skb);
} else {
if (WX_PTYPE_PKT(ptype) != WX_PTYPE_PKT_IP ||
WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
return;
hdr.raw = (void *)skb_network_header(first->skb);
th = tcp_hdr(first->skb);
}
/* skip this packet since it is invalid or the socket is closing */
if (!th || th->fin)
return;
/* sample on all syn packets or once every atr sample count */
if (!th->syn && ring->atr_count < ring->atr_sample_rate)
return;
/* reset sample count */
ring->atr_count = 0;
/* src and dst are inverted, think how the receiver sees them
*
* The input is broken into two sections, a non-compressed section
* containing vm_pool, vlan_id, and flow_type. The rest of the data
* is XORed together and stored in the compressed dword.
*/
input.formatted.vlan_id = htons((u16)ptype);
/* since src port and flex bytes occupy the same word XOR them together
* and write the value to source port portion of compressed dword
*/
if (first->tx_flags & WX_TX_FLAGS_SW_VLAN)
common.port.src ^= th->dest ^ first->skb->protocol;
else if (first->tx_flags & WX_TX_FLAGS_HW_VLAN)
common.port.src ^= th->dest ^ first->skb->vlan_proto;
else
common.port.src ^= th->dest ^ first->protocol;
common.port.dst ^= th->source;
if (WX_PTYPE_PKT_IPV6 & WX_PTYPE_PKT(ptype)) {
input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV6;
common.ip ^= hdr.ipv6->saddr.s6_addr32[0] ^
hdr.ipv6->saddr.s6_addr32[1] ^
hdr.ipv6->saddr.s6_addr32[2] ^
hdr.ipv6->saddr.s6_addr32[3] ^
hdr.ipv6->daddr.s6_addr32[0] ^
hdr.ipv6->daddr.s6_addr32[1] ^
hdr.ipv6->daddr.s6_addr32[2] ^
hdr.ipv6->daddr.s6_addr32[3];
} else {
input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
common.ip ^= hdr.ipv4->saddr ^ hdr.ipv4->daddr;
}
/* This assumes the Rx queue and Tx queue are bound to the same CPU */
txgbe_fdir_add_signature_filter(q_vector->wx, input, common,
ring->queue_index);
}
int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask)
{
u32 fdirm = 0, fdirtcpm = 0, flex = 0;
int index, offset;
/* Program the relevant mask registers. If src/dst_port or src/dst_addr
* are zero, then assume a full mask for that field. Also assume that
* a VLAN of 0 is unspecified, so mask that out as well. L4type
* cannot be masked out in this implementation.
*
* This also assumes IPv4 only. IPv6 masking isn't supported at this
* point in time.
*/
/* verify bucket hash is cleared on hash generation */
if (input_mask->formatted.bkt_hash)
wx_dbg(wx, "bucket hash should always be 0 in mask\n");
/* Program FDIRM and verify partial masks */
switch (input_mask->formatted.vm_pool & 0x7F) {
case 0x0:
fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_POOL;
break;
case 0x7F:
break;
default:
wx_err(wx, "Error on vm pool mask\n");
return -EINVAL;
}
switch (input_mask->formatted.flow_type & TXGBE_ATR_L4TYPE_MASK) {
case 0x0:
fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_L4P;
if (input_mask->formatted.dst_port ||
input_mask->formatted.src_port) {
wx_err(wx, "Error on src/dst port mask\n");
return -EINVAL;
}
break;
case TXGBE_ATR_L4TYPE_MASK:
break;
default:
wx_err(wx, "Error on flow type mask\n");
return -EINVAL;
}
/* Now mask VM pool and destination IPv6 - bits 5 and 2 */
wr32(wx, TXGBE_RDB_FDIR_OTHER_MSK, fdirm);
index = VMDQ_P(0) / 4;
offset = VMDQ_P(0) % 4;
flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index));
flex &= ~(TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 << (offset * 8));
flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6)) << (offset * 8);
switch ((__force u16)input_mask->formatted.flex_bytes & 0xFFFF) {
case 0x0000:
/* Mask Flex Bytes */
flex |= TXGBE_RDB_FDIR_FLEX_CFG_MSK << (offset * 8);
break;
case 0xFFFF:
break;
default:
wx_err(wx, "Error on flexible byte mask\n");
return -EINVAL;
}
wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index), flex);
/* store the TCP/UDP port masks, bit reversed from port layout */
fdirtcpm = ntohs(input_mask->formatted.dst_port);
fdirtcpm <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
fdirtcpm |= ntohs(input_mask->formatted.src_port);
/* write both the same so that UDP and TCP use the same mask */
wr32(wx, TXGBE_RDB_FDIR_TCP_MSK, ~fdirtcpm);
wr32(wx, TXGBE_RDB_FDIR_UDP_MSK, ~fdirtcpm);
wr32(wx, TXGBE_RDB_FDIR_SCTP_MSK, ~fdirtcpm);
/* store source and destination IP masks (little-enian) */
wr32(wx, TXGBE_RDB_FDIR_SA4_MSK,
ntohl(~input_mask->formatted.src_ip[0]));
wr32(wx, TXGBE_RDB_FDIR_DA4_MSK,
ntohl(~input_mask->formatted.dst_ip[0]));
return 0;
}
int txgbe_fdir_write_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id, u8 queue)
{
u32 fdirport, fdirvlan, fdirhash, fdircmd;
int err = 0;
/* currently IPv6 is not supported, must be programmed with 0 */
wr32(wx, TXGBE_RDB_FDIR_IP6(2), ntohl(input->formatted.src_ip[0]));
wr32(wx, TXGBE_RDB_FDIR_IP6(1), ntohl(input->formatted.src_ip[1]));
wr32(wx, TXGBE_RDB_FDIR_IP6(0), ntohl(input->formatted.src_ip[2]));
/* record the source address (little-endian) */
wr32(wx, TXGBE_RDB_FDIR_SA, ntohl(input->formatted.src_ip[0]));
/* record the first 32 bits of the destination address
* (little-endian)
*/
wr32(wx, TXGBE_RDB_FDIR_DA, ntohl(input->formatted.dst_ip[0]));
/* record source and destination port (little-endian)*/
fdirport = ntohs(input->formatted.dst_port);
fdirport <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
fdirport |= ntohs(input->formatted.src_port);
wr32(wx, TXGBE_RDB_FDIR_PORT, fdirport);
/* record packet type and flex_bytes (little-endian) */
fdirvlan = ntohs(input->formatted.flex_bytes);
fdirvlan <<= TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT;
fdirvlan |= ntohs(input->formatted.vlan_id);
wr32(wx, TXGBE_RDB_FDIR_FLEX, fdirvlan);
/* configure FDIRHASH register */
fdirhash = (__force u32)input->formatted.bkt_hash |
TXGBE_RDB_FDIR_HASH_BUCKET_VALID |
TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
/* flush all previous writes to make certain registers are
* programmed prior to issuing the command
*/
WX_WRITE_FLUSH(wx);
/* configure FDIRCMD register */
fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
TXGBE_RDB_FDIR_CMD_LAST | TXGBE_RDB_FDIR_CMD_QUEUE_EN;
if (queue == TXGBE_RDB_FDIR_DROP_QUEUE)
fdircmd |= TXGBE_RDB_FDIR_CMD_DROP;
fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(input->formatted.flow_type);
fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
fdircmd |= TXGBE_RDB_FDIR_CMD_VT_POOL(input->formatted.vm_pool);
wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
err = txgbe_fdir_check_cmd_complete(wx);
if (err)
wx_err(wx, "Flow Director command did not complete!\n");
return err;
}
int txgbe_fdir_erase_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id)
{
u32 fdirhash, fdircmd;
int err = 0;
/* configure FDIRHASH register */
fdirhash = (__force u32)input->formatted.bkt_hash;
fdirhash |= TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
/* flush hash to HW */
WX_WRITE_FLUSH(wx);
/* Query if filter is present */
wr32(wx, TXGBE_RDB_FDIR_CMD, TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT);
err = txgbe_fdir_check_cmd_complete(wx);
if (err) {
wx_err(wx, "Flow Director command did not complete!\n");
return err;
}
fdircmd = rd32(wx, TXGBE_RDB_FDIR_CMD);
/* if filter exists in hardware then remove it */
if (fdircmd & TXGBE_RDB_FDIR_CMD_FILTER_VALID) {
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
WX_WRITE_FLUSH(wx);
wr32(wx, TXGBE_RDB_FDIR_CMD,
TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW);
}
return 0;
}
/**
* txgbe_fdir_enable - Initialize Flow Director control registers
* @wx: pointer to hardware structure
* @fdirctrl: value to write to flow director control register
**/
static void txgbe_fdir_enable(struct wx *wx, u32 fdirctrl)
{
u32 val;
int ret;
/* Prime the keys for hashing */
wr32(wx, TXGBE_RDB_FDIR_HKEY, TXGBE_ATR_BUCKET_HASH_KEY);
wr32(wx, TXGBE_RDB_FDIR_SKEY, TXGBE_ATR_SIGNATURE_HASH_KEY);
wr32(wx, TXGBE_RDB_FDIR_CTL, fdirctrl);
WX_WRITE_FLUSH(wx);
ret = read_poll_timeout(rd32, val, val & TXGBE_RDB_FDIR_CTL_INIT_DONE,
1000, 10000, false, wx, TXGBE_RDB_FDIR_CTL);
if (ret < 0)
wx_dbg(wx, "Flow Director poll time exceeded!\n");
}
/**
* txgbe_init_fdir_signature -Initialize Flow Director sig filters
* @wx: pointer to hardware structure
**/
static void txgbe_init_fdir_signature(struct wx *wx)
{
u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
int index = VMDQ_P(0) / 4;
int offset = VMDQ_P(0) % 4;
u32 flex = 0;
flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index));
flex &= ~(TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 << (offset * 8));
flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6)) << (offset * 8);
wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index), flex);
/* Continue setup of fdirctrl register bits:
* Move the flexible bytes to use the ethertype - shift 6 words
* Set the maximum length per hash bucket to 0xA filters
* Send interrupt when 64 filters are left
*/
fdirctrl |= TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
/* write hashes and fdirctrl register, poll for completion */
txgbe_fdir_enable(wx, fdirctrl);
}
/**
* txgbe_init_fdir_perfect - Initialize Flow Director perfect filters
* @wx: pointer to hardware structure
**/
static void txgbe_init_fdir_perfect(struct wx *wx)
{
u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
/* Continue setup of fdirctrl register bits:
* Turn perfect match filtering on
* Report hash in RSS field of Rx wb descriptor
* Initialize the drop queue
* Move the flexible bytes to use the ethertype - shift 6 words
* Set the maximum length per hash bucket to 0xA filters
* Send interrupt when 64 (0x4 * 16) filters are left
*/
fdirctrl |= TXGBE_RDB_FDIR_CTL_PERFECT_MATCH |
TXGBE_RDB_FDIR_CTL_DROP_Q(TXGBE_RDB_FDIR_DROP_QUEUE) |
TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
/* write hashes and fdirctrl register, poll for completion */
txgbe_fdir_enable(wx, fdirctrl);
}
static void txgbe_fdir_filter_restore(struct wx *wx)
{
struct txgbe_fdir_filter *filter;
struct txgbe *txgbe = wx->priv;
struct hlist_node *node;
u8 queue = 0;
int ret = 0;
spin_lock(&txgbe->fdir_perfect_lock);
if (!hlist_empty(&txgbe->fdir_filter_list))
ret = txgbe_fdir_set_input_mask(wx, &txgbe->fdir_mask);
if (ret)
goto unlock;
hlist_for_each_entry_safe(filter, node,
&txgbe->fdir_filter_list, fdir_node) {
if (filter->action == TXGBE_RDB_FDIR_DROP_QUEUE) {
queue = TXGBE_RDB_FDIR_DROP_QUEUE;
} else {
u32 ring = ethtool_get_flow_spec_ring(filter->action);
if (ring >= wx->num_rx_queues) {
wx_err(wx, "FDIR restore failed, ring:%u\n",
ring);
continue;
}
/* Map the ring onto the absolute queue index */
queue = wx->rx_ring[ring]->reg_idx;
}
ret = txgbe_fdir_write_perfect_filter(wx,
&filter->filter,
filter->sw_idx,
queue);
if (ret)
wx_err(wx, "FDIR restore failed, index:%u\n",
filter->sw_idx);
}
unlock:
spin_unlock(&txgbe->fdir_perfect_lock);
}
void txgbe_configure_fdir(struct wx *wx)
{
wx_disable_sec_rx_path(wx);
if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) {
txgbe_init_fdir_signature(wx);
} else if (test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)) {
txgbe_init_fdir_perfect(wx);
txgbe_fdir_filter_restore(wx);
}
wx_enable_sec_rx_path(wx);
}
void txgbe_fdir_filter_exit(struct wx *wx)
{
struct txgbe_fdir_filter *filter;
struct txgbe *txgbe = wx->priv;
struct hlist_node *node;
spin_lock(&txgbe->fdir_perfect_lock);
hlist_for_each_entry_safe(filter, node,
&txgbe->fdir_filter_list, fdir_node) {
hlist_del(&filter->fdir_node);
kfree(filter);
}
txgbe->fdir_filter_count = 0;
spin_unlock(&txgbe->fdir_perfect_lock);
}
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_FDIR_H_
#define _TXGBE_FDIR_H_
void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
union txgbe_atr_input *input_mask);
void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask);
int txgbe_fdir_write_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id, u8 queue);
int txgbe_fdir_erase_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id);
void txgbe_configure_fdir(struct wx *wx);
void txgbe_fdir_filter_exit(struct wx *wx);
#endif /* _TXGBE_FDIR_H_ */
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd. */
#include <linux/etherdevice.h>
#include <linux/if_ether.h>
#include <linux/string.h>
#include <linux/iopoll.h>
#include <linux/types.h>
#include <linux/pci.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_hw.h"
/**
* txgbe_disable_sec_tx_path - Stops the transmit data path
* @wx: pointer to hardware structure
*
* Stops the transmit data path and waits for the HW to internally empty
* the tx security block
**/
int txgbe_disable_sec_tx_path(struct wx *wx)
{
int val;
wr32m(wx, WX_TSC_CTL, WX_TSC_CTL_TX_DIS, WX_TSC_CTL_TX_DIS);
return read_poll_timeout(rd32, val, val & WX_TSC_ST_SECTX_RDY,
1000, 20000, false, wx, WX_TSC_ST);
}
/**
* txgbe_enable_sec_tx_path - Enables the transmit data path
* @wx: pointer to hardware structure
*
* Enables the transmit data path.
**/
void txgbe_enable_sec_tx_path(struct wx *wx)
{
wr32m(wx, WX_TSC_CTL, WX_TSC_CTL_TX_DIS, 0);
WX_WRITE_FLUSH(wx);
}
/**
* txgbe_init_thermal_sensor_thresh - Inits thermal sensor thresholds
* @wx: pointer to hardware structure
*
* Inits the thermal sensor thresholds according to the NVM map
* and save off the threshold and location values into mac.thermal_sensor_data
**/
static void txgbe_init_thermal_sensor_thresh(struct wx *wx)
{
struct wx_thermal_sensor_data *data = &wx->mac.sensor;
memset(data, 0, sizeof(struct wx_thermal_sensor_data));
/* Only support thermal sensors attached to SP physical port 0 */
if (wx->bus.func)
return;
wr32(wx, TXGBE_TS_CTL, TXGBE_TS_CTL_EVAL_MD);
wr32(wx, WX_TS_INT_EN,
WX_TS_INT_EN_ALARM_INT_EN | WX_TS_INT_EN_DALARM_INT_EN);
wr32(wx, WX_TS_EN, WX_TS_EN_ENA);
data->alarm_thresh = 100;
wr32(wx, WX_TS_ALARM_THRE, 677);
data->dalarm_thresh = 90;
wr32(wx, WX_TS_DALARM_THRE, 614);
}
/**
* txgbe_calc_eeprom_checksum - Calculates and returns the checksum
* @wx: pointer to hardware structure
* @checksum: pointer to cheksum
*
* Returns a negative error code on error
**/
static int txgbe_calc_eeprom_checksum(struct wx *wx, u16 *checksum)
{
u16 *eeprom_ptrs = NULL;
u16 *local_buffer;
int status;
u16 i;
wx_init_eeprom_params(wx);
eeprom_ptrs = kvmalloc_array(TXGBE_EEPROM_LAST_WORD, sizeof(u16),
GFP_KERNEL);
if (!eeprom_ptrs)
return -ENOMEM;
/* Read pointer area */
status = wx_read_ee_hostif_buffer(wx, 0, TXGBE_EEPROM_LAST_WORD, eeprom_ptrs);
if (status != 0) {
wx_err(wx, "Failed to read EEPROM image\n");
kvfree(eeprom_ptrs);
return status;
}
local_buffer = eeprom_ptrs;
for (i = 0; i < TXGBE_EEPROM_LAST_WORD; i++) {
if (wx->mac.type == wx_mac_aml) {
if (i >= TXGBE_EEPROM_I2C_SRART_PTR &&
i < TXGBE_EEPROM_I2C_END_PTR)
local_buffer[i] = 0xffff;
}
if (i != wx->eeprom.sw_region_offset + TXGBE_EEPROM_CHECKSUM)
*checksum += local_buffer[i];
}
kvfree(eeprom_ptrs);
*checksum = TXGBE_EEPROM_SUM - *checksum;
return 0;
}
/**
* txgbe_validate_eeprom_checksum - Validate EEPROM checksum
* @wx: pointer to hardware structure
* @checksum_val: calculated checksum
*
* Performs checksum calculation and validates the EEPROM checksum. If the
* caller does not need checksum_val, the value can be NULL.
**/
int txgbe_validate_eeprom_checksum(struct wx *wx, u16 *checksum_val)
{
u16 read_checksum = 0;
u16 checksum;
int status;
/* Read the first word from the EEPROM. If this times out or fails, do
* not continue or we could be in for a very long wait while every
* EEPROM read fails
*/
status = wx_read_ee_hostif(wx, 0, &checksum);
if (status) {
wx_err(wx, "EEPROM read failed\n");
return status;
}
checksum = 0;
status = txgbe_calc_eeprom_checksum(wx, &checksum);
if (status != 0)
return status;
status = wx_read_ee_hostif(wx, wx->eeprom.sw_region_offset +
TXGBE_EEPROM_CHECKSUM, &read_checksum);
if (status != 0)
return status;
/* Verify read checksum from EEPROM is the same as
* calculated checksum
*/
if (read_checksum != checksum) {
status = -EIO;
wx_err(wx, "Invalid EEPROM checksum\n");
}
/* If the user cares, return the calculated checksum */
if (checksum_val)
*checksum_val = checksum;
return status;
}
static void txgbe_reset_misc(struct wx *wx)
{
wx_reset_misc(wx);
txgbe_init_thermal_sensor_thresh(wx);
}
/**
* txgbe_reset_hw - Perform hardware reset
* @wx: pointer to wx structure
*
* Resets the hardware by resetting the transmit and receive units, masks
* and clears all interrupts, perform a PHY reset, and perform a link (MAC)
* reset.
**/
int txgbe_reset_hw(struct wx *wx)
{
int status;
/* Call adapter stop to disable tx/rx and clear interrupts */
status = wx_stop_adapter(wx);
if (status != 0)
return status;
if (wx->media_type != wx_media_copper) {
u32 val;
val = WX_MIS_RST_LAN_RST(wx->bus.func);
wr32(wx, WX_MIS_RST, val | rd32(wx, WX_MIS_RST));
WX_WRITE_FLUSH(wx);
usleep_range(10, 100);
}
status = wx_check_flash_load(wx, TXGBE_SPI_ILDR_STATUS_LAN_SW_RST(wx->bus.func));
if (status != 0)
return status;
txgbe_reset_misc(wx);
if (wx->mac.type != wx_mac_sp) {
wr32(wx, TXGBE_PX_PF_BME, 0x1);
wr32m(wx, TXGBE_RDM_RSC_CTL, TXGBE_RDM_RSC_CTL_FREE_CTL,
TXGBE_RDM_RSC_CTL_FREE_CTL);
}
wx_clear_hw_cntrs(wx);
/* Store the permanent mac address */
wx_get_mac_addr(wx, wx->mac.perm_addr);
/* Store MAC address from RAR0, clear receive address registers, and
* clear the multicast table. Also reset num_rar_entries to 128,
* since we modify this value when programming the SAN MAC address.
*/
wx->mac.num_rar_entries = TXGBE_RAR_ENTRIES;
wx_init_rx_addrs(wx);
pci_set_master(wx->pdev);
return 0;
}
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_HW_H_
#define _TXGBE_HW_H_
int txgbe_disable_sec_tx_path(struct wx *wx);
void txgbe_enable_sec_tx_path(struct wx *wx);
int txgbe_validate_eeprom_checksum(struct wx *wx, u16 *checksum_val);
int txgbe_reset_hw(struct wx *wx);
#endif /* _TXGBE_HW_H_ */
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
#include <linux/irqdomain.h>
#include <linux/pci.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_sriov.h"
#include "txgbe_type.h"
#include "txgbe_phy.h"
#include "txgbe_irq.h"
#include "txgbe_aml.h"
/**
* txgbe_irq_enable - Enable default interrupt generation settings
* @wx: pointer to private structure
* @queues: enable irqs for queues
**/
void txgbe_irq_enable(struct wx *wx, bool queues)
{
u32 misc_ien = TXGBE_PX_MISC_IEN_MASK;
if (wx->mac.type != wx_mac_sp) {
misc_ien |= TXGBE_PX_MISC_GPIO;
txgbe_gpio_init_aml(wx);
}
wr32(wx, WX_PX_MISC_IEN, misc_ien);
/* unmask interrupt */
wx_intr_enable(wx, TXGBE_INTR_MISC(wx));
if (queues)
wx_intr_enable(wx, TXGBE_INTR_QALL(wx));
}
/**
* txgbe_request_queue_irqs - Initialize MSI-X queue interrupts
* @wx: board private structure
*
* Allocate MSI-X queue vectors and request interrupts from the kernel.
**/
int txgbe_request_queue_irqs(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
int vector, err;
if (!wx->pdev->msix_enabled)
return 0;
for (vector = 0; vector < wx->num_q_vectors; vector++) {
struct wx_q_vector *q_vector = wx->q_vector[vector];
struct msix_entry *entry = &wx->msix_q_entries[vector];
if (q_vector->tx.ring && q_vector->rx.ring)
snprintf(q_vector->name, sizeof(q_vector->name) - 1,
"%s-TxRx-%d", netdev->name, entry->entry);
else
/* skip this unused q_vector */
continue;
err = request_irq(entry->vector, wx_msix_clean_rings, 0,
q_vector->name, q_vector);
if (err) {
wx_err(wx, "request_irq failed for MSIX interrupt %s Error: %d\n",
q_vector->name, err);
goto free_queue_irqs;
}
}
return 0;
free_queue_irqs:
while (vector) {
vector--;
free_irq(wx->msix_q_entries[vector].vector,
wx->q_vector[vector]);
}
return err;
}
static int txgbe_request_link_irq(struct txgbe *txgbe)
{
txgbe->link_irq = irq_find_mapping(txgbe->misc.domain, TXGBE_IRQ_LINK);
return request_threaded_irq(txgbe->link_irq, NULL,
txgbe_link_irq_handler,
IRQF_ONESHOT, "txgbe-link-irq", txgbe);
}
static int txgbe_request_gpio_irq(struct txgbe *txgbe)
{
txgbe->gpio_irq = irq_find_mapping(txgbe->misc.domain, TXGBE_IRQ_GPIO);
return request_threaded_irq(txgbe->gpio_irq, NULL,
txgbe_gpio_irq_handler_aml,
IRQF_ONESHOT, "txgbe-gpio-irq", txgbe);
}
static const struct irq_chip txgbe_irq_chip = {
.name = "txgbe-misc-irq",
};
static int txgbe_misc_irq_domain_map(struct irq_domain *d,
unsigned int irq,
irq_hw_number_t hwirq)
{
struct txgbe *txgbe = d->host_data;
irq_set_chip_data(irq, txgbe);
irq_set_chip(irq, &txgbe->misc.chip);
irq_set_nested_thread(irq, true);
irq_set_noprobe(irq);
return 0;
}
static const struct irq_domain_ops txgbe_misc_irq_domain_ops = {
.map = txgbe_misc_irq_domain_map,
};
static irqreturn_t txgbe_misc_irq_handle(int irq, void *data)
{
struct wx_q_vector *q_vector;
struct txgbe *txgbe = data;
struct wx *wx = txgbe->wx;
u32 eicr;
if (wx->pdev->msix_enabled) {
eicr = wx_misc_isb(wx, WX_ISB_MISC);
txgbe->eicr = eicr;
if (eicr & TXGBE_PX_MISC_IC_VF_MBOX) {
wx_msg_task(txgbe->wx);
wx_intr_enable(wx, TXGBE_INTR_MISC(wx));
}
return IRQ_WAKE_THREAD;
}
eicr = wx_misc_isb(wx, WX_ISB_VEC0);
if (!eicr) {
/* shared interrupt alert!
* the interrupt that we masked before the ICR read.
*/
if (!test_bit(WX_STATE_DOWN, wx->state))
txgbe_irq_enable(wx, true);
return IRQ_NONE; /* Not our interrupt */
}
wx->isb_mem[WX_ISB_VEC0] = 0;
if (!(wx->pdev->msi_enabled))
wr32(wx, WX_PX_INTA, 1);
/* would disable interrupts here but it is auto disabled */
q_vector = wx->q_vector[0];
napi_schedule_irqoff(&q_vector->napi);
txgbe->eicr = wx_misc_isb(wx, WX_ISB_MISC);
return IRQ_WAKE_THREAD;
}
static irqreturn_t txgbe_misc_irq_thread_fn(int irq, void *data)
{
struct txgbe *txgbe = data;
struct wx *wx = txgbe->wx;
unsigned int nhandled = 0;
unsigned int sub_irq;
u32 eicr;
eicr = txgbe->eicr;
if (eicr & (TXGBE_PX_MISC_ETH_LK | TXGBE_PX_MISC_ETH_LKDN |
TXGBE_PX_MISC_ETH_AN)) {
sub_irq = irq_find_mapping(txgbe->misc.domain, TXGBE_IRQ_LINK);
handle_nested_irq(sub_irq);
nhandled++;
}
if (eicr & TXGBE_PX_MISC_GPIO) {
sub_irq = irq_find_mapping(txgbe->misc.domain, TXGBE_IRQ_GPIO);
handle_nested_irq(sub_irq);
nhandled++;
}
if (unlikely(eicr & TXGBE_PX_MISC_IC_TIMESYNC)) {
wx_ptp_check_pps_event(wx);
nhandled++;
}
wx_intr_enable(wx, TXGBE_INTR_MISC(wx));
return (nhandled > 0 ? IRQ_HANDLED : IRQ_NONE);
}
static void txgbe_del_irq_domain(struct txgbe *txgbe)
{
int hwirq, virq;
for (hwirq = 0; hwirq < txgbe->misc.nirqs; hwirq++) {
virq = irq_find_mapping(txgbe->misc.domain, hwirq);
irq_dispose_mapping(virq);
}
irq_domain_remove(txgbe->misc.domain);
}
void txgbe_free_misc_irq(struct txgbe *txgbe)
{
if (txgbe->wx->mac.type != wx_mac_sp)
free_irq(txgbe->gpio_irq, txgbe);
free_irq(txgbe->link_irq, txgbe);
free_irq(txgbe->misc.irq, txgbe);
txgbe_del_irq_domain(txgbe);
txgbe->wx->misc_irq_domain = false;
}
int txgbe_setup_misc_irq(struct txgbe *txgbe)
{
unsigned long flags = IRQF_ONESHOT;
struct wx *wx = txgbe->wx;
int hwirq, err;
txgbe->misc.nirqs = TXGBE_IRQ_MAX;
txgbe->misc.domain = irq_domain_create_simple(NULL, txgbe->misc.nirqs, 0,
&txgbe_misc_irq_domain_ops, txgbe);
if (!txgbe->misc.domain)
return -ENOMEM;
for (hwirq = 0; hwirq < txgbe->misc.nirqs; hwirq++)
irq_create_mapping(txgbe->misc.domain, hwirq);
txgbe->misc.chip = txgbe_irq_chip;
if (wx->pdev->msix_enabled) {
txgbe->misc.irq = wx->msix_entry->vector;
} else {
txgbe->misc.irq = wx->pdev->irq;
if (!wx->pdev->msi_enabled)
flags |= IRQF_SHARED;
}
err = request_threaded_irq(txgbe->misc.irq, txgbe_misc_irq_handle,
txgbe_misc_irq_thread_fn,
flags,
wx->netdev->name, txgbe);
if (err)
goto del_misc_irq;
err = txgbe_request_link_irq(txgbe);
if (err)
goto free_msic_irq;
if (wx->mac.type == wx_mac_sp)
goto skip_sp_irq;
err = txgbe_request_gpio_irq(txgbe);
if (err)
goto free_link_irq;
skip_sp_irq:
wx->misc_irq_domain = true;
return 0;
free_link_irq:
free_irq(txgbe->link_irq, txgbe);
free_msic_irq:
free_irq(txgbe->misc.irq, txgbe);
del_misc_irq:
txgbe_del_irq_domain(txgbe);
return err;
}
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */
void txgbe_irq_enable(struct wx *wx, bool queues);
int txgbe_request_queue_irqs(struct wx *wx);
void txgbe_free_misc_irq(struct txgbe *txgbe);
int txgbe_setup_misc_irq(struct txgbe *txgbe);
@@ -0,0 +1,980 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd. */
#include <linux/types.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/etherdevice.h>
#include <linux/phylink.h>
#include <net/udp_tunnel.h>
#include <net/ip.h>
#include <linux/if_vlan.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_sriov.h"
#include "txgbe_type.h"
#include "txgbe_hw.h"
#include "txgbe_phy.h"
#include "txgbe_aml.h"
#include "txgbe_irq.h"
#include "txgbe_fdir.h"
#include "txgbe_ethtool.h"
char txgbe_driver_name[] = "txgbe";
/* txgbe_pci_tbl - PCI Device ID Table
*
* Wildcard entries (PCI_ANY_ID) should come last
* Last entry must be all 0s
*
* { Vendor ID, Device ID, SubVendor ID, SubDevice ID,
* Class, Class Mask, private data (not used) }
*/
static const struct pci_device_id txgbe_pci_tbl[] = {
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_SP1000) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_WX1820) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5010) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5110) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5025) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5125) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5040) },
{ PCI_VDEVICE(WANGXUN, TXGBE_DEV_ID_AML5140) },
/* required last entry */
{ }
};
#define DEFAULT_DEBUG_LEVEL_SHIFT 3
static void txgbe_check_minimum_link(struct wx *wx)
{
struct pci_dev *pdev;
pdev = wx->pdev;
pcie_print_link_status(pdev);
}
/**
* txgbe_enumerate_functions - Get the number of ports this device has
* @wx: wx structure
*
* This function enumerates the phsyical functions co-located on a single slot,
* in order to determine how many ports a device has. This is most useful in
* determining the required GT/s of PCIe bandwidth necessary for optimal
* performance.
**/
static int txgbe_enumerate_functions(struct wx *wx)
{
struct pci_dev *entry, *pdev = wx->pdev;
int physfns = 0;
list_for_each_entry(entry, &pdev->bus->devices, bus_list) {
/* When the devices on the bus don't all match our device ID,
* we can't reliably determine the correct number of
* functions. This can occur if a function has been direct
* attached to a virtual machine using VT-d.
*/
if (entry->vendor != pdev->vendor ||
entry->device != pdev->device)
return -EINVAL;
physfns++;
}
return physfns;
}
static void txgbe_module_detection_subtask(struct wx *wx)
{
int err;
if (!test_and_clear_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags))
return;
/* wait for SFF module ready */
msleep(200);
err = txgbe_identify_module(wx);
if (err == -ENODEV)
set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags);
}
static void txgbe_link_config_subtask(struct wx *wx)
{
if (!test_and_clear_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags))
return;
txgbe_set_phy_link(wx);
}
/**
* txgbe_service_task - manages and runs subtasks
* @work: pointer to work_struct containing our data
**/
static void txgbe_service_task(struct work_struct *work)
{
struct wx *wx = container_of(work, struct wx, service_task);
txgbe_module_detection_subtask(wx);
txgbe_link_config_subtask(wx);
wx_update_stats(wx);
wx_service_event_complete(wx);
}
static void txgbe_init_service(struct wx *wx)
{
timer_setup(&wx->service_timer, wx_service_timer, 0);
INIT_WORK(&wx->service_task, txgbe_service_task);
clear_bit(WX_STATE_SERVICE_SCHED, wx->state);
}
static void txgbe_up_complete(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
wx_control_hw(wx, true);
wx_configure_vectors(wx);
/* make sure to complete pre-operations */
smp_mb__before_atomic();
clear_bit(WX_STATE_DOWN, wx->state);
wx_napi_enable_all(wx);
switch (wx->mac.type) {
case wx_mac_aml40:
txgbe_setup_link(wx);
phylink_start(wx->phylink);
break;
case wx_mac_aml:
/* Enable TX laser */
wr32m(wx, WX_GPIO_DR, TXGBE_GPIOBIT_1, 0);
txgbe_setup_link(wx);
phylink_start(wx->phylink);
break;
case wx_mac_sp:
phylink_start(wx->phylink);
break;
default:
break;
}
/* clear any pending interrupts, may auto mask */
rd32(wx, WX_PX_IC(0));
rd32(wx, WX_PX_IC(1));
rd32(wx, WX_PX_MISC_IC);
txgbe_irq_enable(wx, true);
/* enable transmits */
netif_tx_start_all_queues(netdev);
mod_timer(&wx->service_timer, jiffies);
/* Set PF Reset Done bit so PF/VF Mail Ops can work */
wr32m(wx, WX_CFG_PORT_CTL, WX_CFG_PORT_CTL_PFRSTD,
WX_CFG_PORT_CTL_PFRSTD);
/* update setting rx tx for all active vfs */
wx_set_all_vfs(wx);
}
static void txgbe_reset(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
u8 old_addr[ETH_ALEN];
int err;
err = txgbe_reset_hw(wx);
if (err != 0)
wx_err(wx, "Hardware Error: %d\n", err);
wx_start_hw(wx);
/* do not flush user set addresses */
memcpy(old_addr, &wx->mac_table[0].addr, netdev->addr_len);
wx_flush_sw_mac_table(wx);
wx_mac_set_default_filter(wx, old_addr);
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset(wx);
}
static void txgbe_disable_device(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
u32 i;
if (test_and_set_bit(WX_STATE_DOWN, wx->state))
return;
wx_disable_pcie_master(wx);
/* disable receives */
wx_disable_rx(wx);
/* disable all enabled rx queues */
for (i = 0; i < wx->num_rx_queues; i++)
/* this call also flushes the previous write */
wx_disable_rx_queue(wx, wx->rx_ring[i]);
netif_tx_stop_all_queues(netdev);
netif_tx_disable(netdev);
wx_irq_disable(wx);
wx_napi_disable_all(wx);
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
if (wx->bus.func < 2)
wr32m(wx, TXGBE_MIS_PRB_CTL, TXGBE_MIS_PRB_CTL_LAN_UP(wx->bus.func), 0);
else
wx_err(wx, "%s: invalid bus lan id %d\n",
__func__, wx->bus.func);
if (wx->num_vfs) {
/* Clear EITR Select mapping */
wr32(wx, WX_PX_ITRSEL, 0);
/* Mark all the VFs as inactive */
for (i = 0; i < wx->num_vfs; i++)
wx->vfinfo[i].clear_to_send = 0;
/* update setting rx tx for all active vfs */
wx_set_all_vfs(wx);
}
if (!(((wx->subsystem_device_id & WX_NCSI_MASK) == WX_NCSI_SUP) ||
((wx->subsystem_device_id & WX_WOL_MASK) == WX_WOL_SUP))) {
/* disable mac transmiter */
wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
}
/* disable transmits in the hardware now that interrupts are off */
for (i = 0; i < wx->num_tx_queues; i++) {
u8 reg_idx = wx->tx_ring[i]->reg_idx;
wr32(wx, WX_PX_TR_CFG(reg_idx), WX_PX_TR_CFG_SWFLSH);
}
/* Disable the Tx DMA engine */
wr32m(wx, WX_TDM_CTL, WX_TDM_CTL_TE, 0);
}
void txgbe_down(struct wx *wx)
{
txgbe_disable_device(wx);
txgbe_reset(wx);
switch (wx->mac.type) {
case wx_mac_aml40:
phylink_stop(wx->phylink);
break;
case wx_mac_aml:
phylink_stop(wx->phylink);
/* Disable TX laser */
wr32m(wx, WX_GPIO_DR, TXGBE_GPIOBIT_1, TXGBE_GPIOBIT_1);
break;
case wx_mac_sp:
phylink_stop(wx->phylink);
break;
default:
break;
}
wx_clean_all_tx_rings(wx);
wx_clean_all_rx_rings(wx);
}
void txgbe_up(struct wx *wx)
{
wx_configure(wx);
wx_ptp_init(wx);
txgbe_up_complete(wx);
}
/**
* txgbe_init_type_code - Initialize the shared code
* @wx: pointer to hardware structure
**/
static void txgbe_init_type_code(struct wx *wx)
{
u8 device_type = wx->subsystem_device_id & 0xF0;
switch (wx->device_id) {
case TXGBE_DEV_ID_SP1000:
case TXGBE_DEV_ID_WX1820:
wx->mac.type = wx_mac_sp;
break;
case TXGBE_DEV_ID_AML5010:
case TXGBE_DEV_ID_AML5110:
case TXGBE_DEV_ID_AML5025:
case TXGBE_DEV_ID_AML5125:
wx->mac.type = wx_mac_aml;
break;
case TXGBE_DEV_ID_AML5040:
case TXGBE_DEV_ID_AML5140:
wx->mac.type = wx_mac_aml40;
break;
default:
wx->mac.type = wx_mac_unknown;
break;
}
switch (device_type) {
case TXGBE_ID_SFP:
wx->media_type = wx_media_fiber;
break;
case TXGBE_ID_XAUI:
case TXGBE_ID_SGMII:
wx->media_type = wx_media_copper;
break;
case TXGBE_ID_KR_KX_KX4:
case TXGBE_ID_MAC_XAUI:
case TXGBE_ID_MAC_SGMII:
wx->media_type = wx_media_backplane;
break;
case TXGBE_ID_SFI_XAUI:
if (wx->bus.func == 0)
wx->media_type = wx_media_fiber;
else
wx->media_type = wx_media_copper;
break;
default:
wx->media_type = wx_media_unknown;
break;
}
}
/**
* txgbe_sw_init - Initialize general software structures (struct wx)
* @wx: board private structure to initialize
**/
static int txgbe_sw_init(struct wx *wx)
{
u16 msix_count = 0;
int err;
wx->mac.num_rar_entries = TXGBE_RAR_ENTRIES;
wx->mac.max_tx_queues = TXGBE_MAX_TXQ;
wx->mac.max_rx_queues = TXGBE_MAX_RXQ;
wx->mac.mcft_size = TXGBE_MC_TBL_SIZE;
wx->mac.vft_size = TXGBE_VFT_TBL_SIZE;
wx->mac.rx_pb_size = TXGBE_RX_PB_SIZE;
wx->mac.tx_pb_size = TXGBE_TDB_PB_SZ;
/* PCI config space info */
err = wx_sw_init(wx);
if (err < 0)
return err;
txgbe_init_type_code(wx);
/* Set common capability flags and settings */
wx->max_q_vectors = TXGBE_MAX_MSIX_VECTORS;
err = wx_get_pcie_msix_counts(wx, &msix_count, TXGBE_MAX_MSIX_VECTORS);
if (err)
wx_err(wx, "Do not support MSI-X\n");
wx->mac.max_msix_vectors = msix_count;
wx->ring_feature[RING_F_RSS].limit = min_t(int, TXGBE_MAX_RSS_INDICES,
num_online_cpus());
wx->rss_enabled = true;
wx->ring_feature[RING_F_FDIR].limit = min_t(int, TXGBE_MAX_FDIR_INDICES,
num_online_cpus());
set_bit(WX_FLAG_FDIR_CAPABLE, wx->flags);
set_bit(WX_FLAG_FDIR_HASH, wx->flags);
wx->atr_sample_rate = TXGBE_DEFAULT_ATR_SAMPLE_RATE;
wx->atr = txgbe_atr;
wx->configure_fdir = txgbe_configure_fdir;
set_bit(WX_FLAG_RSC_CAPABLE, wx->flags);
set_bit(WX_FLAG_RSC_ENABLED, wx->flags);
set_bit(WX_FLAG_MULTI_64_FUNC, wx->flags);
/* enable itr by default in dynamic mode */
wx->adaptive_itr = true;
wx->rx_itr_setting = 1;
wx->tx_itr_setting = 1;
/* set default ring sizes */
wx->tx_ring_count = TXGBE_DEFAULT_TXD;
wx->rx_ring_count = TXGBE_DEFAULT_RXD;
wx->mbx.size = WX_VXMAILBOX_SIZE;
/* set default work limits */
wx->tx_work_limit = TXGBE_DEFAULT_TX_WORK;
wx->rx_work_limit = TXGBE_DEFAULT_RX_WORK;
wx->setup_tc = txgbe_setup_tc;
wx->do_reset = txgbe_do_reset;
set_bit(0, &wx->fwd_bitmask);
switch (wx->mac.type) {
case wx_mac_sp:
break;
case wx_mac_aml:
case wx_mac_aml40:
set_bit(WX_FLAG_RX_MERGE_ENABLED, wx->flags);
set_bit(WX_FLAG_TXHEAD_WB_ENABLED, wx->flags);
set_bit(WX_FLAG_SWFW_RING, wx->flags);
wx->swfw_index = 0;
break;
default:
break;
}
return 0;
}
static void txgbe_init_fdir(struct txgbe *txgbe)
{
txgbe->fdir_filter_count = 0;
spin_lock_init(&txgbe->fdir_perfect_lock);
}
/**
* txgbe_open - Called when a network interface is made active
* @netdev: network interface device structure
*
* Returns 0 on success, negative value on failure
*
* The open entry point is called when a network interface is made
* active by the system (IFF_UP).
**/
static int txgbe_open(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
int err;
err = wx_setup_resources(wx);
if (err)
goto err_reset;
wx_configure(wx);
err = txgbe_setup_misc_irq(wx->priv);
if (err)
goto err_free_resources;
err = txgbe_request_queue_irqs(wx);
if (err)
goto err_free_misc_irq;
/* Notify the stack of the actual queue counts. */
err = netif_set_real_num_tx_queues(netdev, wx->num_tx_queues);
if (err)
goto err_free_irq;
err = netif_set_real_num_rx_queues(netdev, wx->num_rx_queues);
if (err)
goto err_free_irq;
wx_ptp_init(wx);
txgbe_up_complete(wx);
return 0;
err_free_irq:
wx_free_irq(wx);
err_free_misc_irq:
txgbe_free_misc_irq(wx->priv);
wx_reset_interrupt_capability(wx);
err_free_resources:
wx_free_resources(wx);
err_reset:
txgbe_reset(wx);
return err;
}
/**
* txgbe_close_suspend - actions necessary to both suspend and close flows
* @wx: the private wx struct
*
* This function should contain the necessary work common to both suspending
* and closing of the device.
*/
static void txgbe_close_suspend(struct wx *wx)
{
wx_ptp_suspend(wx);
txgbe_disable_device(wx);
wx_free_resources(wx);
}
/**
* txgbe_close - Disables a network interface
* @netdev: network interface device structure
*
* Returns 0, this is not allowed to fail
*
* The close entry point is called when an interface is de-activated
* by the OS. The hardware is still under the drivers control, but
* needs to be disabled. A global MAC reset is issued to stop the
* hardware, and all transmit and receive resources are freed.
**/
static int txgbe_close(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
wx_ptp_stop(wx);
txgbe_down(wx);
wx_free_irq(wx);
txgbe_free_misc_irq(wx->priv);
wx_free_resources(wx);
txgbe_fdir_filter_exit(wx);
wx_control_hw(wx, false);
return 0;
}
static void txgbe_dev_shutdown(struct pci_dev *pdev)
{
struct wx *wx = pci_get_drvdata(pdev);
struct net_device *netdev;
netdev = wx->netdev;
netif_device_detach(netdev);
rtnl_lock();
if (netif_running(netdev))
txgbe_close_suspend(wx);
rtnl_unlock();
wx_control_hw(wx, false);
pci_disable_device(pdev);
}
static void txgbe_shutdown(struct pci_dev *pdev)
{
txgbe_dev_shutdown(pdev);
if (system_state == SYSTEM_POWER_OFF) {
pci_wake_from_d3(pdev, false);
pci_set_power_state(pdev, PCI_D3hot);
}
}
/**
* txgbe_setup_tc - routine to configure net_device for multiple traffic
* classes.
*
* @dev: net device to configure
* @tc: number of traffic classes to enable
*/
int txgbe_setup_tc(struct net_device *dev, u8 tc)
{
struct wx *wx = netdev_priv(dev);
/* Hardware has to reinitialize queues and interrupts to
* match packet buffer alignment. Unfortunately, the
* hardware is not flexible enough to do this dynamically.
*/
if (netif_running(dev))
txgbe_close(dev);
else
txgbe_reset(wx);
wx_clear_interrupt_scheme(wx);
if (tc)
netdev_set_num_tc(dev, tc);
else
netdev_reset_tc(dev);
wx_init_interrupt_scheme(wx);
if (netif_running(dev))
txgbe_open(dev);
return 0;
}
static void txgbe_reinit_locked(struct wx *wx)
{
netif_trans_update(wx->netdev);
mutex_lock(&wx->reset_lock);
set_bit(WX_STATE_RESETTING, wx->state);
txgbe_down(wx);
txgbe_up(wx);
clear_bit(WX_STATE_RESETTING, wx->state);
mutex_unlock(&wx->reset_lock);
}
void txgbe_do_reset(struct net_device *netdev)
{
struct wx *wx = netdev_priv(netdev);
if (netif_running(netdev))
txgbe_reinit_locked(wx);
else
txgbe_reset(wx);
}
static int txgbe_udp_tunnel_sync(struct net_device *dev, unsigned int table)
{
struct wx *wx = netdev_priv(dev);
struct udp_tunnel_info ti;
udp_tunnel_nic_get_port(dev, table, 0, &ti);
switch (ti.type) {
case UDP_TUNNEL_TYPE_VXLAN:
wr32(wx, TXGBE_CFG_VXLAN, ntohs(ti.port));
break;
case UDP_TUNNEL_TYPE_VXLAN_GPE:
wr32(wx, TXGBE_CFG_VXLAN_GPE, ntohs(ti.port));
break;
case UDP_TUNNEL_TYPE_GENEVE:
wr32(wx, TXGBE_CFG_GENEVE, ntohs(ti.port));
break;
default:
break;
}
return 0;
}
static const struct udp_tunnel_nic_info txgbe_udp_tunnels = {
.sync_table = txgbe_udp_tunnel_sync,
.flags = UDP_TUNNEL_NIC_INFO_OPEN_ONLY,
.tables = {
{ .n_entries = 1, .tunnel_types = UDP_TUNNEL_TYPE_VXLAN, },
{ .n_entries = 1, .tunnel_types = UDP_TUNNEL_TYPE_VXLAN_GPE, },
{ .n_entries = 1, .tunnel_types = UDP_TUNNEL_TYPE_GENEVE, },
},
};
static const struct net_device_ops txgbe_netdev_ops = {
.ndo_open = txgbe_open,
.ndo_stop = txgbe_close,
.ndo_change_mtu = wx_change_mtu,
.ndo_start_xmit = wx_xmit_frame,
.ndo_set_rx_mode = wx_set_rx_mode,
.ndo_set_features = wx_set_features,
.ndo_fix_features = wx_fix_features,
.ndo_features_check = wx_features_check,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = wx_set_mac,
.ndo_get_stats64 = wx_get_stats64,
.ndo_vlan_rx_add_vid = wx_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = wx_vlan_rx_kill_vid,
.ndo_hwtstamp_set = wx_hwtstamp_set,
.ndo_hwtstamp_get = wx_hwtstamp_get,
};
/**
* txgbe_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in txgbe_pci_tbl
*
* Returns 0 on success, negative on failure
*
* txgbe_probe initializes an adapter identified by a pci_dev structure.
* The OS initialization, configuring of the wx private structure,
* and a hardware reset occur.
**/
static int txgbe_probe(struct pci_dev *pdev,
const struct pci_device_id __always_unused *ent)
{
struct net_device *netdev;
int err, expected_gts;
struct wx *wx = NULL;
struct txgbe *txgbe;
u16 eeprom_verh = 0, eeprom_verl = 0, offset = 0;
u16 eeprom_cfg_blkh = 0, eeprom_cfg_blkl = 0;
u16 build = 0, major = 0, patch = 0;
u32 etrack_id = 0;
err = pci_enable_device_mem(pdev);
if (err)
return err;
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(&pdev->dev,
"No usable DMA configuration, aborting\n");
goto err_pci_disable_dev;
}
err = pci_request_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM),
txgbe_driver_name);
if (err) {
dev_err(&pdev->dev,
"pci_request_selected_regions failed 0x%x\n", err);
goto err_pci_disable_dev;
}
pci_set_master(pdev);
netdev = devm_alloc_etherdev_mqs(&pdev->dev,
sizeof(struct wx),
TXGBE_MAX_TX_QUEUES,
TXGBE_MAX_RX_QUEUES);
if (!netdev) {
err = -ENOMEM;
goto err_pci_release_regions;
}
SET_NETDEV_DEV(netdev, &pdev->dev);
wx = netdev_priv(netdev);
wx->netdev = netdev;
wx->pdev = pdev;
wx->msg_enable = (1 << DEFAULT_DEBUG_LEVEL_SHIFT) - 1;
wx->hw_addr = devm_ioremap(&pdev->dev,
pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (!wx->hw_addr) {
err = -EIO;
goto err_pci_release_regions;
}
/* The sapphire supports up to 63 VFs per pf, but physical
* function also need one pool for basic networking.
*/
pci_sriov_set_totalvfs(pdev, TXGBE_MAX_VFS_DRV_LIMIT);
wx->driver_name = txgbe_driver_name;
txgbe_set_ethtool_ops(netdev);
netdev->netdev_ops = &txgbe_netdev_ops;
netdev->udp_tunnel_nic_info = &txgbe_udp_tunnels;
/* setup the private structure */
err = txgbe_sw_init(wx);
if (err)
goto err_pci_release_regions;
/* check if flash load is done after hw power up */
err = wx_check_flash_load(wx, TXGBE_SPI_ILDR_STATUS_PERST);
if (err)
goto err_free_mac_table;
err = wx_check_flash_load(wx, TXGBE_SPI_ILDR_STATUS_PWRRST);
if (err)
goto err_free_mac_table;
err = wx_mng_present(wx);
if (err) {
dev_err(&pdev->dev, "Management capability is not present\n");
goto err_free_mac_table;
}
err = txgbe_reset_hw(wx);
if (err) {
dev_err(&pdev->dev, "HW Init failed: %d\n", err);
goto err_free_mac_table;
}
netdev->features = NETIF_F_SG |
NETIF_F_TSO |
NETIF_F_TSO6 |
NETIF_F_RXHASH |
NETIF_F_RXCSUM |
NETIF_F_HW_CSUM;
netdev->gso_partial_features = NETIF_F_GSO_ENCAP_ALL;
netdev->features |= netdev->gso_partial_features;
netdev->features |= NETIF_F_SCTP_CRC;
netdev->vlan_features |= netdev->features | NETIF_F_TSO_MANGLEID;
netdev->hw_enc_features |= netdev->vlan_features;
netdev->features |= NETIF_F_VLAN_FEATURES;
/* copy netdev features into list of user selectable features */
netdev->hw_features |= netdev->features | NETIF_F_RXALL;
netdev->hw_features |= NETIF_F_NTUPLE | NETIF_F_HW_TC;
netdev->features |= NETIF_F_HIGHDMA;
netdev->hw_features |= NETIF_F_GRO;
netdev->features |= NETIF_F_GRO;
netdev->hw_features |= NETIF_F_LRO;
netdev->features |= NETIF_F_LRO;
netdev->features |= NETIF_F_RX_UDP_TUNNEL_PORT;
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
netdev->min_mtu = ETH_MIN_MTU;
netdev->max_mtu = WX_MAX_JUMBO_FRAME_SIZE -
(ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN);
/* make sure the EEPROM is good */
err = txgbe_validate_eeprom_checksum(wx, NULL);
if (err != 0) {
dev_err(&pdev->dev, "The EEPROM Checksum Is Not Valid\n");
wr32(wx, WX_MIS_RST, WX_MIS_RST_SW_RST);
err = -EIO;
goto err_free_mac_table;
}
eth_hw_addr_set(netdev, wx->mac.perm_addr);
wx_mac_set_default_filter(wx, wx->mac.perm_addr);
txgbe_init_service(wx);
err = wx_init_interrupt_scheme(wx);
if (err)
goto err_cancel_service;
/* Save off EEPROM version number and Option Rom version which
* together make a unique identify for the eeprom
*/
wx_read_ee_hostif(wx,
wx->eeprom.sw_region_offset + TXGBE_EEPROM_VERSION_H,
&eeprom_verh);
wx_read_ee_hostif(wx,
wx->eeprom.sw_region_offset + TXGBE_EEPROM_VERSION_L,
&eeprom_verl);
etrack_id = (eeprom_verh << 16) | eeprom_verl;
wx_read_ee_hostif(wx,
wx->eeprom.sw_region_offset + TXGBE_ISCSI_BOOT_CONFIG,
&offset);
/* Make sure offset to SCSI block is valid */
if (!(offset == 0x0) && !(offset == 0xffff)) {
wx_read_ee_hostif(wx, offset + 0x84, &eeprom_cfg_blkh);
wx_read_ee_hostif(wx, offset + 0x83, &eeprom_cfg_blkl);
/* Only display Option Rom if exist */
if (eeprom_cfg_blkl && eeprom_cfg_blkh) {
major = eeprom_cfg_blkl >> 8;
build = (eeprom_cfg_blkl << 8) | (eeprom_cfg_blkh >> 8);
patch = eeprom_cfg_blkh & 0x00ff;
snprintf(wx->eeprom_id, sizeof(wx->eeprom_id),
"0x%08x, %d.%d.%d", etrack_id, major, build,
patch);
} else {
snprintf(wx->eeprom_id, sizeof(wx->eeprom_id),
"0x%08x", etrack_id);
}
} else {
snprintf(wx->eeprom_id, sizeof(wx->eeprom_id),
"0x%08x", etrack_id);
}
if (wx->mac.type == wx_mac_sp &&
((etrack_id & 0xfffff) < 0x20010))
dev_warn(&pdev->dev, "Please upgrade the firmware to 0x20010 or above.\n");
err = txgbe_test_hostif(wx);
if (err != 0) {
dev_err(&pdev->dev, "Mismatched Firmware version\n");
err = -EIO;
goto err_release_hw;
}
txgbe = devm_kzalloc(&pdev->dev, sizeof(*txgbe), GFP_KERNEL);
if (!txgbe) {
err = -ENOMEM;
goto err_release_hw;
}
txgbe->wx = wx;
wx->priv = txgbe;
txgbe_init_fdir(txgbe);
err = txgbe_init_phy(txgbe);
if (err)
goto err_release_hw;
err = register_netdev(netdev);
if (err)
goto err_remove_phy;
pci_set_drvdata(pdev, wx);
netif_tx_stop_all_queues(netdev);
/* calculate the expected PCIe bandwidth required for optimal
* performance. Note that some older parts will never have enough
* bandwidth due to being older generation PCIe parts. We clamp these
* parts to ensure that no warning is displayed, as this could confuse
* users otherwise.
*/
expected_gts = txgbe_enumerate_functions(wx) * 10;
/* don't check link if we failed to enumerate functions */
if (expected_gts > 0)
txgbe_check_minimum_link(wx);
else
dev_warn(&pdev->dev, "Failed to enumerate PF devices.\n");
return 0;
err_remove_phy:
txgbe_remove_phy(txgbe);
err_release_hw:
wx_clear_interrupt_scheme(wx);
wx_control_hw(wx, false);
err_cancel_service:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
err_free_mac_table:
kfree(wx->rss_key);
kfree(wx->mac_table);
err_pci_release_regions:
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_disable_dev:
pci_disable_device(pdev);
return err;
}
/**
* txgbe_remove - Device Removal Routine
* @pdev: PCI device information struct
*
* txgbe_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device. The could be caused by a
* Hot-Plug event, or because the driver is going to be removed from
* memory.
**/
static void txgbe_remove(struct pci_dev *pdev)
{
struct wx *wx = pci_get_drvdata(pdev);
struct txgbe *txgbe = wx->priv;
struct net_device *netdev;
netdev = wx->netdev;
wx_disable_sriov(wx);
unregister_netdev(netdev);
txgbe_fdir_filter_exit(wx);
timer_shutdown_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
txgbe_remove_phy(txgbe);
wx_free_isb_resources(wx);
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
kfree(wx->rss_key);
kfree(wx->mac_table);
wx_clear_interrupt_scheme(wx);
pci_disable_device(pdev);
}
static struct pci_driver txgbe_driver = {
.name = txgbe_driver_name,
.id_table = txgbe_pci_tbl,
.probe = txgbe_probe,
.remove = txgbe_remove,
.shutdown = txgbe_shutdown,
.sriov_configure = wx_pci_sriov_configure,
};
module_pci_driver(txgbe_driver);
MODULE_DEVICE_TABLE(pci, txgbe_pci_tbl);
MODULE_AUTHOR("Beijing WangXun Technology Co., Ltd, <software@trustnetic.com>");
MODULE_DESCRIPTION("WangXun(R) 10/25/40 Gigabit PCI Express Network Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,678 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/gpio/machine.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/property.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/i2c.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/pcs/pcs-xpcs.h>
#include <linux/phylink.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_ptp.h"
#include "../libwx/wx_sriov.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_aml.h"
#include "txgbe_phy.h"
#include "txgbe_hw.h"
static int txgbe_swnodes_register(struct txgbe *txgbe)
{
struct txgbe_nodes *nodes = &txgbe->nodes;
struct pci_dev *pdev = txgbe->wx->pdev;
struct software_node *swnodes;
u32 id;
id = pci_dev_id(pdev);
snprintf(nodes->gpio_name, sizeof(nodes->gpio_name), "txgbe_gpio-%x", id);
snprintf(nodes->i2c_name, sizeof(nodes->i2c_name), "txgbe_i2c-%x", id);
snprintf(nodes->sfp_name, sizeof(nodes->sfp_name), "txgbe_sfp-%x", id);
snprintf(nodes->phylink_name, sizeof(nodes->phylink_name), "txgbe_phylink-%x", id);
swnodes = nodes->swnodes;
/* GPIO 0: tx fault
* GPIO 1: tx disable
* GPIO 2: sfp module absent
* GPIO 3: rx signal lost
* GPIO 4: rate select, 1G(0) 10G(1)
* GPIO 5: rate select, 1G(0) 10G(1)
*/
nodes->gpio_props[0] = PROPERTY_ENTRY_STRING("pinctrl-names", "default");
swnodes[SWNODE_GPIO] = NODE_PROP(nodes->gpio_name, nodes->gpio_props);
nodes->gpio0_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 0, GPIO_ACTIVE_HIGH);
nodes->gpio1_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 1, GPIO_ACTIVE_HIGH);
nodes->gpio2_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 2, GPIO_ACTIVE_LOW);
nodes->gpio3_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 3, GPIO_ACTIVE_HIGH);
nodes->gpio4_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 4, GPIO_ACTIVE_HIGH);
nodes->gpio5_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_GPIO], 5, GPIO_ACTIVE_HIGH);
nodes->i2c_props[0] = PROPERTY_ENTRY_STRING("compatible", "snps,designware-i2c");
nodes->i2c_props[1] = PROPERTY_ENTRY_BOOL("wx,i2c-snps-model");
nodes->i2c_props[2] = PROPERTY_ENTRY_U32("clock-frequency", I2C_MAX_STANDARD_MODE_FREQ);
swnodes[SWNODE_I2C] = NODE_PROP(nodes->i2c_name, nodes->i2c_props);
nodes->i2c_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_I2C]);
nodes->sfp_props[0] = PROPERTY_ENTRY_STRING("compatible", "sff,sfp");
nodes->sfp_props[1] = PROPERTY_ENTRY_REF_ARRAY("i2c-bus", nodes->i2c_ref);
nodes->sfp_props[2] = PROPERTY_ENTRY_REF_ARRAY("tx-fault-gpios", nodes->gpio0_ref);
nodes->sfp_props[3] = PROPERTY_ENTRY_REF_ARRAY("tx-disable-gpios", nodes->gpio1_ref);
nodes->sfp_props[4] = PROPERTY_ENTRY_REF_ARRAY("mod-def0-gpios", nodes->gpio2_ref);
nodes->sfp_props[5] = PROPERTY_ENTRY_REF_ARRAY("los-gpios", nodes->gpio3_ref);
nodes->sfp_props[6] = PROPERTY_ENTRY_REF_ARRAY("rate-select1-gpios", nodes->gpio4_ref);
nodes->sfp_props[7] = PROPERTY_ENTRY_REF_ARRAY("rate-select0-gpios", nodes->gpio5_ref);
swnodes[SWNODE_SFP] = NODE_PROP(nodes->sfp_name, nodes->sfp_props);
nodes->sfp_ref[0] = SOFTWARE_NODE_REFERENCE(&swnodes[SWNODE_SFP]);
nodes->phylink_props[0] = PROPERTY_ENTRY_STRING("managed", "in-band-status");
nodes->phylink_props[1] = PROPERTY_ENTRY_REF_ARRAY("sfp", nodes->sfp_ref);
swnodes[SWNODE_PHYLINK] = NODE_PROP(nodes->phylink_name, nodes->phylink_props);
nodes->group[SWNODE_GPIO] = &swnodes[SWNODE_GPIO];
nodes->group[SWNODE_I2C] = &swnodes[SWNODE_I2C];
nodes->group[SWNODE_SFP] = &swnodes[SWNODE_SFP];
nodes->group[SWNODE_PHYLINK] = &swnodes[SWNODE_PHYLINK];
return software_node_register_node_group(nodes->group);
}
static int txgbe_pcs_read(struct mii_bus *bus, int addr, int devnum, int regnum)
{
struct wx *wx = bus->priv;
u32 offset, val;
if (addr)
return -EOPNOTSUPP;
offset = devnum << 16 | regnum;
/* Set the LAN port indicator to IDA_ADDR */
wr32(wx, TXGBE_XPCS_IDA_ADDR, offset);
/* Read the data from IDA_DATA register */
val = rd32(wx, TXGBE_XPCS_IDA_DATA);
return (u16)val;
}
static int txgbe_pcs_write(struct mii_bus *bus, int addr, int devnum, int regnum, u16 val)
{
struct wx *wx = bus->priv;
u32 offset;
if (addr)
return -EOPNOTSUPP;
offset = devnum << 16 | regnum;
/* Set the LAN port indicator to IDA_ADDR */
wr32(wx, TXGBE_XPCS_IDA_ADDR, offset);
/* Write the data to IDA_DATA register */
wr32(wx, TXGBE_XPCS_IDA_DATA, val);
return 0;
}
static int txgbe_mdio_pcs_init(struct txgbe *txgbe)
{
struct mii_bus *mii_bus;
struct phylink_pcs *pcs;
struct pci_dev *pdev;
struct wx *wx;
int ret = 0;
wx = txgbe->wx;
pdev = wx->pdev;
mii_bus = devm_mdiobus_alloc(&pdev->dev);
if (!mii_bus)
return -ENOMEM;
mii_bus->name = "txgbe_pcs_mdio_bus";
mii_bus->read_c45 = &txgbe_pcs_read;
mii_bus->write_c45 = &txgbe_pcs_write;
mii_bus->parent = &pdev->dev;
mii_bus->phy_mask = ~0;
mii_bus->priv = wx;
snprintf(mii_bus->id, MII_BUS_ID_SIZE, "txgbe_pcs-%x",
pci_dev_id(pdev));
ret = devm_mdiobus_register(&pdev->dev, mii_bus);
if (ret)
return ret;
pcs = xpcs_create_pcs_mdiodev(mii_bus, 0);
if (IS_ERR(pcs))
return PTR_ERR(pcs);
txgbe->pcs = pcs;
return 0;
}
static struct phylink_pcs *txgbe_phylink_mac_select(struct phylink_config *config,
phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
struct txgbe *txgbe = wx->priv;
if (wx->media_type != wx_media_copper)
return txgbe->pcs;
return NULL;
}
static void txgbe_mac_config(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
}
static void txgbe_mac_link_down(struct phylink_config *config,
unsigned int mode, phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
wx->speed = SPEED_UNKNOWN;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going down */
wx_ping_all_vfs_with_link_status(wx, false);
}
static void txgbe_mac_link_up(struct phylink_config *config,
struct phy_device *phy,
unsigned int mode, phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct wx *wx = phylink_to_wx(config);
u32 txcfg, wdg;
wx_fc_enable(wx, tx_pause, rx_pause);
txcfg = rd32(wx, WX_MAC_TX_CFG);
txcfg &= ~WX_MAC_TX_CFG_SPEED_MASK;
switch (speed) {
case SPEED_10000:
txcfg |= WX_MAC_TX_CFG_SPEED_10G;
break;
case SPEED_1000:
case SPEED_100:
case SPEED_10:
txcfg |= WX_MAC_TX_CFG_SPEED_1G;
break;
default:
break;
}
wr32(wx, WX_MAC_TX_CFG, txcfg | WX_MAC_TX_CFG_TE);
/* Re configure MAC Rx */
wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, WX_MAC_RX_CFG_RE);
wr32(wx, WX_MAC_PKT_FLT, WX_MAC_PKT_FLT_PR);
wdg = rd32(wx, WX_MAC_WDG_TIMEOUT);
wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
wx->speed = speed;
wx->last_rx_ptp_check = jiffies;
if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
wx_ptp_reset_cyclecounter(wx);
/* ping all the active vfs to let them know we are going up */
wx_ping_all_vfs_with_link_status(wx, true);
}
static int txgbe_mac_prepare(struct phylink_config *config, unsigned int mode,
phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
wr32m(wx, WX_MAC_TX_CFG, WX_MAC_TX_CFG_TE, 0);
wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, 0);
return txgbe_disable_sec_tx_path(wx);
}
static int txgbe_mac_finish(struct phylink_config *config, unsigned int mode,
phy_interface_t interface)
{
struct wx *wx = phylink_to_wx(config);
txgbe_enable_sec_tx_path(wx);
wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, WX_MAC_RX_CFG_RE);
return 0;
}
static const struct phylink_mac_ops txgbe_mac_ops = {
.mac_select_pcs = txgbe_phylink_mac_select,
.mac_prepare = txgbe_mac_prepare,
.mac_finish = txgbe_mac_finish,
.mac_config = txgbe_mac_config,
.mac_link_down = txgbe_mac_link_down,
.mac_link_up = txgbe_mac_link_up,
};
static int txgbe_phylink_init(struct txgbe *txgbe)
{
struct fwnode_handle *fwnode = NULL;
struct phylink_config *config;
struct wx *wx = txgbe->wx;
phy_interface_t phy_mode;
struct phylink *phylink;
config = &wx->phylink_config;
config->dev = &wx->netdev->dev;
config->type = PHYLINK_NETDEV;
config->mac_capabilities = MAC_10000FD | MAC_1000FD | MAC_100FD |
MAC_SYM_PAUSE | MAC_ASYM_PAUSE;
if (wx->media_type == wx_media_copper) {
phy_mode = PHY_INTERFACE_MODE_XAUI;
__set_bit(PHY_INTERFACE_MODE_XAUI, config->supported_interfaces);
} else {
phy_mode = PHY_INTERFACE_MODE_10GBASER;
fwnode = software_node_fwnode(txgbe->nodes.group[SWNODE_PHYLINK]);
__set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
}
phylink = phylink_create(config, fwnode, phy_mode, &txgbe_mac_ops);
if (IS_ERR(phylink))
return PTR_ERR(phylink);
if (wx->phydev) {
int ret;
ret = phylink_connect_phy(phylink, wx->phydev);
if (ret) {
phylink_destroy(phylink);
return ret;
}
}
wx->phylink = phylink;
return 0;
}
irqreturn_t txgbe_link_irq_handler(int irq, void *data)
{
struct txgbe *txgbe = data;
struct wx *wx = txgbe->wx;
u32 status;
bool up;
status = rd32(wx, TXGBE_CFG_PORT_ST);
up = !!(status & TXGBE_CFG_PORT_ST_LINK_UP);
if (txgbe->pcs)
phylink_pcs_change(txgbe->pcs, up);
else
phylink_mac_change(wx->phylink, up);
return IRQ_HANDLED;
}
static int txgbe_gpio_get(struct gpio_chip *chip, unsigned int offset)
{
struct wx *wx = gpiochip_get_data(chip);
int val;
val = rd32m(wx, WX_GPIO_EXT, BIT(offset));
return !!(val & BIT(offset));
}
static int txgbe_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
{
struct wx *wx = gpiochip_get_data(chip);
u32 val;
val = rd32(wx, WX_GPIO_DDR);
if (BIT(offset) & val)
return GPIO_LINE_DIRECTION_OUT;
return GPIO_LINE_DIRECTION_IN;
}
static int txgbe_gpio_direction_in(struct gpio_chip *chip, unsigned int offset)
{
struct wx *wx = gpiochip_get_data(chip);
unsigned long flags;
raw_spin_lock_irqsave(&wx->gpio_lock, flags);
wr32m(wx, WX_GPIO_DDR, BIT(offset), 0);
raw_spin_unlock_irqrestore(&wx->gpio_lock, flags);
return 0;
}
static int txgbe_gpio_direction_out(struct gpio_chip *chip, unsigned int offset,
int val)
{
struct wx *wx = gpiochip_get_data(chip);
unsigned long flags;
u32 set;
set = val ? BIT(offset) : 0;
raw_spin_lock_irqsave(&wx->gpio_lock, flags);
wr32m(wx, WX_GPIO_DR, BIT(offset), set);
wr32m(wx, WX_GPIO_DDR, BIT(offset), BIT(offset));
raw_spin_unlock_irqrestore(&wx->gpio_lock, flags);
return 0;
}
static int txgbe_gpio_init(struct txgbe *txgbe)
{
struct gpio_chip *gc;
struct device *dev;
struct wx *wx;
int ret;
wx = txgbe->wx;
dev = &wx->pdev->dev;
raw_spin_lock_init(&wx->gpio_lock);
gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
if (!gc)
return -ENOMEM;
gc->label = devm_kasprintf(dev, GFP_KERNEL, "txgbe_gpio-%x",
pci_dev_id(wx->pdev));
if (!gc->label)
return -ENOMEM;
gc->base = -1;
gc->ngpio = 6;
gc->owner = THIS_MODULE;
gc->parent = dev;
gc->fwnode = software_node_fwnode(txgbe->nodes.group[SWNODE_GPIO]);
gc->get = txgbe_gpio_get;
gc->get_direction = txgbe_gpio_get_direction;
gc->direction_input = txgbe_gpio_direction_in;
gc->direction_output = txgbe_gpio_direction_out;
ret = devm_gpiochip_add_data(dev, gc, wx);
if (ret)
return ret;
txgbe->gpio = gc;
return 0;
}
static int txgbe_clock_register(struct txgbe *txgbe)
{
struct pci_dev *pdev = txgbe->wx->pdev;
struct clk_lookup *clock;
char clk_name[32];
struct clk *clk;
snprintf(clk_name, sizeof(clk_name), "i2c_designware.%d",
pci_dev_id(pdev));
clk = clk_register_fixed_rate(NULL, clk_name, NULL, 0, 156250000);
if (IS_ERR(clk))
return PTR_ERR(clk);
clock = clkdev_create(clk, NULL, "%s", clk_name);
if (!clock) {
clk_unregister(clk);
return -ENOMEM;
}
txgbe->clk = clk;
txgbe->clock = clock;
return 0;
}
static int txgbe_i2c_read(void *context, unsigned int reg, unsigned int *val)
{
struct wx *wx = context;
*val = rd32(wx, reg + TXGBE_I2C_BASE);
return 0;
}
static int txgbe_i2c_write(void *context, unsigned int reg, unsigned int val)
{
struct wx *wx = context;
wr32(wx, reg + TXGBE_I2C_BASE, val);
return 0;
}
static const struct regmap_config i2c_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_read = txgbe_i2c_read,
.reg_write = txgbe_i2c_write,
.fast_io = true,
};
static int txgbe_i2c_register(struct txgbe *txgbe)
{
struct platform_device_info info = {};
struct platform_device *i2c_dev;
struct regmap *i2c_regmap;
struct pci_dev *pdev;
struct wx *wx;
wx = txgbe->wx;
pdev = wx->pdev;
i2c_regmap = devm_regmap_init(&pdev->dev, NULL, wx, &i2c_regmap_config);
if (IS_ERR(i2c_regmap)) {
wx_err(wx, "failed to init I2C regmap\n");
return PTR_ERR(i2c_regmap);
}
info.parent = &pdev->dev;
info.fwnode = software_node_fwnode(txgbe->nodes.group[SWNODE_I2C]);
info.name = "i2c_designware";
info.id = pci_dev_id(pdev);
info.res = &DEFINE_RES_IRQ(pdev->irq);
info.num_res = 1;
i2c_dev = platform_device_register_full(&info);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
txgbe->i2c_dev = i2c_dev;
return 0;
}
static int txgbe_sfp_register(struct txgbe *txgbe)
{
struct pci_dev *pdev = txgbe->wx->pdev;
struct platform_device_info info = {};
struct platform_device *sfp_dev;
info.parent = &pdev->dev;
info.fwnode = software_node_fwnode(txgbe->nodes.group[SWNODE_SFP]);
info.name = "sfp";
info.id = pci_dev_id(pdev);
sfp_dev = platform_device_register_full(&info);
if (IS_ERR(sfp_dev))
return PTR_ERR(sfp_dev);
txgbe->sfp_dev = sfp_dev;
return 0;
}
static int txgbe_ext_phy_init(struct txgbe *txgbe)
{
struct phy_device *phydev;
struct mii_bus *mii_bus;
struct pci_dev *pdev;
struct wx *wx;
int ret = 0;
wx = txgbe->wx;
pdev = wx->pdev;
mii_bus = devm_mdiobus_alloc(&pdev->dev);
if (!mii_bus)
return -ENOMEM;
mii_bus->name = "txgbe_mii_bus";
mii_bus->read_c45 = &wx_phy_read_reg_mdi_c45;
mii_bus->write_c45 = &wx_phy_write_reg_mdi_c45;
mii_bus->parent = &pdev->dev;
mii_bus->phy_mask = GENMASK(31, 1);
mii_bus->priv = wx;
snprintf(mii_bus->id, MII_BUS_ID_SIZE, "txgbe-%x", pci_dev_id(pdev));
ret = devm_mdiobus_register(&pdev->dev, mii_bus);
if (ret) {
wx_err(wx, "failed to register MDIO bus: %d\n", ret);
return ret;
}
phydev = phy_find_first(mii_bus);
if (!phydev) {
wx_err(wx, "no PHY found\n");
return -ENODEV;
}
phy_attached_info(phydev);
wx->link = 0;
wx->speed = 0;
wx->duplex = 0;
wx->phydev = phydev;
ret = txgbe_phylink_init(txgbe);
if (ret) {
wx_err(wx, "failed to init phylink: %d\n", ret);
return ret;
}
return 0;
}
int txgbe_init_phy(struct txgbe *txgbe)
{
struct wx *wx = txgbe->wx;
int ret;
switch (wx->mac.type) {
case wx_mac_aml40:
case wx_mac_aml:
return txgbe_phylink_init_aml(txgbe);
case wx_mac_sp:
if (wx->media_type == wx_media_copper)
return txgbe_ext_phy_init(txgbe);
break;
default:
break;
}
ret = txgbe_swnodes_register(txgbe);
if (ret) {
wx_err(wx, "failed to register software nodes\n");
return ret;
}
ret = txgbe_mdio_pcs_init(txgbe);
if (ret) {
wx_err(wx, "failed to init mdio pcs: %d\n", ret);
goto err_unregister_swnode;
}
ret = txgbe_phylink_init(txgbe);
if (ret) {
wx_err(wx, "failed to init phylink\n");
goto err_destroy_xpcs;
}
ret = txgbe_gpio_init(txgbe);
if (ret) {
wx_err(wx, "failed to init gpio\n");
goto err_destroy_phylink;
}
ret = txgbe_clock_register(txgbe);
if (ret) {
wx_err(wx, "failed to register clock: %d\n", ret);
goto err_destroy_phylink;
}
ret = txgbe_i2c_register(txgbe);
if (ret) {
wx_err(wx, "failed to init i2c interface: %d\n", ret);
goto err_unregister_clk;
}
ret = txgbe_sfp_register(txgbe);
if (ret) {
wx_err(wx, "failed to register sfp\n");
goto err_unregister_i2c;
}
return 0;
err_unregister_i2c:
platform_device_unregister(txgbe->i2c_dev);
err_unregister_clk:
clkdev_drop(txgbe->clock);
clk_unregister(txgbe->clk);
err_destroy_phylink:
phylink_destroy(wx->phylink);
err_destroy_xpcs:
xpcs_destroy_pcs(txgbe->pcs);
err_unregister_swnode:
software_node_unregister_node_group(txgbe->nodes.group);
return ret;
}
void txgbe_remove_phy(struct txgbe *txgbe)
{
switch (txgbe->wx->mac.type) {
case wx_mac_aml40:
case wx_mac_aml:
phylink_destroy(txgbe->wx->phylink);
return;
case wx_mac_sp:
if (txgbe->wx->media_type == wx_media_copper) {
rtnl_lock();
phylink_disconnect_phy(txgbe->wx->phylink);
rtnl_unlock();
phylink_destroy(txgbe->wx->phylink);
return;
}
break;
default:
break;
}
platform_device_unregister(txgbe->sfp_dev);
platform_device_unregister(txgbe->i2c_dev);
clkdev_drop(txgbe->clock);
clk_unregister(txgbe->clk);
phylink_destroy(txgbe->wx->phylink);
xpcs_destroy_pcs(txgbe->pcs);
software_node_unregister_node_group(txgbe->nodes.group);
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_PHY_H_
#define _TXGBE_PHY_H_
irqreturn_t txgbe_link_irq_handler(int irq, void *data);
int txgbe_init_phy(struct txgbe *txgbe);
void txgbe_remove_phy(struct txgbe *txgbe);
#endif /* _TXGBE_PHY_H_ */
@@ -0,0 +1,485 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2022 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBE_TYPE_H_
#define _TXGBE_TYPE_H_
#include <linux/property.h>
#include <linux/irq.h>
#include <linux/phy.h>
#include "../libwx/wx_type.h"
/* Device IDs */
#define TXGBE_DEV_ID_SP1000 0x1001
#define TXGBE_DEV_ID_WX1820 0x2001
#define TXGBE_DEV_ID_AML5010 0x5010
#define TXGBE_DEV_ID_AML5110 0x5110
#define TXGBE_DEV_ID_AML5025 0x5025
#define TXGBE_DEV_ID_AML5125 0x5125
#define TXGBE_DEV_ID_AML5040 0x5040
#define TXGBE_DEV_ID_AML5140 0x5140
/* Subsystem IDs */
/* SFP */
#define TXGBE_ID_SP1000_SFP 0x0000
#define TXGBE_ID_WX1820_SFP 0x2000
#define TXGBE_ID_SFP 0x00
/* copper */
#define TXGBE_ID_SP1000_XAUI 0x1010
#define TXGBE_ID_WX1820_XAUI 0x2010
#define TXGBE_ID_XAUI 0x10
#define TXGBE_ID_SP1000_SGMII 0x1020
#define TXGBE_ID_WX1820_SGMII 0x2020
#define TXGBE_ID_SGMII 0x20
/* backplane */
#define TXGBE_ID_SP1000_KR_KX_KX4 0x1030
#define TXGBE_ID_WX1820_KR_KX_KX4 0x2030
#define TXGBE_ID_KR_KX_KX4 0x30
/* MAC Interface */
#define TXGBE_ID_SP1000_MAC_XAUI 0x1040
#define TXGBE_ID_WX1820_MAC_XAUI 0x2040
#define TXGBE_ID_MAC_XAUI 0x40
#define TXGBE_ID_SP1000_MAC_SGMII 0x1060
#define TXGBE_ID_WX1820_MAC_SGMII 0x2060
#define TXGBE_ID_MAC_SGMII 0x60
/* Combined interface*/
#define TXGBE_ID_SFI_XAUI 0x50
/* Revision ID */
#define TXGBE_SP_MPW 1
/**************** SP Registers ****************************/
/* chip control Registers */
#define TXGBE_MIS_RST 0x1000C
#define TXGBE_MIS_RST_MAC_RST(_i) BIT(20 - (_i) * 3)
#define TXGBE_MIS_PRB_CTL 0x10010
#define TXGBE_MIS_PRB_CTL_LAN_UP(_i) BIT(1 - (_i))
/* FMGR Registers */
#define TXGBE_SPI_ILDR_STATUS 0x10120
#define TXGBE_SPI_ILDR_STATUS_PERST BIT(0) /* PCIE_PERST is done */
#define TXGBE_SPI_ILDR_STATUS_PWRRST BIT(1) /* Power on reset is done */
#define TXGBE_SPI_ILDR_STATUS_LAN_SW_RST(_i) BIT((_i) + 9) /* lan soft reset done */
/* Sensors for PVT(Process Voltage Temperature) */
#define TXGBE_TS_CTL 0x10300
#define TXGBE_TS_CTL_EVAL_MD BIT(31)
/* MAC Misc Registers */
#define TXGBE_MAC_MISC_CTL 0x11F00
#define TXGBE_MAC_MISC_CTL_LINK_STS_MOD BIT(0)
#define TXGBE_MAC_MISC_CTL_LINK_PCS FIELD_PREP(BIT(0), 0)
#define TXGBE_MAC_MISC_CTL_LINK_BOTH FIELD_PREP(BIT(0), 1)
/* GPIO register bit */
#define TXGBE_GPIOBIT_0 BIT(0) /* I:tx fault */
#define TXGBE_GPIOBIT_1 BIT(1) /* O:tx disabled */
#define TXGBE_GPIOBIT_2 BIT(2) /* I:sfp module absent */
#define TXGBE_GPIOBIT_3 BIT(3) /* I:rx signal lost */
#define TXGBE_GPIOBIT_4 BIT(4) /* O:rate select, 1G(0) 10G(1) */
#define TXGBE_GPIOBIT_5 BIT(5) /* O:rate select, 1G(0) 10G(1) */
/* Extended Interrupt Enable Set */
#define TXGBE_PX_MISC_ETH_LKDN BIT(8)
#define TXGBE_PX_MISC_DEV_RST BIT(10)
#define TXGBE_PX_MISC_IC_TIMESYNC BIT(11)
#define TXGBE_PX_MISC_ETH_EVENT BIT(17)
#define TXGBE_PX_MISC_ETH_LK BIT(18)
#define TXGBE_PX_MISC_ETH_AN BIT(19)
#define TXGBE_PX_MISC_INT_ERR BIT(20)
#define TXGBE_PX_MISC_IC_VF_MBOX BIT(23)
#define TXGBE_PX_MISC_GPIO BIT(26)
#define TXGBE_PX_MISC_IEN_MASK \
(TXGBE_PX_MISC_ETH_LKDN | TXGBE_PX_MISC_DEV_RST | \
TXGBE_PX_MISC_ETH_EVENT | TXGBE_PX_MISC_ETH_LK | \
TXGBE_PX_MISC_ETH_AN | TXGBE_PX_MISC_INT_ERR | \
TXGBE_PX_MISC_IC_VF_MBOX | TXGBE_PX_MISC_IC_TIMESYNC)
/* Port cfg registers */
#define TXGBE_CFG_PORT_ST 0x14404
#define TXGBE_CFG_PORT_ST_LINK_UP BIT(0)
#define TXGBE_CFG_PORT_ST_LINK_AML_40G BIT(2)
#define TXGBE_CFG_PORT_ST_LINK_AML_25G BIT(3)
#define TXGBE_CFG_PORT_ST_LINK_AML_10G BIT(4)
#define TXGBE_CFG_VXLAN 0x14410
#define TXGBE_CFG_VXLAN_GPE 0x14414
#define TXGBE_CFG_GENEVE 0x14418
/* I2C registers */
#define TXGBE_I2C_BASE 0x14900
/************************************** ETH PHY ******************************/
#define TXGBE_XPCS_IDA_ADDR 0x13000
#define TXGBE_XPCS_IDA_DATA 0x13004
/********************************* Flow Director *****************************/
#define TXGBE_RDB_FDIR_DROP_QUEUE 127
#define TXGBE_RDB_FDIR_CTL 0x19500
#define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3)
#define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4)
#define TXGBE_RDB_FDIR_CTL_DROP_Q(v) FIELD_PREP(GENMASK(14, 8), v)
#define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23, 20), v)
#define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v) FIELD_PREP(GENMASK(27, 24), v)
#define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v) FIELD_PREP(GENMASK(31, 28), v)
#define TXGBE_RDB_FDIR_IP6(_i) (0x1950C + ((_i) * 4)) /* 0-2 */
#define TXGBE_RDB_FDIR_SA 0x19518
#define TXGBE_RDB_FDIR_DA 0x1951C
#define TXGBE_RDB_FDIR_PORT 0x19520
#define TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT 16
#define TXGBE_RDB_FDIR_FLEX 0x19524
#define TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT 16
#define TXGBE_RDB_FDIR_HASH 0x19528
#define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v) FIELD_PREP(GENMASK(31, 16), v)
#define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15)
#define TXGBE_RDB_FDIR_CMD 0x1952C
#define TXGBE_RDB_FDIR_CMD_CMD_MASK GENMASK(1, 0)
#define TXGBE_RDB_FDIR_CMD_CMD(v) FIELD_PREP(GENMASK(1, 0), v)
#define TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW TXGBE_RDB_FDIR_CMD_CMD(1)
#define TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW TXGBE_RDB_FDIR_CMD_CMD(2)
#define TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT TXGBE_RDB_FDIR_CMD_CMD(3)
#define TXGBE_RDB_FDIR_CMD_FILTER_VALID BIT(2)
#define TXGBE_RDB_FDIR_CMD_FILTER_UPDATE BIT(3)
#define TXGBE_RDB_FDIR_CMD_FLOW_TYPE(v) FIELD_PREP(GENMASK(6, 5), v)
#define TXGBE_RDB_FDIR_CMD_DROP BIT(9)
#define TXGBE_RDB_FDIR_CMD_LAST BIT(11)
#define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15)
#define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v) FIELD_PREP(GENMASK(22, 16), v)
#define TXGBE_RDB_FDIR_CMD_VT_POOL(v) FIELD_PREP(GENMASK(29, 24), v)
#define TXGBE_RDB_FDIR_DA4_MSK 0x1953C
#define TXGBE_RDB_FDIR_SA4_MSK 0x19540
#define TXGBE_RDB_FDIR_TCP_MSK 0x19544
#define TXGBE_RDB_FDIR_UDP_MSK 0x19548
#define TXGBE_RDB_FDIR_SCTP_MSK 0x19560
#define TXGBE_RDB_FDIR_HKEY 0x19568
#define TXGBE_RDB_FDIR_SKEY 0x1956C
#define TXGBE_RDB_FDIR_OTHER_MSK 0x19570
#define TXGBE_RDB_FDIR_OTHER_MSK_POOL BIT(2)
#define TXGBE_RDB_FDIR_OTHER_MSK_L4P BIT(3)
#define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4))
#define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0)
#define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC FIELD_PREP(GENMASK(1, 0), 0)
#define TXGBE_RDB_FDIR_FLEX_CFG_MSK BIT(2)
#define TXGBE_RDB_FDIR_FLEX_CFG_OFST(v) FIELD_PREP(GENMASK(7, 3), v)
/*************************** Amber Lite Registers ****************************/
#define TXGBE_PX_PF_BME 0x4B8
#define TXGBE_AML_MAC_TX_CFG 0x11000
#define TXGBE_AML_MAC_TX_CFG_TE BIT(0)
#define TXGBE_AML_MAC_TX_CFG_SPEED_MASK GENMASK(30, 27)
#define TXGBE_AML_MAC_TX_CFG_SPEED_40G FIELD_PREP(GENMASK(30, 27), 0)
#define TXGBE_AML_MAC_TX_CFG_SPEED_25G FIELD_PREP(GENMASK(30, 27), 2)
#define TXGBE_AML_MAC_TX_CFG_SPEED_10G FIELD_PREP(GENMASK(30, 27), 8)
#define TXGBE_RDM_RSC_CTL 0x1200C
#define TXGBE_RDM_RSC_CTL_FREE_CTL BIT(7)
/* Checksum and EEPROM pointers */
#define TXGBE_EEPROM_LAST_WORD 0x800
#define TXGBE_EEPROM_CHECKSUM 0x2F
#define TXGBE_EEPROM_SUM 0xBABA
#define TXGBE_EEPROM_VERSION_L 0x1D
#define TXGBE_EEPROM_VERSION_H 0x1E
#define TXGBE_ISCSI_BOOT_CONFIG 0x07
#define TXGBE_EEPROM_I2C_SRART_PTR 0x580
#define TXGBE_EEPROM_I2C_END_PTR 0x800
#define TXGBE_MAX_MSIX_VECTORS 64
#define TXGBE_MAX_FDIR_INDICES 63
#define TXGBE_MAX_RSS_INDICES 63
#define TXGBE_MAX_RX_QUEUES (TXGBE_MAX_FDIR_INDICES + 1)
#define TXGBE_MAX_TX_QUEUES (TXGBE_MAX_FDIR_INDICES + 1)
#define TXGBE_MAX_TXQ 128
#define TXGBE_MAX_RXQ 128
#define TXGBE_RAR_ENTRIES 128
#define TXGBE_MC_TBL_SIZE 128
#define TXGBE_VFT_TBL_SIZE 128
#define TXGBE_RX_PB_SIZE 512
#define TXGBE_TDB_PB_SZ (160 * 1024) /* 160KB Packet Buffer */
#define TXGBE_MAX_VFS_DRV_LIMIT 63
#define TXGBE_DEFAULT_ATR_SAMPLE_RATE 20
/* Software ATR hash keys */
#define TXGBE_ATR_BUCKET_HASH_KEY 0x3DAD14E2
#define TXGBE_ATR_SIGNATURE_HASH_KEY 0x174D3614
/* Software ATR input stream values and masks */
#define TXGBE_ATR_HASH_MASK 0x7fff
#define TXGBE_ATR_L4TYPE_MASK 0x3
#define TXGBE_ATR_L4TYPE_UDP 0x1
#define TXGBE_ATR_L4TYPE_TCP 0x2
#define TXGBE_ATR_L4TYPE_SCTP 0x3
#define TXGBE_ATR_L4TYPE_IPV6_MASK 0x4
#define TXGBE_ATR_L4TYPE_TUNNEL_MASK 0x10
enum txgbe_atr_flow_type {
TXGBE_ATR_FLOW_TYPE_IPV4 = 0x0,
TXGBE_ATR_FLOW_TYPE_UDPV4 = 0x1,
TXGBE_ATR_FLOW_TYPE_TCPV4 = 0x2,
TXGBE_ATR_FLOW_TYPE_SCTPV4 = 0x3,
TXGBE_ATR_FLOW_TYPE_IPV6 = 0x4,
TXGBE_ATR_FLOW_TYPE_UDPV6 = 0x5,
TXGBE_ATR_FLOW_TYPE_TCPV6 = 0x6,
TXGBE_ATR_FLOW_TYPE_SCTPV6 = 0x7,
TXGBE_ATR_FLOW_TYPE_TUNNELED_IPV4 = 0x10,
TXGBE_ATR_FLOW_TYPE_TUNNELED_UDPV4 = 0x11,
TXGBE_ATR_FLOW_TYPE_TUNNELED_TCPV4 = 0x12,
TXGBE_ATR_FLOW_TYPE_TUNNELED_SCTPV4 = 0x13,
TXGBE_ATR_FLOW_TYPE_TUNNELED_IPV6 = 0x14,
TXGBE_ATR_FLOW_TYPE_TUNNELED_UDPV6 = 0x15,
TXGBE_ATR_FLOW_TYPE_TUNNELED_TCPV6 = 0x16,
TXGBE_ATR_FLOW_TYPE_TUNNELED_SCTPV6 = 0x17,
};
/* Flow Director ATR input struct. */
union txgbe_atr_input {
/* Byte layout in order, all values with MSB first:
*
* vm_pool - 1 byte
* flow_type - 1 byte
* vlan_id - 2 bytes
* dst_ip - 16 bytes
* src_ip - 16 bytes
* src_port - 2 bytes
* dst_port - 2 bytes
* flex_bytes - 2 bytes
* bkt_hash - 2 bytes
*/
struct {
u8 vm_pool;
u8 flow_type;
__be16 vlan_id;
__be32 dst_ip[4];
__be32 src_ip[4];
__be16 src_port;
__be16 dst_port;
__be16 flex_bytes;
__be16 bkt_hash;
} formatted;
__be32 dword_stream[11];
};
/* Flow Director compressed ATR hash input struct */
union txgbe_atr_hash_dword {
struct {
u8 vm_pool;
u8 flow_type;
__be16 vlan_id;
} formatted;
__be32 ip;
struct {
__be16 src;
__be16 dst;
} port;
__be16 flex_bytes;
__be32 dword;
};
enum txgbe_fdir_pballoc_type {
TXGBE_FDIR_PBALLOC_NONE = 0,
TXGBE_FDIR_PBALLOC_64K = 1,
TXGBE_FDIR_PBALLOC_128K = 2,
TXGBE_FDIR_PBALLOC_256K = 3,
};
struct txgbe_fdir_filter {
struct hlist_node fdir_node;
union txgbe_atr_input filter;
u16 sw_idx;
u64 action;
};
/* TX/RX descriptor defines */
#define TXGBE_DEFAULT_TXD 512
#define TXGBE_DEFAULT_TX_WORK 256
#if (PAGE_SIZE < 8192)
#define TXGBE_DEFAULT_RXD 512
#define TXGBE_DEFAULT_RX_WORK 256
#else
#define TXGBE_DEFAULT_RXD 256
#define TXGBE_DEFAULT_RX_WORK 128
#endif
#define TXGBE_INTR_MISC(A) BIT((A)->num_q_vectors)
#define TXGBE_INTR_QALL(A) (TXGBE_INTR_MISC(A) - 1)
#define TXGBE_MAX_EITR GENMASK(11, 3)
extern char txgbe_driver_name[];
void txgbe_down(struct wx *wx);
void txgbe_up(struct wx *wx);
int txgbe_setup_tc(struct net_device *dev, u8 tc);
void txgbe_do_reset(struct net_device *netdev);
#define DECLARE_PHY_INTERFACE_MASK_ZERO(name) \
unsigned long name[PHY_INTERFACE_MODE_MAX] = { 0, }
#define TXGBE_LINK_SPEED_UNKNOWN 0
#define TXGBE_LINK_SPEED_10GB_FULL 4
#define TXGBE_LINK_SPEED_25GB_FULL 0x10
#define TXGBE_LINK_SPEED_40GB_FULL 0x20
#define TXGBE_SFF_IDENTIFIER_SFP 0x3
#define TXGBE_SFF_IDENTIFIER_QSFP 0xC
#define TXGBE_SFF_IDENTIFIER_QSFP_PLUS 0xD
#define TXGBE_SFF_IDENTIFIER_QSFP28 0x11
#define TXGBE_SFF_DA_PASSIVE_CABLE 0x4
#define TXGBE_SFF_DA_ACTIVE_CABLE 0x8
#define TXGBE_SFF_DA_SPEC_ACTIVE_LIMIT 0x4
#define TXGBE_SFF_FCPI4_LIMITING 0x3
#define TXGBE_SFF_10GBASESR_CAPABLE 0x10
#define TXGBE_SFF_10GBASELR_CAPABLE 0x20
#define TXGBE_SFF_25GBASESR_CAPABLE 0x2
#define TXGBE_SFF_25GBASELR_CAPABLE 0x3
#define TXGBE_SFF_25GBASEER_CAPABLE 0x4
#define TXGBE_SFF_25GBASECR_91FEC 0xB
#define TXGBE_SFF_25GBASECR_74FEC 0xC
#define TXGBE_SFF_25GBASECR_NOFEC 0xD
#define TXGBE_SFF_ETHERNET_RSRVD BIT(7)
#define TXGBE_SFF_ETHERNET_40G_CR4 BIT(3)
#define TXGBE_SFF_ETHERNET_40G_SR4 BIT(2)
#define TXGBE_SFF_ETHERNET_40G_LR4 BIT(1)
#define TXGBE_SFF_ETHERNET_40G_ACTIVE BIT(0)
#define TXGBE_SFF_ETHERNET_100G_CR4 0xB
#define TXGBE_PHY_FEC_RS BIT(0)
#define TXGBE_PHY_FEC_BASER BIT(1)
#define TXGBE_PHY_FEC_OFF BIT(2)
#define TXGBE_PHY_FEC_AUTO (TXGBE_PHY_FEC_OFF | \
TXGBE_PHY_FEC_BASER |\
TXGBE_PHY_FEC_RS)
#define FW_PHY_GET_LINK_CMD 0xC0
#define FW_PHY_SET_LINK_CMD 0xC1
#define FW_GET_MODULE_INFO_CMD 0xC5
#define FW_READ_EEPROM_CMD 0xC6
struct txgbe_sff_id {
u8 identifier; /* A0H 0x00 */
u8 com_1g_code; /* A0H 0x06 */
u8 com_10g_code; /* A0H 0x03 */
u8 com_25g_code; /* A0H 0x24 */
u8 cable_spec; /* A0H 0x3C */
u8 cable_tech; /* A0H 0x08 */
u8 vendor_oui0; /* A0H 0x25 */
u8 vendor_oui1; /* A0H 0x26 */
u8 vendor_oui2; /* A0H 0x27 */
u8 transceiver_type; /* A0H 0x83 */
u8 sff_opt1; /* A0H 0xC0 */
u8 reserved[5];
};
struct txgbe_hic_get_module_info {
struct wx_hic_hdr hdr;
struct txgbe_sff_id id;
};
struct txgbe_hic_ephy_setlink {
struct wx_hic_hdr hdr;
u8 speed;
u8 duplex;
u8 autoneg;
u8 fec_mode;
u8 resv[4];
};
struct txgbe_hic_ephy_getlink {
struct wx_hic_hdr hdr;
u8 speed;
u8 duplex;
u8 autoneg;
u8 flow_ctl;
u8 power;
u8 fec_mode;
u8 resv[6];
};
struct txgbe_hic_i2c_read {
struct wx_hic_hdr hdr;
__be32 offset;
__be32 length;
u8 page;
u8 bank;
u8 i2c_address;
u8 resv;
};
#define NODE_PROP(_NAME, _PROP) \
(const struct software_node) { \
.name = _NAME, \
.properties = _PROP, \
}
enum txgbe_swnodes {
SWNODE_GPIO = 0,
SWNODE_I2C,
SWNODE_SFP,
SWNODE_PHYLINK,
SWNODE_MAX
};
struct txgbe_nodes {
char gpio_name[32];
char i2c_name[32];
char sfp_name[32];
char phylink_name[32];
struct property_entry gpio_props[2];
struct property_entry i2c_props[4];
struct property_entry sfp_props[9];
struct property_entry phylink_props[3];
struct software_node_ref_args i2c_ref[1];
struct software_node_ref_args gpio0_ref[1];
struct software_node_ref_args gpio1_ref[1];
struct software_node_ref_args gpio2_ref[1];
struct software_node_ref_args gpio3_ref[1];
struct software_node_ref_args gpio4_ref[1];
struct software_node_ref_args gpio5_ref[1];
struct software_node_ref_args sfp_ref[1];
struct software_node swnodes[SWNODE_MAX];
const struct software_node *group[SWNODE_MAX + 1];
};
enum txgbe_misc_irqs {
TXGBE_IRQ_LINK = 0,
TXGBE_IRQ_GPIO,
TXGBE_IRQ_MAX
};
struct txgbe_irq {
struct irq_chip chip;
struct irq_domain *domain;
int nirqs;
int irq;
};
struct txgbe {
struct wx *wx;
struct txgbe_nodes nodes;
struct txgbe_irq misc;
struct phylink_pcs *pcs;
struct platform_device *sfp_dev;
struct platform_device *i2c_dev;
struct clk_lookup *clock;
struct clk *clk;
struct gpio_chip *gpio;
unsigned int link_irq;
unsigned int gpio_irq;
u32 eicr;
/* flow director */
struct hlist_head fdir_filter_list;
union txgbe_atr_input fdir_mask;
int fdir_filter_count;
spinlock_t fdir_perfect_lock; /* spinlock for FDIR */
DECLARE_PHY_INTERFACE_MASK(link_interfaces);
__ETHTOOL_DECLARE_LINK_MODE_MASK(link_support);
__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
u8 link_port;
};
#endif /* _TXGBE_TYPE_H_ */
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd.
#
# Makefile for the Wangxun(R) 10/25/40GbE virtual functions driver
#
obj-$(CONFIG_TXGBE) += txgbevf.o
txgbevf-objs := txgbevf_main.o
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#include <linux/types.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/etherdevice.h>
#include "../libwx/wx_type.h"
#include "../libwx/wx_hw.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_mbx.h"
#include "../libwx/wx_vf.h"
#include "../libwx/wx_vf_common.h"
#include "../libwx/wx_ethtool.h"
#include "txgbevf_type.h"
/* txgbevf_pci_tbl - PCI Device ID Table
*
* Wildcard entries (PCI_ANY_ID) should come last
* Last entry must be all 0s
*
* { Vendor ID, Device ID, SubVendor ID, SubDevice ID,
* Class, Class Mask, private data (not used) }
*/
static const struct pci_device_id txgbevf_pci_tbl[] = {
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_SP1000) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_WX1820) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML500F) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML510F) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML5024) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML5124) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML503F) },
{ PCI_VDEVICE(WANGXUN, TXGBEVF_DEV_ID_AML513F) },
/* required last entry */
{ }
};
static const struct net_device_ops txgbevf_netdev_ops = {
.ndo_open = wxvf_open,
.ndo_stop = wxvf_close,
.ndo_start_xmit = wx_xmit_frame,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = wx_set_mac_vf,
};
static void txgbevf_set_num_queues(struct wx *wx)
{
u32 def_q = 0, num_tcs = 0;
u16 rss, queue;
int ret = 0;
/* Start with base case */
wx->num_rx_queues = 1;
wx->num_tx_queues = 1;
spin_lock_bh(&wx->mbx.mbx_lock);
/* fetch queue configuration from the PF */
ret = wx_get_queues_vf(wx, &num_tcs, &def_q);
spin_unlock_bh(&wx->mbx.mbx_lock);
if (ret)
return;
/* we need as many queues as traffic classes */
if (num_tcs > 1) {
wx->num_rx_queues = num_tcs;
} else {
rss = min_t(u16, num_online_cpus(), TXGBEVF_MAX_RSS_NUM);
queue = min_t(u16, wx->mac.max_rx_queues, wx->mac.max_tx_queues);
rss = min_t(u16, queue, rss);
if (wx->vfinfo->vf_api >= wx_mbox_api_13) {
wx->num_rx_queues = rss;
wx->num_tx_queues = rss;
}
}
}
static void txgbevf_init_type_code(struct wx *wx)
{
switch (wx->device_id) {
case TXGBEVF_DEV_ID_SP1000:
case TXGBEVF_DEV_ID_WX1820:
wx->mac.type = wx_mac_sp;
break;
case TXGBEVF_DEV_ID_AML500F:
case TXGBEVF_DEV_ID_AML510F:
case TXGBEVF_DEV_ID_AML5024:
case TXGBEVF_DEV_ID_AML5124:
case TXGBEVF_DEV_ID_AML503F:
case TXGBEVF_DEV_ID_AML513F:
wx->mac.type = wx_mac_aml;
break;
default:
wx->mac.type = wx_mac_unknown;
break;
}
}
static int txgbevf_sw_init(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
struct pci_dev *pdev = wx->pdev;
int err;
/* Initialize pcie info and common capability flags */
err = wx_sw_init(wx);
if (err < 0)
goto err_wx_sw_init;
/* Initialize the mailbox */
err = wx_init_mbx_params_vf(wx);
if (err)
goto err_init_mbx_params;
/* max q_vectors */
wx->mac.max_msix_vectors = TXGBEVF_MAX_MSIX_VECTORS;
/* Initialize the device type */
txgbevf_init_type_code(wx);
/* lock to protect mailbox accesses */
spin_lock_init(&wx->mbx.mbx_lock);
err = wx_reset_hw_vf(wx);
if (err) {
wx_err(wx, "PF still in reset state. Is the PF interface up?\n");
goto err_reset_hw;
}
wx_init_hw_vf(wx);
wx_negotiate_api_vf(wx);
if (is_zero_ether_addr(wx->mac.addr))
dev_info(&pdev->dev,
"MAC address not assigned by administrator.\n");
eth_hw_addr_set(netdev, wx->mac.addr);
if (!is_valid_ether_addr(netdev->dev_addr)) {
dev_info(&pdev->dev, "Assigning random MAC address\n");
eth_hw_addr_random(netdev);
ether_addr_copy(wx->mac.addr, netdev->dev_addr);
ether_addr_copy(wx->mac.perm_addr, netdev->dev_addr);
}
wx->mac.max_tx_queues = TXGBEVF_MAX_TX_QUEUES;
wx->mac.max_rx_queues = TXGBEVF_MAX_RX_QUEUES;
/* Enable dynamic interrupt throttling rates */
wx->adaptive_itr = true;
wx->rx_itr_setting = 1;
wx->tx_itr_setting = 1;
/* set default ring sizes */
wx->tx_ring_count = TXGBEVF_DEFAULT_TXD;
wx->rx_ring_count = TXGBEVF_DEFAULT_RXD;
/* set default work limits */
wx->tx_work_limit = TXGBEVF_DEFAULT_TX_WORK;
wx->rx_work_limit = TXGBEVF_DEFAULT_RX_WORK;
wx->set_num_queues = txgbevf_set_num_queues;
switch (wx->mac.type) {
case wx_mac_sp:
break;
case wx_mac_aml:
case wx_mac_aml40:
set_bit(WX_FLAG_RX_MERGE_ENABLED, wx->flags);
set_bit(WX_FLAG_TXHEAD_WB_ENABLED, wx->flags);
break;
default:
break;
}
return 0;
err_reset_hw:
kfree(wx->vfinfo);
err_init_mbx_params:
kfree(wx->rss_key);
kfree(wx->mac_table);
err_wx_sw_init:
return err;
}
/**
* txgbevf_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in txgbevf_pci_tbl
*
* Return: return 0 on success, negative on failure
*
* txgbevf_probe initializes an adapter identified by a pci_dev structure.
* The OS initialization, configuring of the adapter private structure,
* and a hardware reset occur.
**/
static int txgbevf_probe(struct pci_dev *pdev,
const struct pci_device_id __always_unused *ent)
{
struct net_device *netdev;
struct wx *wx = NULL;
int err;
err = pci_enable_device_mem(pdev);
if (err)
return err;
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(&pdev->dev,
"No usable DMA configuration, aborting\n");
goto err_pci_disable_dev;
}
err = pci_request_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM),
dev_driver_string(&pdev->dev));
if (err) {
dev_err(&pdev->dev,
"pci_request_selected_regions failed 0x%x\n", err);
goto err_pci_disable_dev;
}
pci_set_master(pdev);
netdev = devm_alloc_etherdev_mqs(&pdev->dev,
sizeof(struct wx),
TXGBEVF_MAX_TX_QUEUES,
TXGBEVF_MAX_RX_QUEUES);
if (!netdev) {
err = -ENOMEM;
goto err_pci_release_regions;
}
SET_NETDEV_DEV(netdev, &pdev->dev);
wx = netdev_priv(netdev);
wx->netdev = netdev;
wx->pdev = pdev;
wx->msg_enable = netif_msg_init(-1, NETIF_MSG_DRV |
NETIF_MSG_PROBE | NETIF_MSG_LINK);
wx->hw_addr = devm_ioremap(&pdev->dev,
pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (!wx->hw_addr) {
err = -EIO;
goto err_pci_release_regions;
}
wx->b4_addr = devm_ioremap(&pdev->dev,
pci_resource_start(pdev, 4),
pci_resource_len(pdev, 4));
if (!wx->b4_addr) {
err = -EIO;
goto err_pci_release_regions;
}
wx->driver_name = KBUILD_MODNAME;
wx_set_ethtool_ops_vf(netdev);
netdev->netdev_ops = &txgbevf_netdev_ops;
/* setup the private structure */
err = txgbevf_sw_init(wx);
if (err)
goto err_pci_release_regions;
netdev->features |= NETIF_F_HIGHDMA;
eth_hw_addr_set(netdev, wx->mac.perm_addr);
ether_addr_copy(netdev->perm_addr, wx->mac.addr);
wxvf_init_service(wx);
err = wx_init_interrupt_scheme(wx);
if (err)
goto err_free_sw_init;
wx_get_fw_version_vf(wx);
err = register_netdev(netdev);
if (err)
goto err_register;
pci_set_drvdata(pdev, wx);
netif_tx_stop_all_queues(netdev);
return 0;
err_register:
wx_clear_interrupt_scheme(wx);
err_free_sw_init:
timer_delete_sync(&wx->service_timer);
cancel_work_sync(&wx->service_task);
kfree(wx->vfinfo);
kfree(wx->rss_key);
kfree(wx->mac_table);
err_pci_release_regions:
pci_release_selected_regions(pdev,
pci_select_bars(pdev, IORESOURCE_MEM));
err_pci_disable_dev:
pci_disable_device(pdev);
return err;
}
/**
* txgbevf_remove - Device Removal Routine
* @pdev: PCI device information struct
*
* txgbevf_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device. The could be caused by a
* Hot-Plug event, or because the driver is going to be removed from
* memory.
**/
static void txgbevf_remove(struct pci_dev *pdev)
{
wxvf_remove(pdev);
}
static DEFINE_SIMPLE_DEV_PM_OPS(txgbevf_pm_ops, wxvf_suspend, wxvf_resume);
static struct pci_driver txgbevf_driver = {
.name = KBUILD_MODNAME,
.id_table = txgbevf_pci_tbl,
.probe = txgbevf_probe,
.remove = txgbevf_remove,
.shutdown = wxvf_shutdown,
/* Power Management Hooks */
.driver.pm = pm_sleep_ptr(&txgbevf_pm_ops)
};
module_pci_driver(txgbevf_driver);
MODULE_DEVICE_TABLE(pci, txgbevf_pci_tbl);
MODULE_AUTHOR("Beijing WangXun Technology Co., Ltd, <software@trustnetic.com>");
MODULE_DESCRIPTION("WangXun(R) 10/25/40 Gigabit Virtual Function Network Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */
#ifndef _TXGBEVF_TYPE_H_
#define _TXGBEVF_TYPE_H_
/* Device IDs */
#define TXGBEVF_DEV_ID_SP1000 0x1000
#define TXGBEVF_DEV_ID_WX1820 0x2000
#define TXGBEVF_DEV_ID_AML500F 0x500F
#define TXGBEVF_DEV_ID_AML510F 0x510F
#define TXGBEVF_DEV_ID_AML5024 0x5024
#define TXGBEVF_DEV_ID_AML5124 0x5124
#define TXGBEVF_DEV_ID_AML503F 0x503f
#define TXGBEVF_DEV_ID_AML513F 0x513f
#define TXGBEVF_MAX_MSIX_VECTORS 2
#define TXGBEVF_MAX_RSS_NUM 4
#define TXGBEVF_MAX_RX_QUEUES 4
#define TXGBEVF_MAX_TX_QUEUES 4
#define TXGBEVF_DEFAULT_TXD 128
#define TXGBEVF_DEFAULT_RXD 128
#define TXGBEVF_DEFAULT_TX_WORK 256
#define TXGBEVF_DEFAULT_RX_WORK 256
#endif /* _TXGBEVF_TYPE_H_ */