* [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips
@ 2026-10-08 3:51 Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt Birger Koblitz
` (12 more replies)
0 siblings, 13 replies; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
This adds support for the current generation of ASIX network adapter chips,
which are based on the AX88179A. This includes the AX88179A/B (1GBit-PHY),
AX88772D/E (100MBit) and AX88279 (2.5GBit).
The AX179A-based chips all provide both a CDC-NCM compatible USB interface,
and a proprietary vendor interface with more features. By default, the
proprietary vendor interface is not active and Linux will load the CDC-NCM
driver to support the devices. If the ax88179_178a module is configured by
the OS to have precedence over CDC-NCM, then this driver will switch the
device to use the vendor interface, and the device will be controlled by
the ax88179_178a driver when the device is probed again after an automatic
reset of the device bringing up the vendor interface.
The following hardware was tested:
Delock 66046 2.5GBit adapter (AX88279, FW: 1.2.0.0)
TP-Link UE306 1GBit adapter (AX88179B, FW: 1.3.0.0)
Renkforce RF-4708614 1GBit adapter (AX88179A, FW: 1.0.4.0)
UGREEN CR110 100MBit adapter (AX88722E, FW: 1.3.0.0)
The driver supports the following features
- EEE
- TCP segmentation offload
- VLAN filtering/tagging offload
(NETIF_F_HW_VLAN_CTAG_FILTER, NETIF_F_HW_VLAN_CTAG_RX/TX)
- RX/TX checksum offload
- FC/Pause configuration
- EEPROM read access
The code is based on the ASIX 4.1.0 out-of-tree driver published under
the GPL,, the aqc111 driver which provides support for the AX88279A,
and some tracing of USB-transfers of the Windows-driver.
Tested-by: Jianhui Xu <neuromoments@gmail.com>
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
Changes in v18:
- Add also check for PHY_BROKEN_FORCED in phy_mii_ioctl() to prevent
users to circumvent check via ioctl.
- Do not unconditionally dereference phydev->drv when it might be NULL
in phy_ethtool_ksettings_set()
- Fix RTNL deadlock accessing mtu-settings by using dedicated lock for mtu access
- Use get_unaligned_le64() instead of le64_to_cpup() to
support older architectures
- Add check for skb_cloned() before buffer resize
- Add explicit handling of disabling of NETIF_F_HW_VLAN_CTAG_FILTER
- Fix clobbering of AX_VLAN_CONTROL_VFE bit when setting AX_VLAN_CONTROL_WE
- Do no longer use XOR to change AX_VLAN_CONTROL_VFE by conditional clear/set
of bit in if/else block to prevent theoretical de-synching of bit and OS state
- Make sure interface is enabled when resuming phylink in suspend error-path
- Fix leaking in_pm / unbalanced suspend_count in suspend error-path
- Add pm_runtime_put_noidle() to unbind()
- Reorder setting MII_ADVERTISE and genphy_config_aneg()
- Remove possible 1000M-bits reported by AX88772D when reading MII_ESTATUS
- Link to v17: https://lore.kernel.org/r/20261006-ax88179a-v17-0-252814fe35ea@birger-koblitz.de
Changes in v17:
- Fix clang warnings from kernel-robot
- Link to v16: https://lore.kernel.org/r/20261004-ax88179a-v16-0-2a9ae4e68f7b@birger-koblitz.de
Changes in v16:
- Fix error-checking in VLAN-offload code
as Suggested-by: Jianhui Xu <neuromoments@gmail.com>
- Link to v15: https://lore.kernel.org/r/20261002-ax88179a-v15-0-131d9286fcd7@birger-koblitz.de
Changes in v15:
- Removed already committed patch fixing pause watermark register
endianess
- Rebased
- Link to v14: https://lore.kernel.org/r/20260929-ax88179a-v14-0-a1f2249e42ea@birger-koblitz.de
Changes in v14:
- Fix return value handling in VLAN offload functions
- Add explanatory comment for phylink_mac_interrupt()
- Link to v13: https://lore.kernel.org/r/20260927-ax88179a-v13-0-4fac6b3e032e@birger-koblitz.de
Changes in v13:
- Add MODULE_ALIAS to ax88179_178a
- Squashed commit adding netdev2data() into the first one needing it
- Add wrappers ax88179_write_u8/ax88179_write_u16
- Use FIELD_GET() FIELD_PREP() in fixups.
- Add guard for phydev->drv NULL in phy_ethtool_ksettings_set()
- Various endianess fixes in fixups
- Add error checking for too small packets in ax88179a_rx_fixup
- Typo fixed: ax88179_read_cmd -> ax88179_write_cmd setting PHY power
- Changed call to usbnet_nway_reset by phy_ethtool_nway_reset
- ax88179a_bulkin_config() handles FS USB now for all chip versions
- Use phylink versions of calls for get/set_link_ksettings
- Add handling of return values of ax88179_read_cmd() in various places
- Removed usbnet_link_change(dev, 0, 0); at the end of ax88179a_reset().
- Add RTLN-lock to protect AX_MEDIUM_STATUS_MODE register,
WRITE_ONCE/READONCE for rxctl.
- Changed to clearing AX_XGMII_EN for all speeds != SPEED_2500 independent
of chip-id.
- Fix access to AX88179A_MAC_RX_DATA_CDC_CNT by using 2 1-byte writes.
- In ax88179a_mac_link_up initialize link_status.
- Added setting of AX_GPHY_EEE_CTRL to disabled in ax88179a_reset(),
consistent with phylink default.
- Various commit messages fixed
- Conditionally set AX_VLAN_CONTROL_VFE after loop over the VLAN-entries
- Moved code in wrong commits to the right ones (2x).
- Fix races around rx_checksum flag setting.
- Suspend/resume fixes: correct sequence of WoL settings and usbnet/phylink
suspend calls.
- Read revision number into the 4th field of software version
- Kconfig descriptiona and module description mention 2.5GBit
- Fix orphaned defconfigs
- Fix EEPROM access settings derived from chip versions
- Fix EEPROM read error checking
- Return value checking in asix_ax88279_read_status()
- Added clearing of ETHTOOL_LINK_MODE_2500baseT_Full_BIT in lp_advertising
before modifying the bit.
- In asix_ax88279_read_status() add if (!phydev->link) guard
- Linkmode cleaning code shortened.
- Link to v12: https://lore.kernel.org/r/20260916-ax88179a-v12-0-60c04c9924a2@birger-koblitz.de
Changes in v12:
- Fix BCD ranges for USB device detection
- Free SKB in ax88179a_tx_fixup() when skb_linearize() fails
- Use phylink_mii_ioctl instead of usbnet_mii_ioctl
- Link to v11: https://lore.kernel.org/r/20260914-ax88179a-v11-0-5ea7a925ba6d@birger-koblitz.de
Changes in v11:
- Fix sparse warnings
- Link to v10: https://lore.kernel.org/r/20260904-ax88179a-v10-0-b5e60eca7690@birger-koblitz.de
Changes in v10:
- Fix missing variable declaration in ax88179a_status()
- Add reviewed-bys
- Link to v9: https://lore.kernel.org/r/20260902-ax88179a-v9-0-8e6d7710a2ae@birger-koblitz.de
Changes in v9:
- Fix ax88179a_status() to not call carrier-related netif or usbnet methods
- Use genphy_read_mmd_c45() and genphy_write_mmd_c45()
- Link to v8: https://lore.kernel.org/r/20260831-ax88179a-v8-0-de905f2ee426@birger-koblitz.de
Changes in v8:
- Fix resume sequence as Suggested-by: Jianhui Xu <neuromoments@gmail.com>
- Add handling of PHYs with broken forced mode in phylib to handle AX88279 PHY
- Multple formatting fixes
- Fix transiently unused functions ax88179a_mac_disable_tx_lpi() and
ax88179a_mac_enable_tx_lpi()
- Removed unnecessary checks for dev initialized
- Link to v7: https://lore.kernel.org/r/20260810-ax88179a-v7-0-da633191c129@birger-koblitz.de
Changes in v7:
- Add phylink_mac_interrupt() function to phylink and stop
polling the PHY to prevent a race condition between the controller
and phylink when configuring a new link.
- Fix typo in ax88179_read_cmd Reported-by: Jianhui Xu <neuromoments@gmail.com>
- Return -EOPNOTSUPP in asix_ax88279_config_aneg when autoneg is being disabled
- Link to v6: https://lore.kernel.org/r/20260806-ax88179a-v6-0-fde7414619e6@birger-koblitz.de
Changes in v6:
- Use genphy_read_status() in asix_ax88279_read_status()
- Use SGMII and 2500BaseX interfaces for PHY
- Fix speed determination of MAC/PHY link
- Fix bulk transfer configuration to use enums for bulk configuration types
- Link to v5: https://lore.kernel.org/r/20260802-ax88179a-v5-0-dcb9fea4acd4@birger-koblitz.de
Changes in v5:
- Fixed read_status() and config_aneg() in PHY driver
- Introduced netdev2data() as convenience function
- netdev_info instances and debugging relicts removed
- Several instances of badly written/formatted code that was copy and pasted from
the original driver corrected
- select PHYLINK added to Kconfig when phylib dependency added
- PHYLINK selects PHYLIB, so not needed to specify separately
- Link to v4: https://lore.kernel.org/r/20260731-ax88179a-v4-0-2cf1f71b1dd2@birger-koblitz.de
Changes in v4:
- Split driver into library part and part2 for AX88179 and AX88179A-based
controllers
- Driver renamed ax88179
- Improved phylink use: use phylink standard functions for speed and EEE-settings,
correct MAC capabilities, removed ax88179_status() irq-urb callback
- Fixes in PHY driver for AX88179A integrated PHYs
- Link to v3: https://lore.kernel.org/r/20260724-ax88179a-v3-0-bdde4f905883@birger-koblitz.de
Changes in v3:
- Add PHY drivers for the PHYs of the AX88179A-based controllers
- Use phylink for the AX88179A-based chips
- Link to v2: https://lore.kernel.org/r/20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de
Changes in v2:
- Correctly use net-next prefix
- Fix compilation issue in HW support patch
- Split MMD support patch into patches for EEE/new chip support
- Do not use ADVERTISE_RESV but private flag definition
- Fix pause configuration to keep track of settings when autoneg disabled
- Fix issue with unitialized variable reported by kernel test robot <lkp@intel.com>
- Avoid white-space changes
- Link to v1: https://lore.kernel.org/r/20260701-ax88179a-v1-0-13685df67515@birger-koblitz.de
---
Birger Koblitz (13):
phylink: Add phylink_mac_interrupt
phylib: Add support for PHYs with broken forced mode
ax88179_178a: Split driver into library and device specific code
ax88179_178a: Add HW support for AX179A-based chips
ax88179_178a: Add EEE configuration support for AX88179A MACs
ax88179_178a: Add EEE configuration support for AX88179A PHYs
ax88179_178a: Add VLAN offload support for AX88179A
ax88179_178a: Add AX179A/AX279 multicast configuration
ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
ax88179_178a: Add ethtool get_drvinfo
ax88179_178a: Update driver name and information
ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
ax88796b: Add support for AX88772D, AX88179A and AX88279
MAINTAINERS | 6 +
arch/loongarch/configs/loongson32_defconfig | 2 +-
arch/loongarch/configs/loongson64_defconfig | 2 +-
drivers/net/phy/ax88796b.c | 165 ++++
drivers/net/phy/phy.c | 7 +
drivers/net/phy/phylink.c | 20 +
drivers/net/usb/Kconfig | 10 +-
drivers/net/usb/Makefile | 3 +-
drivers/net/usb/ax88179_178a.c | 661 +-------------
drivers/net/usb/ax88179_lib.c | 544 +++++++++++
drivers/net/usb/ax88179_lib.h | 360 ++++++++
drivers/net/usb/ax88179a_devices.c | 1298 +++++++++++++++++++++++++++
include/linux/phy.h | 1 +
include/linux/phylink.h | 1 +
14 files changed, 2457 insertions(+), 623 deletions(-)
---
base-commit: 8df0638138d3e0344fd1fb36cf2d1ca1cf5028f0
change-id: 20260630-ax88179a-a1d89fe21730
Best regards,
--
Birger Koblitz <mail@birger-koblitz.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode Birger Koblitz
` (11 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Add the phylink_mac_interrupt() function to phylink, which
passes a link-change interrupt received by the MAC to phylib.
This function can be used by drivers which want to avoid
polling the PHY and instead rely on an interrupt received by
the MAC when a link-change happens.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/phy/phylink.c | 20 ++++++++++++++++++++
include/linux/phylink.h | 1 +
2 files changed, 21 insertions(+)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index a7d086cdc9b2516571bbcfbc3bf3fb37fc4bf6f4..a3ca7993eb7e0090941843194ca1b924cf8d9e89 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1612,6 +1612,26 @@ static bool phylink_link_is_up(struct phylink *pl)
return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state;
}
+/**
+ * phylink_mac_interrupt() - wrapper for phy_mac_interrupt()
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ *
+ * Passes a link-change interrupt received by the MAC to phylib.
+ * This function may be called in interrupt context, and therefore cannot take
+ * a mutex. However, it is safe to assume that the reference to phy does not
+ * disappear during the call, as only an SFP module may be unplugged to make a
+ * PHY disappear. SFP modules however do not support interrupts.
+ */
+void phylink_mac_interrupt(struct phylink *pl)
+{
+ struct phy_device *phy;
+
+ phy = pl->phydev;
+ if (phy)
+ phy_mac_interrupt(phy);
+}
+EXPORT_SYMBOL_GPL(phylink_mac_interrupt);
+
static void phylink_resolve(struct work_struct *w)
{
struct phylink *pl = container_of(w, struct phylink, resolve);
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 3a88a69882a610afb868b55df6f6e77aea918649..a7b6294cc4a075bd2aaa43ed6484de17a6cb88b1 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -768,6 +768,7 @@ int phylink_ethtool_set_eee(struct phylink *link, struct ethtool_keee *eee);
int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
int phylink_speed_down(struct phylink *pl, bool sync);
int phylink_speed_up(struct phylink *pl);
+void phylink_mac_interrupt(struct phylink *pl);
#define phylink_zero(bm) \
bitmap_zero(bm, __ETHTOOL_LINK_MODE_MASK_NBITS)
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code Birger Koblitz
` (10 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Add support for PHYs which only supported auto-negotiated
speed modes. This prevents a kernel warning and stack-trace within the
phy_state_machine when PHYs return -EOPNOTSUPP from config_aneg()
because they do not support forced modes.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/phy/phy.c | 7 +++++++
include/linux/phy.h | 1 +
2 files changed, 8 insertions(+)
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index fce9bc7be3304b408c9f3ec9b5b95c76d3680a88..079c53516f424ca5f8d50292f1912af04b45b07a 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -354,6 +354,9 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
switch (devad) {
case MII_BMCR:
if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) {
+ if (phydev->drv &&
+ (phydev->drv->flags & PHY_BROKEN_FORCED))
+ return -EOPNOTSUPP;
if (phydev->autoneg == AUTONEG_ENABLE)
change_autoneg = true;
phydev->autoneg = AUTONEG_DISABLE;
@@ -1190,6 +1193,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
duplex != DUPLEX_FULL)))
return -EINVAL;
+ if (autoneg == AUTONEG_DISABLE && phydev->drv &&
+ (phydev->drv->flags & PHY_BROKEN_FORCED))
+ return -EOPNOTSUPP;
+
mutex_lock(&phydev->lock);
phydev->autoneg = autoneg;
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 7c5098a0dd6cd1ffbfc5f55240cd68e41bfb2d73..4143bffdcdb9b293a6e717a41816c05a2c5a252b 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -64,6 +64,7 @@ extern const int phy_basic_ports_array[3];
#define PHY_RST_AFTER_CLK_EN 0x00000002
#define PHY_POLL_CABLE_TEST 0x00000004
#define PHY_ALWAYS_CALL_SUSPEND 0x00000008
+#define PHY_BROKEN_FORCED 0x00000010
#define MDIO_DEVICE_IS_PHY 0x80000000
/**
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
` (9 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Split the ax88179_178a module code into code common to the
AX88179/178a/179a and 279 family of controllers and device
specific code for the currently supported devices based
on the AX88179 and AX88178a.
Rename the module to ax88179 to reflect the broader scope of
controllers supported by the module and add an alias to the old
module name:
MODULE_ALIAS("ax88179_178a");
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/usb/Makefile | 3 +-
drivers/net/usb/ax88179_178a.c | 613 +----------------------------------------
drivers/net/usb/ax88179_lib.c | 461 +++++++++++++++++++++++++++++++
drivers/net/usb/ax88179_lib.h | 195 +++++++++++++
4 files changed, 661 insertions(+), 611 deletions(-)
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 4964f7b326fbcedffc1e5a1bf004aea95560db8c..ddd76fa71e2ee670888df1c9715632e5c04a8149 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -12,7 +12,8 @@ obj-$(CONFIG_USB_HSO) += hso.o
obj-$(CONFIG_USB_LAN78XX) += lan78xx.o
obj-$(CONFIG_USB_NET_AX8817X) += asix.o
asix-y := asix_devices.o asix_common.o ax88172a.o
-obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179_178a.o
+obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179.o
+ax88179-y := ax88179_178a.o ax88179_lib.o
obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o
obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o
obj-$(CONFIG_USB_NET_DM9601) += dm9601.o
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 2ed46ab62c0065d32040439f31d44f118d7e9952..85abccaaa5c67ec43687f5c247efada66411b5e2 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -8,179 +8,12 @@
#include <linux/module.h>
#include <linux/etherdevice.h>
#include <linux/mii.h>
-#include <linux/usb.h>
-#include <linux/crc32.h>
-#include <linux/usb/usbnet.h>
#include <uapi/linux/mdio.h>
#include <linux/mdio.h>
-
-#define AX88179_PHY_ID 0x03
-#define AX_EEPROM_LEN 0x100
-#define AX88179_EEPROM_MAGIC 0x17900b95
-#define AX_MCAST_FLTSIZE 8
-#define AX_MAX_MCAST 64
-#define AX_INT_PPLS_LINK ((u32)BIT(16))
-#define AX_RXHDR_L4_TYPE_MASK 0x1c
-#define AX_RXHDR_L4_TYPE_UDP 4
-#define AX_RXHDR_L4_TYPE_TCP 16
-#define AX_RXHDR_L3CSUM_ERR 2
-#define AX_RXHDR_L4CSUM_ERR 1
-#define AX_RXHDR_CRC_ERR ((u32)BIT(29))
-#define AX_RXHDR_DROP_ERR ((u32)BIT(31))
-#define AX_ACCESS_MAC 0x01
-#define AX_ACCESS_PHY 0x02
-#define AX_ACCESS_EEPROM 0x04
-#define AX_ACCESS_EFUS 0x05
-#define AX_RELOAD_EEPROM_EFUSE 0x06
-#define AX_PAUSE_WATERLVL_LOW 0x54
-#define AX_PAUSE_WATERLVL_HIGH 0x55
-
-#define PHYSICAL_LINK_STATUS 0x02
- #define AX_USB_SS 0x04
- #define AX_USB_HS 0x02
-
-#define GENERAL_STATUS 0x03
-/* Check AX88179 version. UA1:Bit2 = 0, UA2:Bit2 = 1 */
- #define AX_SECLD 0x04
-
-#define AX_SROM_ADDR 0x07
-#define AX_SROM_CMD 0x0a
- #define EEP_RD 0x04
- #define EEP_BUSY 0x10
-
-#define AX_SROM_DATA_LOW 0x08
-#define AX_SROM_DATA_HIGH 0x09
-
-#define AX_RX_CTL 0x0b
- #define AX_RX_CTL_DROPCRCERR 0x0100
- #define AX_RX_CTL_IPE 0x0200
- #define AX_RX_CTL_START 0x0080
- #define AX_RX_CTL_AP 0x0020
- #define AX_RX_CTL_AM 0x0010
- #define AX_RX_CTL_AB 0x0008
- #define AX_RX_CTL_AMALL 0x0002
- #define AX_RX_CTL_PRO 0x0001
- #define AX_RX_CTL_STOP 0x0000
-
-#define AX_NODE_ID 0x10
-#define AX_MULFLTARY 0x16
-
-#define AX_MEDIUM_STATUS_MODE 0x22
- #define AX_MEDIUM_GIGAMODE 0x01
- #define AX_MEDIUM_FULL_DUPLEX 0x02
- #define AX_MEDIUM_EN_125MHZ 0x08
- #define AX_MEDIUM_RXFLOW_CTRLEN 0x10
- #define AX_MEDIUM_TXFLOW_CTRLEN 0x20
- #define AX_MEDIUM_RECEIVE_EN 0x100
- #define AX_MEDIUM_PS 0x200
- #define AX_MEDIUM_JUMBO_EN 0x8040
-
-#define AX_MONITOR_MOD 0x24
- #define AX_MONITOR_MODE_RWLC 0x02
- #define AX_MONITOR_MODE_RWMP 0x04
- #define AX_MONITOR_MODE_PMEPOL 0x20
- #define AX_MONITOR_MODE_PMETYPE 0x40
-
-#define AX_GPIO_CTRL 0x25
- #define AX_GPIO_CTRL_GPIO3EN 0x80
- #define AX_GPIO_CTRL_GPIO2EN 0x40
- #define AX_GPIO_CTRL_GPIO1EN 0x20
-
-#define AX_PHYPWR_RSTCTL 0x26
- #define AX_PHYPWR_RSTCTL_BZ 0x0010
- #define AX_PHYPWR_RSTCTL_IPRL 0x0020
- #define AX_PHYPWR_RSTCTL_AT 0x1000
-
-#define AX_RX_BULKIN_QCTRL 0x2e
-#define AX_CLK_SELECT 0x33
- #define AX_CLK_SELECT_BCS 0x01
- #define AX_CLK_SELECT_ACS 0x02
- #define AX_CLK_SELECT_ULR 0x08
-
-#define AX_RXCOE_CTL 0x34
- #define AX_RXCOE_IP 0x01
- #define AX_RXCOE_TCP 0x02
- #define AX_RXCOE_UDP 0x04
- #define AX_RXCOE_TCPV6 0x20
- #define AX_RXCOE_UDPV6 0x40
-
-#define AX_TXCOE_CTL 0x35
- #define AX_TXCOE_IP 0x01
- #define AX_TXCOE_TCP 0x02
- #define AX_TXCOE_UDP 0x04
- #define AX_TXCOE_TCPV6 0x20
- #define AX_TXCOE_UDPV6 0x40
-
-#define AX_LEDCTRL 0x73
-
-#define GMII_PHY_PHYSR 0x11
- #define GMII_PHY_PHYSR_SMASK 0xc000
- #define GMII_PHY_PHYSR_GIGA 0x8000
- #define GMII_PHY_PHYSR_100 0x4000
- #define GMII_PHY_PHYSR_FULL 0x2000
- #define GMII_PHY_PHYSR_LINK 0x400
-
-#define GMII_LED_ACT 0x1a
- #define GMII_LED_ACTIVE_MASK 0xff8f
- #define GMII_LED0_ACTIVE BIT(4)
- #define GMII_LED1_ACTIVE BIT(5)
- #define GMII_LED2_ACTIVE BIT(6)
-
-#define GMII_LED_LINK 0x1c
- #define GMII_LED_LINK_MASK 0xf888
- #define GMII_LED0_LINK_10 BIT(0)
- #define GMII_LED0_LINK_100 BIT(1)
- #define GMII_LED0_LINK_1000 BIT(2)
- #define GMII_LED1_LINK_10 BIT(4)
- #define GMII_LED1_LINK_100 BIT(5)
- #define GMII_LED1_LINK_1000 BIT(6)
- #define GMII_LED2_LINK_10 BIT(8)
- #define GMII_LED2_LINK_100 BIT(9)
- #define GMII_LED2_LINK_1000 BIT(10)
- #define LED0_ACTIVE BIT(0)
- #define LED0_LINK_10 BIT(1)
- #define LED0_LINK_100 BIT(2)
- #define LED0_LINK_1000 BIT(3)
- #define LED0_FD BIT(4)
- #define LED0_USB3_MASK 0x001f
- #define LED1_ACTIVE BIT(5)
- #define LED1_LINK_10 BIT(6)
- #define LED1_LINK_100 BIT(7)
- #define LED1_LINK_1000 BIT(8)
- #define LED1_FD BIT(9)
- #define LED1_USB3_MASK 0x03e0
- #define LED2_ACTIVE BIT(10)
- #define LED2_LINK_1000 BIT(13)
- #define LED2_LINK_100 BIT(12)
- #define LED2_LINK_10 BIT(11)
- #define LED2_FD BIT(14)
- #define LED_VALID BIT(15)
- #define LED2_USB3_MASK 0x7c00
-
-#define GMII_PHYPAGE 0x1e
-#define GMII_PHY_PAGE_SELECT 0x1f
- #define GMII_PHY_PGSEL_EXT 0x0007
- #define GMII_PHY_PGSEL_PAGE0 0x0000
- #define GMII_PHY_PGSEL_PAGE3 0x0003
- #define GMII_PHY_PGSEL_PAGE5 0x0005
+#include "ax88179_lib.h"
static int ax88179_reset(struct usbnet *dev);
-struct ax88179_data {
- u8 eee_enabled;
- u8 eee_active;
- u16 rxctl;
- u8 in_pm;
- u32 wol_supported;
- u32 wolopts;
- u8 disconnecting;
-};
-
-struct ax88179_int_data {
- __le32 intdata1;
- __le32 intdata2;
-};
-
static const struct {
unsigned char ctrl, timer_l, timer_h, size, ifg;
} AX88179_BULKIN_SIZE[] = {
@@ -190,165 +23,6 @@ static const struct {
{7, 0xcc, 0x4c, 0x18, 8},
};
-static void ax88179_set_pm_mode(struct usbnet *dev, bool pm_mode)
-{
- struct ax88179_data *ax179_data = dev->driver_priv;
-
- ax179_data->in_pm = pm_mode;
-}
-
-static int ax88179_in_pm(struct usbnet *dev)
-{
- struct ax88179_data *ax179_data = dev->driver_priv;
-
- return ax179_data->in_pm;
-}
-
-static int __ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
- u16 size, void *data)
-{
- int ret;
- int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16);
- struct ax88179_data *ax179_data = dev->driver_priv;
-
- BUG_ON(!dev);
-
- if (!ax88179_in_pm(dev))
- fn = usbnet_read_cmd;
- else
- fn = usbnet_read_cmd_nopm;
-
- ret = fn(dev, cmd, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- value, index, data, size);
-
- if (unlikely((ret < 0) && !(ret == -ENODEV && ax179_data->disconnecting)))
- netdev_warn(dev->net, "Failed to read reg index 0x%04x: %d\n",
- index, ret);
-
- return ret;
-}
-
-static int __ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
- u16 size, const void *data)
-{
- int ret;
- int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16);
- struct ax88179_data *ax179_data = dev->driver_priv;
-
- BUG_ON(!dev);
-
- if (!ax88179_in_pm(dev))
- fn = usbnet_write_cmd;
- else
- fn = usbnet_write_cmd_nopm;
-
- ret = fn(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
- value, index, data, size);
-
- if (unlikely((ret < 0) && !(ret == -ENODEV && ax179_data->disconnecting)))
- netdev_warn(dev->net, "Failed to write reg index 0x%04x: %d\n",
- index, ret);
-
- return ret;
-}
-
-static void ax88179_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value,
- u16 index, u16 size, void *data)
-{
- u16 buf;
-
- if (2 == size) {
- buf = *((u16 *)data);
- cpu_to_le16s(&buf);
- usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR |
- USB_RECIP_DEVICE, value, index, &buf,
- size);
- } else {
- usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR |
- USB_RECIP_DEVICE, value, index, data,
- size);
- }
-}
-
-static int ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
- u16 size, void *data)
-{
- int ret;
-
- if (2 == size) {
- u16 buf = 0;
- ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
- le16_to_cpus(&buf);
- *((u16 *)data) = buf;
- } else if (4 == size) {
- u32 buf = 0;
- ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
- le32_to_cpus(&buf);
- *((u32 *)data) = buf;
- } else {
- ret = __ax88179_read_cmd(dev, cmd, value, index, size, data);
- }
-
- return ret;
-}
-
-static int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
- u16 size, const void *data)
-{
- int ret;
-
- if (2 == size) {
- u16 buf;
- buf = *((u16 *)data);
- cpu_to_le16s(&buf);
- ret = __ax88179_write_cmd(dev, cmd, value, index,
- size, &buf);
- } else {
- ret = __ax88179_write_cmd(dev, cmd, value, index,
- size, data);
- }
-
- return ret;
-}
-
-static void ax88179_status(struct usbnet *dev, struct urb *urb)
-{
- struct ax88179_int_data *event;
- u32 link;
-
- if (urb->actual_length < 8)
- return;
-
- event = urb->transfer_buffer;
- le32_to_cpus((void *)&event->intdata1);
-
- link = (((__force u32)event->intdata1) & AX_INT_PPLS_LINK) >> 16;
-
- if (netif_carrier_ok(dev->net) != link) {
- usbnet_link_change(dev, link, 1);
- if (!link)
- netdev_info(dev->net, "ax88179 - Link status is: 0\n");
- }
-}
-
-static int ax88179_mdio_read(struct net_device *netdev, int phy_id, int loc)
-{
- struct usbnet *dev = netdev_priv(netdev);
- u16 res;
-
- ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res);
- return res;
-}
-
-static void ax88179_mdio_write(struct net_device *netdev, int phy_id, int loc,
- int val)
-{
- struct usbnet *dev = netdev_priv(netdev);
- u16 res = (u16) val;
-
- ax88179_write_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res);
-}
-
static inline int ax88179_phy_mmd_indirect(struct usbnet *dev, u16 prtad,
u16 devad)
{
@@ -510,146 +184,11 @@ static void ax88179_disconnect(struct usb_interface *intf)
usbnet_disconnect(intf);
}
-static void
-ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
-{
- struct usbnet *dev = netdev_priv(net);
- struct ax88179_data *priv = dev->driver_priv;
-
- wolinfo->supported = priv->wol_supported;
- wolinfo->wolopts = priv->wolopts;
-}
-
-static int
-ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
-{
- struct usbnet *dev = netdev_priv(net);
- struct ax88179_data *priv = dev->driver_priv;
-
- if (wolinfo->wolopts & ~(priv->wol_supported))
- return -EINVAL;
-
- priv->wolopts = wolinfo->wolopts;
-
- return 0;
-}
-
static int ax88179_get_eeprom_len(struct net_device *net)
{
return AX_EEPROM_LEN;
}
-static int
-ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom,
- u8 *data)
-{
- struct usbnet *dev = netdev_priv(net);
- u16 *eeprom_buff;
- int first_word, last_word;
- int i, ret;
-
- if (eeprom->len == 0)
- return -EINVAL;
-
- eeprom->magic = AX88179_EEPROM_MAGIC;
-
- first_word = eeprom->offset >> 1;
- last_word = (eeprom->offset + eeprom->len - 1) >> 1;
- eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16),
- GFP_KERNEL);
- if (!eeprom_buff)
- return -ENOMEM;
-
- /* ax88179/178A returns 2 bytes from eeprom on read */
- for (i = first_word; i <= last_word; i++) {
- ret = __ax88179_read_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2,
- &eeprom_buff[i - first_word]);
- if (ret < 0) {
- kfree(eeprom_buff);
- return -EIO;
- }
- }
-
- memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len);
- kfree(eeprom_buff);
- return 0;
-}
-
-static int
-ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom,
- u8 *data)
-{
- struct usbnet *dev = netdev_priv(net);
- u16 *eeprom_buff;
- int first_word;
- int last_word;
- int ret;
- int i;
-
- netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n",
- eeprom->len, eeprom->offset, eeprom->magic);
-
- if (eeprom->len == 0)
- return -EINVAL;
-
- if (eeprom->magic != AX88179_EEPROM_MAGIC)
- return -EINVAL;
-
- first_word = eeprom->offset >> 1;
- last_word = (eeprom->offset + eeprom->len - 1) >> 1;
-
- eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16),
- GFP_KERNEL);
- if (!eeprom_buff)
- return -ENOMEM;
-
- /* align data to 16 bit boundaries, read the missing data from
- the EEPROM */
- if (eeprom->offset & 1) {
- ret = ax88179_read_cmd(dev, AX_ACCESS_EEPROM, first_word, 1, 2,
- &eeprom_buff[0]);
- if (ret < 0) {
- netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", first_word);
- goto free;
- }
- }
-
- if ((eeprom->offset + eeprom->len) & 1) {
- ret = ax88179_read_cmd(dev, AX_ACCESS_EEPROM, last_word, 1, 2,
- &eeprom_buff[last_word - first_word]);
- if (ret < 0) {
- netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", last_word);
- goto free;
- }
- }
-
- memcpy((u8 *)eeprom_buff + (eeprom->offset & 1), data, eeprom->len);
-
- for (i = first_word; i <= last_word; i++) {
- netdev_dbg(net, "write to EEPROM at offset 0x%02x, data 0x%04x\n",
- i, eeprom_buff[i - first_word]);
- ret = ax88179_write_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2,
- &eeprom_buff[i - first_word]);
- if (ret < 0) {
- netdev_err(net, "Failed to write EEPROM at offset 0x%02x.\n", i);
- goto free;
- }
- msleep(20);
- }
-
- /* reload EEPROM data */
- ret = ax88179_write_cmd(dev, AX_RELOAD_EEPROM_EFUSE, 0x0000, 0, 0, NULL);
- if (ret < 0) {
- netdev_err(net, "Failed to reload EEPROM data\n");
- goto free;
- }
-
- ret = 0;
-free:
- kfree(eeprom_buff);
- return ret;
-}
-
static int ax88179_get_link_ksettings(struct net_device *net,
struct ethtool_link_ksettings *cmd)
{
@@ -864,125 +403,6 @@ static const struct ethtool_ops ax88179_ethtool_ops = {
.get_ts_info = ethtool_op_get_ts_info,
};
-static void ax88179_set_multicast(struct net_device *net)
-{
- struct usbnet *dev = netdev_priv(net);
- struct ax88179_data *data = dev->driver_priv;
- u8 *m_filter = ((u8 *)dev->data);
-
- data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_IPE);
-
- if (net->flags & IFF_PROMISC) {
- data->rxctl |= AX_RX_CTL_PRO;
- } else if (net->flags & IFF_ALLMULTI ||
- netdev_mc_count(net) > AX_MAX_MCAST) {
- data->rxctl |= AX_RX_CTL_AMALL;
- } else if (netdev_mc_empty(net)) {
- /* just broadcast and directed */
- } else {
- /* We use dev->data for our 8 byte filter buffer
- * to avoid allocating memory that is tricky to free later
- */
- u32 crc_bits;
- struct netdev_hw_addr *ha;
-
- memset(m_filter, 0, AX_MCAST_FLTSIZE);
-
- netdev_for_each_mc_addr(ha, net) {
- crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
- *(m_filter + (crc_bits >> 3)) |= (1 << (crc_bits & 7));
- }
-
- ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_MULFLTARY,
- AX_MCAST_FLTSIZE, AX_MCAST_FLTSIZE,
- m_filter);
-
- data->rxctl |= AX_RX_CTL_AM;
- }
-
- ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_RX_CTL,
- 2, 2, &data->rxctl);
-}
-
-static int
-ax88179_set_features(struct net_device *net, netdev_features_t features)
-{
- u8 tmp;
- struct usbnet *dev = netdev_priv(net);
- netdev_features_t changed = net->features ^ features;
-
- if (changed & NETIF_F_IP_CSUM) {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
- tmp ^= AX_TXCOE_TCP | AX_TXCOE_UDP;
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
- }
-
- if (changed & NETIF_F_IPV6_CSUM) {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
- tmp ^= AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6;
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
- }
-
- if (changed & NETIF_F_RXCSUM) {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
- tmp ^= AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
- AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
- }
-
- return 0;
-}
-
-static int ax88179_change_mtu(struct net_device *net, int new_mtu)
-{
- struct usbnet *dev = netdev_priv(net);
- u16 tmp16;
-
- WRITE_ONCE(net->mtu, new_mtu);
- dev->hard_mtu = net->mtu + net->hard_header_len;
-
- if (net->mtu > 1500) {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
- 2, 2, &tmp16);
- tmp16 |= AX_MEDIUM_JUMBO_EN;
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
- 2, 2, &tmp16);
- } else {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
- 2, 2, &tmp16);
- tmp16 &= ~AX_MEDIUM_JUMBO_EN;
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
- 2, 2, &tmp16);
- }
-
- /* max qlen depend on hard_mtu and rx_urb_size */
- usbnet_update_max_qlen(dev);
-
- return 0;
-}
-
-static int ax88179_set_mac_addr(struct net_device *net, void *p)
-{
- struct usbnet *dev = netdev_priv(net);
- struct sockaddr *addr = p;
- int ret;
-
- if (netif_running(net))
- return -EBUSY;
- if (!is_valid_ether_addr(addr->sa_data))
- return -EADDRNOTAVAIL;
-
- eth_hw_addr_set(net, addr->sa_data);
-
- /* Set the MAC address */
- ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
- ETH_ALEN, net->dev_addr);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
static const struct net_device_ops ax88179_netdev_ops = {
.ndo_open = usbnet_open,
.ndo_stop = usbnet_stop,
@@ -1249,35 +669,6 @@ static int ax88179_led_setting(struct usbnet *dev)
return 0;
}
-static void ax88179_get_mac_addr(struct usbnet *dev)
-{
- u8 mac[ETH_ALEN];
-
- memset(mac, 0, sizeof(mac));
-
- /* Maybe the boot loader passed the MAC address via device tree */
- if (!eth_platform_get_mac_address(&dev->udev->dev, mac)) {
- netif_dbg(dev, ifup, dev->net,
- "MAC address read from device tree");
- } else {
- ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
- ETH_ALEN, mac);
- netif_dbg(dev, ifup, dev->net,
- "MAC address read from ASIX chip");
- }
-
- if (is_valid_ether_addr(mac)) {
- eth_hw_addr_set(dev->net, mac);
- if (!is_local_ether_addr(mac))
- dev->net->addr_assign_type = NET_ADDR_PERM;
- } else {
- netdev_info(dev->net, "invalid MAC address, using random\n");
- }
-
- ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN, ETH_ALEN,
- dev->net->dev_addr);
-}
-
static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
{
struct ax88179_data *ax179_data;
@@ -1292,6 +683,7 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
return -ENOMEM;
dev->driver_priv = ax179_data;
+ mutex_init(&ax179_data->medium_status_mutex);
dev->net->netdev_ops = &ax88179_netdev_ops;
dev->net->ethtool_ops = &ax88179_ethtool_ops;
@@ -1951,3 +1343,4 @@ module_usb_driver(ax88179_178a_driver);
MODULE_DESCRIPTION("ASIX AX88179/178A based USB 3.0/2.0 Gigabit Ethernet Devices");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("ax88179_178a");
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
new file mode 100644
index 0000000000000000000000000000000000000000..8627023efbb69082b2ec1c7b93793ec1a55a57be
--- /dev/null
+++ b/drivers/net/usb/ax88179_lib.c
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet Devices
+ *
+ * Copyright (C) 2011-2013 ASIX
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/rtnetlink.h>
+#include "ax88179_lib.h"
+
+void ax88179_set_pm_mode(struct usbnet *dev, bool pm_mode)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+
+ ax179_data->in_pm = pm_mode;
+}
+
+static int ax88179_in_pm(struct usbnet *dev)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+
+ return ax179_data->in_pm;
+}
+
+int __ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+ u16 size, void *data)
+{
+ int (*fn)(struct usbnet *dev, u8 cmd, u8 rtype, u16 val, u16 i, void *d, u16 size);
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ int ret;
+
+ if (!ax88179_in_pm(dev))
+ fn = usbnet_read_cmd;
+ else
+ fn = usbnet_read_cmd_nopm;
+
+ ret = fn(dev, cmd, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, data, size);
+
+ if (unlikely(ret < 0 && !(ret == -ENODEV && ax179_data->disconnecting)))
+ netdev_warn(dev->net, "Failed to read reg index 0x%04x: %d\n",
+ index, ret);
+
+ return ret;
+}
+
+static int __ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value,
+ u16 index, u16 size, const void *data)
+{
+ int (*fn)(struct usbnet *dev, u8 cmd, u8 rtype, u16 val, u16 i, const void *d, u16 size);
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ int ret;
+
+ if (!ax88179_in_pm(dev))
+ fn = usbnet_write_cmd;
+ else
+ fn = usbnet_write_cmd_nopm;
+
+ ret = fn(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, index, data, size);
+
+ if (unlikely(ret < 0 && !(ret == -ENODEV && ax179_data->disconnecting)))
+ netdev_warn(dev->net, "Failed to write reg index 0x%04x: %d\n",
+ index, ret);
+
+ return ret;
+}
+
+void ax88179_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value,
+ u16 index, u16 size, void *data)
+{
+ u16 buf;
+
+ if (size == 2) {
+ buf = *((u16 *)data);
+ cpu_to_le16s(&buf);
+ usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, value, index, &buf,
+ size);
+ } else {
+ usbnet_write_cmd_async(dev, cmd, USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, value, index, data,
+ size);
+ }
+}
+
+int ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+ u16 size, void *data)
+{
+ int ret;
+
+ if (size == 2) {
+ u16 buf = 0;
+
+ ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
+ le16_to_cpus(&buf);
+ *((u16 *)data) = buf;
+ } else if (size == 4) {
+ u32 buf = 0;
+
+ ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
+ le32_to_cpus(&buf);
+ *((u32 *)data) = buf;
+ } else {
+ ret = __ax88179_read_cmd(dev, cmd, value, index, size, data);
+ }
+
+ return ret;
+}
+
+int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+ u16 size, const void *data)
+{
+ int ret;
+
+ if (size == 2) {
+ u16 buf;
+
+ buf = *((u16 *)data);
+ cpu_to_le16s(&buf);
+ ret = __ax88179_write_cmd(dev, cmd, value, index,
+ size, &buf);
+ } else {
+ ret = __ax88179_write_cmd(dev, cmd, value, index,
+ size, data);
+ }
+
+ return ret;
+}
+
+void ax88179_status(struct usbnet *dev, struct urb *urb)
+{
+ struct ax88179_int_data *event;
+ u32 link;
+
+ if (urb->actual_length < 8)
+ return;
+
+ event = urb->transfer_buffer;
+ le32_to_cpus((void *)&event->intdata1);
+
+ link = (((__force u32)event->intdata1) & AX_INT_PPLS_LINK) >> 16;
+
+ if (netif_carrier_ok(dev->net) != link) {
+ usbnet_link_change(dev, link, 1);
+ if (!link)
+ netdev_info(dev->net, "ax88179 - Link status is: 0\n");
+ }
+}
+
+int ax88179_mdio_read(struct net_device *netdev, int phy_id, int loc)
+{
+ struct usbnet *dev = netdev_priv(netdev);
+ u16 res;
+
+ ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res);
+ return res;
+}
+
+void ax88179_mdio_write(struct net_device *netdev, int phy_id, int loc, int val)
+{
+ struct usbnet *dev = netdev_priv(netdev);
+ u16 res = (u16)val;
+
+ ax88179_write_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)loc, 2, &res);
+}
+
+void ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *priv = dev->driver_priv;
+
+ wolinfo->supported = priv->wol_supported;
+ wolinfo->wolopts = priv->wolopts;
+}
+
+int ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *priv = dev->driver_priv;
+
+ if (wolinfo->wolopts & ~(priv->wol_supported))
+ return -EINVAL;
+
+ priv->wolopts = wolinfo->wolopts;
+
+ return 0;
+}
+
+int ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u16 *eeprom_buff;
+ int first_word, last_word;
+ int i, ret;
+
+ if (eeprom->len == 0)
+ return -EINVAL;
+
+ eeprom->magic = AX88179_EEPROM_MAGIC;
+
+ first_word = eeprom->offset >> 1;
+ last_word = (eeprom->offset + eeprom->len - 1) >> 1;
+ eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16),
+ GFP_KERNEL);
+ if (!eeprom_buff)
+ return -ENOMEM;
+
+ /* ax88179/178A returns 2 bytes from eeprom on read */
+ for (i = first_word; i <= last_word; i++) {
+ ret = __ax88179_read_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2,
+ &eeprom_buff[i - first_word]);
+ if (ret < 0) {
+ kfree(eeprom_buff);
+ return -EIO;
+ }
+ }
+
+ memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len);
+ kfree(eeprom_buff);
+ return 0;
+}
+
+int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u16 *eeprom_buff;
+ int first_word;
+ int last_word;
+ int ret;
+ int i;
+
+ netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n",
+ eeprom->len, eeprom->offset, eeprom->magic);
+
+ if (eeprom->len == 0)
+ return -EINVAL;
+
+ if (eeprom->magic != AX88179_EEPROM_MAGIC)
+ return -EINVAL;
+
+ first_word = eeprom->offset >> 1;
+ last_word = (eeprom->offset + eeprom->len - 1) >> 1;
+
+ eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16),
+ GFP_KERNEL);
+ if (!eeprom_buff)
+ return -ENOMEM;
+
+ /* align data to 16 bit boundaries, read the missing data from
+ * the EEPROM
+ */
+ if (eeprom->offset & 1) {
+ ret = ax88179_read_cmd(dev, AX_ACCESS_EEPROM, first_word, 1, 2,
+ &eeprom_buff[0]);
+ if (ret < 0) {
+ netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", first_word);
+ goto free;
+ }
+ }
+
+ if ((eeprom->offset + eeprom->len) & 1) {
+ ret = ax88179_read_cmd(dev, AX_ACCESS_EEPROM, last_word, 1, 2,
+ &eeprom_buff[last_word - first_word]);
+ if (ret < 0) {
+ netdev_err(net, "Failed to read EEPROM at offset 0x%02x.\n", last_word);
+ goto free;
+ }
+ }
+
+ memcpy((u8 *)eeprom_buff + (eeprom->offset & 1), data, eeprom->len);
+
+ for (i = first_word; i <= last_word; i++) {
+ netdev_dbg(net, "write to EEPROM at offset 0x%02x, data 0x%04x\n",
+ i, eeprom_buff[i - first_word]);
+ ret = ax88179_write_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2,
+ &eeprom_buff[i - first_word]);
+ if (ret < 0) {
+ netdev_err(net, "Failed to write EEPROM at offset 0x%02x.\n", i);
+ goto free;
+ }
+ msleep(20);
+ }
+
+ /* reload EEPROM data */
+ ret = ax88179_write_cmd(dev, AX_RELOAD_EEPROM_EFUSE, 0x0000, 0, 0, NULL);
+ if (ret < 0) {
+ netdev_err(net, "Failed to reload EEPROM data\n");
+ goto free;
+ }
+
+ ret = 0;
+free:
+ kfree(eeprom_buff);
+ return ret;
+}
+
+void ax88179_set_multicast(struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *data = dev->driver_priv;
+ u8 *m_filter = ((u8 *)dev->data);
+
+ data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_IPE);
+
+ if (net->flags & IFF_PROMISC) {
+ data->rxctl |= AX_RX_CTL_PRO;
+ } else if (net->flags & IFF_ALLMULTI ||
+ netdev_mc_count(net) > AX_MAX_MCAST) {
+ data->rxctl |= AX_RX_CTL_AMALL;
+ } else if (netdev_mc_empty(net)) {
+ /* just broadcast and directed */
+ } else {
+ /* We use dev->data for our 8 byte filter buffer
+ * to avoid allocating memory that is tricky to free later
+ */
+ u32 crc_bits;
+ struct netdev_hw_addr *ha;
+
+ memset(m_filter, 0, AX_MCAST_FLTSIZE);
+
+ netdev_for_each_mc_addr(ha, net) {
+ crc_bits = ether_crc(ETH_ALEN, ha->addr) >> 26;
+ *(m_filter + (crc_bits >> 3)) |= (1 << (crc_bits & 7));
+ }
+
+ ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_MULFLTARY,
+ AX_MCAST_FLTSIZE, AX_MCAST_FLTSIZE,
+ m_filter);
+
+ data->rxctl |= AX_RX_CTL_AM;
+ }
+
+ ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_RX_CTL,
+ 2, 2, &data->rxctl);
+}
+
+int ax88179_set_features(struct net_device *net, netdev_features_t features)
+{
+ u8 tmp;
+ struct usbnet *dev = netdev_priv(net);
+ netdev_features_t changed = net->features ^ features;
+
+ if (changed & NETIF_F_IP_CSUM) {
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
+ tmp ^= AX_TXCOE_TCP | AX_TXCOE_UDP;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
+ }
+
+ if (changed & NETIF_F_IPV6_CSUM) {
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
+ tmp ^= AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, &tmp);
+ }
+
+ if (changed & NETIF_F_RXCSUM) {
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
+ tmp ^= AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
+ AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
+ }
+
+ return 0;
+}
+
+void ax88179_get_mac_addr(struct usbnet *dev)
+{
+ u8 mac[ETH_ALEN];
+
+ memset(mac, 0, sizeof(mac));
+
+ /* Maybe the boot loader passed the MAC address via device tree */
+ if (!eth_platform_get_mac_address(&dev->udev->dev, mac)) {
+ netif_dbg(dev, ifup, dev->net,
+ "MAC address read from device tree");
+ } else {
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
+ ETH_ALEN, mac);
+ netif_dbg(dev, ifup, dev->net,
+ "MAC address read from ASIX chip");
+ }
+
+ if (is_valid_ether_addr(mac)) {
+ eth_hw_addr_set(dev->net, mac);
+ if (!is_local_ether_addr(mac))
+ dev->net->addr_assign_type = NET_ADDR_PERM;
+ } else {
+ netdev_info(dev->net, "invalid MAC address, using random\n");
+ }
+
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN, ETH_ALEN,
+ dev->net->dev_addr);
+}
+
+int ax88179_change_mtu(struct net_device *net, int new_mtu)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *data;
+ u16 tmp16;
+ int err;
+
+ data = dev->driver_priv;
+ mutex_lock(&data->medium_status_mutex);
+ if (net->mtu > 1500) {
+ err = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
+ 2, 2, &tmp16);
+ if (err < 0)
+ goto err_out;
+ tmp16 |= AX_MEDIUM_JUMBO_EN;
+ err = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
+ 2, 2, &tmp16);
+ if (err < 0)
+ goto err_out;
+ } else {
+ err = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
+ 2, 2, &tmp16);
+ if (err < 0)
+ goto err_out;
+ tmp16 &= ~AX_MEDIUM_JUMBO_EN;
+ err = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
+ 2, 2, &tmp16);
+ if (err < 0)
+ goto err_out;
+ }
+ WRITE_ONCE(net->mtu, new_mtu);
+ dev->hard_mtu = net->mtu + net->hard_header_len;
+ mutex_unlock(&data->medium_status_mutex);
+
+ /* max qlen depend on hard_mtu and rx_urb_size */
+ usbnet_update_max_qlen(dev);
+
+ return 0;
+
+err_out:
+ mutex_unlock(&data->medium_status_mutex);
+ return err;
+}
+
+int ax88179_set_mac_addr(struct net_device *net, void *p)
+{
+ struct usbnet *dev = netdev_priv(net);
+ struct sockaddr *addr = p;
+ int ret;
+
+ if (netif_running(net))
+ return -EBUSY;
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ eth_hw_addr_set(net, addr->sa_data);
+
+ /* Set the MAC address */
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
+ ETH_ALEN, net->dev_addr);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
new file mode 100644
index 0000000000000000000000000000000000000000..9111a1395eb2f7208c7e90c93027845423bbda00
--- /dev/null
+++ b/drivers/net/usb/ax88179_lib.h
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <linux/usb.h>
+#include <linux/crc32.h>
+#include <linux/usb/usbnet.h>
+
+#ifndef __LINUX_USBNET_AX88179_H
+#define __LINUX_USBNET_AX88179_H
+
+#define AX88179_PHY_ID 0x03
+#define AX_EEPROM_LEN 0x100
+#define AX88179_EEPROM_MAGIC 0x17900b95
+#define AX_MCAST_FLTSIZE 8
+#define AX_MAX_MCAST 64
+#define AX_INT_PPLS_LINK ((u32)BIT(16))
+#define AX_RXHDR_L4_TYPE_MASK 0x1c
+#define AX_RXHDR_L4_TYPE_UDP 4
+#define AX_RXHDR_L4_TYPE_TCP 16
+#define AX_RXHDR_L3CSUM_ERR 2
+#define AX_RXHDR_L4CSUM_ERR 1
+#define AX_RXHDR_CRC_ERR ((u32)BIT(29))
+#define AX_RXHDR_DROP_ERR ((u32)BIT(31))
+#define AX_ACCESS_MAC 0x01
+#define AX_ACCESS_PHY 0x02
+#define AX_ACCESS_EEPROM 0x04
+#define AX_ACCESS_EFUS 0x05
+#define AX_RELOAD_EEPROM_EFUSE 0x06
+#define AX_PAUSE_WATERLVL_LOW 0x54
+#define AX_PAUSE_WATERLVL_HIGH 0x55
+
+#define PHYSICAL_LINK_STATUS 0x02
+ #define AX_USB_SS 0x04
+ #define AX_USB_HS 0x02
+
+#define GENERAL_STATUS 0x03
+/* Check AX88179 version. UA1:Bit2 = 0, UA2:Bit2 = 1 */
+ #define AX_SECLD 0x04
+
+#define AX_SROM_ADDR 0x07
+#define AX_SROM_CMD 0x0a
+ #define EEP_RD 0x04
+ #define EEP_BUSY 0x10
+
+#define AX_SROM_DATA_LOW 0x08
+#define AX_SROM_DATA_HIGH 0x09
+
+#define AX_RX_CTL 0x0b
+ #define AX_RX_CTL_DROPCRCERR 0x0100
+ #define AX_RX_CTL_IPE 0x0200
+ #define AX_RX_CTL_START 0x0080
+ #define AX_RX_CTL_AP 0x0020
+ #define AX_RX_CTL_AM 0x0010
+ #define AX_RX_CTL_AB 0x0008
+ #define AX_RX_CTL_AMALL 0x0002
+ #define AX_RX_CTL_PRO 0x0001
+ #define AX_RX_CTL_STOP 0x0000
+
+#define AX_NODE_ID 0x10
+#define AX_MULFLTARY 0x16
+
+#define AX_MEDIUM_STATUS_MODE 0x22
+ #define AX_MEDIUM_GIGAMODE 0x01
+ #define AX_MEDIUM_FULL_DUPLEX 0x02
+ #define AX_MEDIUM_EN_125MHZ 0x08
+ #define AX_MEDIUM_RXFLOW_CTRLEN 0x10
+ #define AX_MEDIUM_TXFLOW_CTRLEN 0x20
+ #define AX_MEDIUM_RECEIVE_EN 0x100
+ #define AX_MEDIUM_PS 0x200
+ #define AX_MEDIUM_JUMBO_EN 0x8040
+
+#define AX_MONITOR_MOD 0x24
+ #define AX_MONITOR_MODE_RWLC 0x02
+ #define AX_MONITOR_MODE_RWMP 0x04
+ #define AX_MONITOR_MODE_PMEPOL 0x20
+ #define AX_MONITOR_MODE_PMETYPE 0x40
+
+#define AX_GPIO_CTRL 0x25
+ #define AX_GPIO_CTRL_GPIO3EN 0x80
+ #define AX_GPIO_CTRL_GPIO2EN 0x40
+ #define AX_GPIO_CTRL_GPIO1EN 0x20
+
+#define AX_PHYPWR_RSTCTL 0x26
+ #define AX_PHYPWR_RSTCTL_BZ 0x0010
+ #define AX_PHYPWR_RSTCTL_IPRL 0x0020
+ #define AX_PHYPWR_RSTCTL_AT 0x1000
+
+#define AX_RX_BULKIN_QCTRL 0x2e
+#define AX_CLK_SELECT 0x33
+ #define AX_CLK_SELECT_BCS 0x01
+ #define AX_CLK_SELECT_ACS 0x02
+ #define AX_CLK_SELECT_ULR 0x08
+
+#define AX_RXCOE_CTL 0x34
+ #define AX_RXCOE_IP 0x01
+ #define AX_RXCOE_TCP 0x02
+ #define AX_RXCOE_UDP 0x04
+ #define AX_RXCOE_TCPV6 0x20
+ #define AX_RXCOE_UDPV6 0x40
+
+#define AX_TXCOE_CTL 0x35
+ #define AX_TXCOE_IP 0x01
+ #define AX_TXCOE_TCP 0x02
+ #define AX_TXCOE_UDP 0x04
+ #define AX_TXCOE_TCPV6 0x20
+ #define AX_TXCOE_UDPV6 0x40
+
+#define AX_LEDCTRL 0x73
+
+#define GMII_PHY_PHYSR 0x11
+ #define GMII_PHY_PHYSR_SMASK 0xc000
+ #define GMII_PHY_PHYSR_GIGA 0x8000
+ #define GMII_PHY_PHYSR_100 0x4000
+ #define GMII_PHY_PHYSR_FULL 0x2000
+ #define GMII_PHY_PHYSR_LINK 0x400
+
+#define GMII_LED_ACT 0x1a
+ #define GMII_LED_ACTIVE_MASK 0xff8f
+ #define GMII_LED0_ACTIVE BIT(4)
+ #define GMII_LED1_ACTIVE BIT(5)
+ #define GMII_LED2_ACTIVE BIT(6)
+
+#define GMII_LED_LINK 0x1c
+ #define GMII_LED_LINK_MASK 0xf888
+ #define GMII_LED0_LINK_10 BIT(0)
+ #define GMII_LED0_LINK_100 BIT(1)
+ #define GMII_LED0_LINK_1000 BIT(2)
+ #define GMII_LED1_LINK_10 BIT(4)
+ #define GMII_LED1_LINK_100 BIT(5)
+ #define GMII_LED1_LINK_1000 BIT(6)
+ #define GMII_LED2_LINK_10 BIT(8)
+ #define GMII_LED2_LINK_100 BIT(9)
+ #define GMII_LED2_LINK_1000 BIT(10)
+ #define LED0_ACTIVE BIT(0)
+ #define LED0_LINK_10 BIT(1)
+ #define LED0_LINK_100 BIT(2)
+ #define LED0_LINK_1000 BIT(3)
+ #define LED0_FD BIT(4)
+ #define LED0_USB3_MASK 0x001f
+ #define LED1_ACTIVE BIT(5)
+ #define LED1_LINK_10 BIT(6)
+ #define LED1_LINK_100 BIT(7)
+ #define LED1_LINK_1000 BIT(8)
+ #define LED1_FD BIT(9)
+ #define LED1_USB3_MASK 0x03e0
+ #define LED2_ACTIVE BIT(10)
+ #define LED2_LINK_1000 BIT(13)
+ #define LED2_LINK_100 BIT(12)
+ #define LED2_LINK_10 BIT(11)
+ #define LED2_FD BIT(14)
+ #define LED_VALID BIT(15)
+ #define LED2_USB3_MASK 0x7c00
+
+#define GMII_PHYPAGE 0x1e
+#define GMII_PHY_PAGE_SELECT 0x1f
+ #define GMII_PHY_PGSEL_EXT 0x0007
+ #define GMII_PHY_PGSEL_PAGE0 0x0000
+ #define GMII_PHY_PGSEL_PAGE3 0x0003
+ #define GMII_PHY_PGSEL_PAGE5 0x0005
+
+struct ax88179_data {
+ struct mutex medium_status_mutex; /* protect AX_MEDIUM_STATUS_MODE */
+ u8 eee_enabled;
+ u8 eee_active;
+ u16 rxctl;
+ u8 in_pm;
+ u32 wol_supported;
+ u32 wolopts;
+ u8 disconnecting;
+};
+
+struct ax88179_int_data {
+ __le32 intdata1;
+ __le32 intdata2;
+};
+
+void ax88179_set_pm_mode(struct usbnet *dev, bool pm_mode);
+int __ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data);
+int ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data);
+int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size,
+ const void *data);
+void ax88179_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index,
+ u16 size, void *data);
+int ax88179_mdio_read(struct net_device *netdev, int phy_id, int loc);
+void ax88179_mdio_write(struct net_device *netdev, int phy_id, int loc, int val);
+void ax88179_status(struct usbnet *dev, struct urb *urb);
+void ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo);
+int ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo);
+int ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data);
+int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data);
+void ax88179_set_multicast(struct net_device *net);
+int ax88179_set_features(struct net_device *net, netdev_features_t features);
+void ax88179_get_mac_addr(struct usbnet *dev);
+int ax88179_change_mtu(struct net_device *net, int new_mtu);
+int ax88179_set_mac_addr(struct net_device *net, void *p);
+#endif /*__LINUX_USBNET_AX88179_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (2 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
` (8 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
This adds bindings and HW support for AX179A-based USB-Ethernet
controllers. The AX179A-family of chips consists of the
AX88279 (2.5GBit PHY)
AX88179A/B (1GBit PHY, B variant has wider temperature range)
AX772D/E (100Mbit PHY)
The controllers all have the same vendor and device ID
(0x0b95, 0x1790) and are distinguished by their BCD device versions,
which are
2.00 AX88179A/B
3.00 AX88772D/E
4.00 AX88279
For all chips, the driver calls the same ax88179a_bind() function
and the chips are then distinguished by the chip version and
BCD device ID. The AX179A-based chips all provide both a CDC-NCM
compatible USB interface, and a proprietary vendor interface. By default,
the proprietary vendor interface is not active and Linux will load the
CDC-NCM driver to support the devices. If the ax88179_178a module is
configured by the OS to have precedence over CDC-NCM, then this driver
will switch the device to use the vendor interface, and the device will
be controlled by the ax88179_178a driver when the device is probed again
after an automatic reset by the device.
Also add a convenience function for the ethtool ops that use
phylink and need to retrieve the ax88179_data structure from
net_device: netdev2data().
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
MAINTAINERS | 6 +
drivers/net/usb/Kconfig | 2 +-
drivers/net/usb/Makefile | 2 +-
drivers/net/usb/ax88179_178a.c | 25 +
drivers/net/usb/ax88179_lib.c | 30 ++
drivers/net/usb/ax88179_lib.h | 161 ++++++
drivers/net/usb/ax88179a_devices.c | 980 +++++++++++++++++++++++++++++++++++++
7 files changed, 1204 insertions(+), 2 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 72ca3aab2106691882299ecaa7644436296905a6..600178e48c6246931fc813bce14abee41d306ea8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4021,6 +4021,12 @@ S: Maintained
F: Documentation/devicetree/bindings/net/asix,ax88796c.yaml
F: drivers/net/ethernet/asix/ax88796c_*
+ASIX AX88179 USB ETHERNET DRIVER
+M: Birger Koblitz <mail@birger-koblitz.de>
+L: netdev@vger.kernel.org
+S: Maintained
+F: drivers/net/usb/ax88179*
+
ASIX PHY DRIVER [RUST]
M: FUJITA Tomonori <fujita.tomonori@gmail.com>
R: Trevor Gross <tmgross@umich.edu>
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 52a5c0922c79fc52906d4d4040ec2b3411ff0984..115990b0d9537aea2776544c9768b034f32aa7a4 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -199,7 +199,7 @@ config USB_NET_AX88179_178A
tristate "ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet"
depends on USB_USBNET
select CRC32
- select PHYLIB
+ select PHYLINK
default y
help
This option adds support for ASIX AX88179 based USB 3.0/2.0
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index ddd76fa71e2ee670888df1c9715632e5c04a8149..2ecead0181eaf8d66fb6f5dbd6f2905b22b6eb57 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_USB_LAN78XX) += lan78xx.o
obj-$(CONFIG_USB_NET_AX8817X) += asix.o
asix-y := asix_devices.o asix_common.o ax88172a.o
obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179.o
-ax88179-y := ax88179_178a.o ax88179_lib.o
+ax88179-y := ax88179_178a.o ax88179a_devices.o ax88179_lib.o
obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o
obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o
obj-$(CONFIG_USB_NET_DM9601) += dm9601.o
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 85abccaaa5c67ec43687f5c247efada66411b5e2..071b03b4f145c05fd2391546655cf569cf6ea4cd 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -685,6 +685,19 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
dev->driver_priv = ax179_data;
mutex_init(&ax179_data->medium_status_mutex);
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS,
+ 1, 1, &ax179_data->chip_version);
+ if (ret < 0)
+ goto err_nodev;
+
+ ax179_data->chip_version = (ax179_data->chip_version & 0xf0) >> 4;
+ ax179_data->is_ax88772d = 0;
+ ax179_data->ip_align = 1;
+ ax179_data->eeprom_read_cmd = AX_ACCESS_EEPROM;
+ ax179_data->eeprom_write_cmd = AX_ACCESS_EEPROM;
+ ax179_data->eeprom_block = 2;
+ ax179_data->eeprom_wen = 0;
+
dev->net->netdev_ops = &ax88179_netdev_ops;
dev->net->ethtool_ops = &ax88179_ethtool_ops;
dev->net->needed_headroom = 8;
@@ -1271,6 +1284,18 @@ static const struct driver_info at_umc2000sp_info = {
static const struct usb_device_id products[] = {
{
+ /* ASIX AX88179A USB 3.2 1000Mbit Ethernet */
+ USB_DEVICE_VER(0x0b95, 0x1790, 0x0200, 0x0200),
+ .driver_info = (unsigned long)&ax88179a_info,
+}, {
+ /* ASIX AX88772D USB 2.0 100Mbit Ethernet */
+ USB_DEVICE_VER(0x0b95, 0x1790, 0x0300, 0x0300),
+ .driver_info = (unsigned long)&ax88772d_info,
+}, {
+ /* ASIX AX88279 USB 3.2 2500Mbit Ethernet */
+ USB_DEVICE_VER(0x0b95, 0x1790, 0x0400, 0x0400),
+ .driver_info = (unsigned long)&ax88279_info,
+}, {
/* ASIX AX88179 10/100/1000 */
USB_DEVICE_AND_INTERFACE_INFO(0x0b95, 0x1790, 0xff, 0xff, 0),
.driver_info = (unsigned long)&ax88179_info,
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index 8627023efbb69082b2ec1c7b93793ec1a55a57be..0efc2eb95e4b319fe8b39c23f4b4d8e14d5e9bce 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -129,6 +129,28 @@ int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
return ret;
}
+int ax88179_write_u16(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 data)
+{
+ u16 buf = data;
+
+ cpu_to_le16s(&buf);
+ return __ax88179_write_cmd(dev, cmd, value, index, 2, &buf);
+}
+
+int ax88179_write_u8(struct usbnet *dev, u8 cmd, u16 value, u16 index, u8 data)
+{
+ u8 buf = data;
+
+ return __ax88179_write_cmd(dev, cmd, value, index, 1, &buf);
+}
+
+struct ax88179_data *netdev2data(struct net_device *net)
+{
+ struct usbnet *dev = netdev_priv(net);
+
+ return dev->driver_priv;
+}
+
void ax88179_status(struct usbnet *dev, struct urb *urb)
{
struct ax88179_int_data *event;
@@ -340,6 +362,7 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features)
{
u8 tmp;
struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *data = dev->driver_priv;
netdev_features_t changed = net->features ^ features;
if (changed & NETIF_F_IP_CSUM) {
@@ -355,10 +378,17 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features)
}
if (changed & NETIF_F_RXCSUM) {
+ /* When we disable rx-checksumming, we need to clear the rx_checksum
+ * flag before disabling the engine
+ */
+ if (!(features & NETIF_F_RXCSUM))
+ data->rx_checksum = 0;
+
ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
tmp ^= AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
+ data->rx_checksum = !!(features & NETIF_F_RXCSUM);
}
return 0;
diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
index 9111a1395eb2f7208c7e90c93027845423bbda00..adc2b4598bb52d80c18a6e2cc467144d3bd57a88 100644
--- a/drivers/net/usb/ax88179_lib.h
+++ b/drivers/net/usb/ax88179_lib.h
@@ -2,6 +2,7 @@
#include <linux/usb.h>
#include <linux/crc32.h>
+#include <linux/phylink.h>
#include <linux/usb/usbnet.h>
#ifndef __LINUX_USBNET_AX88179_H
@@ -25,17 +26,34 @@
#define AX_ACCESS_EEPROM 0x04
#define AX_ACCESS_EFUS 0x05
#define AX_RELOAD_EEPROM_EFUSE 0x06
+#define AX88179A_WAKEUP_SETTING 0x07
+#define AX_FW_MODE 0x08
+#define AX_GPHY_CTL 0x0F
+#define AX88179A_FLASH_READ 0x21
+#define AX88179A_FLASH_WEN 0x22
+#define AX88179A_FLASH_WDIS 0x23
+#define AX88179A_FLASH_WRITE 0x24
+#define AX88179A_PHY_CLAUSE45 0x27
+#define AX88179A_FLASH_ERASE_SECTION 0x28
+#define AX88179A_ACCESS_BL 0x2A
+#define AX88179A_PHY_POWER 0x31
+#define AX88179A_AUTODETACH 0xC0
+
#define AX_PAUSE_WATERLVL_LOW 0x54
#define AX_PAUSE_WATERLVL_HIGH 0x55
+#define AX_FW_MODE_179A 0x01
#define PHYSICAL_LINK_STATUS 0x02
#define AX_USB_SS 0x04
#define AX_USB_HS 0x02
+ #define AX_USB_FS 0x01
#define GENERAL_STATUS 0x03
/* Check AX88179 version. UA1:Bit2 = 0, UA2:Bit2 = 1 */
#define AX_SECLD 0x04
+#define AX_CHIP_STATUS 0x05
+
#define AX_SROM_ADDR 0x07
#define AX_SROM_CMD 0x0a
#define EEP_RD 0x04
@@ -55,6 +73,15 @@
#define AX_RX_CTL_PRO 0x0001
#define AX_RX_CTL_STOP 0x0000
+#define AX88179A_ETH_TX_GAP 0x0D
+
+#define AX88179A_BFM_DATA 0x0E
+ #define AX_TX_QUEUE_CFG 0x02
+ #define AX_TX_QUEUE_SET 0x08
+ #define AX_TX_Q1_AHB_FC_EN 0x10
+ #define AX_TX_Q2_AHB_FC_EN 0x20
+ #define AX_XGMII_EN 0x80
+
#define AX_NODE_ID 0x10
#define AX_MULFLTARY 0x16
@@ -84,7 +111,20 @@
#define AX_PHYPWR_RSTCTL_IPRL 0x0020
#define AX_PHYPWR_RSTCTL_AT 0x1000
+#define AX88179A_VLAN_ID_ADDRESS 0x2A
+
+#define AX88179A_VLAN_ID_CONTROL 0x2B
+ #define AX_VLAN_CONTROL_WE 0x0001
+ #define AX_VLAN_CONTROL_RD 0x0002
+ #define AX_VLAN_CONTROL_VSO 0x0010
+ #define AX_VLAN_CONTROL_VFE 0x0020
+
+#define AX88179A_VLAN_ID_DATA0 0x2C
+#define AX88179A_VLAN_ID_DATA1 0x2D
+
#define AX_RX_BULKIN_QCTRL 0x2e
+#define AX_GPHY_EEE_CTRL 0x01
+
#define AX_CLK_SELECT 0x33
#define AX_CLK_SELECT_BCS 0x01
#define AX_CLK_SELECT_ACS 0x02
@@ -104,7 +144,51 @@
#define AX_TXCOE_TCPV6 0x20
#define AX_TXCOE_UDPV6 0x40
+#define AX88179A_MAC_BM_INT_MASK 0x41
+#define AX88179A_MAC_BM_RX_DMA_CTL 0x43
+#define AX88179A_MAC_BM_TX_DMA_CTL 0x46
+
+#define AX88179A_MAC_RX_STATUS_CDC 0x6D
+ #define AX_LSOFC_WCNT_7_ACCESS 0x03
+ #define AX_GMII_CRC_APPEND 0x10
+
#define AX_LEDCTRL 0x73
+#define AX88179A_MAC_ARC_CTRL 0x9E
+#define AX88179A_MAC_SWP_CTRL 0xB1
+
+#define AX88179A_MAC_TX_PAUSE 0xB2
+
+#define AX88179A_MAC_CDC_DELAY_TX 0xB5
+
+#define AX88179A_MAC_PATH 0xB7
+ #define AX_MAC_RX_PATH_READY 0x01
+ #define AX_MAC_TX_PATH_READY 0x02
+
+#define AX88179A_NEW_PAUSE_CTRL 0xB8
+ #define AX_NEW_PAUSE_EN 0x01
+
+#define AX88179A_MAC_BULK_OUT_CTRL 0xB9
+ #define AX_MAC_EFF_EN 0x02
+
+#define AX88179A_MAC_RX_DATA_CDC_CNT 0xC0
+ #define AX_MAC_LSO_ERR_EN 0x04
+ #define AX_MAC_MIQFFCTRL_FORMAT 0x10
+ #define AX_MAC_MIQFFCTRL_DROP_CRC 0x20
+
+#define AX88179A_AUTODETACH_DELAY (5UL << 8)
+#define AX88179A_AUTODETACH_EN 1
+
+#define AX88179A_MAC_LSO_ENHANCE_CTRL 0xC3
+ #define AX_LSO_ENHANCE_EN 0x01
+
+#define AX88179A_MAC_TX_HDR_CKSUM 0xCC
+#define AX88179A_EP5_EHR 0xF9
+
+#define AX_PHY_POWER 0x02
+
+#define EPHY_LOW_POWER_EN 0x01
+#define S5_WOL_EN 0x04
+#define S5_WOL_LOW_POWER 0x20
#define GMII_PHY_PHYSR 0x11
#define GMII_PHY_PHYSR_SMASK 0xc000
@@ -157,6 +241,55 @@
#define GMII_PHY_PGSEL_PAGE3 0x0003
#define GMII_PHY_PGSEL_PAGE5 0x0005
+#define AX88179A_SW_REVISION 0xFC
+#define AX88179A_SW_VERSION 0xFD
+
+/* TX Descriptor */
+#define AX179A_TX_DESC_LEN_MASK 0x1FFFFF
+#define AX179A_TX_DESC_DROP_PADD BIT(28)
+#define AX179A_TX_DESC_VLAN BIT(29)
+#define AX179A_TX_DESC_MSS GENMASK_U64(46, 32)
+#define AX179A_TX_DESC_VLAN_ID GENMASK_U64(63, 48)
+
+/* RX Packet Descriptor */
+#define AX179A_RX_PD_L4_ERR BIT(0)
+#define AX179A_RX_PD_L3_ERR BIT(1)
+#define AX179A_RX_PD_L4_TYPE_MASK 0x1C
+#define AX179A_RX_PD_L4_UDP 0x04
+#define AX179A_RX_PD_L4_TCP 0x10
+#define AX179A_RX_PD_L3_TYPE_MASK 0x60
+#define AX179A_RX_PD_L3_IP 0x20
+#define AX179A_RX_PD_L3_IP6 0x40
+
+#define AX179A_RX_PD_VLAN BIT(10)
+#define AX179A_RX_PD_RX_OK BIT(11)
+#define AX179A_RX_PD_DROP BIT(31)
+#define AX179A_RX_PD_LEN GENMASK(30, 16)
+#define AX179A_RX_PD_VLAN_SHIFT 0x20
+
+/* RX Descriptor header */
+#define AX179A_RX_DH_PKT_CNT_MASK 0x1FFF
+#define AX179A_RX_DH_DESC_OFFSET GENMASK(31, 13)
+
+#define AX179A_RX_HW_PAD 0x02
+
+#define AX_ADVERTISE_2500 0x1000
+
+enum ax_ether_link_speed {
+ ETHER_LINK_NONE = 0,
+ ETHER_LINK_10 = 1,
+ ETHER_LINK_100 = 2,
+ ETHER_LINK_1000 = 3,
+ ETHER_LINK_2500 = 4,
+};
+
+enum ax_chip_version {
+ AX_VERSION_INVALID = 0x0,
+ AX_VERSION_AX88179 = 0x4,
+ AX_VERSION_AX88179A = 0x6, /* Also AX88772D */
+ AX_VERSION_AX88279 = 0x7,
+};
+
struct ax88179_data {
struct mutex medium_status_mutex; /* protect AX_MEDIUM_STATUS_MODE */
u8 eee_enabled;
@@ -166,6 +299,22 @@ struct ax88179_data {
u32 wol_supported;
u32 wolopts;
u8 disconnecting;
+ u8 chip_version;
+ u8 fw_version[4];
+ u8 is_ax88772d;
+ u8 ip_align;
+ u8 link;
+ u8 speed;
+ u8 full_duplex;
+ u8 rx_checksum;
+ u8 eeprom_read_cmd;
+ u8 eeprom_write_cmd;
+ u8 eeprom_wen;
+ u16 eeprom_block;
+ struct mii_bus *mdio;
+ struct phy_device *phydev;
+ struct phylink *phylink;
+ struct phylink_config phylink_config;
};
struct ax88179_int_data {
@@ -173,6 +322,10 @@ struct ax88179_int_data {
__le32 intdata2;
};
+struct ax_bulkin_settings {
+ unsigned char ctrl, timer_l, timer_h, size, ifg;
+};
+
void ax88179_set_pm_mode(struct usbnet *dev, bool pm_mode);
int __ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data);
int ax88179_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size, void *data);
@@ -180,8 +333,11 @@ int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 size
const void *data);
void ax88179_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index,
u16 size, void *data);
+int ax88179_write_u16(struct usbnet *dev, u8 cmd, u16 value, u16 index, u16 data);
+int ax88179_write_u8(struct usbnet *dev, u8 cmd, u16 value, u16 index, u8 data);
int ax88179_mdio_read(struct net_device *netdev, int phy_id, int loc);
void ax88179_mdio_write(struct net_device *netdev, int phy_id, int loc, int val);
+struct ax88179_data *netdev2data(struct net_device *net);
void ax88179_status(struct usbnet *dev, struct urb *urb);
void ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo);
int ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo);
@@ -192,4 +348,9 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features);
void ax88179_get_mac_addr(struct usbnet *dev);
int ax88179_change_mtu(struct net_device *net, int new_mtu);
int ax88179_set_mac_addr(struct net_device *net, void *p);
+
+extern const struct driver_info ax88179a_info;
+extern const struct driver_info ax88772d_info;
+extern const struct driver_info ax88279_info;
+
#endif /*__LINUX_USBNET_AX88179_H */
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
new file mode 100644
index 0000000000000000000000000000000000000000..ebc6ac2c0b27333d60742422d81e63882ddb1005
--- /dev/null
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -0,0 +1,980 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/module.h>
+#include <linux/phylink.h>
+#include <linux/if_vlan.h>
+#include "ax88179_lib.h"
+
+#define AX88279_EEPROM_LEN 0x4000
+#define AX88179A_EEPROM_LEN (32 * 20)
+
+enum ax_bulk_in_speeds {
+ BULK_IN_SPEED_2G5 = 0,
+ BULK_IN_SPEED_1G_SS = 1,
+ BULK_IN_SPEED_1G_HS = 2,
+ BULK_IN_SPEED_100_FULL_SS = 3,
+ BULK_IN_SPEED_100_HALF_SS = 4,
+ BULK_IN_SPEED_100_FULL_HS = 5,
+ BULK_IN_SPEED_100_HALF_HS = 6,
+ BULK_IN_SPEED_FS = 7,
+};
+
+static const struct ax_bulkin_settings AX88179A_BULKIN_SIZE[] = {
+ [BULK_IN_SPEED_1G_SS] = {5, 0x7B, 0x00, 0x17, 0x0F},
+ [BULK_IN_SPEED_1G_HS] = {5, 0xC0, 0x02, 0x06, 0x0F},
+ [BULK_IN_SPEED_100_FULL_SS] = {7, 0xF0, 0x00, 0x0C, 0x0F},
+ [BULK_IN_SPEED_100_HALF_SS] = {6, 0x00, 0x00, 0x06, 0x0F},
+ [BULK_IN_SPEED_100_FULL_HS] = {5, 0xC0, 0x04, 0x06, 0x0F},
+ [BULK_IN_SPEED_100_HALF_HS] = {7, 0xC0, 0x04, 0x06, 0x0F},
+ [BULK_IN_SPEED_FS] = {7, 0x00, 0x00, 0x03, 0x3F},
+};
+
+static const struct ax_bulkin_settings AX88772D_BULKIN_SIZE[] = {
+ [BULK_IN_SPEED_100_FULL_HS] = {5, 0xC0, 0x04, 0x06, 0x0F},
+ [BULK_IN_SPEED_100_HALF_HS] = {7, 0xC0, 0x04, 0x06, 0x0F},
+ [BULK_IN_SPEED_FS] = {7, 0x00, 0x00, 0x03, 0x3F},
+};
+
+static const struct ax_bulkin_settings AX88279_BULKIN_SIZE[] = {
+ [BULK_IN_SPEED_2G5] = {5, 0x10, 0x01, 0x11, 0x0F},
+ [BULK_IN_SPEED_1G_SS] = {7, 0xB3, 0x01, 0x11, 0x0F},
+ [BULK_IN_SPEED_1G_HS] = {7, 0xC0, 0x02, 0x06, 0x0F},
+ [BULK_IN_SPEED_100_FULL_SS] = {7, 0x80, 0x01, 0x03, 0x0F},
+ [BULK_IN_SPEED_100_HALF_SS] = {7, 0x80, 0x01, 0x03, 0x0F},
+ [BULK_IN_SPEED_100_FULL_HS] = {7, 0x80, 0x01, 0x03, 0x0F},
+ [BULK_IN_SPEED_100_HALF_HS] = {7, 0x80, 0x01, 0x03, 0x0F},
+ [BULK_IN_SPEED_FS] = {7, 0x00, 0x00, 0x03, 0x3F},
+};
+
+static int ax88179_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+ struct usbnet *dev = bus->priv;
+ struct ax88179_data *priv;
+ u16 res;
+ int ret;
+
+ priv = dev->driver_priv;
+ /* When reading PHYSID, return unused PHY-IDs from the ASIX vendor range */
+ if (phy_id == AX88179_PHY_ID && regnum == MII_PHYSID1)
+ return 0x003b;
+ if (phy_id == AX88179_PHY_ID && regnum == MII_PHYSID2) {
+ if (priv->chip_version == AX_VERSION_AX88179A && priv->is_ax88772d)
+ return 0x772d;
+ else if (priv->chip_version == AX_VERSION_AX88179A)
+ return 0x179a;
+ else if (priv->chip_version == AX_VERSION_AX88279)
+ return 0x2790;
+ }
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)regnum, 2, &res);
+ if (ret < 0)
+ return ret;
+ return res;
+}
+
+static int ax88179_mdiobus_write(struct mii_bus *bus, int phy_id, int regnum, u16 val)
+{
+ struct usbnet *dev = bus->priv;
+
+ return ax88179_write_u16(dev, AX_ACCESS_PHY, phy_id, (__u16)regnum, val);
+}
+
+static int ax179a_read_mmd(struct usbnet *dev, u16 dev_addr, u16 reg)
+{
+ u16 res;
+ int ret;
+
+ ret = ax88179_read_cmd(dev, AX88179A_PHY_CLAUSE45, dev_addr, reg, 2, &res);
+ if (ret < 0)
+ return ret;
+ return res;
+}
+
+static int ax179a_write_mmd(struct usbnet *dev, u16 dev_addr, u16 reg, u16 data)
+{
+ return ax88179_write_cmd(dev, AX88179A_PHY_CLAUSE45, dev_addr, reg, 2, &data);
+}
+
+static int ax88179_mdiobus_read_c45(struct mii_bus *bus, int addr, int devnum, int regnum)
+{
+ struct usbnet *dev = bus->priv;
+
+ if (addr != AX88179_PHY_ID)
+ return -EINVAL;
+
+ return ax179a_read_mmd(dev, devnum, regnum);
+}
+
+static int ax88179_mdiobus_write_c45(struct mii_bus *bus, int addr, int devnum,
+ int regnum, u16 val)
+{
+ struct usbnet *dev = bus->priv;
+
+ if (addr != AX88179_PHY_ID)
+ return -EINVAL;
+
+ return ax179a_write_mmd(dev, devnum, regnum, val);
+}
+
+static void ax88179a_status(struct usbnet *dev, struct urb *urb)
+{
+ struct ax88179_data *data = dev->driver_priv;
+
+ if (urb->actual_length < 8)
+ return;
+
+ phylink_mac_interrupt(data->phylink);
+}
+
+static int ax88179a_auto_detach(struct usbnet *dev)
+{
+ u16 tmp16;
+
+ tmp16 = AX88179A_AUTODETACH_DELAY;
+ ax88179_write_cmd(dev, AX88179A_AUTODETACH, tmp16, 0, 0, NULL);
+ return 0;
+}
+
+static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ const struct ax_bulkin_settings *bulkin_data;
+ int index = 0;
+
+ switch (speed) {
+ case ETHER_LINK_2500: /* AX88279 only */
+ index = BULK_IN_SPEED_2G5;
+ break;
+
+ case ETHER_LINK_1000: /* AX88279 & AX88178A */
+ if (link_sts & AX_USB_SS)
+ index = BULK_IN_SPEED_1G_SS;
+ else if (link_sts & AX_USB_HS)
+ index = BULK_IN_SPEED_1G_HS;
+ break;
+
+ case ETHER_LINK_100:
+ if (link_sts & AX_USB_SS)
+ index = BULK_IN_SPEED_100_FULL_SS;
+ else if (link_sts & AX_USB_HS)
+ index = BULK_IN_SPEED_100_FULL_HS;
+ if (!full_duplex)
+ index++;
+ break;
+
+ case ETHER_LINK_10:
+ index = BULK_IN_SPEED_FS;
+ break;
+
+ default: /* No link */
+ index = BULK_IN_SPEED_FS;
+ }
+
+ if (link_sts & AX_USB_FS)
+ index = BULK_IN_SPEED_FS;
+
+ if (ax179_data->chip_version == AX_VERSION_AX88279) {
+ bulkin_data = AX88279_BULKIN_SIZE;
+ } else {
+ if (ax179_data->is_ax88772d)
+ bulkin_data = AX88772D_BULKIN_SIZE;
+ else
+ bulkin_data = AX88179A_BULKIN_SIZE;
+ }
+
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, &bulkin_data[index]);
+}
+
+static void ax88179a_get_pauseparam(struct net_device *net, struct ethtool_pauseparam *pause)
+{
+ struct ax88179_data *data = netdev2data(net);
+
+ phylink_ethtool_get_pauseparam(data->phylink, pause);
+}
+
+static int ax88179a_set_pauseparam(struct net_device *net, struct ethtool_pauseparam *pause)
+{
+ struct ax88179_data *data = netdev2data(net);
+
+ return phylink_ethtool_set_pauseparam(data->phylink, pause);
+}
+
+static int ax88179a_get_link_ksettings(struct net_device *net,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct ax88179_data *data = netdev2data(net);
+
+ return phylink_ethtool_ksettings_get(data->phylink, cmd);
+}
+
+static int ax88179a_set_link_ksettings(struct net_device *net,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct ax88179_data *data = netdev2data(net);
+
+ return phylink_ethtool_ksettings_set(data->phylink, cmd);
+}
+
+static int ax88179a_get_eeprom_len(struct net_device *net)
+{
+ struct ax88179_data *ax179_data = netdev2data(net);
+
+ if (ax179_data->chip_version >= AX_VERSION_AX88279)
+ return AX88279_EEPROM_LEN;
+ else
+ return AX88179A_EEPROM_LEN;
+}
+
+static const struct ethtool_ops ax88179a_ethtool_ops = {
+ .get_link = ethtool_op_get_link,
+ .get_msglevel = usbnet_get_msglevel,
+ .set_msglevel = usbnet_set_msglevel,
+ .get_wol = ax88179_get_wol,
+ .set_wol = ax88179_set_wol,
+ .get_eeprom_len = ax88179a_get_eeprom_len,
+ .get_eeprom = ax88179_get_eeprom,
+ .set_eeprom = ax88179_set_eeprom,
+ .nway_reset = phy_ethtool_nway_reset,
+ .get_link_ksettings = ax88179a_get_link_ksettings,
+ .set_link_ksettings = ax88179a_set_link_ksettings,
+ .get_pauseparam = ax88179a_get_pauseparam,
+ .set_pauseparam = ax88179a_set_pauseparam,
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
+static void ax88179a_mdio_unregister(struct ax88179_data *data)
+{
+ mdiobus_unregister(data->mdio);
+ mdiobus_free(data->mdio);
+}
+
+static int ax88179a_init_phy(struct usbnet *dev)
+{
+ struct ax88179_data *data = dev->driver_priv;
+ int ret;
+
+ data->phydev = mdiobus_get_phy(data->mdio, AX88179_PHY_ID);
+ if (!data->phydev) {
+ netdev_err(dev->net, "Could not find PHY\n");
+ return -ENODEV;
+ }
+
+ data->phydev->irq = PHY_MAC_INTERRUPT;
+ ret = phylink_connect_phy(data->phylink, data->phydev);
+ if (ret) {
+ netdev_err(dev->net, "Could not connect PHY\n");
+ return ret;
+ }
+
+ phy_suspend(data->phydev);
+ data->phydev->mac_managed_pm = true;
+
+ phy_attached_info(data->phydev);
+
+ return 0;
+}
+
+static void ax88179a_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ /* Nothing to do */
+}
+
+static void ax88179a_mac_link_down(struct phylink_config *config,
+ unsigned int mode, phy_interface_t interface)
+{
+ /* Nothing to do */
+}
+
+static void ax88179a_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy,
+ unsigned int phy_mode, phy_interface_t interface,
+ int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+ struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ u8 link_sts = 0, reg8[3];
+ u8 bulk_config_speed = 0;
+ u16 mode;
+
+ /* Stop RX/TX for link configuration */
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, AX_RX_CTL_STOP);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 0);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_CDC_DELAY_TX, 1, 0xa5);
+
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 2, 0x0410);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_ETH_TX_GAP, 1, 0);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_EP5_EHR, 1, 0x07);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_NEW_PAUSE_CTRL, 1,
+ 0x28 | AX_NEW_PAUSE_EN);
+
+ mode = AX_MEDIUM_RECEIVE_EN;
+ if (tx_pause)
+ mode |= AX_MEDIUM_TXFLOW_CTRLEN;
+ if (rx_pause)
+ mode |= AX_MEDIUM_RXFLOW_CTRLEN;
+
+ switch (speed) {
+ case SPEED_2500:
+ reg8[0] = 0x00;
+ reg8[1] = 0xF8;
+ reg8[2] = 0x07;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_TX_PAUSE, 3, 3, reg8);
+
+ reg8[0] = 0x78;
+ reg8[1] = (AX_LSOFC_WCNT_7_ACCESS << 5);
+ reg8[2] = 0;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_STATUS_CDC, 3, 3, reg8);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 0x40);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT + 1, 1,
+ AX_MAC_MIQFFCTRL_FORMAT | AX_MAC_MIQFFCTRL_DROP_CRC |
+ AX_MAC_LSO_ERR_EN);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, AX_XGMII_EN);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_LSO_ENHANCE_CTRL, 1,
+ 0x1C | AX_LSO_ENHANCE_EN);
+
+ mode |= AX_MEDIUM_GIGAMODE | AX_MEDIUM_FULL_DUPLEX;
+ bulk_config_speed = ETHER_LINK_2500;
+
+ break;
+
+ case SPEED_1000:
+ mode |= AX_MEDIUM_GIGAMODE;
+ bulk_config_speed = ETHER_LINK_1000;
+ fallthrough;
+
+ case SPEED_100:
+ reg8[0] = 0x78;
+ reg8[1] = (AX_LSOFC_WCNT_7_ACCESS << 5) | AX_GMII_CRC_APPEND;
+ reg8[2] = 0;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_STATUS_CDC, 3, 3, reg8);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 0x40);
+ if (!bulk_config_speed)
+ bulk_config_speed = ETHER_LINK_100;
+ break;
+
+ case SPEED_10:
+ reg8[0] = 0xFA;
+ reg8[1] = (AX_LSOFC_WCNT_7_ACCESS << 5) | AX_GMII_CRC_APPEND;
+ reg8[2] = 0xFF;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_STATUS_CDC, 3, 3, reg8);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 0xFA);
+
+ bulk_config_speed = ETHER_LINK_10;
+ break;
+ }
+
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, 1, 1, &link_sts);
+ ax88179a_bulkin_config(dev, link_sts, bulk_config_speed, !!duplex);
+
+ if (speed != SPEED_2500)
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 0);
+
+ if (duplex)
+ mode |= AX_MEDIUM_FULL_DUPLEX;
+
+ mutex_lock(&ax179_data->medium_status_mutex);
+ if (dev->net->mtu > 1500)
+ mode |= AX_MEDIUM_JUMBO_EN;
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, mode);
+
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, READ_ONCE(ax179_data->rxctl));
+ mutex_unlock(&ax179_data->medium_status_mutex);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1,
+ AX_MAC_RX_PATH_READY | AX_MAC_TX_PATH_READY);
+}
+
+static const struct phylink_mac_ops ax88179a_phylink_mac_ops = {
+ .mac_config = ax88179a_mac_config,
+ .mac_link_down = ax88179a_mac_link_down,
+ .mac_link_up = ax88179a_mac_link_up,
+};
+
+static int ax88179a_phylink_setup(struct usbnet *dev)
+{
+ struct ax88179_data *data = dev->driver_priv;
+ phy_interface_t phy_if_mode;
+ struct phylink *phylink;
+
+ data->phylink_config.dev = &dev->net->dev;
+ data->phylink_config.type = PHYLINK_NETDEV;
+ data->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_100;
+ if (data->is_ax88772d)
+ data->phylink_config.mac_capabilities |= MAC_10;
+ else if (data->chip_version < AX_VERSION_AX88279)
+ data->phylink_config.mac_capabilities |= MAC_10 | MAC_1000;
+ else
+ data->phylink_config.mac_capabilities |= MAC_1000 | MAC_2500FD;
+
+ if (!data->is_ax88772d) {
+ data->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD;
+ data->phylink_config.eee_enabled_default = false;
+ }
+
+ if (data->chip_version == AX_VERSION_AX88279) {
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ data->phylink_config.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ data->phylink_config.supported_interfaces);
+ phy_if_mode = PHY_INTERFACE_MODE_2500BASEX;
+ } else {
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ data->phylink_config.supported_interfaces);
+ phy_if_mode = PHY_INTERFACE_MODE_SGMII;
+ }
+
+ phylink = phylink_create(&data->phylink_config, dev->net->dev.fwnode,
+ phy_if_mode, &ax88179a_phylink_mac_ops);
+ if (IS_ERR(phylink))
+ return PTR_ERR(phylink);
+
+ data->phylink = phylink;
+ return 0;
+}
+
+static int ax88179a_init_mdio(struct usbnet *dev)
+{
+ struct ax88179_data *data = dev->driver_priv;
+ int ret;
+
+ data->mdio = mdiobus_alloc();
+ if (!data->mdio)
+ return -ENOMEM;
+
+ data->mdio->priv = dev;
+ data->mdio->read = ax88179_mdiobus_read;
+ data->mdio->write = ax88179_mdiobus_write;
+ data->mdio->read_c45 = ax88179_mdiobus_read_c45;
+ data->mdio->write_c45 = ax88179_mdiobus_write_c45;
+ data->mdio->name = "AX88179A MDIO Bus";
+ data->mdio->phy_mask = ~(1 << AX88179_PHY_ID);
+ /* mii bus name is usb-<usb bus number>-<usb device number> */
+ snprintf(data->mdio->id, MII_BUS_ID_SIZE, "usb-%03d:%03d",
+ dev->udev->bus->busnum, dev->udev->devnum);
+
+ ret = mdiobus_register(data->mdio);
+ if (ret) {
+ netdev_err(dev->net, "Could not register MDIO bus (err %d)\n", ret);
+ mdiobus_free(data->mdio);
+ data->mdio = NULL;
+ }
+
+ return ret;
+}
+
+static int ax88179a_mii_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
+{
+ struct ax88179_data *data = netdev2data(net);
+
+ return phylink_mii_ioctl(data->phylink, rq, cmd);
+}
+
+static const struct net_device_ops ax88179a_netdev_ops = {
+ .ndo_open = usbnet_open,
+ .ndo_stop = usbnet_stop,
+ .ndo_start_xmit = usbnet_start_xmit,
+ .ndo_tx_timeout = usbnet_tx_timeout,
+ .ndo_get_stats64 = dev_get_tstats64,
+ .ndo_change_mtu = ax88179_change_mtu,
+ .ndo_set_mac_address = ax88179_set_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_eth_ioctl = ax88179a_mii_ioctl,
+};
+
+static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct ax88179_data *ax179_data;
+ int ret;
+
+ /* Check if vendor configuration */
+ if (udev->actconfig->desc.bConfigurationValue != 1) {
+ netdev_info(dev->net, "Switching to vendor mode\n");
+ usb_driver_set_configuration(udev, 1);
+ return -ENODEV;
+ }
+
+ ret = usbnet_get_endpoints(dev, intf);
+ if (ret < 0)
+ return ret;
+
+ ax179_data = kzalloc_obj(*ax179_data);
+ if (!ax179_data)
+ return -ENOMEM;
+
+ dev->driver_priv = ax179_data;
+ mutex_init(&ax179_data->medium_status_mutex);
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS,
+ 1, 1, &ax179_data->chip_version);
+ if (ret < 0)
+ goto err_nodev;
+
+ ax179_data->chip_version = (ax179_data->chip_version & 0xf0) >> 4;
+ ax179_data->is_ax88772d = 0;
+ if (ax179_data->chip_version == AX_VERSION_AX88179A) {
+ if (le16_to_cpu(udev->descriptor.bcdDevice) == 0x300)
+ ax179_data->is_ax88772d = 1;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ ret = ax88179_read_cmd(dev, AX88179A_ACCESS_BL, AX88179A_SW_VERSION + i,
+ 1, 1, &ax179_data->fw_version[i]);
+ if (ret < 0)
+ ax179_data->fw_version[i] = 0xff;
+ }
+ ret = ax88179_read_cmd(dev, AX88179A_ACCESS_BL, AX88179A_SW_REVISION,
+ 1, 1, &ax179_data->fw_version[3]);
+ if (ret < 0)
+ ax179_data->fw_version[3] = 0xff;
+
+ netdev_info(dev->net, "AX88179A/279/772D Chip Version: %x, FW: %d.%d.%d.%d\n",
+ ax179_data->chip_version,
+ ax179_data->fw_version[0], ax179_data->fw_version[1],
+ ax179_data->fw_version[2], ax179_data->fw_version[3]);
+
+ /* The AX88279 requires both the AX_RX_CTL_IPE and AX_RX_CTL_DROPCRCERR
+ * bits set in AX_RX_CTL for creating correct RX-URBs. AX_RX_CTL_DROPCRCERR
+ * is anyway set for all chips, make sure AX_RX_CTL_IPE is set via ip_align.
+ * Also configure eeprom access parameters.
+ */
+ if (ax179_data->chip_version == AX_VERSION_AX88279) {
+ ax179_data->ip_align = 1;
+ ax179_data->eeprom_read_cmd = AX88179A_FLASH_READ;
+ ax179_data->eeprom_write_cmd = AX88179A_FLASH_WRITE;
+ ax179_data->eeprom_block = 256;
+ ax179_data->eeprom_wen = 1;
+ } else {
+ ax179_data->ip_align = 0;
+ ax179_data->eeprom_read_cmd = AX_ACCESS_EFUS;
+ ax179_data->eeprom_write_cmd = AX_ACCESS_EFUS;
+ ax179_data->eeprom_block = 20;
+ ax179_data->eeprom_wen = 0;
+ }
+
+ dev->net->netdev_ops = &ax88179a_netdev_ops;
+ dev->net->ethtool_ops = &ax88179a_ethtool_ops;
+ dev->net->needed_headroom = 8;
+ dev->net->needed_tailroom = 8;
+ dev->net->min_mtu = ETH_MIN_MTU;
+ dev->hard_mtu = 9 * 1024;
+ dev->net->max_mtu = dev->hard_mtu - dev->net->hard_header_len;
+
+ if (!ax179_data->is_ax88772d)
+ dev->mii.supports_gmii = 1;
+
+ dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM |
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO;
+
+ dev->net->hw_features |= dev->net->features;
+
+ dev->net->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM |
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO;
+
+ netif_set_tso_max_size(dev->net, 16384);
+
+ /* Enable Transmission of Link Speed byte in interrupt URB */
+ ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
+ ax88179_write_cmd(dev, AX_RELOAD_EEPROM_EFUSE, 0, 0, 0, NULL);
+
+ /* Read MAC address from DTB or ASIX chip */
+ ax88179_get_mac_addr(dev);
+ memcpy(dev->net->perm_addr, dev->net->dev_addr, ETH_ALEN);
+
+ /* Power PHY for probing */
+ ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, AX_PHY_POWER);
+ msleep(250);
+
+ ret = ax88179a_init_mdio(dev);
+ if (ret)
+ goto err_nodev;
+
+ ret = ax88179a_phylink_setup(dev);
+ if (ret)
+ goto phylink_err;
+
+ ret = ax88179a_init_phy(dev);
+ if (ret)
+ goto initphy_err;
+
+ return 0;
+
+initphy_err:
+ phylink_destroy(ax179_data->phylink);
+phylink_err:
+ ax88179a_mdio_unregister(ax179_data);
+err_nodev:
+ kfree(ax179_data);
+ ax179_data = NULL;
+
+ return ret;
+}
+
+static void ax88179a_unbind(struct usbnet *dev, struct usb_interface *intf)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+
+ /* Configure RX control register => stop operation */
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, AX_RX_CTL_STOP);
+
+ rtnl_lock();
+ phylink_disconnect_phy(ax179_data->phylink);
+ rtnl_unlock();
+ phylink_destroy(ax179_data->phylink);
+ ax88179a_mdio_unregister(ax179_data);
+
+ ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
+
+ kfree(ax179_data);
+}
+
+static void ax88179a_rx_checksum(struct sk_buff *skb, u64 pkt_desc)
+{
+ u32 pkt_type;
+
+ skb->ip_summed = CHECKSUM_NONE;
+ /* checksum error bit is set */
+ if (pkt_desc & AX179A_RX_PD_L4_ERR || pkt_desc & AX179A_RX_PD_L3_ERR)
+ return;
+
+ pkt_type = pkt_desc & AX179A_RX_PD_L4_TYPE_MASK;
+ /* It must be a TCP or UDP packet with a valid checksum */
+ if (pkt_type == AX179A_RX_PD_L4_TCP || pkt_type == AX179A_RX_PD_L4_UDP)
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+}
+
+static int ax88179a_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ struct sk_buff *ax_skb;
+ u32 hdr_off, pkt_end;
+ __le64 *pkt_desc_ptr;
+ u16 vlan_tag;
+ u16 pkt_cnt;
+ u64 rx_hdr;
+
+ /* SKB contents for AX179A-based chips:
+ * <packet 1>
+ * ...
+ * <packet N>
+ * <per-packet metadata entry 1>
+ * ...
+ * <per-packet metadata entry N>
+ * <rx_hdr>
+ *
+ * where:
+ * <packet N> contains pkt_len data bytes and padding:
+ * 2 bytes of IP alignment (optional, depends on AX_RX_CTL_IPE flag)
+ * packet data received
+ * optional padding to 8-bytes boundary
+ * <per-packet metadata entry N> contains 8 bytes:
+ * pkt_len and fields AX_RXHDR_*
+ * <rx-hdr> contains 8 bytes:
+ * pkt_cnt and hdr_off (offset of <per-packet metadata entry 1>)
+ *
+ * pkt_cnt is number of entries in the per-packet metadata array.
+ */
+
+ if (!skb || skb->len < sizeof(rx_hdr))
+ goto err;
+
+ /* RX Descriptor Header */
+ skb_trim(skb, skb->len - sizeof(rx_hdr));
+ rx_hdr = get_unaligned_le64((__le64 *)skb_tail_pointer(skb));
+
+ /* Check these packets */
+ hdr_off = FIELD_GET(AX179A_RX_DH_DESC_OFFSET, rx_hdr);
+ pkt_cnt = rx_hdr & AX179A_RX_DH_PKT_CNT_MASK;
+
+ /* Consistency check header position */
+ if (hdr_off != skb->len - (pkt_cnt * sizeof(rx_hdr)))
+ goto err;
+
+ /* Make sure that the bounds of the metadata array are inside the SKB
+ * (and in front of the counter at the end).
+ */
+ if (pkt_cnt * 8 + hdr_off > skb->len)
+ goto err;
+
+ /* Packets must not overlap the metadata array */
+ skb_trim(skb, hdr_off);
+
+ if (!pkt_cnt)
+ goto err;
+
+ /* Get the first RX packet descriptor */
+ pkt_desc_ptr = (__le64 *)(skb->data + hdr_off);
+
+ pkt_end = 0;
+ while (pkt_cnt--) {
+ u64 pkt_desc = get_unaligned_le64(pkt_desc_ptr);
+ u32 pkt_len_plus_padd;
+ u32 pkt_len;
+
+ pkt_len = FIELD_GET(AX179A_RX_PD_LEN, pkt_desc);
+ pkt_len -= (ax179_data->ip_align ? 2 : 0);
+ pkt_len_plus_padd = ((pkt_len + 7 + (ax179_data->ip_align ? 2 : 0)) & 0x7FFF8);
+
+ pkt_end += pkt_len_plus_padd;
+ if (pkt_end > hdr_off || (pkt_cnt == 0 && pkt_end != hdr_off))
+ goto err;
+
+ if (pkt_desc & AX179A_RX_PD_DROP || !(pkt_desc & AX179A_RX_PD_RX_OK) ||
+ pkt_len > (dev->hard_mtu + AX179A_RX_HW_PAD) || pkt_len < 2 + ETH_HLEN) {
+ skb_pull(skb, pkt_len_plus_padd);
+
+ /* Next RX Packet Descriptor */
+ pkt_desc_ptr++;
+ continue;
+ }
+
+ ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len);
+ if (!ax_skb)
+ goto err;
+
+ skb_put(ax_skb, pkt_len);
+ memcpy(ax_skb->data, skb->data + (ax179_data->ip_align ? AX179A_RX_HW_PAD : 0),
+ pkt_len);
+
+ if (ax179_data->rx_checksum)
+ ax88179a_rx_checksum(ax_skb, pkt_desc);
+
+ if (pkt_desc & AX179A_RX_PD_VLAN) {
+ vlan_tag = pkt_desc >> AX179A_RX_PD_VLAN_SHIFT;
+ __vlan_hwaccel_put_tag(ax_skb, htons(ETH_P_8021Q), vlan_tag);
+ }
+
+ usbnet_skb_return(dev, ax_skb);
+ skb_pull(skb, pkt_len_plus_padd);
+
+ /* Next RX Packet Header */
+ pkt_desc_ptr++;
+ }
+
+ return 1;
+
+err:
+ return 0;
+}
+
+static struct sk_buff *ax88179a_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
+{
+ u64 tx_desc = skb->len & AX179A_TX_DESC_LEN_MASK;
+ int frame_size = dev->maxpacket;
+ struct sk_buff *ax_skb;
+ u64 *tx_desc_ptr;
+ int padding_size;
+ int headroom;
+ int tailroom;
+ u16 tci = 0;
+
+ /* TSO MSS */
+ tx_desc |= FIELD_PREP(AX179A_TX_DESC_MSS, (u64)skb_shinfo(skb)->gso_size);
+
+ headroom = (skb->len + sizeof(tx_desc)) % 8;
+ padding_size = headroom ? 8 - headroom : 0;
+
+ if (((skb->len + sizeof(tx_desc) + padding_size) % frame_size) == 0) {
+ padding_size += 8;
+ tx_desc |= AX179A_TX_DESC_DROP_PADD;
+ }
+
+ if ((dev->net->features & NETIF_F_HW_VLAN_CTAG_TX) && (vlan_get_tag(skb, &tci) >= 0)) {
+ tx_desc |= AX179A_TX_DESC_VLAN;
+ tx_desc |= FIELD_PREP(AX179A_TX_DESC_VLAN_ID, (u64)tci);
+ }
+
+ if (!dev->can_dma_sg && (dev->net->features & NETIF_F_SG) && skb_linearize(skb)) {
+ dev_kfree_skb_any(skb);
+ return NULL;
+ }
+
+ headroom = skb_headroom(skb);
+ tailroom = skb_tailroom(skb);
+
+ if (skb_cloned(skb) || !(headroom >= sizeof(tx_desc) && tailroom >= padding_size)) {
+ ax_skb = skb_copy_expand(skb, sizeof(tx_desc), padding_size, flags);
+ dev_kfree_skb_any(skb);
+ skb = ax_skb;
+ if (!skb)
+ return NULL;
+ }
+ if (padding_size != 0)
+ skb_put_zero(skb, padding_size);
+ /* Copy TX header */
+ tx_desc_ptr = skb_push(skb, sizeof(tx_desc));
+ put_unaligned_le64(tx_desc, tx_desc_ptr);
+
+ usbnet_set_skb_tx_stats(skb, 1, 0);
+
+ return skb;
+}
+
+static int ax88179a_reset(struct usbnet *dev)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ u16 *tmp16;
+ u8 buf[5];
+ u16 rxctl;
+ u8 *tmp;
+
+ tmp16 = (u16 *)buf;
+ tmp = (u8 *)buf;
+
+ /* Power up ethernet PHY */
+ ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, AX_PHY_POWER);
+ msleep(250);
+
+ /* Ethernet PHY Auto Detach*/
+ ax88179a_auto_detach(dev);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_BULK_OUT_CTRL, 1, AX_MAC_EFF_EN);
+
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 0);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 0x04);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 0x10);
+
+ *tmp = 0;
+ if (dev->net->features & NETIF_F_HW_VLAN_CTAG_FILTER)
+ *tmp |= AX_VLAN_CONTROL_VFE;
+ if (dev->net->features & NETIF_F_HW_VLAN_CTAG_RX)
+ *tmp |= AX_VLAN_CONTROL_VSO;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, tmp);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_INT_MASK, 1, 0xff);
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_RX_DMA_CTL, 1, 0);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_TX_DMA_CTL, 1, 0);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_ARC_CTRL, 1, 0);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_SWP_CTRL, 1, 0);
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_TX_HDR_CKSUM, 1, 0);
+
+ /* Read MAC address from DTB or asix chip */
+ ax88179_get_mac_addr(dev);
+ memcpy(dev->net->perm_addr, dev->net->dev_addr, ETH_ALEN);
+
+ /* The Bulk-Register configuration for the AX88179A is done in
+ * ax88179a_mac_link_up(), once the link is up for a given link and USB-speed.
+ */
+ if (ax179_data->is_ax88772d)
+ dev->rx_urb_size = 1024 * 24;
+ else
+ dev->rx_urb_size = 1024 * 48;
+
+ /* Enable checksum offload */
+ if (dev->net->features & NETIF_F_RXCSUM) {
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1,
+ AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
+ AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6);
+ ax179_data->rx_checksum = 1;
+ } else {
+ ax179_data->rx_checksum = 0;
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 0);
+ }
+
+ ax88179_write_u8(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1,
+ AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP |
+ AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6);
+
+ /* Configure RX control register => start operation */
+ rxctl = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START | AX_RX_CTL_AP |
+ AX_RX_CTL_AMALL | AX_RX_CTL_AB;
+ if (ax179_data->ip_align)
+ rxctl |= AX_RX_CTL_IPE;
+ if (dev->net->flags & IFF_PROMISC)
+ rxctl |= AX_RX_CTL_PRO;
+ WRITE_ONCE(ax179_data->rxctl, rxctl);
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, rxctl);
+
+ if (ax179_data->chip_version < AX_VERSION_AX88179A)
+ *tmp = AX_MONITOR_MODE_PMETYPE | AX_MONITOR_MODE_PMEPOL | AX_MONITOR_MODE_RWMP;
+ else
+ *tmp = AX_MONITOR_MODE_RWMP;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, tmp);
+
+ /* Configure default medium type => giga */
+ *tmp16 = AX_MEDIUM_RECEIVE_EN | AX_MEDIUM_TXFLOW_CTRLEN |
+ AX_MEDIUM_RXFLOW_CTRLEN | AX_MEDIUM_FULL_DUPLEX;
+ if (!ax179_data->is_ax88772d)
+ *tmp16 |= AX_MEDIUM_GIGAMODE;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, tmp16);
+
+ /* Check if WoL is supported */
+ ax179_data->wol_supported = 0;
+ if (ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD,
+ 1, 1, tmp) > 0)
+ ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY;
+
+ /* Ensure EEE state is off, consistent with phylink setup */
+ ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, false, 0, NULL);
+
+ phylink_start(ax179_data->phylink);
+
+ return 0;
+}
+
+static int ax88179a_stop(struct usbnet *dev)
+{
+ struct ax88179_data *ax179_data = dev->driver_priv;
+ u16 reg16;
+
+ phylink_stop(ax179_data->phylink);
+
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
+ reg16 &= ~AX_MEDIUM_RECEIVE_EN;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
+
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 0);
+
+ ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
+
+ return 0;
+}
+
+const struct driver_info ax88179a_info = {
+ .description = "ASIX AX88179A USB 3.2 Gigabit Ethernet",
+ .bind = ax88179a_bind,
+ .unbind = ax88179a_unbind,
+ .status = ax88179a_status,
+ .reset = ax88179a_reset,
+ .stop = ax88179a_stop,
+ .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_MULTI_PACKET | FLAG_AVOID_UNLINK_URBS,
+ .rx_fixup = ax88179a_rx_fixup,
+ .tx_fixup = ax88179a_tx_fixup,
+};
+
+const struct driver_info ax88772d_info = {
+ .description = "ASIX AX88772D/E USB 2.0 Fast Ethernet",
+ .bind = ax88179a_bind,
+ .unbind = ax88179a_unbind,
+ .status = ax88179a_status,
+ .reset = ax88179a_reset,
+ .stop = ax88179a_stop,
+ .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_MULTI_PACKET | FLAG_AVOID_UNLINK_URBS,
+ .rx_fixup = ax88179a_rx_fixup,
+ .tx_fixup = ax88179a_tx_fixup,
+};
+
+const struct driver_info ax88279_info = {
+ .description = "ASIX AX88279 USB 3.2 2.5Gigabit Ethernet",
+ .bind = ax88179a_bind,
+ .unbind = ax88179a_unbind,
+ .status = ax88179a_status,
+ .reset = ax88179a_reset,
+ .stop = ax88179a_stop,
+ .flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_MULTI_PACKET | FLAG_AVOID_UNLINK_URBS,
+ .rx_fixup = ax88179a_rx_fixup,
+ .tx_fixup = ax88179a_tx_fixup,
+};
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (3 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
` (7 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
The AX88179A uses a simple HW configuration for EEE
via a single EEE configuration register without LPI timer support
Add support for setting this EEE enable/disable register in implementations
of mac_enable_tx_lpi/mac_disable_tx_lpi.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/usb/ax88179a_devices.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index ebc6ac2c0b27333d60742422d81e63882ddb1005..692ede39c9e9a0cf546733c8d0f71a199385eb6f 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -395,10 +395,27 @@ static void ax88179a_mac_link_up(struct phylink_config *config,
AX_MAC_RX_PATH_READY | AX_MAC_TX_PATH_READY);
}
+static void ax88179a_mac_disable_tx_lpi(struct phylink_config *config)
+{
+ struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
+
+ ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, false, 0, NULL);
+}
+
+static int ax88179a_mac_enable_tx_lpi(struct phylink_config *config, u32 timer, bool tx_clk_stop)
+{
+ struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
+
+ /* AX88179A does not provide LPI timer registers */
+ return ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, true, 0, NULL);
+}
+
static const struct phylink_mac_ops ax88179a_phylink_mac_ops = {
.mac_config = ax88179a_mac_config,
.mac_link_down = ax88179a_mac_link_down,
.mac_link_up = ax88179a_mac_link_up,
+ .mac_disable_tx_lpi = ax88179a_mac_disable_tx_lpi,
+ .mac_enable_tx_lpi = ax88179a_mac_enable_tx_lpi,
};
static int ax88179a_phylink_setup(struct usbnet *dev)
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (4 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
` (6 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Configure phylink to work with the PHYs in the AX88179A controllers
and provide ethtool get/set eee operations calling into phylink.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/usb/ax88179a_devices.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index 692ede39c9e9a0cf546733c8d0f71a199385eb6f..c41d9c2dc737a3bbebd4dd969de244ba7a4b3a11 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -225,6 +225,20 @@ static int ax88179a_get_eeprom_len(struct net_device *net)
return AX88179A_EEPROM_LEN;
}
+static int ax88179a_get_eee(struct net_device *net, struct ethtool_keee *edata)
+{
+ struct ax88179_data *ax179_data = netdev2data(net);
+
+ return phylink_ethtool_get_eee(ax179_data->phylink, edata);
+}
+
+static int ax88179a_set_eee(struct net_device *net, struct ethtool_keee *edata)
+{
+ struct ax88179_data *ax179_data = netdev2data(net);
+
+ return phylink_ethtool_set_eee(ax179_data->phylink, edata);
+}
+
static const struct ethtool_ops ax88179a_ethtool_ops = {
.get_link = ethtool_op_get_link,
.get_msglevel = usbnet_get_msglevel,
@@ -234,6 +248,8 @@ static const struct ethtool_ops ax88179a_ethtool_ops = {
.get_eeprom_len = ax88179a_get_eeprom_len,
.get_eeprom = ax88179_get_eeprom,
.set_eeprom = ax88179_set_eeprom,
+ .get_eee = ax88179a_get_eee,
+ .set_eee = ax88179a_set_eee,
.nway_reset = phy_ethtool_nway_reset,
.get_link_ksettings = ax88179a_get_link_ksettings,
.set_link_ksettings = ax88179a_set_link_ksettings,
@@ -451,6 +467,10 @@ static int ax88179a_phylink_setup(struct usbnet *dev)
phy_if_mode = PHY_INTERFACE_MODE_SGMII;
}
+ memcpy(data->phylink_config.lpi_interfaces,
+ data->phylink_config.supported_interfaces,
+ sizeof(data->phylink_config.lpi_interfaces));
+
phylink = phylink_create(&data->phylink_config, dev->net->dev.fwnode,
phy_if_mode, &ax88179a_phylink_mac_ops);
if (IS_ERR(phylink))
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (5 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
` (5 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
The AX88179A-based chips support VLAN offload. Add configuration
support in netdev_ops. Features supported are:
NETIF_F_HW_VLAN_CTAG_TX, NETIF_F_HW_VLAN_CTAG_RX
and NETIF_F_HW_VLAN_CTAG_FILTER.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Error-checking fixes Suggested-by: Jianhui Xu <neuromoments@gmail.com>
---
drivers/net/usb/ax88179a_devices.c | 152 ++++++++++++++++++++++++++++++++++++-
1 file changed, 151 insertions(+), 1 deletion(-)
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index c41d9c2dc737a3bbebd4dd969de244ba7a4b3a11..bdc67cbc17287e2969b36430c672887ee63d18ec 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -258,6 +258,88 @@ static const struct ethtool_ops ax88179a_ethtool_ops = {
.get_ts_info = ethtool_op_get_ts_info,
};
+static int ax88179a_vlan_rx_kill_vid(struct net_device *net, __be16 proto, u16 vid)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u8 vlan_ctrl;
+ int ret = 0;
+ u16 reg16;
+ u8 reg8;
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+ vlan_ctrl = reg8;
+
+ /* Address */
+ reg8 = (vid / 16);
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ /* Data */
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ if (ret < 0)
+ return ret;
+ reg16 &= ~(1 << (vid % 16));
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ if (ret < 0)
+ return ret;
+
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ax88179a_vlan_rx_add_vid(struct net_device *net, __be16 proto, u16 vid)
+{
+ struct usbnet *dev = netdev_priv(net);
+ u8 vlan_ctrl;
+ int ret = 0;
+ u16 reg16;
+ u8 reg8;
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+ vlan_ctrl = reg8;
+
+ /* Address */
+ reg8 = (vid / 16);
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ /* Data */
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ if (ret < 0)
+ return ret;
+ reg16 |= (1 << (vid % 16));
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ if (ret < 0)
+ return ret;
+
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static void ax88179a_mdio_unregister(struct ax88179_data *data)
{
mdiobus_unregister(data->mdio);
@@ -517,6 +599,69 @@ static int ax88179a_mii_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
return phylink_mii_ioctl(data->phylink, rq, cmd);
}
+static int ax88179a_set_features(struct net_device *net, netdev_features_t features)
+{
+ struct usbnet *dev = netdev_priv(net);
+ netdev_features_t changed;
+ int ret;
+ u8 tmp;
+
+ changed = net->features ^ features;
+
+ ret = ax88179_set_features(net, features);
+ if (ret)
+ return ret;
+
+ if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) {
+ if (features & NETIF_F_HW_VLAN_CTAG_FILTER) {
+ for (int i = 0; i < 256; i++) {
+ /* Address */
+ tmp = i;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS,
+ 1, 1, &tmp);
+ /* Data */
+ ax88179_write_u16(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0,
+ 2, 0);
+ }
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC,
+ AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ tmp |= AX_VLAN_CONTROL_VFE | AX_VLAN_CONTROL_WE;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC,
+ AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC,
+ AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ tmp &= ~(AX_VLAN_CONTROL_VFE | AX_VLAN_CONTROL_WE);
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC,
+ AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ if (changed & NETIF_F_HW_VLAN_CTAG_RX) {
+ ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL,
+ 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ if (features & NETIF_F_HW_VLAN_CTAG_RX)
+ tmp |= AX_VLAN_CONTROL_VSO;
+ else
+ tmp &= ~AX_VLAN_CONTROL_VSO;
+ ret = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL,
+ 1, 1, &tmp);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
static const struct net_device_ops ax88179a_netdev_ops = {
.ndo_open = usbnet_open,
.ndo_stop = usbnet_stop,
@@ -527,6 +672,9 @@ static const struct net_device_ops ax88179a_netdev_ops = {
.ndo_set_mac_address = ax88179_set_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_eth_ioctl = ax88179a_mii_ioctl,
+ .ndo_set_features = ax88179a_set_features,
+ .ndo_vlan_rx_add_vid = ax88179a_vlan_rx_add_vid,
+ .ndo_vlan_rx_kill_vid = ax88179a_vlan_rx_kill_vid,
};
static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
@@ -612,9 +760,11 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
dev->mii.supports_gmii = 1;
dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM |
- NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO;
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO |
+ NETIF_F_HW_VLAN_CTAG_TX;
dev->net->hw_features |= dev->net->features;
+ dev->net->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER;
dev->net->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM |
NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO;
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (6 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
` (4 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
Add support for conditionally setting the ip_alignement flag
AX_RX_CTL_IPE in AX_RX_CTL and make sure that AX_RX_CTL_DROPCRCERR
is also set to be consistent with the initial configuration in
ax88179_reset()
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/usb/ax88179_lib.c | 8 ++++++--
drivers/net/usb/ax88179a_devices.c | 1 +
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index 0efc2eb95e4b319fe8b39c23f4b4d8e14d5e9bce..ea19700c8230f7b51bd1f6edf44133c31b7298a1 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -321,10 +321,14 @@ int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8
void ax88179_set_multicast(struct net_device *net)
{
struct usbnet *dev = netdev_priv(net);
- struct ax88179_data *data = dev->driver_priv;
u8 *m_filter = ((u8 *)dev->data);
+ struct ax88179_data *data;
+
+ data = dev->driver_priv;
- data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_IPE);
+ data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_DROPCRCERR);
+ if (data->ip_align)
+ data->rxctl |= AX_RX_CTL_IPE;
if (net->flags & IFF_PROMISC) {
data->rxctl |= AX_RX_CTL_PRO;
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index bdc67cbc17287e2969b36430c672887ee63d18ec..e86cbe3e4400fa2c8e1a6a864fc8e1d328f13724 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -672,6 +672,7 @@ static const struct net_device_ops ax88179a_netdev_ops = {
.ndo_set_mac_address = ax88179_set_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_eth_ioctl = ax88179a_mii_ioctl,
+ .ndo_set_rx_mode = ax88179_set_multicast,
.ndo_set_features = ax88179a_set_features,
.ndo_vlan_rx_add_vid = ax88179a_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = ax88179a_vlan_rx_kill_vid,
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (7 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
` (3 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
The suspend and resume functions are called via the usb_driver structure,
for which the driver has only a single instance. Add wrapper functions
for the different implementations for the AX88179 and AX179A architecutres
in ax88179_lib which calls the 2 implementations in ax88179_178a and
ax88179a_devices, respectively.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Suggested-by: Jianhui Xu <neuromoments@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/usb/ax88179_178a.c | 9 ++-
drivers/net/usb/ax88179_lib.c | 19 ++++++
drivers/net/usb/ax88179_lib.h | 4 ++
drivers/net/usb/ax88179a_devices.c | 119 ++++++++++++++++++++++++++++++++++++-
4 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 071b03b4f145c05fd2391546655cf569cf6ea4cd..ef0856627d08b456afb1569e8a4459b0e526ab46 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -685,6 +685,9 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
dev->driver_priv = ax179_data;
mutex_init(&ax179_data->medium_status_mutex);
+ ax179_data->resume = ax88179_resume;
+ ax179_data->suspend = ax88179_suspend;
+
ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS,
1, 1, &ax179_data->chip_version);
if (ret < 0)
@@ -1356,9 +1359,9 @@ static struct usb_driver ax88179_178a_driver = {
.name = "ax88179_178a",
.id_table = products,
.probe = usbnet_probe,
- .suspend = ax88179_suspend,
- .resume = ax88179_resume,
- .reset_resume = ax88179_resume,
+ .suspend = ax88179_suspend_wrapper,
+ .resume = ax88179_resume_wrapper,
+ .reset_resume = ax88179_resume_wrapper,
.disconnect = ax88179_disconnect,
.supports_autosuspend = 1,
.disable_hub_initiated_lpm = 1,
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index ea19700c8230f7b51bd1f6edf44133c31b7298a1..6ccfd8c1c93ffcab7ede6cc00ce0bbd0d5fb30ba 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -493,3 +493,22 @@ int ax88179_set_mac_addr(struct net_device *net, void *p)
return 0;
}
+int ax88179_suspend_wrapper(struct usb_interface *intf, pm_message_t message)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct ax88179_data *priv;
+
+ priv = dev->driver_priv;
+
+ return priv->suspend(intf, message);
+}
+
+int ax88179_resume_wrapper(struct usb_interface *intf)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct ax88179_data *priv;
+
+ priv = dev->driver_priv;
+ return priv->resume(intf);
+}
+
diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
index adc2b4598bb52d80c18a6e2cc467144d3bd57a88..401c0bed3d28b4b94f0a0cad7509cd7087ef0639 100644
--- a/drivers/net/usb/ax88179_lib.h
+++ b/drivers/net/usb/ax88179_lib.h
@@ -315,6 +315,8 @@ struct ax88179_data {
struct phy_device *phydev;
struct phylink *phylink;
struct phylink_config phylink_config;
+ int (*resume)(struct usb_interface *intf);
+ int (*suspend)(struct usb_interface *intf, pm_message_t message);
};
struct ax88179_int_data {
@@ -348,6 +350,8 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features);
void ax88179_get_mac_addr(struct usbnet *dev);
int ax88179_change_mtu(struct net_device *net, int new_mtu);
int ax88179_set_mac_addr(struct net_device *net, void *p);
+int ax88179_suspend_wrapper(struct usb_interface *intf, pm_message_t message);
+int ax88179_resume_wrapper(struct usb_interface *intf);
extern const struct driver_info ax88179a_info;
extern const struct driver_info ax88772d_info;
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index e86cbe3e4400fa2c8e1a6a864fc8e1d328f13724..e9913a7c703905c9422405c8158bf068f1d94083 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -5,6 +5,8 @@
#include <linux/if_vlan.h>
#include "ax88179_lib.h"
+static int ax88179a_reset(struct usbnet *dev);
+
#define AX88279_EEPROM_LEN 0x4000
#define AX88179A_EEPROM_LEN (32 * 20)
@@ -126,6 +128,63 @@ static void ax88179a_status(struct usbnet *dev, struct urb *urb)
phylink_mac_interrupt(data->phylink);
}
+static int ax88179a_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct ax88179_data *priv;
+ u8 tmp8 = 0;
+ u16 tmp16;
+ int ret;
+
+ priv = dev->driver_priv;
+ ax88179_set_pm_mode(dev, true);
+
+ if (netif_running(dev->net)) {
+ rtnl_lock();
+ phylink_suspend(priv->phylink, !!priv->wolopts);
+ rtnl_unlock();
+ }
+
+ ret = usbnet_suspend(intf, message);
+ if (ret && netif_running(dev->net)) {
+ rtnl_lock();
+ phylink_resume(priv->phylink);
+ rtnl_unlock();
+ ax88179_set_pm_mode(dev, false);
+ return ret;
+ }
+
+ /* Enable WoL */
+ if (priv->wolopts) {
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8);
+ tmp8 &= ~(AX_MONITOR_MODE_RWLC | AX_MONITOR_MODE_RWMP);
+ if (priv->wolopts & WAKE_PHY)
+ tmp8 |= AX_MONITOR_MODE_RWLC;
+ if (priv->wolopts & WAKE_MAGIC)
+ tmp8 |= AX_MONITOR_MODE_RWMP;
+
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8);
+
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16);
+ tmp16 |= AX_MEDIUM_RECEIVE_EN;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16);
+
+ if (priv->chip_version == AX_VERSION_AX88279)
+ ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 8,
+ EPHY_LOW_POWER_EN | S5_WOL_EN
+ | S5_WOL_LOW_POWER | 0x8000, 0, NULL);
+ else
+ ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 0,
+ EPHY_LOW_POWER_EN, 0, NULL);
+
+ } else if (priv->chip_version == AX_VERSION_AX88279) {
+ ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 8, 0x8000, 0, NULL);
+ }
+
+ ax88179_set_pm_mode(dev, false);
+ return 0;
+}
+
static int ax88179a_auto_detach(struct usbnet *dev)
{
u16 tmp16;
@@ -135,6 +194,47 @@ static int ax88179a_auto_detach(struct usbnet *dev)
return 0;
}
+static int ax88179a_resume(struct usb_interface *intf)
+{
+ struct usbnet *dev = usb_get_intfdata(intf);
+ struct ax88179_data *ax179_data;
+ u8 reg8 = 0;
+ int ret;
+
+ ax179_data = dev->driver_priv;
+ ax88179_set_pm_mode(dev, true);
+
+ ret = ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
+ if (ret < 0)
+ goto err_out;
+
+ if (!(reg8 & AX_PHY_POWER)) {
+ reg8 = AX_PHY_POWER;
+ ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
+ msleep(250);
+ }
+ ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
+
+ /* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle.
+ * PHY-power is re-enabled in ax88179a_reset()
+ */
+ ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
+ msleep(250);
+
+ ax88179a_reset(dev);
+
+ if (netif_running(dev->net)) {
+ rtnl_lock();
+ phylink_resume(ax179_data->phylink);
+ rtnl_unlock();
+ }
+
+err_out:
+ ax88179_set_pm_mode(dev, false);
+
+ return usbnet_resume(intf);
+}
+
static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
{
struct ax88179_data *ax179_data = dev->driver_priv;
@@ -749,6 +849,9 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
ax179_data->eeprom_wen = 0;
}
+ ax179_data->resume = ax88179a_resume;
+ ax179_data->suspend = ax88179a_suspend;
+
dev->net->netdev_ops = &ax88179a_netdev_ops;
dev->net->ethtool_ops = &ax88179a_ethtool_ops;
dev->net->needed_headroom = 8;
@@ -796,6 +899,13 @@ static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
if (ret)
goto initphy_err;
+ /* Keep this interface runtime-PM active by taking a usage ref.
+ * Prevents runtime suspend while bound and avoids resume paths
+ * that could deadlock (autoresume under RTNL while USB PM lock
+ * is held, phylink/MDIO wants RTNL).
+ */
+ pm_runtime_get_noresume(&intf->dev);
+
return 0;
initphy_err:
@@ -823,7 +933,8 @@ static void ax88179a_unbind(struct usbnet *dev, struct usb_interface *intf)
ax88179a_mdio_unregister(ax179_data);
ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
-
+ /* Drop the PM usage ref taken in bind() */
+ pm_runtime_put_noidle(&intf->dev);
kfree(ax179_data);
}
@@ -1108,7 +1219,11 @@ static int ax88179a_reset(struct usbnet *dev)
/* Ensure EEE state is off, consistent with phylink setup */
ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, false, 0, NULL);
- phylink_start(ax179_data->phylink);
+ /* ax88179a_reset() may also be called from resume context,
+ * phylink_resume() will start phylink, then.
+ */
+ if (!ax179_data->in_pm)
+ phylink_start(ax179_data->phylink);
return 0;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (8 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information Birger Koblitz
` (2 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Add ax88179a_get_drvinfo() as implementation of get_drvinfo, in order
to provide information about the device firmware.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/usb/ax88179a_devices.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index e9913a7c703905c9422405c8158bf068f1d94083..4ba2b85a5ce6f95dfb182c67667e0565d02877b4 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -235,6 +235,20 @@ static int ax88179a_resume(struct usb_interface *intf)
return usbnet_resume(intf);
}
+static void ax88179a_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
+{
+ struct ax88179_data *priv = netdev2data(net);
+
+ /* Inherit standard device info */
+ usbnet_get_drvinfo(net, info);
+ if (priv->chip_version < AX_VERSION_AX88179A)
+ return;
+
+ snprintf(info->fw_version, sizeof(info->fw_version), "%d.%d.%d.%d",
+ priv->fw_version[0], priv->fw_version[1],
+ priv->fw_version[2], priv->fw_version[3]);
+}
+
static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
{
struct ax88179_data *ax179_data = dev->driver_priv;
@@ -340,6 +354,7 @@ static int ax88179a_set_eee(struct net_device *net, struct ethtool_keee *edata)
}
static const struct ethtool_ops ax88179a_ethtool_ops = {
+ .get_drvinfo = ax88179a_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel,
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (9 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz, Nicolai Buchwitz
Add additionally supported devices to Kconfig description, add
further requirements such as PHYLINK.
Update driver name in usb_driver and update MODULE_DESCRIPTION
to include the additionally supported chips.
Change Kconfig symbol name from USB_NET_AX88179_178A to USB_NET_AX88179
and update loongson32_defconfig/loongson64_defconfig to use that
symbol.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
---
arch/loongarch/configs/loongson32_defconfig | 2 +-
arch/loongarch/configs/loongson64_defconfig | 2 +-
drivers/net/usb/Kconfig | 10 +++++++---
drivers/net/usb/Makefile | 2 +-
drivers/net/usb/ax88179_178a.c | 8 ++++----
5 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/arch/loongarch/configs/loongson32_defconfig b/arch/loongarch/configs/loongson32_defconfig
index dbef879935f322e6167b4ed878ad4cb20048c6db..a039b8e8d242073c16ea281cf8734068a1a9b29f 100644
--- a/arch/loongarch/configs/loongson32_defconfig
+++ b/arch/loongarch/configs/loongson32_defconfig
@@ -641,7 +641,7 @@ CONFIG_USB_RTL8150=m
CONFIG_USB_RTL8152=m
CONFIG_USB_USBNET=m
# CONFIG_USB_NET_AX8817X is not set
-# CONFIG_USB_NET_AX88179_178A is not set
+# CONFIG_USB_NET_AX88179 is not set
CONFIG_USB_NET_CDC_EEM=m
CONFIG_USB_NET_HUAWEI_CDC_NCM=m
CONFIG_USB_NET_CDC_MBIM=m
diff --git a/arch/loongarch/configs/loongson64_defconfig b/arch/loongarch/configs/loongson64_defconfig
index 4f8bb77b8a1e6e30bcbd6e96d7e38b5054630ea0..b30415c03fa185cae77e81ded929c743ef22ee40 100644
--- a/arch/loongarch/configs/loongson64_defconfig
+++ b/arch/loongarch/configs/loongson64_defconfig
@@ -657,7 +657,7 @@ CONFIG_USB_RTL8150=m
CONFIG_USB_RTL8152=m
CONFIG_USB_USBNET=m
# CONFIG_USB_NET_AX8817X is not set
-# CONFIG_USB_NET_AX88179_178A is not set
+# CONFIG_USB_NET_AX88179 is not set
CONFIG_USB_NET_CDC_EEM=m
CONFIG_USB_NET_HUAWEI_CDC_NCM=m
CONFIG_USB_NET_CDC_MBIM=m
diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 115990b0d9537aea2776544c9768b034f32aa7a4..27862e380bdb4a54d184fa8618e4c9b348354e84 100644
--- a/drivers/net/usb/Kconfig
+++ b/drivers/net/usb/Kconfig
@@ -195,11 +195,12 @@ config USB_NET_AX8817X
This driver creates an interface named "ethX", where X depends on
what other networking devices you have in use.
-config USB_NET_AX88179_178A
- tristate "ASIX AX88179/178A USB 3.0/2.0 to Gigabit Ethernet"
+config USB_NET_AX88179
+ tristate "ASIX AX88179/179A/178A USB 2.0/3.0 to 100M/1G/2.5GbE Adapters"
depends on USB_USBNET
- select CRC32
select PHYLINK
+ select AX88796B_PHY
+ select CRC32
default y
help
This option adds support for ASIX AX88179 based USB 3.0/2.0
@@ -207,6 +208,9 @@ config USB_NET_AX88179_178A
This driver should work with at least the following devices:
* ASIX AX88179
+ * ASIX AX88179A/B
+ * ASIX AX88279
+ * ASIX AX88772D/E
* ASIX AX88178A
* Sitcomm LN-032
diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
index 2ecead0181eaf8d66fb6f5dbd6f2905b22b6eb57..4026f19ecb826a6465de38f1d9bb0e4c0af80421 100644
--- a/drivers/net/usb/Makefile
+++ b/drivers/net/usb/Makefile
@@ -12,7 +12,7 @@ obj-$(CONFIG_USB_HSO) += hso.o
obj-$(CONFIG_USB_LAN78XX) += lan78xx.o
obj-$(CONFIG_USB_NET_AX8817X) += asix.o
asix-y := asix_devices.o asix_common.o ax88172a.o
-obj-$(CONFIG_USB_NET_AX88179_178A) += ax88179.o
+obj-$(CONFIG_USB_NET_AX88179) += ax88179.o
ax88179-y := ax88179_178a.o ax88179a_devices.o ax88179_lib.o
obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o
obj-$(CONFIG_USB_NET_CDC_EEM) += cdc_eem.o
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index ef0856627d08b456afb1569e8a4459b0e526ab46..ff68454667b08237252ccb57b0aae229f017e8e2 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -1355,8 +1355,8 @@ static const struct usb_device_id products[] = {
};
MODULE_DEVICE_TABLE(usb, products);
-static struct usb_driver ax88179_178a_driver = {
- .name = "ax88179_178a",
+static struct usb_driver ax88179_driver = {
+ .name = "ax88179",
.id_table = products,
.probe = usbnet_probe,
.suspend = ax88179_suspend_wrapper,
@@ -1367,8 +1367,8 @@ static struct usb_driver ax88179_178a_driver = {
.disable_hub_initiated_lpm = 1,
};
-module_usb_driver(ax88179_178a_driver);
+module_usb_driver(ax88179_driver);
-MODULE_DESCRIPTION("ASIX AX88179/178A based USB 3.0/2.0 Gigabit Ethernet Devices");
+MODULE_DESCRIPTION("ASIX AX88179/179A/178A/279 based USB 3.0/2.0 100M/1G/2.5GbE Controllers");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ax88179_178a");
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (10 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
The AX88179A/772D devices have 32 efuses with 20 bytes each,
which can be randomly programmed. The AX88279 has 16K FLASH.
Provide ethtool read capability for these devices. However,
no write access is provided.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/usb/ax88179_178a.c | 6 +++++
drivers/net/usb/ax88179_lib.c | 56 ++++++++++++++++++++++++++++++++----------
2 files changed, 49 insertions(+), 13 deletions(-)
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index ff68454667b08237252ccb57b0aae229f017e8e2..56fb2e5efeeae3bb1fca26e04b8bee1971f8011a 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -725,6 +725,12 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
ax88179_reset(dev);
return 0;
+
+err_nodev:
+ kfree(ax179_data);
+ ax179_data = NULL;
+
+ return ret;
}
static void ax88179_unbind(struct usbnet *dev, struct usb_interface *intf)
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index 6ccfd8c1c93ffcab7ede6cc00ce0bbd0d5fb30ba..32787b02d7889b1c82b829a55f1f1ede91ae684a 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -210,36 +210,61 @@ int ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
return 0;
}
+static void
+ax88179_eeprom_access_params(struct ax88179_data *ax179_data, int i, u16 *value, u16 *idx)
+{
+ /* AX88179 has a word-addressable EEPROM
+ * AX88179A uses EFUSES with 20 bytes length
+ * AX88279 has an EEPROM addressable in 256 byte blocks
+ */
+ if (ax179_data->chip_version < AX_VERSION_AX88179A) {
+ *value = i;
+ *idx = 1;
+ } else if (ax179_data->chip_version == AX_VERSION_AX88279) {
+ *value = (i * ax179_data->eeprom_block) >> 16;
+ *idx = (i * ax179_data->eeprom_block) & 0xffff;
+ } else {
+ *value = i << 4;
+ *idx = 0;
+ }
+}
+
int ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
{
struct usbnet *dev = netdev_priv(net);
- u16 *eeprom_buff;
- int first_word, last_word;
- int i, ret;
+ struct ax88179_data *ax179_data;
+ int first, last, i, ret;
+ u8 *eeprom_buff;
+
+ ax179_data = dev->driver_priv;
if (eeprom->len == 0)
return -EINVAL;
eeprom->magic = AX88179_EEPROM_MAGIC;
- first_word = eeprom->offset >> 1;
- last_word = (eeprom->offset + eeprom->len - 1) >> 1;
- eeprom_buff = kmalloc_array(last_word - first_word + 1, sizeof(u16),
- GFP_KERNEL);
+ first = eeprom->offset / ax179_data->eeprom_block;
+ last = (eeprom->offset + eeprom->len - 1) / ax179_data->eeprom_block;
+
+ eeprom_buff = kzalloc((last - first + 1) * ax179_data->eeprom_block, GFP_KERNEL);
if (!eeprom_buff)
return -ENOMEM;
- /* ax88179/178A returns 2 bytes from eeprom on read */
- for (i = first_word; i <= last_word; i++) {
- ret = __ax88179_read_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2,
- &eeprom_buff[i - first_word]);
- if (ret < 0) {
+ for (i = first; i <= last; i++) {
+ u16 value, idx;
+
+ ax88179_eeprom_access_params(ax179_data, i, &value, &idx);
+ ret = __ax88179_read_cmd(dev, ax179_data->eeprom_read_cmd,
+ value, idx, ax179_data->eeprom_block,
+ eeprom_buff + (i - first) * ax179_data->eeprom_block);
+
+ if (ret != ax179_data->eeprom_block) {
kfree(eeprom_buff);
return -EIO;
}
}
- memcpy(data, (u8 *)eeprom_buff + (eeprom->offset & 1), eeprom->len);
+ memcpy(data, eeprom_buff + eeprom->offset % ax179_data->eeprom_block, eeprom->len);
kfree(eeprom_buff);
return 0;
}
@@ -247,12 +272,17 @@ int ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8
int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
{
struct usbnet *dev = netdev_priv(net);
+ struct ax88179_data *ax179_data;
u16 *eeprom_buff;
int first_word;
int last_word;
int ret;
int i;
+ ax179_data = dev->driver_priv;
+ if (ax179_data->chip_version >= AX_VERSION_AX88179A)
+ return -EOPNOTSUPP;
+
netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n",
eeprom->len, eeprom->offset, eeprom->magic);
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (11 preceding siblings ...)
2026-10-08 3:51 ` [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-10-08 3:51 ` Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
12 siblings, 1 reply; 27+ messages in thread
From: Birger Koblitz @ 2026-10-08 3:51 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Jakub Kicinski, Paolo Abeni,
Russell King, Andrew Lunn, Heiner Kallweit, Huacai Chen,
WANG Xuerui, Eric Dumazet, Eric Dumazet
Cc: linux-usb, netdev, linux-kernel, loongarch, Jianhui Xu,
Birger Koblitz
The AX88772D, AX88179A and AX88279 are 100MBit, 1GBit and respectively
2.5 GBit PHYs that are found in ASIX USB-Ethernet controllers of the
same name.
Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
drivers/net/phy/ax88796b.c | 165 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 165 insertions(+)
diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
index f20ddf64914993f7097070d1f56c1103ec1e40e8..99abb0af3296dc05b86f38d211e1bf96b5dc468a 100644
--- a/drivers/net/phy/ax88796b.c
+++ b/drivers/net/phy/ax88796b.c
@@ -13,6 +13,19 @@
#define PHY_ID_ASIX_AX88772A 0x003b1861
#define PHY_ID_ASIX_AX88772C 0x003b1881
#define PHY_ID_ASIX_AX88796B 0x003b1841
+#define PHY_ID_ASIX_AX88772D 0x003b772d
+#define PHY_ID_ASIX_AX88179A 0x003b179a
+#define PHY_ID_ASIX_AX88279 0x003b2790
+
+#define AX_ADVERTISE_2500 0x1000
+
+/* MII Vendor registers */
+#define AX_CTRL_STATUS 0x1d
+#define AX_CTRL_STATUS_SPEED_MASK 0x0c
+#define AX_CTRL_STATUS_SPEED_10 0x0
+#define AX_CTRL_STATUS_SPEED_100 0x4
+#define AX_CTRL_STATUS_SPEED_1000 0x8
+#define AX_CTRL_STATUS_SPEED_2500 0xc
MODULE_DESCRIPTION("Asix PHY driver");
MODULE_AUTHOR("Michael Schmitz <schmitzmic@gmail.com>");
@@ -83,6 +96,107 @@ static int asix_ax88772a_read_status(struct phy_device *phydev)
return 0;
}
+static int asix_ax88279_read_status(struct phy_device *phydev)
+{
+ int ret, val;
+
+ ret = genphy_read_status(phydev);
+ if (ret)
+ return ret;
+
+ if (!phydev->link)
+ return 0;
+
+ /* Read actual speed from vendor register */
+ val = phy_read(phydev, AX_CTRL_STATUS);
+ if (val < 0)
+ return val;
+
+ switch (val & AX_CTRL_STATUS_SPEED_MASK) {
+ case AX_CTRL_STATUS_SPEED_2500:
+ phydev->speed = SPEED_2500;
+ break;
+ case AX_CTRL_STATUS_SPEED_1000:
+ phydev->speed = SPEED_1000;
+ break;
+ case AX_CTRL_STATUS_SPEED_100:
+ phydev->speed = SPEED_100;
+ break;
+ case AX_CTRL_STATUS_SPEED_10:
+ phydev->speed = SPEED_10;
+ }
+
+ /* MDIO_AN_10GBT_STAT_LP2_5G is broken, but we can deduce that
+ * the link-partner advertised 2500M if remotely AN succceded
+ * for link speed > 1000M and we locally have a link speed of
+ * 2500M
+ */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->lp_advertising);
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+ if (val >= 0 && val & MDIO_AN_10GBT_STAT_REMOK) {
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+ phydev->lp_advertising,
+ phydev->speed == SPEED_2500);
+ }
+ /* Only supports full duplex */
+ phydev->duplex = DUPLEX_FULL;
+
+ /* PHY switches interface between 2.5GBit and slower modes */
+ if (phydev->speed == SPEED_2500)
+ phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+ else
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+
+ return 0;
+}
+
+static int asix_ax88279_config_aneg(struct phy_device *phydev)
+{
+ bool adv_2500;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE) {
+ phydev_warn(phydev, "Disabling autoneg is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ adv_2500 = linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising);
+ ret = phy_modify(phydev, MII_ADVERTISE, AX_ADVERTISE_2500,
+ adv_2500 ? AX_ADVERTISE_2500 : 0);
+ if (ret < 0)
+ return ret;
+
+ return genphy_config_aneg(phydev);
+}
+
+static int asix_ax88279_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */
+ phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+ linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array),
+ phydev->supported);
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* AX88279 does not support reported 100baseT-half duplex mode */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported);
+
+ return 0;
+}
+
+static int asix_ax88279_config_init(struct phy_device *phydev)
+{
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces);
+
+ return 0;
+}
+
static void asix_ax88772a_link_change_notify(struct phy_device *phydev)
{
/* Reset PHY, otherwise MII_LPA will provide outdated information.
@@ -94,6 +208,25 @@ static void asix_ax88772a_link_change_notify(struct phy_device *phydev)
}
}
+static int asix_ax88772D_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */
+ phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+ ret = genphy_read_abilities(phydev);
+ if (ret < 0)
+ return ret;
+
+ /* AX88772D does not support (possibly) reported 1000baseT modes */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported);
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, phydev->supported);
+
+ return 0;
+}
+
static struct phy_driver asix_driver[] = {
{
PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
@@ -111,6 +244,35 @@ static struct phy_driver asix_driver[] = {
.suspend = genphy_suspend,
.resume = genphy_resume,
.soft_reset = asix_soft_reset,
+}, {
+ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772D),
+ .name = "Asix Electronics AX88772D",
+ .flags = PHY_IS_INTERNAL,
+ .get_features = asix_ax88772D_get_features,
+ .read_mmd = genphy_read_mmd_c45,
+ .write_mmd = genphy_write_mmd_c45,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+}, {
+ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88179A),
+ .name = "Asix Electronics AX88179A",
+ .flags = PHY_IS_INTERNAL,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .read_mmd = genphy_read_mmd_c45,
+ .write_mmd = genphy_write_mmd_c45,
+}, {
+ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88279),
+ .name = "Asix Electronics AX88279",
+ .flags = PHY_IS_INTERNAL | PHY_BROKEN_FORCED,
+ .get_features = asix_ax88279_get_features,
+ .read_status = asix_ax88279_read_status,
+ .config_aneg = asix_ax88279_config_aneg,
+ .config_init = asix_ax88279_config_init,
+ .read_mmd = genphy_read_mmd_c45,
+ .write_mmd = genphy_write_mmd_c45,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
}, {
PHY_ID_MATCH_MODEL(PHY_ID_ASIX_AX88796B),
.name = "Asix Electronics AX88796B",
@@ -124,6 +286,9 @@ static const struct mdio_device_id __maybe_unused asix_tbl[] = {
{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) },
{ PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) },
{ PHY_ID_MATCH_MODEL(PHY_ID_ASIX_AX88796B) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772D) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88179A) },
+ { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88279) },
{ }
};
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs
2026-10-08 3:51 ` [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> The AX88179A uses a simple HW configuration for EEE
> via a single EEE configuration register without LPI timer support
> Add support for setting this EEE enable/disable register in implementations
> of mac_enable_tx_lpi/mac_disable_tx_lpi.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=5
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A
2026-10-08 3:51 ` [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] net: usb: ax88179a: missing WE command in VLAN filter table init loop
- [Medium] net: usb: ax88179a: ignored USB write errors in VLAN filter init
- [Medium] net: usb: ax88179a: uninitialized stack memory use on short USB read
--
commit 86960e6590c9a326bcedbb3ba8fde9f53c488e20
Author: Birger Koblitz <mail@birger-koblitz.de>
ax88179_178a: Add VLAN offload support for AX88179A
The AX88179A-based chips support VLAN offload. Add configuration
support in netdev_ops. Features supported are:
NETIF_F_HW_VLAN_CTAG_TX, NETIF_F_HW_VLAN_CTAG_RX
and NETIF_F_HW_VLAN_CTAG_FILTER.
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index c41d9c2dc737a..bdc67cbc17287 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -258,6 +258,88 @@ static const struct ethtool_ops ax88179a_ethtool_ops = {
> .get_ts_info = ethtool_op_get_ts_info,
> };
>
> +static int ax88179a_vlan_rx_kill_vid(struct net_device *net, __be16 proto, u16 vid)
> +{
> + struct usbnet *dev = netdev_priv(net);
> + u8 vlan_ctrl;
> + int ret = 0;
> + u16 reg16;
> + u8 reg8;
> +
> + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
> + if (ret < 0)
> + return ret;
[Severity: Medium]
If a malicious or buggy USB device responds to the read request with a
zero-length packet, ax88179_read_cmd() will return 0 and skip copying data,
leaving reg8 uninitialized.
Since the error check only verifies if ret < 0, does this allow uninitialized
stack memory to be used for hardware control and eventually written back to
the MAC? This also applies to the reads in ax88179a_vlan_rx_add_vid() and
ax88179a_set_features().
[ ... ]
> +static int ax88179a_set_features(struct net_device *net, netdev_features_t features)
> +{
> + struct usbnet *dev = netdev_priv(net);
> + netdev_features_t changed;
> + int ret;
> + u8 tmp;
> +
> + changed = net->features ^ features;
> +
> + ret = ax88179_set_features(net, features);
> + if (ret)
> + return ret;
> +
> + if (changed & NETIF_F_HW_VLAN_CTAG_FILTER) {
> + if (features & NETIF_F_HW_VLAN_CTAG_FILTER) {
> + for (int i = 0; i < 256; i++) {
> + /* Address */
> + tmp = i;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS,
> + 1, 1, &tmp);
> + /* Data */
> + ax88179_write_u16(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0,
> + 2, 0);
> + }
[Severity: Medium]
These ax88179_write_cmd() and ax88179_write_u16() calls lack error checking.
Could this prevent an early abort on device disconnect or hardware failure,
resulting in the thread blocking through 512 failing USB control transfers?
[Severity: High]
Does this loop fail to actually initialize the hardware filter table for the
first 255 entries?
The write enable command (AX_VLAN_CONTROL_WE) is only sent once after the
loop finishes, which suggests that only the last address (255) is written
to the SRAM while the first 255 entries are ignored. Should the WE trigger be
included inside the loop?
> + ret = ax88179_read_cmd(dev, AX_ACCESS_MAC,
> + AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> + if (ret < 0)
> + return ret;
> + tmp |= AX_VLAN_CONTROL_VFE | AX_VLAN_CONTROL_WE;
> + ret = ax88179_write_cmd(dev, AX_ACCESS_MAC,
> + AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> + if (ret < 0)
> + return ret;
> + } else {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=7
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code
2026-10-08 3:51 ` [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] net: incomplete locking of AX_MEDIUM_STATUS_MODE using medium_status_mutex
- [Medium] net: logic error evaluating old MTU value for jumbo frame config
- [Medium] net: ax88179: data race on rxctl between set_multicast and link_reset
--
commit c3c28c1c33511966acdb47b60e1d21464314591e
Author: Birger Koblitz <mail@birger-koblitz.de>
Subject: ax88179_178a: Split driver into library and device specific code
This commit splits the ax88179_178a module code into a common library for the
AX88179/178a/179a and 279 controllers, and device-specific code. The module
is renamed to ax88179 to reflect the broader scope.
> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> --- /dev/null
> +++ b/drivers/net/usb/ax88179_lib.c
[ ... ]
> +void ax88179_set_multicast(struct net_device *net)
> +{
> + struct usbnet *dev = netdev_priv(net);
> + struct ax88179_data *data = dev->driver_priv;
[ ... ]
> + if (net->flags & IFF_PROMISC) {
> + data->rxctl |= AX_RX_CTL_PRO;
> + } else if (net->flags & IFF_ALLMULTI ||
[ ... ]
> + data->rxctl |= AX_RX_CTL_AM;
> + }
> +
> + ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_RX_CTL,
> + 2, 2, &data->rxctl);
> +}
[Severity: Medium]
Can a data race occur on rxctl between this asynchronous write and the
synchronous write in ax88179_link_reset()?
The ax88179_link_reset() function runs in a workqueue and reads
ax179_data->rxctl to write it synchronously to the hardware. Since
ax88179_set_multicast() runs in softirq context, it can preempt
ax88179_link_reset().
If ax88179_set_multicast() interrupts ax88179_link_reset() after the
latter has read ax179_data->rxctl but before its synchronous USB
control message is submitted:
1. ax88179_set_multicast() updates ax179_data->rxctl and enqueues an
asynchronous write URB.
2. ax88179_link_reset() resumes and submits a synchronous write URB
with the old rxctl value.
The host controller will execute the async URB first and the sync URB
second. Could this revert the hardware to the old multicast configuration
while the driver state retains the new configuration, potentially causing
dropped multicast packets until the configuration is changed again?
[ ... ]
> +int ax88179_change_mtu(struct net_device *net, int new_mtu)
> +{
> + struct usbnet *dev = netdev_priv(net);
> + struct ax88179_data *data;
> + u16 tmp16;
> + int err;
> +
> + data = dev->driver_priv;
> + mutex_lock(&data->medium_status_mutex);
[Severity: High]
Is this mutex fully protecting the AX_MEDIUM_STATUS_MODE register?
This newly added medium_status_mutex correctly serializes accesses to
the AX_MEDIUM_STATUS_MODE register in ax88179_change_mtu(). However,
other functions like ax88179_link_reset(), ax88179_net_reset(), and
ax88179_stop() perform read-modify-write operations on the exact same
register without acquiring this mutex.
Since ax88179_link_reset() is executed asynchronously from the
usbnet_deferred_kevent workqueue, could it overlap with
ax88179_change_mtu() and cause concurrent lockless modifications
to clobber the MTU or link duplex settings in the hardware?
> + if (net->mtu > 1500) {
[Severity: Medium]
Does this check the old MTU value instead of the new one?
Because WRITE_ONCE(net->mtu, new_mtu) was moved to the end of
ax88179_change_mtu(), this condition evaluates the old net->mtu value.
If the MTU is being increased from 1500 to 9000, jumbo frames would
be incorrectly left disabled in hardware. Conversely, if it is being
decreased from 9000 to 1500, they would be incorrectly left enabled.
> + err = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
> + 2, 2, &tmp16);
[ ... ]
> + tmp16 &= ~AX_MEDIUM_JUMBO_EN;
> + err = ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
> + 2, 2, &tmp16);
> + if (err < 0)
> + goto err_out;
> + }
> + WRITE_ONCE(net->mtu, new_mtu);
> + dev->hard_mtu = net->mtu + net->hard_header_len;
> + mutex_unlock(&data->medium_status_mutex);
> +
> + /* max qlen depend on hard_mtu and rx_urb_size */
> + usbnet_update_max_qlen(dev);
> +
> + return 0;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=3
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt
2026-10-08 3:51 ` [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> Add the phylink_mac_interrupt() function to phylink, which
> passes a link-change interrupt received by the MAC to phylib.
> This function can be used by drivers which want to avoid
> polling the PHY and instead rely on an interrupt received by
> the MAC when a link-change happens.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=1
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration
2026-10-08 3:51 ` [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] net: ax88179_set_multicast() data race on data->rxctl
--
commit 547e4ec41af3f2201955124dd2564e630771c80f
Author: Birger Koblitz <mail@birger-koblitz.de>
ax88179_178a: Add AX179A/AX279 multicast configuration
This commit adds support for conditionally setting the AX_RX_CTL_IPE flag
in AX_RX_CTL based on ip_align. It also ensures AX_RX_CTL_DROPCRCERR is
set to match the initial configuration in ax88179_reset().
> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> index 0efc2eb95e4b3..ea19700c8230f 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -321,10 +321,14 @@ int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8
> void ax88179_set_multicast(struct net_device *net)
> {
> struct usbnet *dev = netdev_priv(net);
> - struct ax88179_data *data = dev->driver_priv;
> u8 *m_filter = ((u8 *)dev->data);
> + struct ax88179_data *data;
> +
> + data = dev->driver_priv;
>
> - data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_IPE);
> + data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_DROPCRCERR);
> + if (data->ip_align)
> + data->rxctl |= AX_RX_CTL_IPE;
[Severity: High]
Does this non-atomic modification of data->rxctl introduce a data race?
It appears data->rxctl is updated here without lock synchronization. If
ax88179a_mac_link_up() is executing concurrently, could it fetch an
intermediate torn state?
For example, a concurrent reader might observe the state like this:
ax88179a_mac_link_up()
ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, READ_ONCE(ax179_data->rxctl));
Could this result in corrupting the hardware register by committing a
partially updated rxctl state to the device?
>
> if (net->flags & IFF_PROMISC) {
> data->rxctl |= AX_RX_CTL_PRO;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=8
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs
2026-10-08 3:51 ` [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> Configure phylink to work with the PHYs in the AX88179A controllers
> and provide ethtool get/set eee operations calling into phylink.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=6
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips
2026-10-08 3:51 ` [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] net: usb: ax88179_178a: UAF on module unbind due to FLAG_AVOID_UNLINK_URBS
- [Medium] net: usb: ax88179_178a: Endianness bug in AX_PAUSE_WATERLVL_LOW/HIGH
- [Medium] net: usb: ax88179_178a: Double VLAN tagging in tx_fixup
- [Medium] net: usb: ax88179_178a: Uninitialized memory written to MAC on read failure
- [Medium] net: usb: ax88179_178a: Uninitialized bulkin_data written on link_sts read failure
- [Medium] net: usb: ax88179_178a: Race condition on AX_MEDIUM_STATUS_MODE
- [Low] net: usb: ax88179_178a: Unconditional AX_TXCOE_CTL overwrite breaks features
--
commit 76ec5050c43a29d1f21256eec4c8a403f01e4f88
Author: Birger Koblitz <mail@birger-koblitz.de>
ax88179_178a: Add HW support for AX179A-based chips
This patch adds hardware support for AX179A-based USB-Ethernet controllers.
It introduces a new ax88179a_bind flow, registers new ethtool ops utilizing
phylink, and maps device IDs for the AX88179A, AX88772D, and AX88279 chips.
> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
[ ... ]
> @@ -355,10 +378,17 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features)
> }
>
> if (changed & NETIF_F_RXCSUM) {
> + /* When we disable rx-checksumming, we need to clear the rx_checksum
> + * flag before disabling the engine
> + */
> + if (!(features & NETIF_F_RXCSUM))
> + data->rx_checksum = 0;
> +
> ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
> tmp ^= AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
> AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6;
> ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 1, &tmp);
[Severity: Medium]
What happens if the USB control read fails here?
If ax88179_read_cmd() returns a negative error due to a USB timeout or
disconnect, the local variable tmp remains uninitialized. The code ignores
the return value, applies a bitwise modification, and writes the uninitialized
stack memory back to the MAC, which can cause unpredictable hardware state
corruption.
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
[ ... ]
> +static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
> +{
[ ... ]
> + if (ax179_data->chip_version == AX_VERSION_AX88279) {
> + bulkin_data = AX88279_BULKIN_SIZE;
> + } else {
> + if (ax179_data->is_ax88772d)
> + bulkin_data = AX88772D_BULKIN_SIZE;
> + else
> + bulkin_data = AX88179A_BULKIN_SIZE;
> + }
> +
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, &bulkin_data[index]);
[Severity: Medium]
Could this write uninitialized bulkin_data to the hardware?
In ax88179a_bulkin_config(), link_sts is populated via ax88179_read_cmd().
If the USB read fails, link_sts remains 0. When speed is ETHER_LINK_100
and link_sts is 0, the logic falls through the AX_USB_SS and AX_USB_HS
checks, leaving index at 0 (or 1 if half-duplex).
For AX88179A_BULKIN_SIZE, index 0 corresponds to BULK_IN_SPEED_2G5, which
is intentionally omitted from the struct initializer and is thus implicitly
zero-filled. The function would then proceed to write 5 bytes of pure zeros
to AX_RX_BULKIN_QCTRL. This could corrupt the MAC's RX DMA timers and size
thresholds, preventing the hardware from buffering packets correctly.
[ ... ]
> +static void ax88179a_mac_link_up(struct phylink_config *config,
> + struct phy_device *phy,
> + unsigned int phy_mode, phy_interface_t interface,
> + int speed, int duplex,
> + bool tx_pause, bool rx_pause)
> +{
> + struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
> + struct ax88179_data *ax179_data = dev->driver_priv;
> + u8 link_sts = 0, reg8[3];
> + u8 bulk_config_speed = 0;
> + u16 mode;
> +
> + /* Stop RX/TX for link configuration */
> + ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, AX_RX_CTL_STOP);
> + ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 0);
> +
> + ax88179_write_u8(dev, AX_ACCESS_MAC, AX88179A_MAC_CDC_DELAY_TX, 1, 0xa5);
> +
> + ax88179_write_u16(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 2, 0x0410);
[Severity: Medium]
Does this 16-bit write accidentally swap the byte ordering of the LOW and HIGH
constants on little-endian hosts?
The ax88179_write_u16 helper internally applies cpu_to_le16s() to the data.
On little-endian architectures, the value 0x0410 is stored in memory as 0x10
(LSB) followed by 0x04 (MSB).
When sent to the MAC, 0x10 is written to the first register
(AX_PAUSE_WATERLVL_LOW) and 0x04 is written to the second
(AX_PAUSE_WATERLVL_HIGH). This dangerously inverses the intended
logic, setting the LOW threshold higher than the HIGH threshold, which may
corrupt the hardware MAC pause frame control logic.
The correct 16-bit sequence here should have been 0x1004.
[ ... ]
> + if (duplex)
> + mode |= AX_MEDIUM_FULL_DUPLEX;
> +
> + mutex_lock(&ax179_data->medium_status_mutex);
> + if (dev->net->mtu > 1500)
> + mode |= AX_MEDIUM_JUMBO_EN;
> + ax88179_write_u16(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, mode);
[Severity: Medium]
Is there a race condition with ax88179_change_mtu() modifying
AX_MEDIUM_STATUS_MODE?
The medium_status_mutex is correctly utilized here to serialize updates to
the AX_MEDIUM_STATUS_MODE register. However, ax88179_change_mtu() performs
an unprotected read-modify-write on the same register without acquiring
this mutex.
Because ax88179a_mac_link_up() executes dynamically in the phylink worker
thread and ax88179_change_mtu() runs under the RTNL lock, these two paths can
execute concurrently. This unprotected race condition can inadvertently drop
or corrupt the jumbo frame enable flag or receive-enable bits during dynamic
link renegotiations.
[ ... ]
> +static void ax88179a_unbind(struct usbnet *dev, struct usb_interface *intf)
> +{
> + struct ax88179_data *ax179_data = dev->driver_priv;
> +
> + /* Configure RX control register => stop operation */
> + ax88179_write_u16(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, AX_RX_CTL_STOP);
> +
> + rtnl_lock();
> + phylink_disconnect_phy(ax179_data->phylink);
> + rtnl_unlock();
> + phylink_destroy(ax179_data->phylink);
> + ax88179a_mdio_unregister(ax179_data);
> +
> + ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
> +
> + kfree(ax179_data);
[Severity: High]
Does this introduce a use-after-free during module unbind due to the driver
registering with FLAG_AVOID_UNLINK_URBS?
During device disconnect or unbind, usbnet_disconnect() brings the network
device down. Due to FLAG_AVOID_UNLINK_URBS, it intentionally skips calling
usbnet_terminate_urbs(), meaning the RX and TX URBs remain active in the
USB host controller.
usbnet_disconnect() then immediately calls this .unbind() callback, which
unconditionally frees the driver's private data via kfree(ax179_data).
When a pending RX URB later completes, it invokes ax88179a_rx_fixup(),
which dereferences the freed ax179_data pointer.
[ ... ]
> +static int ax88179a_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
> +{
> + struct ax88179_data *ax179_data = dev->driver_priv;
[ ... ]
> + pkt_end = 0;
> + while (pkt_cnt--) {
> + u64 pkt_desc = get_unaligned_le64(pkt_desc_ptr);
> + u32 pkt_len_plus_padd;
> + u32 pkt_len;
> +
> + pkt_len = FIELD_GET(AX179A_RX_PD_LEN, pkt_desc);
> + pkt_len -= (ax179_data->ip_align ? 2 : 0);
[Severity: High]
This is where the freed ax179_data pointer is dereferenced by a
concurrently completing RX URB, as mentioned in the ax88179a_unbind() comment.
[ ... ]
> +static struct sk_buff *ax88179a_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
> +{
[ ... ]
> + if (((skb->len + sizeof(tx_desc) + padding_size) % frame_size) == 0) {
> + padding_size += 8;
> + tx_desc |= AX179A_TX_DESC_DROP_PADD;
> + }
> +
> + if ((dev->net->features & NETIF_F_HW_VLAN_CTAG_TX) && (vlan_get_tag(skb, &tci) >= 0)) {
[Severity: Medium]
Will this erroneously double-tag packets that already contain a payload VLAN
tag?
Unlike skb_vlan_tag_present(), vlan_get_tag() falls back to extracting the
tag directly from the SKB payload if it is not present in the SKB's
out-of-band metadata.
If a user transmits a packet that is natively VLAN-tagged inside its
payload (e.g., via a raw socket or bridge), vlan_get_tag() will return
true and extract the tag. The driver then sets AX179A_TX_DESC_VLAN,
incorrectly instructing the hardware MAC to insert a second, duplicate
VLAN tag into the packet. This can cause double-tagging protocol violations.
[ ... ]
> +static int ax88179a_reset(struct usbnet *dev)
> +{
[ ... ]
> + /* Enable checksum offload */
> + if (dev->net->features & NETIF_F_RXCSUM) {
> + ax88179_write_u8(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1,
> + AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |
> + AX_RXCOE_TCPV6 | AX_RXCOE_UDPV6);
> + ax179_data->rx_checksum = 1;
> + } else {
> + ax179_data->rx_checksum = 0;
> + ax88179_write_u8(dev, AX_ACCESS_MAC, AX_RXCOE_CTL, 1, 0);
> + }
> +
> + ax88179_write_u8(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1,
> + AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP |
> + AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6);
[Severity: Low]
Does this unconditional hardware enablement break the ethtool netdev
feature synchronization?
This logic blindly enables all transmit checksum offloads without checking
against the OS's dev->net->features. If a system administrator manually
disables TX checksums via ethtool, the feature bit is cleared.
If the device subsequently resets (e.g., from an MTU change or USB
timeout), hardware TX checksumming will be re-enabled without the OS's
knowledge, desynchronizing the netdev feature state from the active
hardware state.
[ ... ]
> +static int ax88179a_stop(struct usbnet *dev)
> +{
> + struct ax88179_data *ax179_data = dev->driver_priv;
> + u16 reg16;
> +
> + phylink_stop(ax179_data->phylink);
> +
> + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
> + reg16 &= ~AX_MEDIUM_RECEIVE_EN;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
[Severity: Medium]
Similar to the issue in ax88179_set_features(), could this write uninitialized
memory back to the hardware?
An uninitialized stack variable reg16 is passed to ax88179_read_cmd() to read
the MAC's medium status mode. If the USB control read fails, reg16 is left
containing uninitialized stack memory. The code ignores the return value,
applies a bitwise modification, and writes the garbage back via
ax88179_write_cmd().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=4
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode
2026-10-08 3:51 ` [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] phy: TOCTOU race allowing NULL pointer dereference in phy_mii_ioctl and phy_ethtool_ksettings_set
- [Medium] net: phy: data race modifying bitfields in phy_mii_ioctl
- [Medium] net: phy: SIOCSMIIREG C45 writes misinterpret MMDs as C22 registers
--
commit 38a1dbebb552f1b80a0aa4bb3db03459a68b3811
Author: Birger Koblitz <mail@birger-koblitz.de>
phylib: Add support for PHYs with broken forced mode
This adds support for PHYs which only support auto-negotiated speed modes,
preventing a kernel warning and stack-trace when PHYs return -EOPNOTSUPP
from config_aneg() for forced modes.
> diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
> index fce9bc7be3304..079c53516f424 100644
> --- a/drivers/net/phy/phy.c
> +++ b/drivers/net/phy/phy.c
> @@ -354,6 +354,9 @@ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd)
> switch (devad) {
> case MII_BMCR:
[Severity: Medium]
Does this switch statement evaluate Clause 45 MMDs against Clause 22
register macros?
In phy_mii_ioctl(), devad contains the MMD for C45 writes:
case SIOCSMIIREG:
if (mdio_phy_id_is_c45(mii_data->phy_id)) {
prtad = mdio_phy_id_prtad(mii_data->phy_id);
devad = mdio_phy_id_devad(mii_data->phy_id);
If a user issues an SIOCSMIIREG ioctl targeting a Clause 45 PHY and
writes to MMD 4 (PHY XS), devad will be 4. Doesn't this hit the
MII_ADVERTISE case below, incorrectly corrupting phydev->advertising?
Additionally, will the newly added logic here improperly intercept C45 writes
to MMD 0?
> if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) {
> + if (phydev->drv &&
> + (phydev->drv->flags & PHY_BROKEN_FORCED))
[Severity: High]
Can the compiler double-fetch phydev->drv here without locks, risking a NULL
pointer dereference?
If an asynchronous driver unbind occurs via sysfs:
echo <phy_id> > /sys/bus/mdio_bus/drivers/<drv>/unbind
This calls phy_remove(), which clears phydev->drv to NULL without acquiring
phydev->lock. Could the second fetch of phydev->drv in the condition above
yield NULL, leading to a kernel panic when dereferencing flags?
> + return -EOPNOTSUPP;
> if (phydev->autoneg == AUTONEG_ENABLE)
> change_autoneg = true;
> phydev->autoneg = AUTONEG_DISABLE;
[Severity: Medium]
Is it safe to modify phydev->autoneg and phydev->duplex locklessly here?
These fields are part of a bitfield block in struct phy_device:
unsigned autoneg:1;
unsigned link:1;
unsigned autoneg_complete:1;
phy_state_machine() modifies adjacent bitfields (like link and
autoneg_complete) asynchronously while holding phydev->lock. Because they
share the same memory word, could this lockless assignment in phy_mii_ioctl()
cause a read-modify-write race that silently overwrites the state machine's
updates?
[ ... ]
> @@ -1190,6 +1193,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
> duplex != DUPLEX_FULL)))
> return -EINVAL;
>
> + if (autoneg == AUTONEG_DISABLE && phydev->drv &&
> + (phydev->drv->flags & PHY_BROKEN_FORCED))
[Severity: High]
Similar to the check in phy_mii_ioctl(), can the compiler double-fetch
phydev->drv here before phydev->lock is taken?
If phy_remove() clears phydev->drv concurrently, could the second load yield
NULL and cause a dereference crash?
> + return -EOPNOTSUPP;
> +
> mutex_lock(&phydev->lock);
> phydev->autoneg = autoneg;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=2
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo
2026-10-08 3:51 ` [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> Add ax88179a_get_drvinfo() as implementation of get_drvinfo, in order
> to provide information about the device firmware.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=10
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
2026-10-08 3:51 ` [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] net: ax88179a_reset() clobbers user-configured MAC address during resume
- [Medium] net: ax88179a_resume() skips phylink_resume() on hardware read error
- [Medium] ax88179_178a: ignores usbnet_suspend() return value in suspend
--
commit d715e3f0710659f4025065a805e16bf7832459fd
Author: Birger Koblitz <mail@birger-koblitz.de>
ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
The suspend and resume functions are called via the usb_driver structure,
for which the driver has only a single instance. Add wrapper functions
for the different implementations for the AX88179 and AX179A architecutres
in ax88179_lib which calls the 2 implementations in ax88179_178a and
ax88179a_devices, respectively.
> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 071b03b4f145c..ef0856627d08b 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c
[ ... ]
> @@ -1356,9 +1359,9 @@ static struct usb_driver ax88179_178a_driver = {
> .name = "ax88179_178a",
> .id_table = products,
> .probe = usbnet_probe,
> - .suspend = ax88179_suspend,
> - .resume = ax88179_resume,
> - .reset_resume = ax88179_resume,
> + .suspend = ax88179_suspend_wrapper,
[Severity: Medium]
If usbnet_suspend() aborts an autosuspend (e.g. because of an active TX
queue, returning -EBUSY), ax88179_suspend() ignores the error and continues
to force the MAC and PHY into low-power mode (AX_MEDIUM_RECEIVE_EN disabled,
AX_RX_CTL_STOP, etc.).
This stalls the active network stack because the hardware is stopped but the
USB core reports success to the PM core. The patch refactors this into
ax88179_suspend_wrapper without fixing the underlying bug.
Does this need to be fixed to properly handle usbnet_suspend() return values
in ax88179_suspend() to avoid hardware stalling?
> + .resume = ax88179_resume_wrapper,
> + .reset_resume = ax88179_resume_wrapper,
> .disconnect = ax88179_disconnect,
> .supports_autosuspend = 1,
> .disable_hub_initiated_lpm = 1,
[ ... ]
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index e86cbe3e4400f..e9913a7c70390 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
[ ... ]
> @@ -135,6 +194,47 @@ static int ax88179a_auto_detach(struct usbnet *dev)
> return 0;
> }
>
> +static int ax88179a_resume(struct usb_interface *intf)
> +{
> + struct usbnet *dev = usb_get_intfdata(intf);
> + struct ax88179_data *ax179_data;
> + u8 reg8 = 0;
> + int ret;
> +
> + ax179_data = dev->driver_priv;
> + ax88179_set_pm_mode(dev, true);
> +
> + ret = ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
> + if (ret < 0)
> + goto err_out;
[Severity: Medium]
In ax88179a_resume(), if reading the PHY power state fails (ret < 0),
execution jumps directly to the err_out label.
This bypasses the if (netif_running(dev->net)) { phylink_resume(...) } block.
Because phylink_suspend() was previously called during ax88179a_suspend(),
bypassing the resume call leaves the phylink state machine permanently
suspended. Even if the hardware error was transient, link state updates will
no longer be processed, resulting in a dead network interface that cannot
recover.
Can this be modified so phylink_resume() runs even on hardware error, or is
there another way to prevent the state from becoming stuck?
> +
> + if (!(reg8 & AX_PHY_POWER)) {
> + reg8 = AX_PHY_POWER;
> + ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
> + msleep(250);
> + }
> + ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
> +
> + /* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle.
> + * PHY-power is re-enabled in ax88179a_reset()
> + */
> + ax88179_write_u8(dev, AX88179A_PHY_POWER, 0, 0, 0);
> + msleep(250);
> +
> + ax88179a_reset(dev);
[Severity: High]
Does this clobber a user-configured MAC address?
During resume, ax88179a_resume() unconditionally calls ax88179a_reset().
ax88179a_reset() calls ax88179_get_mac_addr(dev), which reads the default MAC
address from the hardware EEPROM or DTB and writes it into dev->net->dev_addr.
It then copies this value into dev->net->perm_addr.
If the user had previously configured a custom MAC address, this custom
address is permanently overwritten by the hardware default on every
suspend/resume cycle. Furthermore, modifying perm_addr outside of device
probe violates the invariant that it holds the permanent unalterable
hardware address.
Should the resume path instead restore the existing dev_addr to the device
registers without calling ax88179_get_mac_addr()?
> +
> + if (netif_running(dev->net)) {
> + rtnl_lock();
> + phylink_resume(ax179_data->phylink);
> + rtnl_unlock();
> + }
> +
> +err_out:
> + ax88179_set_pm_mode(dev, false);
> +
> + return usbnet_resume(intf);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=9
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information
2026-10-08 3:51 ` [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> Add additionally supported devices to Kconfig description, add
> further requirements such as PHYLINK.
>
> Update driver name in usb_driver and update MODULE_DESCRIPTION
> to include the additionally supported chips.
>
> Change Kconfig symbol name from USB_NET_AX88179_178A to USB_NET_AX88179
> and update loongson32_defconfig/loongson64_defconfig to use that
> symbol.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=11
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
2026-10-08 3:51 ` [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
> The AX88179A/772D devices have 32 efuses with 20 bytes each,
> which can be randomly programmed. The AX88279 has 16K FLASH.
>
> Provide ethtool read capability for these devices. However,
> no write access is provided.
>
> Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=12
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279
2026-10-08 3:51 ` [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
@ 2026-10-09 3:51 ` sashiko-bot
0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-10-09 3:51 UTC (permalink / raw)
To: Birger Koblitz; +Cc: linux-usb
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] net: phy: ax88796b: missing 10M half-duplex cleanup causing mismatch
- [Medium] net: phy: ax88796b: missing aneg restart when changing only 2.5G advertisement
- [Low] net: phy: asix: Dead code in asix_ax88772D_get_features()
--
commit 86e7d1e2ad82ba6062c7e69eccd21649d9c81f47
Author: Birger Koblitz <mail@birger-koblitz.de>
Subject: ax88796b: Add support for AX88772D, AX88179A and AX88279
This commit adds driver support for 100MBit, 1GBit, and 2.5 GBit PHYs
found in ASIX USB-Ethernet controllers.
> diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
> index f20ddf6491499..99abb0af3296d 100644
> --- a/drivers/net/phy/ax88796b.c
> +++ b/drivers/net/phy/ax88796b.c
[ ... ]
> @@ -83,6 +96,107 @@ static int asix_ax88772a_read_status(struct phy_device *phydev)
> return 0;
> }
>
> +static int asix_ax88279_read_status(struct phy_device *phydev)
> +{
[ ... ]
> +static int asix_ax88279_config_aneg(struct phy_device *phydev)
> +{
> + bool adv_2500;
> + int ret;
> +
> + if (phydev->autoneg == AUTONEG_DISABLE) {
> + phydev_warn(phydev, "Disabling autoneg is not supported\n");
> + return -EOPNOTSUPP;
> + }
> +
> + adv_2500 = linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising);
> + ret = phy_modify(phydev, MII_ADVERTISE, AX_ADVERTISE_2500,
> + adv_2500 ? AX_ADVERTISE_2500 : 0);
> + if (ret < 0)
> + return ret;
> +
> + return genphy_config_aneg(phydev);
[Severity: Medium]
Will this fail to restart auto-negotiation if a user only changes the
2.5G advertisement?
Because phy_modify() returns 0 on success, the changed state of the
AX_ADVERTISE_2500 bit is lost. When the rest of the configuration is delegated
to genphy_config_aneg(), it internally evaluates standard advertisement bits
using a mask that excludes this custom 2.5G bit. If no standard changes are
detected, genphy_config_aneg() returns without restarting hardware
auto-negotiation, and the link partner won't be notified of the new 2.5G
advertisement.
Could we check if the register was actually modified, perhaps by using
phy_modify_changed(), and manually trigger a restart or pass the changed state
to __genphy_config_aneg()?
> +}
> +
> +static int asix_ax88279_get_features(struct phy_device *phydev)
> +{
> + int ret;
> +
> + /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */
> + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
> +
> + linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array),
> + phydev->supported);
> +
> + ret = genphy_c45_pma_read_abilities(phydev);
> + if (ret < 0)
> + return ret;
> +
> + /* AX88279 does not support reported 100baseT-half duplex mode */
> + linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported);
[Severity: High]
Does this code also need to clear the 10M half-duplex capability?
The read status function unconditionally forces phydev->duplex to DUPLEX_FULL,
indicating the device only supports full duplex. However,
genphy_c45_pma_read_abilities() populates both 10baseT_Full_BIT and
10baseT_Half_BIT. Since the 10M half-duplex bit isn't cleared here along
with the 100M one, the PHY might still advertise 10M half-duplex capability.
If it negotiates 10M half-duplex with a link partner, could this lead to a
duplex mismatch between the MAC (forced to full) and the link partner?
> +
> + return 0;
> +}
[ ... ]
> +static int asix_ax88772D_get_features(struct phy_device *phydev)
> +{
> + int ret;
> +
> + /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */
> + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
[Severity: Low]
Is the assignment to mmds_present needed here?
It seems this might be dead code, as genphy_read_abilities() and its helpers
like genphy_c45_read_eee_abilities() do not appear to check the mmds_present
mask to determine capabilities.
> +
> + ret = genphy_read_abilities(phydev);
> + if (ret < 0)
> + return ret;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008-ax88179a-v18-0-6fa1d4c40422@birger-koblitz.de?part=13
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-10-09 3:51 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-08 3:51 [PATCH net-next v18 00/13] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-10-08 3:51 ` [PATCH net-next v18 01/13] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 02/13] phylib: Add support for PHYs with broken forced mode Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 03/13] ax88179_178a: Split driver into library and device specific code Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 04/13] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 05/13] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 06/13] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 07/13] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 08/13] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 09/13] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 10/13] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 11/13] ax88179_178a: Update driver name and information Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 12/13] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
2026-10-08 3:51 ` [PATCH net-next v18 13/13] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
2026-10-09 3:51 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox