mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips
@ 2026-09-16  3:23 Birger Koblitz
  2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
                   ` (14 more replies)
  0 siblings, 15 replies; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:23 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Tested-by: Jianhui Xu <neuromoments@gmail.com>
---
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 (15):
      phylink: Add phylink_mac_interrupt
      phylib: Add support for PHYs with broken forced mode
      ax88179_178a: Fix endianness of pause watermark register
      ax88179_178a: Split driver into library and device specific code
      ax88179_178a: Add netdev2data() convenience function
      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 +
 drivers/net/phy/ax88796b.c         |  161 +++++
 drivers/net/phy/phy.c              |    4 +
 drivers/net/phy/phylink.c          |   16 +
 drivers/net/usb/Kconfig            |   10 +-
 drivers/net/usb/Makefile           |    3 +-
 drivers/net/usb/ax88179_178a.c     |  664 ++------------------
 drivers/net/usb/ax88179_lib.c      |  506 +++++++++++++++
 drivers/net/usb/ax88179_lib.h      |  358 +++++++++++
 drivers/net/usb/ax88179a_devices.c | 1221 ++++++++++++++++++++++++++++++++++++
 include/linux/phy.h                |    1 +
 include/linux/phylink.h            |    1 +
 12 files changed, 2327 insertions(+), 624 deletions(-)
---
base-commit: 1bb784eb6e38fd73143f021608e4ef3095d0c0d7
change-id: 20260630-ax88179a-a1d89fe21730

Best regards,
-- 
Birger Koblitz <mail@birger-koblitz.de>


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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 | 16 ++++++++++++++++
 include/linux/phylink.h   |  1 +
 2 files changed, 17 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 3ec3bb43910953f3d57a4601633d0c3be65e959d..96ed13458e9f399fa911a145613d213b1c4433c8 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1612,6 +1612,22 @@ 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.
+ */
+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 1dda5c7ed5f146930d1acc6f06072e6755167af6..f6c1db837fb5062c6d42ffc6489ec06ae84e535b 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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
  2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
                   ` (12 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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 | 4 ++++
 include/linux/phy.h   | 1 +
 2 files changed, 5 insertions(+)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index fce9bc7be3304b408c9f3ec9b5b95c76d3680a88..b83f938fe7c3bd619ae453434fd9582c0a055a57 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1190,6 +1190,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
 	      duplex != DUPLEX_FULL)))
 		return -EINVAL;
 
+	if (autoneg == AUTONEG_DISABLE &&
+	    (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 5f8d65868e0ff558c163421cfea5a6f3bd73a53e..d5f8e527ce8dfefd40a8b34e7c2f9adbcd6c87c1 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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
  2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
  2026-09-16  3:24 ` [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
                   ` (11 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

The 16-bit pause watermark register is little endian as
described in the ASIX 4.1.0 out-of-tree driver. Correct the
register byte sequence but also swap the configuration values
used in the code in order to keep the current behaviour.

The endianness is relevant for 16-bit writes to the register.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/usb/ax88179_178a.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 81d8412ce8e2fac981df076ea1385e6e87525cfe..2ed46ab62c0065d32040439f31d44f118d7e9952 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -32,8 +32,8 @@
 #define AX_ACCESS_EEPROM			0x04
 #define AX_ACCESS_EFUS				0x05
 #define AX_RELOAD_EEPROM_EFUSE			0x06
-#define AX_PAUSE_WATERLVL_HIGH			0x54
-#define AX_PAUSE_WATERLVL_LOW			0x55
+#define AX_PAUSE_WATERLVL_LOW			0x54
+#define AX_PAUSE_WATERLVL_HIGH			0x55
 
 #define PHYSICAL_LINK_STATUS			0x02
 	#define	AX_USB_SS		0x04
@@ -1619,11 +1619,10 @@ static int ax88179_reset(struct usbnet *dev)
 	dev->rx_urb_size = 1024 * 20;
 
 	*tmp = 0x34;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, tmp);
 
 	*tmp = 0x52;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH,
-			  1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);
 
 	/* Enable checksum offload */
 	*tmp = AX_RXCOE_IP | AX_RXCOE_TCP | AX_RXCOE_UDP |

-- 
2.47.3


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (2 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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.

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 | 611 +----------------------------------------
 drivers/net/usb/ax88179_lib.c  | 444 ++++++++++++++++++++++++++++++
 drivers/net/usb/ax88179_lib.h  | 194 +++++++++++++
 4 files changed, 641 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..6aa35c8eb594fda38f7680f8801f0d82090c1efd 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;
diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
new file mode 100644
index 0000000000000000000000000000000000000000..761701ebc0692338b5c0a9f73d0b6965cda87d6e
--- /dev/null
+++ b/drivers/net/usb/ax88179_lib.c
@@ -0,0 +1,444 @@
+// 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 "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);
+	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;
+}
+
+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..b2ee1356727cf4b3fa05f0b0d5fd863ac3f9a87d
--- /dev/null
+++ b/drivers/net/usb/ax88179_lib.h
@@ -0,0 +1,194 @@
+/* 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 {
+	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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (3 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
                   ` (9 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

Add a convenience function for the ethtool ops that use
phylink and need to retrieve the ax88179_data structure from
net_device.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
 drivers/net/usb/ax88179_lib.c | 7 +++++++
 drivers/net/usb/ax88179_lib.h | 1 +
 2 files changed, 8 insertions(+)

diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index 761701ebc0692338b5c0a9f73d0b6965cda87d6e..ea36aef3d3e45ee4f84362a1b87628a12a355bbf 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -128,6 +128,13 @@ int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
 	return ret;
 }
 
+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;
diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
index b2ee1356727cf4b3fa05f0b0d5fd863ac3f9a87d..9ff2a94a6fb2de4ee85f25e913ae2fe263bcea7c 100644
--- a/drivers/net/usb/ax88179_lib.h
+++ b/drivers/net/usb/ax88179_lib.h
@@ -181,6 +181,7 @@ 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);
+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);

-- 
2.47.3


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (4 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
                   ` (8 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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.

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     |  12 +
 drivers/net/usb/ax88179_lib.h      | 159 ++++++
 drivers/net/usb/ax88179a_devices.c | 973 +++++++++++++++++++++++++++++++++++++
 6 files changed, 1152 insertions(+), 2 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 0b42e898f4d8e4892e943bb3db839ff9afcd3569..442e8ea3ca04e41037021fe04570549ecdde49ab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4020,6 +4020,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 6aa35c8eb594fda38f7680f8801f0d82090c1efd..a2c596305f7988175ef56afbc853e798406159f6 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -1270,6 +1270,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.h b/drivers/net/usb/ax88179_lib.h
index 9ff2a94a6fb2de4ee85f25e913ae2fe263bcea7c..2e4d35c74ee8b32cd2916924b85d5427860bb08c 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,56 @@
 	#define GMII_PHY_PGSEL_PAGE3	0x0003
 	#define GMII_PHY_PGSEL_PAGE5	0x0005
 
+/* 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_MASK		0x7FFF
+#define AX179A_TX_DESC_MSS_SHIFT	0x20
+#define AX179A_TX_DESC_VLAN_MASK	0xFFFF
+#define AX179A_TX_DESC_VLAN_SHIFT	0x30
+
+/* 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_MASK	0x7FFF0000
+#define AX179A_RX_PD_LEN_SHIFT	0x10
+#define AX179A_RX_PD_VLAN_SHIFT	0x20
+
+/* RX Descriptor header */
+#define AX179A_RX_DH_PKT_CNT_MASK		0x1FFF
+#define AX179A_RX_DH_DESC_OFFSET_MASK	0xFFFFE000
+#define AX179A_RX_DH_DESC_OFFSET_SHIFT	0x0D
+
+#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 {
 	u8  eee_enabled;
 	u8  eee_active;
@@ -165,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 {
@@ -172,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);
@@ -192,4 +346,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..5dac7a137c323ba18375aadd631f7bad6de22a93
--- /dev/null
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -0,0 +1,973 @@
+// 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;
+
+	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;
+	}
+
+	ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)regnum, 2, &res);
+	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_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)regnum, 2, &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 (ax179_data->chip_version == AX_VERSION_AX88279 && (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_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		= usbnet_nway_reset,
+	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
+	.set_link_ksettings	= phy_ethtool_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 tmp8, link_sts, reg8[3];
+	u8 bulk_config_speed = 0;
+	u16 tmp16, mode;
+
+	/* Stop RX/TX for link configuration */
+	tmp16 = AX_RX_CTL_STOP;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &tmp16);
+	tmp8 = 0;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, &tmp8);
+
+	tmp8 = 0xa5;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_CDC_DELAY_TX, 1, 1, &tmp8);
+
+	tmp16 = 0x0410;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 2, 2, &tmp16);
+
+	tmp8 = 0;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_ETH_TX_GAP, 1, 1, &tmp8);
+
+	tmp8 = 0x07;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_EP5_EHR, 1, 1, &tmp8);
+
+	tmp8 = 0x28 | AX_NEW_PAUSE_EN;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_NEW_PAUSE_CTRL, 1, 1, &tmp8);
+
+	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);
+
+		reg8[0] = 0x40;
+		reg8[1] = AX_MAC_MIQFFCTRL_FORMAT | AX_MAC_MIQFFCTRL_DROP_CRC | AX_MAC_LSO_ERR_EN;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 2, 2, reg8);
+
+		tmp8 = AX_XGMII_EN;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
+
+		tmp8 = 0x1C | AX_LSO_ENHANCE_EN;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_LSO_ENHANCE_CTRL, 1, 1, &tmp8);
+
+		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);
+
+		tmp8 = 0x40;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 1, &tmp8);
+		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);
+
+		tmp8 = 0xFA;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 1, &tmp8);
+
+		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 (ax179_data->chip_version < AX_VERSION_AX88279) {
+		tmp8 = 0;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
+	}
+
+	if (duplex)
+		mode |= AX_MEDIUM_FULL_DUPLEX;
+
+	if (dev->net->mtu > 1500)
+		mode |= AX_MEDIUM_JUMBO_EN;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &mode);
+
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->rxctl);
+
+	tmp8 = AX_MAC_RX_PATH_READY | AX_MAC_TX_PATH_READY;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, &tmp8);
+}
+
+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;
+	u8 reg8;
+
+	/* 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;
+
+	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, (0xFD + i),
+				       1, 1, &ax179_data->fw_version[i]);
+		if (ret < 0)
+			ax179_data->fw_version[i] = 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 |
+			      NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX |
+			      NETIF_F_HW_VLAN_CTAG_FILTER;
+
+	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 */
+	reg8 = AX_PHY_POWER;
+	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+	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;
+	u16 tmp16;
+	u8 tmp8;
+
+	/* Configure RX control register => stop operation */
+	tmp16 = AX_RX_CTL_STOP;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &tmp16);
+
+	rtnl_lock();
+	phylink_disconnect_phy(ax179_data->phylink);
+	rtnl_unlock();
+	phylink_destroy(ax179_data->phylink);
+	ax88179a_mdio_unregister(ax179_data);
+
+	tmp8 = 0;
+	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &tmp8);
+
+	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;
+	u64 *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 = *(u64 *)skb_tail_pointer(skb);
+
+	/* Check these packets */
+	hdr_off = (rx_hdr & AX179A_RX_DH_DESC_OFFSET_MASK) >> AX179A_RX_DH_DESC_OFFSET_SHIFT;
+	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 = (u64 *)(skb->data + hdr_off);
+	le64_to_cpus(pkt_desc_ptr);
+
+	pkt_end = 0;
+	while (pkt_cnt--) {
+		u64 pkt_desc = *pkt_desc_ptr;
+		u32 pkt_len_plus_padd;
+		u32 pkt_len;
+
+		pkt_len = (u32)((pkt_desc & AX179A_RX_PD_LEN_MASK) >> AX179A_RX_PD_LEN_SHIFT)
+			  - (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)) {
+			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 |= ((u64)(skb_shinfo(skb)->gso_size & AX179A_TX_DESC_MSS_MASK)) <<
+		   AX179A_TX_DESC_MSS_SHIFT;
+
+	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 |= ((u64)tci & AX179A_TX_DESC_VLAN_MASK) << AX179A_TX_DESC_VLAN_SHIFT;
+	}
+
+	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 (!(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];
+	u8 *tmp;
+
+	tmp16 = (u16 *)buf;
+	tmp = (u8 *)buf;
+
+	/* Power up ethernet PHY */
+	*tmp = AX_PHY_POWER;
+	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, tmp);
+	msleep(250);
+
+	/* Ethernet PHY Auto Detach*/
+	ax88179a_auto_detach(dev);
+
+	*tmp = AX_MAC_EFF_EN;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_BULK_OUT_CTRL, 1, 1, tmp);
+
+	*tmp16 = 0;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, tmp16);
+
+	*tmp = 0x04;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);
+	*tmp = 0x10;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, tmp);
+
+	*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);
+
+	*tmp = 0xff;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_INT_MASK, 1, 1, tmp);
+
+	*tmp = 0;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_RX_DMA_CTL, 1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_BM_TX_DMA_CTL, 1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_ARC_CTRL, 1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_SWP_CTRL, 1, 1, tmp);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_TX_HDR_CKSUM, 1, 1, tmp);
+
+	/* 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 */
+	*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);
+	ax179_data->rx_checksum = 1;
+
+	*tmp = AX_TXCOE_IP | AX_TXCOE_TCP | AX_TXCOE_UDP |
+	       AX_TXCOE_TCPV6 | AX_TXCOE_UDPV6;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_TXCOE_CTL, 1, 1, tmp);
+
+	/* Configure RX control register => start operation */
+	ax179_data->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)
+		ax179_data->rxctl |= AX_RX_CTL_IPE;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->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;
+
+	phylink_start(ax179_data->phylink);
+
+	usbnet_link_change(dev, 0, 0);
+
+	return 0;
+}
+
+static int ax88179a_stop(struct usbnet *dev)
+{
+	struct ax88179_data *ax179_data = dev->driver_priv;
+	u16 reg16;
+	u8 reg8;
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &reg16);
+	reg16 &= ~AX_MEDIUM_RECEIVE_EN;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &reg16);
+
+	reg16 = 0;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &reg16);
+
+	reg8 = 0;
+	ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+
+	phylink_stop(ax179_data->phylink);
+
+	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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (5 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

The AX88179A uses a simple HW configuration for EEE
via a single EEE configuration register without LPI timer support
Add support for this EEE enable/disable register and replace the EEE.

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 5dac7a137c323ba18375aadd631f7bad6de22a93..785919e14e861e5e44a67a7407690a0cd8ddb180 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -384,10 +384,27 @@ static void ax88179a_mac_link_up(struct phylink_config *config,
 	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, &tmp8);
 }
 
+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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (6 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:24   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	Nicolai Buchwitz

Configure phylink to work with the PHYs in the AX88197A 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 785919e14e861e5e44a67a7407690a0cd8ddb180..6bf1ac17aa1d5abfc854eb3ac0896c69875ea0d7 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -206,6 +206,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,
@@ -215,6 +229,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		= usbnet_nway_reset,
 	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
 	.set_link_ksettings	= phy_ethtool_set_link_ksettings,
@@ -440,6 +456,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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (7 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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>
---
 drivers/net/usb/ax88179_lib.c      |   2 +
 drivers/net/usb/ax88179a_devices.c | 102 +++++++++++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
index ea36aef3d3e45ee4f84362a1b87628a12a355bbf..3324069e90fc352048817963f27a49ed3872b348 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -346,6 +346,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) {
@@ -365,6 +366,7 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features)
 		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/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
index 6bf1ac17aa1d5abfc854eb3ac0896c69875ea0d7..481139f71ac26f218b672622eec93c7a026c0089 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -239,6 +239,62 @@ 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;
+	u16 reg16;
+	u8 reg8;
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+	vlan_ctrl = reg8;
+
+	/* Address */
+	reg8 = (vid / 16);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, &reg8);
+
+	/* Data */
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+	reg16 &= ~(1 << (vid % 16));
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	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;
+	u16 reg16;
+	u8 reg8;
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+	vlan_ctrl = reg8;
+
+	/* Address */
+	reg8 = (vid / 16);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, &reg8);
+
+	/* Data */
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+	reg16 |= (1 << (vid % 16));
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	return 0;
+}
+
 static void ax88179a_mdio_unregister(struct ax88179_data *data)
 {
 	mdiobus_unregister(data->mdio);
@@ -506,6 +562,49 @@ 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) {
+		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+		tmp ^= AX_VLAN_CONTROL_VFE;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+		if (features & NETIF_F_HW_VLAN_CTAG_FILTER) {
+			for (int i = 0; i < 256; i++) {
+				u16 tmp16 = 0;
+				/* Address */
+				tmp = i;
+				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS,
+						  1, 1, &tmp);
+				/* Data */
+				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0,
+						  2, 2, &tmp16);
+				tmp = AX_VLAN_CONTROL_WE;
+				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL,
+						  1, 1, &tmp);
+			}
+		}
+	}
+
+	if (changed & NETIF_F_HW_VLAN_CTAG_RX) {
+		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+		tmp ^= AX_VLAN_CONTROL_VSO;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
+	}
+
+	return 0;
+}
+
 static const struct net_device_ops ax88179a_netdev_ops = {
 	.ndo_open		= usbnet_open,
 	.ndo_stop		= usbnet_stop,
@@ -516,6 +615,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)

-- 
2.47.3


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (8 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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 3324069e90fc352048817963f27a49ed3872b348..708b0b23f1363daf90f329a3e6b6a2721d110387 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -305,10 +305,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 481139f71ac26f218b672622eec93c7a026c0089..e77450bc3299fb1a3698b36cab2785e82cf759a6 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -615,6 +615,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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (9 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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 | 95 +++++++++++++++++++++++++++++++++++++-
 4 files changed, 123 insertions(+), 4 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index a2c596305f7988175ef56afbc853e798406159f6..93031b9ee92bc64c53387291616aa3b40db6feb9 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -684,6 +684,9 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
 
 	dev->driver_priv = ax179_data;
 
+	ax179_data->resume = ax88179_resume;
+	ax179_data->suspend = ax88179_suspend;
+
 	dev->net->netdev_ops = &ax88179_netdev_ops;
 	dev->net->ethtool_ops = &ax88179_ethtool_ops;
 	dev->net->needed_headroom = 8;
@@ -1342,9 +1345,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 708b0b23f1363daf90f329a3e6b6a2721d110387..3b498206c50d5b5d9d5cd489114ef28bf280fe97 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -455,3 +455,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 2e4d35c74ee8b32cd2916924b85d5427860bb08c..16a6dcbd6e66e37d3da0e57d2800c29d8a95a34a 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 {
@@ -346,6 +348,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 e77450bc3299fb1a3698b36cab2785e82cf759a6..ff41d20c219d46392c2633a1eaff021385490b0e 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)
 
@@ -123,6 +125,53 @@ 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;
+	u16 tmp16;
+	u8 tmp8;
+
+	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();
+	}
+
+	/* Enable WoL */
+	if (priv->wolopts) {
+		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8);
+		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);
+	}
+
+	usbnet_suspend(intf, message);
+	ax88179_set_pm_mode(dev, false);
+	return 0;
+}
+
 static int ax88179a_auto_detach(struct usbnet *dev)
 {
 	u16 tmp16;
@@ -132,6 +181,43 @@ 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;
+
+	ax179_data = dev->driver_priv;
+	ax88179_set_pm_mode(dev, true);
+
+	ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+	if (!(reg8 & AX_PHY_POWER)) {
+		reg8 = AX_PHY_POWER;
+		ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+		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 ax88179_reset()
+	 */
+	reg8 = 0;
+	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+	msleep(250);
+
+	ax88179a_reset(dev);
+
+	if (netif_running(dev->net)) {
+		rtnl_lock();
+		phylink_resume(ax179_data->phylink);
+		rtnl_unlock();
+	}
+
+	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;
@@ -687,6 +773,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;
@@ -1048,7 +1137,11 @@ static int ax88179a_reset(struct usbnet *dev)
 			     1, 1, &tmp) > 0)
 		ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY;
 
-	phylink_start(ax179_data->phylink);
+	/* ax88179a_reset() may also be called from resume context, phylink
+	 * is already started, then.
+	 */
+	if (!ax179_data->in_pm)
+		phylink_start(ax179_data->phylink);
 
 	usbnet_link_change(dev, 0, 0);
 

-- 
2.47.3


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (10 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information Birger Koblitz
                   ` (2 subsequent siblings)
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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 ff41d20c219d46392c2633a1eaff021385490b0e..6225b88681e623a570cf75aef8d10e70d3ab2cbf 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -218,6 +218,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;
@@ -307,6 +321,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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (11 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
  2026-09-16  3:24 ` [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu,
	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.

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/Kconfig        | 10 +++++++---
 drivers/net/usb/Makefile       |  2 +-
 drivers/net/usb/ax88179_178a.c |  8 ++++----
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
index 115990b0d9537aea2776544c9768b034f32aa7a4..4158455a2da76cd43a9a9d9d0e8c66028d57b095 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 3.0/2.0 to Gigabit Ethernet"
 	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 93031b9ee92bc64c53387291616aa3b40db6feb9..2601a8875b9e2644780d35375744875197277712 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -1341,8 +1341,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,
@@ -1353,7 +1353,7 @@ 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 based USB 3.0/2.0 Gigabit Ethernet Devices");
 MODULE_LICENSE("GPL");

-- 
2.47.3


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (12 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  2026-09-16  3:24 ` [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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 | 19 +++++++++++++++
 drivers/net/usb/ax88179_lib.c  | 54 ++++++++++++++++++++++++++++++++----------
 2 files changed, 61 insertions(+), 12 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 2601a8875b9e2644780d35375744875197277712..4c69568ec4a2387573808c29a0cbf86b77657038 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -687,6 +687,19 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
 	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)
+		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;
@@ -711,6 +724,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 3b498206c50d5b5d9d5cd489114ef28bf280fe97..db77b7d4539ec52756a93d3c56d46155de5570df 100644
--- a/drivers/net/usb/ax88179_lib.c
+++ b/drivers/net/usb/ax88179_lib.c
@@ -194,36 +194,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]);
+	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 < 0) {
 			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;
 }
@@ -231,12 +256,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	[flat|nested] 31+ messages in thread

* [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279
  2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
                   ` (13 preceding siblings ...)
  2026-09-16  3:24 ` [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-09-16  3:24 ` Birger Koblitz
  2026-09-17 21:25   ` netdev-bot+sashiko
  14 siblings, 1 reply; 31+ messages in thread
From: Birger Koblitz @ 2026-09-16  3:24 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz, Jianhui Xu

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 | 161 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 161 insertions(+)

diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
index f20ddf64914993f7097070d1f56c1103ec1e40e8..62f6beb25f41e703dc02add7358917d59ac8f2ac 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,103 @@ 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;
+
+	/* Read actual speed from vendor register */
+	val = phy_read(phydev, AX_CTRL_STATUS);
+	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
+	 */
+	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;
+	}
+
+	ret = genphy_config_aneg(phydev);
+
+	if (ret < 0)
+		return ret;
+
+	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);
+
+	return ret;
+}
+
+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 +204,25 @@ static void asix_ax88772a_link_change_notify(struct phy_device *phydev)
 	}
 }
 
+static int asix_ax88772D_get_features(struct phy_device *phydev)
+{
+	int ret;
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = {};
+
+	/* 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 reported 1000baseT mode */
+	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, mask);
+	linkmode_andnot(phydev->supported, phydev->supported, mask);
+
+	return 0;
+}
+
 static struct phy_driver asix_driver[] = {
 {
 	PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
@@ -111,6 +240,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 +282,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	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt
  2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 · Low: 1

- [Medium] `phylink_mac_interrupt()`
  (drivers/net/phy/phylink.c:1621-1628) reads `pl->phydev` with no…
- [Low] Claim that the newly exported `phylink_mac_interrupt()`
  (drivers/net/phy/phylink.c) and its prototype…

--

phylink: Add phylink_mac_interrupt

This adds an exported phylink_mac_interrupt() helper which forwards a
link-change interrupt received by the MAC to phylib by calling
phy_mac_interrupt() on pl->phydev.  It is intended for drivers that want to
avoid polling the PHY.

(no Link: tags in the commit header)

> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index 3ec3bb4391095..96ed13458e9f3 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
> @@ -1612,6 +1612,22 @@ 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.
> + */
> +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);

[Severity: Medium]
Is the unsynchronized read of pl->phydev here safe?  Every other reader of
that field in phylink.c is serialized, and the struct member carries the
comment describing that rule:

	/* Serialize updates to pl->phydev with phylink_resolve() */
	struct mutex phydev_mutex;

phylink_resolve() and phylink_bringup_phy() take pl->phydev_mutex, and the
ethtool/ioctl/phylink_speed_up()/phylink_speed_down() readers run under
ASSERT_RTNL().  Since this new helper is documented as being called from a
MAC link-change interrupt, it can take neither the mutex nor RTNL, and the
signature (struct phylink * only) gives the caller no way to pin the
phy_device across the call.

Can this race with teardown?  phylink_disconnect_phy() is the only writer
that clears the pointer:

drivers/net/phy/phylink.c:phylink_disconnect_phy() {
	mutex_lock(&pl->phydev_mutex);
	phy = pl->phydev;
	if (phy) {
		...
		pl->phydev = NULL;
		...
	}
	mutex_unlock(&pl->phydev_mutex);

	if (phy) {
		flush_work(&pl->resolve);
		phy_disconnect(phy);
	}
}

So the sequence would be:

CPU0 (MAC link IRQ)
phylink_mac_interrupt()
	phy = pl->phydev;		/* latched */

CPU1 (RTNL)
phylink_disconnect_phy()
	pl->phydev = NULL;
	phy_disconnect(phy)		/* phy_stop_machine() ->
					   cancel_delayed_work_sync(
						&phydev->state_queue) */

CPU0 resumes
	phy_mac_interrupt(phy)
		phy_trigger_machine(phy)
			phy_queue_state_machine()
				mod_delayed_work(..., &phy->state_queue, ...)

Does that re-arm the PHY state machine work after the teardown path already
did its synchronous cancel, so the state machine runs on a detached phydev?

For an SFP cage the phy_device is also freed while the interface is still
up:

drivers/net/phy/sfp.c:sfp_sm_phy_detach() {
	sfp_remove_phy(sfp->sfp_bus);	/* -> phylink_sfp_disconnect_phy()
					   -> phylink_disconnect_phy() */
	phy_device_remove(sfp->mod_phy);
	phy_device_free(sfp->mod_phy);
	sfp->mod_phy = NULL;
}

Can the mod_delayed_work() above then write into the freed phy_device?

Separately, the load of pl->phydev is a plain access to a field written
under a mutex on another CPU.  Should this at least be READ_ONCE()?  As
written, nothing prevents the compiler from re-loading pl->phydev when
forming the call argument, which would make the "if (phy)" test useless and
pass NULL into phy_mac_interrupt() -> mod_delayed_work().

Would it make sense to either make pl->phydev lookup IRQ-safe (RCU with
deferred free, or hold a device reference), or to document in the kernel-doc
that the caller must quiesce its link interrupt before
phylink_disconnect_phy()/phylink_destroy()?  The added kernel-doc currently
states no calling context or lifetime contract.

> +
>  static void phylink_resolve(struct work_struct *w)
>  {
>  	struct phylink *pl = container_of(w, struct phylink, resolve);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode
  2026-09-16  3:24 ` [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 1 · Low: 0 (1)

- [High] NULL pointer dereference of `phydev->drv` in
  phy_ethtool_ksettings_set() (drivers/net/phy/phy.c:1193-1195).
- [Medium] Incomplete gating of PHY_BROKEN_FORCED: the new flag is
  consulted only in phy_ethtool_ksettings_set()…

Pre-existing issues:
- [Low] Concern that PHY_BROKEN_FORCED has no in-tree user, making the
  patch behaviorally inert (dead branch in phy_ethtool_ksettings_set())…

--

phylib: Add support for PHYs with broken forced mode

Adds a PHY_BROKEN_FORCED driver flag and makes
phy_ethtool_ksettings_set() reject AUTONEG_DISABLE requests with
-EOPNOTSUPP for drivers that set it.  The stated goal is to avoid the
WARN and stack trace in the PHY state machine when config_aneg()
returns -EOPNOTSUPP for forced modes.

> diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
> index fce9bc7be3304..b83f938fe7c3b 100644
> --- a/drivers/net/phy/phy.c
> +++ b/drivers/net/phy/phy.c
> @@ -1190,6 +1190,10 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
>  	      duplex != DUPLEX_FULL)))
>  		return -EINVAL;
>  
> +	if (autoneg == AUTONEG_DISABLE &&
> +	    (phydev->drv->flags & PHY_BROKEN_FORCED))
> +		return -EOPNOTSUPP;
> +

[Severity: High]
Can phydev->drv be NULL here?

The rest of phylib treats a NULL drv as a reachable state while the
phy_device is still attached to a netdev.  phy_remove() in
phy_device.c ends with:

	phydev->drv = NULL;

without detaching the PHY from the netdev, so ndev->phydev and
phydev->attached_dev stay set after a driver unbind through
/sys/bus/mdio_bus/drivers/<drv>/unbind.

The callee a few lines below in this same function guards for exactly
that:

drivers/net/phy/phy.c:_phy_start_aneg() {
	lockdep_assert_held(&phydev->lock);

	if (!phydev->drv)
		return -EIO;
	...
}

and so do phy_config_inband() and phy_ethtool_nway_reset().  The entry
point does not:

drivers/net/phy/phy.c:phy_ethtool_set_link_ksettings() {
	if (!phydev)
		return -ENODEV;

	return phy_ethtool_ksettings_set(phydev, cmd);
}

So for "ethtool -s <dev> autoneg off speed 100 duplex full" after a PHY
driver unbind, does this new test dereference a NULL phydev->drv and turn
the previous -EIO return into an oops?

There is a second aspect: the read is also outside mutex_lock(&phydev->lock),
and phy_remove() clears the pointer without holding phydev->lock or rtnl, so
moving the test under the mutex would not close the window either.  Would an
explicit check be preferable, e.g.

	if (autoneg == AUTONEG_DISABLE && phydev->drv &&
	    (phydev->drv->flags & PHY_BROKEN_FORCED))
		return -EOPNOTSUPP;

or an early "if (!phydev->drv) return -EIO;" matching the convention used
elsewhere in this file?

[Severity: Medium]
Is phy_ethtool_ksettings_set() the only path that commits AUTONEG_DISABLE?

The SIOCSMIIREG handler in phy_mii_ioctl() also writes forced mode with no
PHY_BROKEN_FORCED test:

drivers/net/phy/phy.c:phy_mii_ioctl() {
	...
		case MII_BMCR:
			if ((val & (BMCR_RESET | BMCR_ANENABLE)) == 0) {
				if (phydev->autoneg == AUTONEG_ENABLE)
					change_autoneg = true;
				phydev->autoneg = AUTONEG_DISABLE;
	...
		mdiobus_write(phydev->mdio.bus, prtad, devad, val);
	...
		if (change_autoneg)
			return phy_start_aneg(phydev);
}

For a PHY flagged PHY_BROKEN_FORCED whose config_aneg() returns
-EOPNOTSUPP (asix_ax88279_config_aneg(), added later in this series),
phy_start_aneg() -> _phy_start_aneg() -> phy_config_aneg() fails and the
error reaches the ioctl caller, but neither the BMCR write nor
phydev->autoneg = AUTONEG_DISABLE is rolled back.

On the next transition to PHY_UP (ifdown/ifup, link flap, cable test exit),
does the state machine then hit the same WARN this patch aims to remove?

drivers/net/phy/phy.c:_phy_state_machine() {
	if (state_work == PHY_STATE_WORK_ANEG) {
		err = _phy_start_aneg(phydev);
		func = &_phy_start_aneg;
	}
	...
	if (err < 0)
		phy_error_precise(phydev, func, err);
}

phy_mii_ioctl() is exported and wired as ndo_eth_ioctl by a number of MAC
drivers, directly or via phy_do_ioctl_running().  For the device in this
series the MAC routes ndo_eth_ioctl to phylink_mii_ioctl(), whose
SIOCSMIIREG case uses phylink_phy_write() and does not touch
phydev->autoneg, so that particular combination does not reach the WARN
today.  Would placing the gate where autoneg is actually committed, in
_phy_start_aneg() or phy_config_aneg(), cover both entry points?

> diff --git a/include/linux/phy.h b/include/linux/phy.h
> index 5f8d65868e0ff..d5f8e527ce8df 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

[Severity: Low]
This isn't a bug introduced by this patch, but a note on flag adoption.

The value 0x10 does not collide with any existing PHY_* flag, and there is
an in-tree user by the end of the series: drivers/net/phy/ax88796b.c sets

	.flags		= PHY_IS_INTERNAL | PHY_BROKEN_FORCED,

on the AX88279 entry, whose asix_ax88279_config_aneg() returns -EOPNOTSUPP
for AUTONEG_DISABLE, so the new branch is not dead code.

Two existing drivers already reject forced mode in their config_aneg()
without setting the new flag, bcm84881_config_aneg() and
en8811h_config_aneg().  That is pre-existing behaviour and not changed
here; would converting them be a reasonable follow-up?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register
  2026-09-16  3:24 ` [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 · Low: 0

- [Medium] Commit subject/message mismatch: the patch is titled
  "ax88179_178a: Fix endianness of pause watermark register" but…

--

ax88179_178a: Fix endianness of pause watermark register

This swaps the register addresses bound to AX_PAUSE_WATERLVL_LOW and
AX_PAUSE_WATERLVL_HIGH (0x54 and 0x55), and swaps the two call sites in
ax88179_reset() so that the same values keep landing in the same physical
registers as before.

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 81d8412ce8e2f..2ed46ab62c006 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c
> @@ -32,8 +32,8 @@
>  #define AX_ACCESS_EEPROM			0x04
>  #define AX_ACCESS_EFUS				0x05
>  #define AX_RELOAD_EEPROM_EFUSE			0x06
> -#define AX_PAUSE_WATERLVL_HIGH			0x54
> -#define AX_PAUSE_WATERLVL_LOW			0x55
> +#define AX_PAUSE_WATERLVL_LOW			0x54
> +#define AX_PAUSE_WATERLVL_HIGH			0x55

[Severity: Medium]
Does the subject line match what this patch does?  The subject says
"Fix endianness of pause watermark register", but the only change is
renaming which macro maps to 0x54 and which maps to 0x55, plus a matching
swap of the two call sites below.

A grep of the tree at this commit shows the macros have exactly two users:

  drivers/net/usb/ax88179_178a.c:
    #define AX_PAUSE_WATERLVL_LOW    0x54
    #define AX_PAUSE_WATERLVL_HIGH   0x55
    ax88179_reset():
      ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, tmp);
      ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);

Both of those are size 1 writes, and the endianness conversion in
ax88179_write_cmd() only runs for size 2:

  static int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
                               u16 size, const void *data)
  {
          if (2 == size) {
                  u16 buf;
                  buf = *((u16 *)data);
                  cpu_to_le16s(&buf);
                  ret = __ax88179_write_cmd(dev, cmd, value, index,
                                            size, &buf);
          } else {
  ...

So before and after this patch the driver writes 0x34 to MAC register 0x55
and 0x52 to MAC register 0x54, and no 16-bit access to 0x54/0x55 exists in
the tree at this point.  The commit message itself says the change is
behaviour preserving:

  "but also swap the configuration values used in the code in order to
   keep the current behaviour"
  "The endianness is relevant for 16-bit writes to the register."

Would a subject describing this as a correction of the register byte-order
definitions, or as preparation for the later 16-bit writes, be more
accurate?  As written, the "Fix ..." subject with no Fixes: tag reads like a
bug fix on a path that is not reachable yet, which could attract a stable
backport of a no-op change.

>  #define PHYSICAL_LINK_STATUS			0x02
>  	#define	AX_USB_SS		0x04
> @@ -1619,11 +1619,10 @@ static int ax88179_reset(struct usbnet *dev)
>  	dev->rx_urb_size = 1024 * 20;
>  
>  	*tmp = 0x34;
> -	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH, 1, 1, tmp);
>  
>  	*tmp = 0x52;
> -	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH,
> -			  1, 1, tmp);
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code
  2026-09-16  3:24 ` [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 0 (1) · Medium: 1 (5) · Low: 1

- [Medium] The module is renamed from ax88179_178a.ko to ax88179.ko
  (drivers/net/usb/Makefile: `obj-$(CONFIG_USB_NET_AX88179_178A) +=…
- [Low] New-file hygiene problems in the added files.

Pre-existing issues:
- [High] ax88179_get_eeprom() (drivers/net/usb/ax88179_lib.c) can copy
  uninitialized kernel heap bytes to userspace when the attached USB…
- [Medium] ax88179_set_features() (drivers/net/usb/ax88179_lib.c:338-364)
  ignores every command result.
- [Medium] ax88179_change_mtu() (drivers/net/usb/ax88179_lib.c:400-426)
  commits the software MTU first (`WRITE_ONCE(net->mtu, new_mtu);…
- [Medium] ax88179_set_mac_addr() (drivers/net/usb/ax88179_lib.c:425-443)
  calls eth_hw_addr_set(net, addr->sa_data) before issuing the…
- [Medium] ax88179_mdio_read() (drivers/net/usb/ax88179_lib.c:151-158)
  discards the return value of ax88179_read_cmd() and returns the output…
- [Medium] ax88179_set_eeprom() (drivers/net/usb/ax88179_lib.c:229-297)
  mixes byte-oriented userspace data with native-word register helpers,…

--

ax88179_178a: Split driver into library and device specific code

This splits the ax88179_178a driver into a shared library part
(ax88179_lib.c plus ax88179_lib.h) holding the register access helpers and
the generic net_device/ethtool callbacks, and device specific code that
stays in ax88179_178a.c.  The resulting module is renamed to ax88179.

A few questions on the move below, mostly about behaviour that comes along
with the code as it becomes shared by future controller back-ends.

> diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
> index 4964f7b326fbc..ddd76fa71e2ee 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

[Severity: Medium]
This changes the module name from ax88179_178a to ax88179, and no
MODULE_ALIAS("ax88179_178a") is added anywhere in the ax88179 sources.

Does that break existing configuration that is keyed on the old module
name?  modprobe ax88179_178a and modinfo ax88179_178a stop resolving, and
entries such as

	blacklist ax88179_178a

in /etc/modprobe.d/*.conf, options/install lines, /etc/modules entries and
initramfs module lists silently stop applying, so a blacklist that used to
keep the in-tree driver away now lets it bind again.

The driver structure in ax88179_178a.c also still says

	static struct usb_driver ax88179_178a_driver = {
		.name =		"ax88179_178a",

so the sysfs directory name and the module name diverge.

Would ax88179_178a-y := ax88179_178a.o ax88179_lib.o, or adding
MODULE_ALIAS("ax88179_178a"), keep the old name working?

[ ... ]

> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> new file mode 100644
> index 0000000000000..761701ebc0692
> --- /dev/null
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -0,0 +1,444 @@

[ ... ]

> +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_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;
> +}

[Severity: Medium]
This is a pre-existing issue and not introduced by this patch, since
ax88179_mdio_read() is moved verbatim, but is a failed PHY read here
indistinguishable from a genuine read of 0x0000?

The two-byte branch of ax88179_read_cmd() above stores its zero initialised
temporary into the caller's variable regardless of the transfer result:

	u16 buf = 0;

	ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
	le16_to_cpus(&buf);
	*((u16 *)data) = buf;

and ax88179_mdio_read() discards ret, so via usbnet_mii_ioctl() ->
generic_mii_ioctl() a SIOCGMIIREG copies the fabricated 0x0000 into val_out
and reports success.  The in-kernel mii_* helpers see the same value.

[ ... ]

> +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;
> +}

[Severity: High]
This is a pre-existing issue that this patch moves rather than introduces,
but can ax88179_get_eeprom() hand uninitialised slab bytes to userspace when
the device answers the vendor EEPROM read short?

eeprom_buff comes from kmalloc_array(), so it is uninitialised, and only
ret < 0 is treated as an error.  __usbnet_read_cmd() in usbnet.c only fills
the bytes it actually received and returns the transferred count:

	if (err > 0 && err <= size) {
		if (data)
			memcpy(data, buf, err);
	}

A device replying with 0 or 1 byte therefore returns 0 or 1 here, which
passes the ret < 0 test, leaving 2 or 1 bytes of that word untouched.  The
final memcpy() then copies the full requested range (up to AX_EEPROM_LEN)
into the ethtool buffer, and ethtool_get_any_eeprom() copies it out to
userspace.

Should this require exactly 2 transferred bytes per word, or use a zeroed
allocation?

> +int ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom, u8 *data)
> +{
[ ... ]
> +	if (eeprom->offset & 1) {
> +		ret = ax88179_read_cmd(dev, AX_ACCESS_EEPROM, first_word, 1, 2,
> +				       &eeprom_buff[0]);
[ ... ]
> +	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]);

[Severity: Medium]
This is a pre-existing issue as well, only comment style changed in the
move, but is the write path byte-swapped relative to the read path on
big-endian hosts?

Byte oriented user data is memcpy()d into the u16 array and each word then
goes through ax88179_write_cmd() with size 2, which applies
cpu_to_le16s():

	if (size == 2) {
		u16 buf;

		buf = *((u16 *)data);
		cpu_to_le16s(&buf);

while ax88179_get_eeprom() reads through __ax88179_read_cmd() and returns
raw device bytes.

The boundary words look affected too: they are fetched with
ax88179_read_cmd() (which applies le16_to_cpus()) and then patched at a byte
offset, so an unaligned partial write can modify the wrong EEPROM byte on
big-endian systems.

[ ... ]

> +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);
> +	}
[ ... ]
> +	return 0;
> +}

[Severity: Medium]
This isn't a bug introduced by this patch either, the function is moved
verbatim with only the static qualifier dropped, but what happens here if
the register read fails?

tmp is uninitialised, and the one byte path of ax88179_read_cmd() passes the
caller's pointer straight to __usbnet_read_cmd(), which leaves the
destination untouched on error or a zero length reply.  The code then XORs
tmp and programs it into AX_TXCOE_CTL/AX_RXCOE_CTL, so an arbitrary stack
byte ends up in the checksum control register, and the device can recover
that byte by reversing the known XOR mask.

Since 0 is returned unconditionally, __netdev_update_features() records the
requested mask as active and short-circuits an identical retry, leaving the
stack advertising offloads the hardware is not performing.  Should the
command results be checked and propagated?

[ ... ]

> +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;
> +}

[Severity: Medium]
This is a pre-existing issue carried over by the move, but can this leave
reception disabled on the interface?

The read-modify-write of AX_MEDIUM_STATUS_MODE is unchecked, and the
two-byte branch of ax88179_read_cmd() hands out its zero initialised
temporary on failure, so a failed read makes the driver write 0x8040 (jumbo
branch) or 0x0000 (non-jumbo branch) into the medium control register,
clearing AX_MEDIUM_RECEIVE_EN together with the gigabit, duplex, 125 MHz and
flow control enables.

The software state is already committed by WRITE_ONCE(net->mtu, new_mtu)
before that, and 0 is returned regardless, so netif_set_mtu_ext() records
success and rejects an identical retry because net->mtu already matches.

> +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;
> +}
> +

[Severity: Medium]
This is a pre-existing issue too, only the continuation line indentation
changed in the move, but should the old address be restored when the
AX_NODE_ID write fails?

eth_hw_addr_set() has already updated dev_addr at that point, and
netif_set_mac_address() does not roll back either.  It guards the callback
with

	if (memcmp(dev->dev_addr, ss->__data, dev->addr_len)) {
		err = ops->ndo_set_mac_address(dev, ss);

so an identical retry finds dev_addr already equal to the requested address,
skips the driver entirely and returns 0.  The chip keeps filtering on the old
node ID while the kernel believes the new address is active.

> diff --git a/drivers/net/usb/ax88179_lib.h b/drivers/net/usb/ax88179_lib.h
> new file mode 100644
> index 0000000000000..b2ee1356727cf
> --- /dev/null
> +++ b/drivers/net/usb/ax88179_lib.h
> @@ -0,0 +1,194 @@
> +/* 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

[Severity: Low]
This isn't a bug, but the three includes sit above the multiple inclusion
guard, so they are re-processed on every inclusion of ax88179_lib.h.  Could
they be moved below the #define?

The guard name __LINUX_USBNET_AX88179_H also does not follow the file name
ax88179_lib.h.

One more style nit: ax88179_lib.c ends with a blank line after the closing
brace of ax88179_set_mac_addr(), which checkpatch reports as a blank line at
end of file.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function
  2026-09-16  3:24 ` [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 0 · Low: 1

- [Low] drivers/net/usb/ax88179_lib.c:131 adds the globally visible
  helper netdev2data() which has no caller at this commit, and the…

--

ax88179_178a: Add netdev2data() convenience function

This adds a small non-static helper netdev2data() to ax88179_lib.c, plus
its prototype in ax88179_lib.h, which returns the ax88179_data structure
belonging to a net_device via netdev_priv() and usbnet->driver_priv.

> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> index 761701ebc0692..ea36aef3d3e45 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -128,6 +128,13 @@ int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
>  	return ret;
>  }
>  
> +struct ax88179_data *netdev2data(struct net_device *net)
> +{
> +	struct usbnet *dev = netdev_priv(net);
> +
> +	return dev->driver_priv;
> +}
> +

[Severity: Low]
This isn't a bug, but netdev2data() has no caller at this point in the
series, and the commit message describes the users in the present tense:

    Add a convenience function for the ethtool ops that use
    phylink and need to retrieve the ax88179_data structure from
    net_device.

Checking the end of the series, the callers do arrive later: the patch
adding HW support for AX179A-based chips introduces
drivers/net/usb/ax88179a_devices.c, and together with the follow-ups it
calls netdev2data() from ax88179a_get_drvinfo(),
ax88179a_get_pauseparam(), ax88179a_set_pauseparam(),
ax88179a_get_eeprom_len(), ax88179a_get_eee(), ax88179a_set_eee() and
ax88179a_mii_ioctl().

Would it be clearer to say the ethtool ops using this helper are added in
a following patch of this series?

Also, the existing open-coded netdev_priv() plus driver_priv sequences in
ax88179_lib.c and ax88179_178a.c are left as they are. Was leaving them
unconverted intentional, or is that planned as a separate cleanup?

> void ax88179_status(struct usbnet *dev, struct urb *urb)
>  {
>  	struct ax88179_int_data *event;

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips
  2026-09-16  3:24 ` [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 27 potential
issue(s) to consider:

Critical: 0 · High: 4 · Medium: 19 · Low: 4

- [High] ax88179a_bind() advertises NETIF_F_HW_VLAN_CTAG_FILTER in
  dev->features/hw_features while ax88179a_netdev_ops provides neither…
- [High] The three new device IDs inherit the single usb_driver
  .suspend/.resume/.reset_resume of the legacy AX88179 code…
- [High] ax88179a_rx_fixup() dereferences device-controlled metadata with
  plain native-endian u64 pointer loads: `rx_hdr = *(u64…
- [High] ax88179a_rx_fixup() enforces only an upper packet-length bound
  (`pkt_len > dev->hard_mtu + AX179A_RX_HW_PAD`) and no minimum, so a…
- [Medium] ax88179a_bind() puts
  NETIF_F_IP_CSUM/NETIF_F_IPV6_CSUM/NETIF_F_RXCSUM/VLAN offloads into…
- [Medium] ax88179a_bind() sets
  ax179_data->eeprom_read_cmd/eeprom_write_cmd/eeprom_block/eeprom_wen…
- [Medium] ax88179a_ethtool_ops installs .nway_reset = usbnet_nway_reset,
  but ax88179a_bind() never fills in dev->mii (only…
- [Medium] ax88179a_stop() prepares `reg8 = 0` to power the Ethernet PHY
  down but then calls ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1,…
- [Medium] ax88179a_bulkin_config() starts with `int index = 0` and the
  ETHER_LINK_1000 / ETHER_LINK_100 cases only assign index when link_sts…
- [Medium] ax88179a_ethtool_ops mixes abstraction layers: pause
  parameters and SIOCxMIIREG go through phylink…
- [Medium] In ax88179a_reset() the WoL capability probe passes `&tmp` - a
  `u8 **` - as the destination of a 1-byte control read, while `tmp` is…
- [Medium] ax88179_mdiobus_read() ignores the return value of
  ax88179_read_cmd() and returns the u16 `res`, which ax88179_read_cmd()…
- [Medium] At this commit ax88179a_netdev_ops omits .ndo_set_rx_mode
  although the shared library provides ax88179_set_multicast() (which…
- [Medium] ax88179a_tx_fixup() writes into the SKB tailroom
  (skb_put_zero()) and headroom (skb_push() + put_unaligned_le64())…
- [Medium] Nothing bridges phylink's carrier-up transition to usbnet's
  RX-URB submission.
- [Medium] ax88179a_reset() calls phylink_start() but usbnet_open() has
  failure paths after info->reset() - info->check_connect() and…
- [Medium] All three new driver_infos set FLAG_AVOID_UNLINK_URBS, so
  usbnet_stop() skips usbnet_terminate_urbs() and RX URBs stay submitted…
- [Medium] ax88179a_stop() tears the MAC down (read-modify-write clearing
  AX_MEDIUM_RECEIVE_EN, then AX_RX_CTL = 0) before calling…
- [Medium] No lock serializes MAC register access and the shared
  ax179_data fields between the phylink resolve worker…
- [Medium] ax88179a_reset() calls phylink_start() and then
  usbnet_link_change(dev, 0, 0), giving the netdev two independent…
- [Medium] In ax88179a_mac_link_up() the SPEED_2500 branch (reachable
  only on the AX88279, the only chip advertising MAC_2500FD) programs…
- [Medium] In the SPEED_2500 branch of ax88179a_mac_link_up(), the two-
  byte write to AX88179A_MAC_RX_DATA_CDC_CNT passes the byte array…
- [Medium] net/usb: Silent packet drop on TX memory allocation failure
- [Low] The commit message tells users to give 'the ax88179_178a module'
  precedence over CDC-NCM, but the series builds ax88179.ko…
- [Low] ax88179a_tx_fixup() prepends an 8-byte TX descriptor and appends
  up to 16 bytes of padding but calls usbnet_set_skb_tx_stats(skb, 1,…
- [Low] ax88179a_mac_link_up() declares `u8 link_sts` without
  initialization, calls ax88179_read_cmd(dev, AX_ACCESS_MAC,…
- [Low] ax88179a_bind() powers the PHY (write AX_PHY_POWER to vendor
  command AX88179A_PHY_POWER, then msleep(250)) before the fallible…

--

ax88179_178a: Add HW support for AX179A-based chips

This adds a new ax88179a_devices.c to the ax88179 module, with bind,
reset, stop, rx_fixup and tx_fixup callbacks plus phylink/mdio support
for the AX88179A/B, AX88772D/E and AX88279 chips, which are matched by
BCD device version behind the shared 0x0b95:0x1790 id.  Kconfig switches
the driver from PHYLIB to PHYLINK.

A number of questions below.  Several of them appear to be resolved by
later patches in the same series; those are noted inline, since the
intermediate tree state is what a bisect would land on.

> diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
> index ddd76fa71e2ee..2ecead0181eaf 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

[Severity: Low]
The commit message says:

  "If the ax88179_178a module is configured by the OS to have precedence
   over CDC-NCM, ..."

but the Makefile builds ax88179.ko, not ax88179_178a.ko.  Should the
commit message name the module ax88179, so that modprobe softdep or
blacklist rules written from it actually match?

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 6aa35c8eb594f..a2c596305f798 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c
> @@ -1270,6 +1270,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,
> +}, {

[Severity: High]
These three new ids share the single usb_driver .suspend/.resume/
.reset_resume of the legacy code (ax88179_suspend()/ax88179_resume()).
Can that path oops on the new chips?

ax88179_resume() ends up in the legacy ax88179_reset(), which calls:

	mii_nway_restart(&dev->mii);

but ax88179a_bind() only ever sets dev->mii.supports_gmii and never
installs dev->mii.mdio_read/mdio_write, so mii_nway_restart() would call
through a NULL mdio_write.

The same path also restores AX88179_BULKIN_SIZE[0] and rx_urb_size =
20K, and never calls phylink_suspend()/phylink_resume() even though the
driver sets phydev->mac_managed_pm = true.

The later patch "ax88179_178a: Add Suspend/resume support for
AX88179A/772D/279" adds ax88179a_suspend()/ax88179a_resume() and
dispatches via priv->suspend/priv->resume, so is the concern only that
this commit and the next few are not individually bisectable?

> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> new file mode 100644
> index 0000000000000..5dac7a137c323
> --- /dev/null
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -0,0 +1,973 @@

[ ... ]

> +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 int ax88179_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)
> +{

[ ... ]

> +	ax88179_read_cmd(dev, AX_ACCESS_PHY, phy_id, (__u16)regnum, 2, &res);
> +	return res;
> +}

[Severity: Medium]
Should ax88179_mdiobus_read() propagate the transfer error here?  The
return value of ax88179_read_cmd() is dropped, and for size == 2 that
helper zeroes its temporary before the transfer:

drivers/net/usb/ax88179_lib.c:ax88179_read_cmd() {
	if (size == 2) {
		u16 buf = 0;

		ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
		le16_to_cpus(&buf);
		*((u16 *)data) = buf;
	...
}

So a failed USB control transfer is reported to phylib as a successful
read of 0 (BMSR/BMCR == 0) rather than a negative errno.  The C45
accessors in this file do check ret < 0, which makes the C22 path look
inconsistent.

[ ... ]

> +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_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;

[ ... ]

> +	if (ax179_data->chip_version == AX_VERSION_AX88279 && (link_sts & AX_USB_FS))
> +		index = BULK_IN_SPEED_FS;

[Severity: Medium]
What happens on an AX88179A or AX88772D attached to a full-speed port?
link_sts has AX_USB_FS, so neither the AX_USB_SS nor the AX_USB_HS test
matches and index stays at 0 (or 1 for 100 Mbit half duplex).  The
full-speed override above is gated on chip_version == AX_VERSION_AX88279,
so it does not help these two chips.

AX88772D_BULKIN_SIZE[] has no designated initializer for index 0 or 1, so
the write below sends an all-zero {ctrl, timer_l, timer_h, size, ifg}
block:

	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, &bulkin_data[index]);

Since both tables do define BULK_IN_SPEED_FS, full-speed operation seems
to be expected; should the override drop the chip_version test?

[ ... ]

> +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,

[Severity: Medium]
At this commit ax88179a_get_eeprom_len() advertises 0x4000 bytes for the
AX88279 and 640 bytes otherwise, but the installed callbacks still use
the legacy word-addressed protocol:

drivers/net/usb/ax88179_lib.c:ax88179_get_eeprom() {
	...
	__ax88179_read_cmd(dev, AX_ACCESS_EEPROM, i, 1, 2, &eeprom_buff[i - first_word]);
	...
}

They read none of the ax179_data->eeprom_read_cmd/eeprom_write_cmd/
eeprom_block/eeprom_wen fields that ax88179a_bind() sets, so 'ethtool -e'
services offsets up to 0x3FFF over AX_ACCESS_EEPROM instead of the flash
or eFuse command, and 'ethtool -E' ignores eeprom_wen.  The later patch
"ax88179_178a: Add support for AX88179A/772D/279 EEPROM access" wires
these fields up - would it be better to advertise the new length only
once the matching accessors are in place?

> +	.nway_reset		= usbnet_nway_reset,

[Severity: Medium]
Can usbnet_nway_reset() ever work for this driver?  It starts with:

drivers/net/usb/usbnet.c:usbnet_nway_reset() {
	if (!dev->mii.mdio_write)
		return -EOPNOTSUPP;
	...
}

and ax88179a_bind() only sets dev->mii.supports_gmii, leaving mdio_write
NULL, so 'ethtool -r' always fails.  Should this be
phylink_ethtool_nway_reset()?

> +	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
> +	.set_link_ksettings	= phy_ethtool_set_link_ksettings,
> +	.get_pauseparam		= ax88179a_get_pauseparam,
> +	.set_pauseparam		= ax88179a_set_pauseparam,
> +	.get_ts_info		= ethtool_op_get_ts_info,
> +};

[Severity: Medium]
This table mixes the two abstractions: pause parameters and the MII
ioctl go through phylink, while link settings go straight to phylib and
bypass the phylink instance the driver just created.

phy_ethtool_set_link_ksettings() validates against phydev->supported
only, and phylink_bringup_phy() masks pl->supported and phy->advertising
rather than phy->supported.  For the AX88279 the phylink_config declares
MAC_100 | MAC_1000 | MAC_2500FD and no MAC_10, so a user can still force
a mode outside the MAC capability set, and pl->link_config is left stale.
Would phylink_ethtool_ksettings_get()/phylink_ethtool_ksettings_set() be
the right callbacks here?

[ ... ]

> +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 tmp8, link_sts, reg8[3];

[ ... ]

> +	switch (speed) {
> +	case SPEED_2500:

[ ... ]

> +		reg8[0] = 0x40;
> +		reg8[1] = AX_MAC_MIQFFCTRL_FORMAT | AX_MAC_MIQFFCTRL_DROP_CRC | AX_MAC_LSO_ERR_EN;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 2, 2, reg8);

[Severity: Medium]
Does this write swap the two bytes on big-endian hosts?  reg8 is a u8
array with an explicit byte order, but the helper reinterprets any
2-byte payload as a native u16:

drivers/net/usb/ax88179_lib.c:ax88179_write_cmd() {
	if (size == 2) {
		u16 buf;

		buf = *((u16 *)data);
		cpu_to_le16s(&buf);
	...
}

Would two separate 1-byte writes, or a __le16, be clearer here?

> +
> +		tmp8 = AX_XGMII_EN;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
> +
> +		tmp8 = 0x1C | AX_LSO_ENHANCE_EN;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_LSO_ENHANCE_CTRL, 1, 1, &tmp8);
> +
> +		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:

[ ... ]

> +		tmp8 = 0x40;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_RX_DATA_CDC_CNT, 1, 1, &tmp8);

[ ... ]

> +	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 (ax179_data->chip_version < AX_VERSION_AX88279) {
> +		tmp8 = 0;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
> +	}

[Severity: Medium]
Is this condition inverted?  AX_XGMII_EN in AX88179A_BFM_DATA is only
ever set in the SPEED_2500 branch, which is reachable just on the
AX88279 (the only chip advertising MAC_2500FD), yet the clearing write is
gated on chip_version < AX_VERSION_AX88279.

Related: the SPEED_1000/100/10 branches rewrite only the first byte of
AX88179A_MAC_RX_DATA_CDC_CNT, so after an AX88279 link at 2.5 Gbit
renegotiates down, the second byte keeps AX_MAC_MIQFFCTRL_FORMAT |
AX_MAC_MIQFFCTRL_DROP_CRC | AX_MAC_LSO_ERR_EN, and
AX88179A_MAC_LSO_ENHANCE_CTRL plus the 2.5G AX88179A_MAC_TX_PAUSE triple
also survive.  Should the lower-speed paths restore all of these?

[Severity: Low]
Above, link_sts is declared uninitialized and the read result is not
checked before it is passed to ax88179a_bulkin_config().  For size == 1
ax88179_read_cmd() forwards straight to __ax88179_read_cmd() and leaves
the caller's buffer untouched on error, unlike the size == 2/4 paths
which pre-zero a temporary.  Should link_sts be initialized, or the
return value checked, so a failing control transfer does not pick the
bulk-in block from a stale stack value?

> +	if (duplex)
> +		mode |= AX_MEDIUM_FULL_DUPLEX;
> +
> +	if (dev->net->mtu > 1500)
> +		mode |= AX_MEDIUM_JUMBO_EN;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &mode);
> +
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->rxctl);

[Severity: Medium]
What serializes these register accesses against the RTNL paths?
ax88179a_mac_link_up() runs from phylink_resolve(), which holds only
pl->phydev_mutex / phy->lock / pl->state_mutex and never RTNL, while
ndo_change_mtu does a read-modify-write of the same register:

drivers/net/usb/ax88179_lib.c:ax88179_change_mtu() {
	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);
	...
}

So 'ip link set mtu 9000' concurrent with a link event can lose either
update.  ax179_data->rxctl is likewise written in ax88179a_reset() and
read here with no lock and no READ_ONCE()/WRITE_ONCE().

> +
> +	tmp8 = AX_MAC_RX_PATH_READY | AX_MAC_TX_PATH_READY;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, &tmp8);
> +}

[Severity: Medium]
Does anything re-submit RX URBs after this link-up?  ax88179a_reset()
ends with usbnet_link_change(dev, 0, 0), whose deferred
__handle_link_change() runs unlink_urbs(dev, &dev->rxq) while the carrier
is down, and usbnet_bh() only refills the RX queue when
netif_carrier_ok() is true and the work has been queued:

usbnet_link_change() -> __handle_link_change() {
	if (!netif_carrier_ok(dev->net))
		unlink_urbs(dev, &dev->rxq);
	else
		queue_work(system_bh_wq, &dev->bh_work);
}

phylink calls netif_carrier_on() from the resolver, which schedules
linkwatch but never queue_work(system_bh_wq, &dev->bh_work).  With a
static IP and no outgoing traffic, is the interface left with no RX URBs
queued after the cable is plugged in?

[ ... ]

> +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,
> +};

[Severity: Medium]
With no .ndo_set_features here, does toggling the advertised offloads do
anything?  ax88179a_bind() puts NETIF_F_IP_CSUM/NETIF_F_IPV6_CSUM/
NETIF_F_RXCSUM and the VLAN bits into hw_features, so
__netdev_update_features() accepts the change and updates only the
software bitmap.  ax88179a_reset() unconditionally enables the checksum
engines and sets ax179_data->rx_checksum = 1, and ax88179a_rx_fixup()
tests that private flag, so 'ethtool -K eth0 rx off' reports success
while frames keep being marked CHECKSUM_UNNECESSARY.  The later patch
"ax88179_178a: Add VLAN offload support for AX88179A" installs
.ndo_set_features - should it come first, or the features be advertised
later?

[Severity: Medium]
.ndo_set_rx_mode is also missing at this commit, although the shared
library already provides ax88179_set_multicast() and the legacy
ax88179_netdev_ops installs it, so promiscuous and multicast list
changes never reach the hardware (AX_RX_CTL_PRO is never set).  Plain
multicast happens to work because ax88179a_reset() sets AX_RX_CTL_AMALL.
The later patch "ax88179_178a: Add AX179A/AX279 multicast configuration"
adds the callback.

> +static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
> +{

[ ... ]

> +	dev->net->netdev_ops = &ax88179a_netdev_ops;
> +	dev->net->ethtool_ops = &ax88179a_ethtool_ops;

[ ... ]

> +	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 |
> +			      NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX |
> +			      NETIF_F_HW_VLAN_CTAG_FILTER;
> +
> +	dev->net->hw_features |= dev->net->features;

[Severity: High]
Does register_netdev() reject this combination?  The features above
include NETIF_F_HW_VLAN_CTAG_FILTER while ax88179a_netdev_ops has
neither .ndo_vlan_rx_add_vid nor .ndo_vlan_rx_kill_vid at this commit:

net/core/dev.c:register_netdevice() {
	if (((dev->hw_features | dev->features) & NETIF_F_HW_VLAN_CTAG_FILTER) &&
	    (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
	     !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
		netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
		ret = -EINVAL;
	...
}

That would make every AX88179A/772D/279 device fail to probe until the
later patch "ax88179_178a: Add VLAN offload support for AX88179A" adds
the two callbacks.  Could the filter bit be advertised in that patch
instead, so this commit probes on its own?

[ ... ]

> +	/* Power PHY for probing */
> +	reg8 = AX_PHY_POWER;
> +	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
> +	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;
> +}

[Severity: Low]
Should the error labels also write 0 back to AX88179A_PHY_POWER?  The
PHY is powered up before ax88179a_init_mdio(), ax88179a_phylink_setup()
and ax88179a_init_phy(), and the unwind path only releases software
objects, so a failed bind (for example mdiobus_alloc() returning NULL or
mdiobus_register() failing) leaves the PHY powered with no driver
attached.  ax88179a_unbind() does the power-down write.

[ ... ]

> +static int ax88179a_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
> +{

[ ... ]

> +	if (!skb || skb->len < sizeof(rx_hdr))
> +		goto err;
> +
> +	/* RX Descriptor Header */
> +	skb_trim(skb, skb->len - sizeof(rx_hdr));
> +	rx_hdr = *(u64 *)skb_tail_pointer(skb);

[Severity: High]
Are these u64 loads safe with respect to alignment and byte order?

usbnet allocates the RX buffer with __netdev_alloc_skb_ip_align(), so
skb->data is offset by NET_IP_ALIGN, and the trim length comes from the
device's URB actual_length, so the address above is unaligned by
construction on architectures that do not fix up unaligned 64-bit loads.

rx_hdr is also never byte-swapped, so on big-endian hosts hdr_off and
pkt_cnt below are decoded from the wrong halves.  The existing
ax88179_rx_fixup() uses get_unaligned_le32() for exactly this.

> +	/* Get the first RX packet descriptor */
> +	pkt_desc_ptr = (u64 *)(skb->data + hdr_off);
> +	le64_to_cpus(pkt_desc_ptr);
> +
> +	pkt_end = 0;
> +	while (pkt_cnt--) {
> +		u64 pkt_desc = *pkt_desc_ptr;

Same question here: hdr_off is only constrained by
hdr_off == skb->len - pkt_cnt * 8, never to an 8-byte boundary, and
le64_to_cpus() is applied once, in place, to the first descriptor only -
every later iteration reads its descriptor unconverted.  Would
get_unaligned_le64() per descriptor cover both problems?

> +		pkt_len = (u32)((pkt_desc & AX179A_RX_PD_LEN_MASK) >> AX179A_RX_PD_LEN_SHIFT)
> +			  - (ax179_data->ip_align ? 2 : 0);

[ ... ]

> +		if (pkt_desc & AX179A_RX_PD_DROP || !(pkt_desc & AX179A_RX_PD_RX_OK) ||
> +		    pkt_len > (dev->hard_mtu + AX179A_RX_HW_PAD)) {
> +			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);

[Severity: High]
Only an upper bound on pkt_len is checked.  Can a descriptor with RX_OK
and pkt_len between 0 and 13 leak uninitialized memory?

usbnet_skb_return() calls eth_type_trans() unconditionally, and the short
pull there is ignored:

include/linux/etherdevice.h:eth_skb_pull_mac() {
	struct ethhdr *eth = (struct ethhdr *)skb->data;
	skb_pull_inline(skb, ETH_HLEN);
	return eth;
}

usbnet's own guard does not apply, because rx_process() returns early for
FLAG_MULTI_PACKET drivers:

drivers/net/usb/usbnet.c:rx_process() {
	if (dev->driver_info->flags & FLAG_MULTI_PACKET)
		return -EALREADY;

	if (skb->len < ETH_HLEN) { ... }
}

With an AF_PACKET listener, dev_parse_header() then copies h_source into
sockaddr_ll.sll_addr.  Should the loop also reject pkt_len < ETH_HLEN
(or ETH_ZLEN)?

[ ... ]

> +static struct sk_buff *ax88179a_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
> +{

[ ... ]

> +	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 (!(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);

[Severity: Medium]
Both out-of-memory exits in ax88179a_tx_fixup() - this
skb_linearize() failure and the skb_copy_expand() failure a few
lines below - free the skb and return NULL.  For a driver_info with
FLAG_MULTI_PACKET set, is that distinguishable from "packet
collected, waiting for more"?

drivers/net/usb/usbnet.c:usbnet_start_xmit() {
	if (info->tx_fixup) {
		skb = info->tx_fixup (dev, skb, flags);
		if (!skb) {
			/* packet collected; minidriver waiting for more */
			if (info->flags & FLAG_MULTI_PACKET)
				goto not_drop;
			...
			goto drop;
		}
	}
	...
}

All three of ax88179a_info, ax88772d_info and ax88279_info set
FLAG_MULTI_PACKET, so the NULL return takes the not_drop path: the
core skips the tx_dropped accounting done at the drop label and
returns NETDEV_TX_OK.  The frame is freed here and never counted
anywhere, so it is invisible in both dev_get_tstats64() and
ifconfig/ip -s output, which makes an allocation failure under
memory pressure look like a silent black hole rather than a
counter the admin can see.

Should these two paths bump the drop counter themselves (the usual
spelling for a MULTI_PACKET minidriver being
dev_core_stats_tx_dropped_inc(dev->net) before returning NULL),
since the MULTI_PACKET contract puts the accounting on the driver?

[Severity: Medium]
Should this test skb_cloned()/skb_header_cloned() as well?  The decision
to modify in place is based only on the available headroom and tailroom,
and because ax88179a_bind() sets needed_headroom = needed_tailroom = 8, a
cloned linear skb (tap or AF_PACKET clone, TCP retransmit clone, tc
mirred) normally passes the size test and then gets skb_put_zero() and
skb_push() applied to the shared head.  skb_linearize() only unclones
non-linear skbs.  The sibling function guards the same sequence:

drivers/net/usb/ax88179_178a.c:ax88179_tx_fixup() {
	if ((skb_header_cloned(skb) || headroom < 0) &&
	    pskb_expand_head(skb, ...))
	...
}

> +
> +	usbnet_set_skb_tx_stats(skb, 1, 0);

[Severity: Low]
This passes a zero byte delta, but the function prepended an 8-byte
descriptor and up to 16 bytes of padding.  With FLAG_MULTI_PACKET set,
usbnet_start_xmit() accounts the full transfer:

drivers/net/usb/usbnet.c:usbnet_start_xmit() {
	entry->length += length;
	...
}

so tx_bytes counts the USB framing too (1500 becomes 1512+).  cdc_ncm
passes a negative delta for this.  The pre-existing ax88179_tx_fixup()
uses the same zero-delta convention, so this may be intentional.

> +static int ax88179a_reset(struct usbnet *dev)
> +{

[ ... ]

> +	/* 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;

[Severity: Medium]
Is &tmp intended here?  tmp is the u8 * pointing at the local buf[5], so
&tmp is a u8 ** and the 1-byte read lands on the low byte of the pointer
variable itself rather than in the scratch buffer; the register value
that was meant to be inspected is discarded.  Every other call in this
function passes tmp.  It is latent today because tmp is not used
afterwards, but a later use or reordering would make it corrupt the
pointer.

> +
> +	phylink_start(ax179_data->phylink);
> +
> +	usbnet_link_change(dev, 0, 0);
> +
> +	return 0;
> +}

[Severity: Medium]
phylink is started here, but usbnet_open() has failure paths after
info->reset() - info->check_connect() and usbnet_status_start() - that
'goto done' and return an error without calling info->stop, so
ax88179a_stop()/phylink_stop() never runs.  Does that leave phylink
started on a netdev whose ndo_open failed, with the resolver free to call
ax88179a_mac_link_up() and raise the carrier while no RX URBs exist?  The
next successful open would then call phylink_start() a second time, and
phy_start() WARNs when the PHY is not in PHY_READY or PHY_HALTED.

[Severity: Medium]
These two calls also give the netdev two independent owners of the
carrier bit: usbnet_link_change(dev, 0, 0) calls netif_carrier_off()
directly behind phylink's back.  If a resolve completes with the link up
between the two statements, phylink has already called
netif_carrier_on() and programmed the MAC, and the following
netif_carrier_off() leaves the interface NO-CARRIER.

Since data->phydev->irq = PHY_MAC_INTERRUPT (no polling), nothing
schedules another resolve to repair the state until a physical link flap,
and the deferred __handle_link_change() unlinks the RX URBs as well.

> +static int ax88179a_stop(struct usbnet *dev)
> +{
> +	struct ax88179_data *ax179_data = dev->driver_priv;
> +	u16 reg16;
> +	u8 reg8;
> +
> +	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &reg16);
> +	reg16 &= ~AX_MEDIUM_RECEIVE_EN;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &reg16);
> +
> +	reg16 = 0;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &reg16);
> +
> +	reg8 = 0;
> +	ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);

[Severity: Medium]
Should this be ax88179_write_cmd()?  reg8 is set to 0 to power the PHY
down, but the read merely overwrites reg8 with the device's answer and
programs nothing, so the AX_PHY_POWER bit set in ax88179a_bind() and
ax88179a_reset() stays set after 'ip link set down'.  ax88179a_unbind()
has the intended form:

	tmp8 = 0;
	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &tmp8);

> +
> +	phylink_stop(ax179_data->phylink);
> +
> +	return 0;
> +}

[Severity: Medium]
Would it be safer to call phylink_stop() before the disable writes
above?  ax88179a_mac_link_up() runs from phylink's resolve work item
holding only pl->state_mutex, so a link-up landing in this window
re-writes AX_RX_CTL (with AX_RX_CTL_START), AX_MEDIUM_STATUS_MODE (with
AX_MEDIUM_RECEIVE_EN) and AX88179A_MAC_PATH after the teardown; the
read-modify-write of AX_MEDIUM_STATUS_MODE can also be lost.  Since
ax88179a_mac_link_down() is an empty stub, phylink_stop() cannot undo it.

> +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,
> +};

[Severity: Medium]
What kills the RX URBs for these driver_infos?  FLAG_AVOID_UNLINK_URBS
makes usbnet_stop() skip usbnet_terminate_urbs(), and ax88179a_stop()
neither cancels nor drains them, so with the interface already down at
unbind time the URBs are killed by USB core, rx_complete() calls
defer_bh() -> queue_work(system_bh_wq, &dev->bh_work), and
usbnet_disconnect() only does cancel_work_sync(&dev->kevent) before
free_netdev().  Can that work item then run on a freed struct usbnet?
aqc111 uses the same flag combination, so this may be inherited from
usbnet rather than new here.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs
  2026-09-16  3:24 ` [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 · Low: 3

- [Medium] The AX179A EEE control register (AX_GPHY_CTL /
  AX_GPHY_EEE_CTRL) is never forced to a known state, while the driver…
- [Low] In this patch alone, ax88179a_phylink_mac_ops gains
  .mac_enable_tx_lpi/.mac_disable_tx_lpi while ax88179a_phylink_setup()…
- [Low] The changelog of "ax88179_178a: Add EEE configuration support for
  AX88179A MACs" ends with the truncated/incorrect sentence "Add support…
- [Low] At this commit, ax88179a_ethtool_ops
  (drivers/net/usb/ax88179a_devices.c:209) has no .get_eee/.set_eee…

--

ax88179_178a: Add EEE configuration support for AX88179A MACs

This adds mac_enable_tx_lpi and mac_disable_tx_lpi callbacks to
ax88179a_phylink_mac_ops, which write the single AX_GPHY_EEE_CTRL register
of the AX88179A family.  The chip has no LPI timer registers, so the timer
argument is ignored.

A couple of questions below.

> ax88179_178a: Add EEE configuration support for AX88179A MACs
>
>     The AX88179A uses a simple HW configuration for EEE
>     via a single EEE configuration register without LPI timer support
>     Add support for this EEE enable/disable register and replace the EEE.

[Severity: Low]
The last sentence looks unfinished: "Add support for this EEE
enable/disable register and replace the EEE."  What is being replaced?
The diff is purely additive (17 insertions, no deletions) and
ax88179a_devices.c has no prior EEE handling apart from the
eee_enabled_default assignment in ax88179a_phylink_setup().

Also, the subject and body mention only AX88179A MACs, but
ax88179a_phylink_mac_ops is the single ops table passed to
phylink_create() by ax88179a_phylink_setup() for every chip handled by
this file, including AX88772D/E and AX88279.  Could the message state
that these chips share the same single-register EEE semantics?

> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 5dac7a137c323..785919e14e861 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -384,10 +384,27 @@ static void ax88179a_mac_link_up(struct phylink_config *config,
>  	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_PATH, 1, 1, &tmp8);
>  }
>  
> +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);
> +}

[Severity: Medium]
Is AX_GPHY_EEE_CTRL ever brought to a known state at bind or reset?

Neither ax88179a_bind() nor ax88179a_reset() writes AX_GPHY_EEE_CTRL, and
these two new callbacks are its only writers.  Meanwhile
ax88179a_phylink_setup() tells phylink that EEE is off:

	if (!data->is_ax88772d) {
		data->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD;
		data->phylink_config.eee_enabled_default = false;
	}

phylink cannot clear it either, because the disable path only runs for a
bit phylink itself set:

drivers/net/phy/phylink.c:phylink_deactivate_lpi() {
	if (pl->mac_enable_tx_lpi) {
		pl->mac_enable_tx_lpi = false;
		...
		pl->mac_ops->mac_disable_tx_lpi(pl->config);
}

So if the chip or firmware power-on default of AX_GPHY_EEE_CTRL is
"enabled", does the MAC stay in LPI mode while phylink and ethtool
--show-eee report EEE as disabled?

The older MAC path in this driver does force the state explicitly:
ax88179_reset() calls ax88179_disable_eee().  lan78xx does the same by
clearing MAC_CR_EEE_EN_ during probe, and lan78xx_mac_enable_tx_lpi()
documents that it relies on that.  Would an explicit write of
AX_GPHY_EEE_CTRL to false in ax88179a_reset() be appropriate here?

> +
> +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,
>  };

[Severity: Low]
With this commit applied on its own, does registering these two callbacks
have the opposite effect of what is intended?

ax88179a_phylink_setup() sets lpi_capabilities but leaves
phylink_config.lpi_interfaces zeroed (the config is zero-allocated in
ax88179a_bind()), and phylink gates all MAC EEE handling on that bitmap:

drivers/net/phy/phylink.c:phylink_create() {
	pl->mac_supports_eee = pl->mac_supports_eee_ops &&
			       pl->config->lpi_capabilities &&
			       !phy_interface_empty(pl->config->lpi_interfaces);
}

So mac_supports_eee_ops becomes true while mac_supports_eee stays false.
ax88179a_mac_enable_tx_lpi() is then unreachable, since phylink_link_up()
only calls phylink_activate_lpi() when pl->mac_supports_eee, and
phylink_bringup_phy() takes this branch instead:

	} else if (pl->mac_supports_eee_ops) {
		/* MAC supports phylink EEE, but wants EEE always disabled. */
		phy_disable_eee(phy);

This resolves later in the same series: the following patch,
"ax88179_178a: Add EEE configuration support for AX88179A PHYs", adds the
memcpy() of supported_interfaces into lpi_interfaces in
ax88179a_phylink_setup(), after which mac_supports_eee becomes true.  Is
it worth folding that lpi_interfaces initialisation into this patch so the
intermediate state does not force PHY EEE off?

[Severity: Low]
In the same vein, at this commit ax88179a_ethtool_ops has no .get_eee or
.set_eee, and eee_enabled_default is false, so nothing can set
pl->eee_cfg.tx_lpi_enabled and ethtool EEE requests are rejected by the
core.

The following patch in the series adds ax88179a_get_eee() and
ax88179a_set_eee() forwarding to phylink_ethtool_get_eee() and
phylink_ethtool_set_eee() and wires them into ax88179a_ethtool_ops, so the
user-visible path only appears there.  Would it read better to add the
ethtool plumbing together with the callbacks that it drives?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs
  2026-09-16  3:24 ` [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-09-17 21:24   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:24 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 0 · Low: 2

- [Low] The patch exposes .get_eee/.set_eee (ax88179a_devices.c:209-221,
  table entries at :232-233) and activates the LPI path by populating…
- [Low] The commit message contains a non-existent part number:
  "Configure phylink to work with the PHYs in the AX88197A controllers"…

--

ax88179_178a: Add EEE configuration support for AX88179A PHYs

This adds .get_eee/.set_eee to ax88179a_ethtool_ops, both forwarding to
phylink, and copies supported_interfaces into
phylink_config.lpi_interfaces in ax88179a_phylink_setup() so phylink
considers the MAC LPI capable.

>     ax88179_178a: Add EEE configuration support for AX88179A PHYs
>
>     Configure phylink to work with the PHYs in the AX88197A controllers
>     and provide ethtool get/set eee operations calling into phylink.

[Severity: Low]
Should "AX88197A" read "AX88179A" here?  The driver only knows
AX_VERSION_AX88179A, AX88772D and AX88279, so the part number in the
changelog does not match anything in the tree.

Also, would it help to spell out which variants are affected?
ax88179a_phylink_setup() and ax88179a_ethtool_ops are shared by all three
chips, and dev->net->ethtool_ops = &ax88179a_ethtool_ops is assigned in
ax88179a_bind() without a chip check, so AX88279 gains user-configurable
EEE too, while AX88772D stays unsupported because lpi_capabilities is left
at 0 for it:

	if (!data->is_ax88772d) {
		data->phylink_config.lpi_capabilities = MAC_100FD | MAC_1000FD;
		data->phylink_config.eee_enabled_default = false;
	}

and phylink_ethtool_get_eee()/phylink_ethtool_set_eee() then return
-EOPNOTSUPP for that variant.

Finally, the message says only "configure phylink to work with the PHYs",
which does not mention the actual mechanism being added, namely declaring
the MAC's LPI-capable interfaces so phylink_create() sets
mac_supports_eee.

> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 785919e14e861..6bf1ac17aa1d5 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -206,6 +206,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);
> +}
> +

[ ... ]

> @@ -440,6 +456,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));
> +

[Severity: Low]
This isn't a bug, but what does userspace see for the Tx LPI timer now?

ax88179a_phylink_setup() never assigns phylink_config.lpi_timer_default,
so phylink_create() leaves pl->eee_cfg.tx_lpi_timer at 0, and
ax88179a_get_eee() -> phylink_ethtool_get_eee() reports 0 us via
ethtool --show-eee.

In the other direction, ethtool --set-eee <dev> tx-lpi-timer N goes
through ax88179a_set_eee() -> phylink_ethtool_set_eee() -> eee_to_eeecfg()
which stores the value, but the MAC callback drops it:

	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);
	}

so the value is accepted and echoed back by get_eee without ever reaching
hardware.

For the record, this same combination already exists upstream:
ksz_phylink_get_caps() copies supported_interfaces into lpi_interfaces
without setting lpi_timer_default, and
ksz_phylink_mac_enable_tx_lpi() documents "@timer: timer value before
entering LPI (unused)" and just returns 0.  Would setting
lpi_timer_default to whatever fixed timeout the PHY uses still be worth
doing here, so --show-eee reports something meaningful?

>  	phylink = phylink_create(&data->phylink_config, dev->net->dev.fwnode,
>  				 phy_if_mode, &ax88179a_phylink_mac_ops);
>  	if (IS_ERR(phylink))

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A
  2026-09-16  3:24 ` [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider:

Critical: 0 · High: 4 · Medium: 5 · Low: 0

- [High] ax88179a_set_features()
  (drivers/net/usb/ax88179a_devices.c:578-603) clobbers the persistent…
- [High] This patch newly installs `.ndo_set_features =
  ax88179a_set_features` on the AX88179A ops table…
- [High] net: missing endianness conversion for subsequent Rx descriptors
  in ax88179a_rx_fixup
- [High] net: unswapped read of rx_hdr yields garbage on big-endian
  architectures
- [Medium] The series is not bisectable: the VLAN feature bits are
  advertised three commits earlier than the callbacks this patch adds,…
- [Medium] The new VLAN register sequences ignore every USB register-
  access error and still report success.
- [Medium] All three new functions read AX88179A_VLAN_ID_CONTROL into an
  uninitialised local u8 and ignore the result, then transmit a value…
- [Medium] The new `data->rx_checksum` write in ax88179_set_features()
  (drivers/net/usb/ax88179_lib.c:369) races with, and is ordered wrongly…
- [Medium] Enabling NETIF_F_HW_VLAN_CTAG_FILTER makes
  ax88179a_set_features() (drivers/net/usb/ax88179a_devices.c:582-596)…

--

ax88179_178a: Add VLAN offload support for AX88179A

This adds ndo_vlan_rx_add_vid/ndo_vlan_rx_kill_vid and an AX88179A specific
ndo_set_features to program the hardware VLAN filter table and the VLAN
control register, and starts tracking the RX checksum state in
ax88179_set_features(). A few questions below about the register
programming and about how the series is split.

> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> index ea36aef3d3e45..3324069e90fc3 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -346,6 +346,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) {
> @@ -365,6 +366,7 @@ int ax88179_set_features(struct net_device *net, netdev_features_t features)
>  		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;

[Severity: High]
One more question about the RX path that this rx_checksum tracking
feeds into.  ax88179a_rx_fixup() picks up the 8-byte hardware footer
with a plain native load:

	rx_hdr = *(u64 *)skb_tail_pointer(skb);

The device writes that footer little-endian, like the per-packet
descriptors that follow it, but here there is no le64 conversion at
all.  On big-endian hosts, do hdr_off and pkt_cnt extracted from
rx_hdr come out as nonsense, so the bounds check against skb->len
fails and the whole URB worth of packets is dropped (or, worse, a
plausible-looking hdr_off points the descriptor pointer somewhere
inside the payload)?

There is a second concern with the cast: skb_tail_pointer(skb) is not
guaranteed to be 8-byte aligned, and dereferencing it as u64 * traps
on architectures that do not tolerate unaligned accesses.
get_unaligned_le64(skb_tail_pointer(skb)) would address both the
alignment and the byte order in one step.

Is the driver intended to be little-endian only, or should this and
the descriptor reads use the explicit le64 accessors?  If the former,
that restriction is not stated anywhere and the driver still builds on
big-endian configurations.

[Severity: High]
While checking who consumes the new data->rx_checksum flag I ended
up in ax88179a_rx_fixup(), and the descriptor decoding there looks
endianness-broken for all but the first packet of a batch.

le64_to_cpus(pkt_desc_ptr) is called once, before the loop, so it
byte-swaps only the descriptor at the head of the array.  Inside the
loop pkt_desc_ptr is then incremented and each following descriptor is
read natively:

	le64_to_cpus(pkt_desc_ptr);

	pkt_end = 0;
	while (pkt_cnt--) {
		u64 pkt_desc = *pkt_desc_ptr;

On a big-endian host, is every packet after the first parsed from a
byte-reversed descriptor?  If so the extracted length and status
fields are garbage, the pkt_end/pkt_len arithmetic derived from them
is meaningless, and the subsequent skb_pull()/skb_trim() on the batch
SKB can be asked to move past skb->len.  The same descriptor also
gates ax88179a_rx_checksum(ax_skb, pkt_desc), so the CHECKSUM_*
decision this patch now makes conditional on data->rx_checksum would
be taken from bits that never came from the hardware.

Would converting per iteration, e.g. u64 pkt_desc =
get_unaligned_le64(pkt_desc_ptr) (or le64_to_cpu(*pkt_desc_ptr))
inside the loop and dropping the single pre-loop swap, be the intended
fix?  Or is the array meant to be swapped in one go somewhere I am not
seeing?

[Severity: Medium]
Is the update order here correct for the disable case?  AX_RXCOE_CTL is
programmed first and data->rx_checksum is cleared afterwards.

The reader lives in the usbnet BH work item:

drivers/net/usb/ax88179a_devices.c:ax88179a_rx_fixup() {
	...
	if (ax179_data->rx_checksum)
		ax88179a_rx_checksum(ax_skb, pkt_desc);
	...
}

Writers run in process context under RTNL (ndo_set_features, and
ax88179a_reset() via usbnet_open), the reader runs from bh_work, and
neither side uses READ_ONCE()/WRITE_ONCE().  In the window between the
AX_RXCOE_CTL write and the flag update, can descriptors produced with the
checksum engine already off still be consumed with rx_checksum == 1 and
end up marked CHECKSUM_UNNECESSARY?

Would clearing the flag before disabling the engine, and setting it after
enabling it, be the safer order?

> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 6bf1ac17aa1d5..481139f71ac26 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -239,6 +239,62 @@ 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;
> +	u16 reg16;
> +	u8 reg8;
> +
> +	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
> +	vlan_ctrl = reg8;

[Severity: Medium]
Can reg8 be used uninitialised here?  For a 1-byte access
ax88179_read_cmd() passes the caller's buffer straight through:

drivers/net/usb/ax88179_lib.c:ax88179_read_cmd() {
	...
	} else {
		ret = __ax88179_read_cmd(dev, cmd, value, index, size, data);
	}
	...
}

and the destination is only written on a successful, non-short transfer:

drivers/net/usb/usbnet.c:__usbnet_read_cmd() {
	...
	if (err > 0 && err <= size) {
		if (data)
			memcpy(data, buf, err);
	...
}

On -EPIPE/-ETIMEDOUT/-ENODEV or a short reply, reg8 keeps whatever was on
the stack, and that value is then copied to vlan_ctrl and written back into
AX88179A_VLAN_ID_CONTROL together with RD and WE.  Does this both disclose
a byte of stack to the device and allow arbitrary VFE/VSO bits to be
programmed?  The same pattern appears in ax88179a_vlan_rx_add_vid() and in
ax88179a_set_features() with u8 tmp.

> +
> +	/* Address */
> +	reg8 = (vid / 16);
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, &reg8);
> +
> +	/* Data */
> +	reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
> +
> +	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
> +	reg16 &= ~(1 << (vid % 16));
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
> +
> +	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
> +	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
> +
> +	return 0;
> +}

[Severity: Medium]
Should the return values of these register accesses be propagated instead
of returning 0 unconditionally?

For the 2-byte row read, a failure still stores 0 into the caller's
variable:

drivers/net/usb/ax88179_lib.c:ax88179_read_cmd() {
	if (size == 2) {
		u16 buf = 0;

		ret = __ax88179_read_cmd(dev, cmd, value, index, size, &buf);
		le16_to_cpus(&buf);
		*((u16 *)data) = buf;
	}
	...
}

so if the read of AX88179A_VLAN_ID_DATA0 fails, reg16 becomes 0 and the
following write-back clears the other 15 VIDs sharing that row.  A failed
address write can also apply the modification to whichever row was
previously selected.

Since __vlan_vid_add() records the VID when the callback returns 0:

net/8021q/vlan_core.c:__vlan_vid_add() {
	...
	err = ops->ndo_vlan_rx_add_vid(dev, proto, vid);
	if (err)
		return err;
	...
}

can the software VLAN list end up claiming VIDs that were never programmed,
or VIDs that were silently erased?  ax88179a_bind() already checks the
return values of these same helpers.

> +
> +static int ax88179a_vlan_rx_add_vid(struct net_device *net, __be16 proto, u16 vid)
> +{

[ ... same read-modify-write sequence as kill_vid, same questions apply ... ]

> +	return 0;
> +}
> +
>  static void ax88179a_mdio_unregister(struct ax88179_data *data)
>  {
>  	mdiobus_unregister(data->mdio);
> @@ -506,6 +562,49 @@ 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) {
> +		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> +		tmp ^= AX_VLAN_CONTROL_VFE;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> +		if (features & NETIF_F_HW_VLAN_CTAG_FILTER) {
> +			for (int i = 0; i < 256; i++) {
> +				u16 tmp16 = 0;
> +				/* Address */
> +				tmp = i;
> +				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS,
> +						  1, 1, &tmp);
> +				/* Data */
> +				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0,
> +						  2, 2, &tmp16);
> +				tmp = AX_VLAN_CONTROL_WE;
> +				ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL,
> +						  1, 1, &tmp);
> +			}
> +		}
> +	}

[Severity: High]
Does "tmp = AX_VLAN_CONTROL_WE" wipe the VFE and VSO bits that live in the
same register?  All four bits share AX88179A_VLAN_ID_CONTROL:

drivers/net/usb/ax88179_lib.h:
	#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

The loop writes a bare 0x01, so it clears the VFE bit that was just set a
few lines above, and also clears VSO even though NETIF_F_HW_VLAN_CTAG_RX
did not change.  The other new call sites keep the current contents:

	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);

and ax88179a_reset() programs those bits from net->features, which suggests
they are meant to persist:

	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;

Because VFE/VSO are then updated with read/XOR/write, does the hardware end
up inverted with respect to net->features?  After "ethtool -K ethX
rx-vlan-filter on" the filter is left off in hardware, and the following
"rx-vlan-filter off" XORs VFE back on with an empty table, dropping all
VLAN tagged RX while the stack believes filtering is disabled.

For reference, aqc111_set_features() re-reads the control register after
the clearing loop and re-asserts the bit:

drivers/net/usb/aqc111.c:aqc111_set_features() {
	...
	aqc111_read_cmd(dev, AQ_ACCESS_MAC, SFR_VLAN_ID_CONTROL, 1, 1, &reg8);
	reg8 |= SFR_VLAN_CONTROL_VFE;
	...
}

Was that step meant to be carried over here?

[Severity: Medium]
Is it acceptable to hold RTNL across this loop?  ndo_set_features is called
from __netdev_update_features() with RTNL held, and each iteration issues
three synchronous control transfers via ax88179_write_cmd() ->
usbnet_write_cmd() -> usb_control_msg(), i.e. 768 blocking bus
transactions with USB_CTRL_SET_TIMEOUT each.

On a busy bus, or when a transfer stalls and hits the timeout, this blocks
all other network configuration in the system for a long time.  Could the
clear be limited to rows actually in use, batched, or moved out of the RTNL
section?

> +
> +	if (changed & NETIF_F_HW_VLAN_CTAG_RX) {
> +		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> +		tmp ^= AX_VLAN_CONTROL_VSO;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &tmp);
> +	}
> +
> +	return 0;
> +}
> +
>  static const struct net_device_ops ax88179a_netdev_ops = {
>  	.ndo_open		= usbnet_open,
>  	.ndo_stop		= usbnet_stop,
> @@ -516,6 +615,9 @@ static int ax88179a_mii_ioctl(struct net_device *net, struct ifreq *rq, int cmd)
>  	.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,
>  };

[Severity: High]
Wiring up .ndo_set_features here also makes the shared checksum/TSO
programming in ax88179_set_features() reachable on this chip family for the
first time.  Is that consistent with ax88179a_reset()?

usbnet_open() runs the reset on every ifup:

drivers/net/usb/usbnet.c:usbnet_open() {
	...
	if (info->reset) {
		retval = info->reset(dev);
	...
}

and ax88179a_reset() programs the engines absolutely, ignoring
net->features:

	*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);
	ax179_data->rx_checksum = 1;

while ax88179_set_features() programs them relatively with read/XOR/write.
So after "ethtool -K ethX tx off; ip link set ethX down; ip link set ethX
up", hardware offload is on while the feature bits say off, and the next
"ethtool -K ethX tx on" XORs the engine off while the stack thinks offload
is active.

ax88179a_tx_fixup() sets no per-packet checksum request bit, so with
AX_TXCOE_CTL cleared and NETIF_F_IP_CSUM advertised, can packets go out
with uncomputed TCP/UDP checksums?  On the RX side, rx_checksum forced back
to 1 with the RXCOE engine off would make ax88179a_rx_checksum() mark
packets CHECKSUM_UNNECESSARY from descriptor bits the hardware no longer
produces.

Would absolute programming from features (and/or having reset honour
net->features) be the right fix?

Also, neither the new .ndo_set_features hook for checksum/TSO nor the new
data->rx_checksum tracking is mentioned in a changelog titled "Add VLAN
offload support for AX88179A".

[Severity: Medium]
Is the series bisectable with respect to these two callbacks?  The commit
"ax88179_178a: Add HW support for AX179A-based chips" already advertises
the filter bit in ax88179a_bind():

	dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM |
			      NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO |
			      NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX |
			      NETIF_F_HW_VLAN_CTAG_FILTER;

	dev->net->hw_features |= dev->net->features;

but ndo_vlan_rx_add_vid/ndo_vlan_rx_kill_vid only appear in this patch, and
register_netdevice() rejects that combination:

net/core/dev.c:register_netdevice() {
	...
	if (((dev->hw_features | dev->features) &
	     NETIF_F_HW_VLAN_CTAG_FILTER) &&
	    (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
	     !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
		netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
		ret = -EINVAL;
		goto err_uninit;
	}
	...
}

usbnet_probe() calls info->bind() before register_netdev(), so on the
commits between the feature advertisement and this patch, does probe fail
with -EINVAL and a warning for the whole AX179A family?  Could the feature
advertisement be moved into this patch (or the callbacks moved earlier) so
every commit in the series probes?

The changelog also lists NETIF_F_HW_VLAN_CTAG_TX, although nothing in this
diff touches TX tagging; that already exists in ax88179a_tx_fixup() from
the earlier commit.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration
  2026-09-16  3:24 ` [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 1 · Low: 0

- [High] ax88179_set_multicast() (drivers/net/usb/ax88179_lib.c:305) now
  only sets AX_RX_CTL_IPE when data->ip_align is non-zero (line 314).
- [Medium] Registering ax88179_set_multicast() as .ndo_set_rx_mode for
  the AX88179A family (drivers/net/usb/ax88179a_devices.c:618) creates a…

--

ax88179_178a: Add AX179A/AX279 multicast configuration

This makes AX_RX_CTL_IPE conditional on the new ip_align field in the
shared ax88179_set_multicast() helper, adds AX_RX_CTL_DROPCRCERR to the
base value, and registers the helper as .ndo_set_rx_mode for the
AX88179A device family.

> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> index 3324069e90fc3..708b0b23f1363 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -305,10 +305,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]
Can this break RX on the legacy AX88179/AX88178A devices at this commit?

ax88179_set_multicast() is also the rx-mode handler of the older driver:

drivers/net/usb/ax88179_178a.c:ax88179_netdev_ops = {
	...
	.ndo_set_rx_mode	= ax88179_set_multicast,
	...
};

but at this commit ax88179_bind() in ax88179_178a.c only kzalloc's the
private data and never assigns ip_align (grep for ip_align in that file
finds only netdev_alloc_skb_ip_align() calls), so "if (data->ip_align)"
is always false there and the async AX_RX_CTL write at the end of
ax88179_set_multicast() clears AX_RX_CTL_IPE.

That contradicts what ax88179_reset() programs:

drivers/net/usb/ax88179_178a.c:ax88179_reset() {
	...
	/* Configure RX control register => start operation */
	*tmp16 = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_IPE | AX_RX_CTL_START |
		 AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB;
	...
}

and ax88179_rx_fixup() in the same file still strips the pad
unconditionally, with no ip_align test (unlike ax88179a_devices.c, which
uses "ax179_data->ip_align ? 2 : 0"):

drivers/net/usb/ax88179_178a.c:ax88179_rx_fixup() {
	...
			/* Skip IP alignment pseudo header */
			skb_pull(skb, 2);
	...
		memcpy(ax_skb->data, skb->data + 2, pkt_len);
	...
}

So after the first rx-mode update following reset (dev_set_rx_mode() at
interface open, a promisc/allmulti change, or an unprivileged multicast
join via IP_ADD_MEMBERSHIP or MLD), does every received frame get
delivered shifted by two bytes, with the memcpy() path pulling up to two
bytes from the following packet slot into the frame handed to the stack
while the descriptor-derived CHECKSUM_UNNECESSARY is still applied?

Additional detail from the series: the later commit "ax88179_178a: Add
support for AX88179A/772D/279 EEPROM access" adds
"ax179_data->ip_align = 1;" to ax88179_bind() in ax88179_178a.c, so the
final state of the series is fine. That leaves this commit through
"ax88179_178a: Add ethtool get_drvinfo" / "ax88179_178a: Update driver
name and information" with broken RX on legacy devices. Would it make
sense to move that ip_align assignment into this patch so the tree stays
bisectable?

>  
>  	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 481139f71ac26..e77450bc3299f 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -615,6 +615,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,

[Severity: Medium]
Does this registration introduce an unsynchronized writer of
ax179_data->rxctl for the AX88179A family?

ax88179a_netdev_ops has neither .ndo_set_rx_mode_async nor
.ndo_change_rx_flags, so __dev_set_rx_mode() calls the handler directly
from dev_set_rx_mode():

net/core/dev_addr_lists.c:dev_set_rx_mode() {
	netif_addr_lock_bh(dev);
	__dev_set_rx_mode(dev);
	netif_addr_unlock_bh(dev);
}

ax88179_set_multicast() then does a multi-step read-modify-write of
data->rxctl (base value, then |= AX_RX_CTL_IPE, then |= PRO/AMALL/AM
with an async multicast-hash URB submitted in between) and finally
programs the register with ax88179_write_cmd_async().

Meanwhile ax88179a_mac_link_up() reads the same field and writes it
synchronously from the phylink resolve path, holding only phylink's
state_mutex:

drivers/net/usb/ax88179a_devices.c:ax88179a_mac_link_up() {
	...
	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->rxctl);
	...
}

and ax88179a_reset() rewrites both rxctl and the register:

drivers/net/usb/ax88179a_devices.c:ax88179a_reset() {
	...
	ax179_data->rxctl = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START |
			    AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB;
	...
}

The lock sets do not intersect, so can the phylink path latch an
intermediate rxctl value that is missing AX_RX_CTL_AM/AMALL/PRO? And
since one side uses an async URB while the other uses a synchronous
control transfer, can the two AX_RX_CTL writes also complete out of
order and leave the hardware filter without the multicast or promisc
bits until the next filter change or link event?

The same pattern already exists in the legacy driver's
ax88179_link_reset(), but for the AX88179A family this concurrency is
new here, and it is still present at the series tip. Would routing the
filter update through a deferred path (usbnet's EVENT_SET_RX_MODE /
driver_info->set_rx_mode, or .ndo_set_rx_mode_async) put it in the same
process context as the link and reset writers?

>  	.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,

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
  2026-09-16  3:24 ` [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider:

Critical: 0 · High: 2 · Medium: 4 · Low: 3

- [High] ax88179a_suspend() (drivers/net/usb/ax88179a_devices.c:170-172)
  calls usbnet_suspend(intf, message) but discards its return value and…
- [High] ax88179a_resume() (drivers/net/usb/ax88179a_devices.c:210-214)
  and ax88179a_suspend() (:138-142) call rtnl_lock() from inside the…
- [Medium] The new WoL programming in ax88179a_suspend()
  (drivers/net/usb/ax88179a_devices.c:146-152) performs a purely…
- [Medium] The new PM callbacks consume the results of ax88179_read_cmd()
  without checking the status, and for one-byte reads the destination is…
- [Medium] ax88179a_stop() (drivers/net/usb/ax88179a_devices.c:1163-1165)
  sets 'reg8 = 0;' and then calls ax88179_read_cmd(dev,…
- [Medium] The new ax88179a_resume() calls ax88179a_reset()
  (drivers/net/usb/ax88179a_devices.c:208), which unconditionally…
- [Low] The comment added in ax88179a_reset()
  (drivers/net/usb/ax88179a_devices.c:1140-1142) claims that when the…
- [Low] The comment added in ax88179a_resume()
  (drivers/net/usb/ax88179a_devices.c:201-203) says 'PHY-power is re-…
- [Low] The new PM callbacks issue a chain of vendor control transfers
  whose results are all discarded and then report success.

--

ax88179_178a: Add Suspend/resume support for AX88179A/772D/279

This adds suspend and resume handling for the AX88179A/772D/279 chips by
storing per-device suspend/resume function pointers in struct ax88179_data
and calling them from new wrapper functions registered in the single
usb_driver instance. A few questions on the new PM paths below.

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index a2c596305f798..93031b9ee92bc 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c

[ ... ]

> @@ -1342,9 +1345,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 708b0b23f1363..3b498206c50d5 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -455,3 +455,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/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index e77450bc3299f..ff41d20c219d4 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c

[ ... ]

> @@ -123,6 +125,53 @@ 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;
> +	u16 tmp16;
> +	u8 tmp8;
> +
> +	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();
> +	}
> +
> +	/* Enable WoL */
> +	if (priv->wolopts) {
> +		ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD, 1, 1, &tmp8);

[Severity: Medium]
tmp8 is declared without an initialiser and the read status is dropped
here.  For a one-byte access ax88179_read_cmd() hands the caller's variable
straight to __usbnet_read_cmd(), which only fills it in when the control
transfer returns a positive length:

drivers/net/usb/ax88179_lib.c:ax88179_read_cmd() {
	if (size == 2) {
		u16 buf = 0;
	...
	} else {
		ret = __ax88179_read_cmd(dev, cmd, value, index, size, data);
	}
}

If that read fails or comes back zero-length, is an indeterminate stack byte
then ORed with the WoL bits, written into the MAC monitor-mode register and
sent out on the control pipe to the device?

The same shape appears in ax88179a_resume() below, where the uninitialised
reg8 is tested immediately after an unchecked AX88179A_PHY_POWER read.
Would initialising both variables and checking the read status help here?

The legacy ax88179_suspend() has the same unchecked read-modify-write, but
these are new instances.

> +		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);

[Severity: Medium]
This read-modify-write only sets wake-source bits, it never clears the ones
that are already set.  ax88179a_reset() seeds that register with
magic-packet wake on every open and on every resume:

	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);

Does selecting WAKE_PHY alone therefore leave magic-packet wake still armed?

And with wolopts == 0 this branch is skipped entirely, so does RWMP stay
armed even though userspace asked for no WoL, while ax88179_get_wol()
reports 0?

> +
> +		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);
> +	}
> +
> +	usbnet_suspend(intf, message);
> +	ax88179_set_pm_mode(dev, false);
> +	return 0;
> +}

[Severity: High]
usbnet_suspend() can fail, and its result is discarded here:

drivers/net/usb/usbnet.c:usbnet_suspend() {
	if (!dev->suspend_count++) {
		spin_lock_irq(&dev->txq.lock);
		/* don't autosuspend while transmitting */
		if (dev->txq.qlen && PMSG_IS_AUTO(message)) {
			dev->suspend_count--;
			spin_unlock_irq(&dev->txq.lock);
			return -EBUSY;
	...
}

On that path no detach or URB teardown happens and suspend_count is put back
to 0, yet ax88179a_suspend() returns 0 and ax88179_suspend_wrapper() passes
that 0 to the USB core, so the interface is suspended anyway.

The following resume then runs usbnet_resume():

	if (!--dev->suspend_count) {

on a zero unsigned char, which wraps to 255, so the status URB restart, the
RX URB resubmission, the deferred TX anchor flush and the EVENT_DEV_ASLEEP
clearing are all skipped, and suspend_count stays off by one for the rest of
the binding.  Does the receive path stay dead until ifdown/ifup or a replug?

The driver keeps .supports_autosuspend = 1 and takes no PM usage reference,
and ax88179a_info sets FLAG_AVOID_UNLINK_URBS so usbnet_stop() does not call
usbnet_terminate_urbs(), which leaves txq.qlen non-zero when the autosuspend
timer fires, i.e. exactly the -EBUSY condition.

Should the error be propagated, and the WoL/phylink work above unwound on
that path?

>  
>  static int ax88179a_auto_detach(struct usbnet *dev)
>  {
>  	u16 tmp16;
> @@ -132,6 +181,43 @@ 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;
> +
> +	ax179_data = dev->driver_priv;
> +	ax88179_set_pm_mode(dev, true);
> +
> +	ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
> +	if (!(reg8 & AX_PHY_POWER)) {

[Severity: Medium]
This branch depends on the PHY power register, and ax88179a_stop() looks
like it meant to write that register but calls the read helper instead:

drivers/net/usb/ax88179a_devices.c:ax88179a_stop() {
	reg8 = 0;
	ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
}

ax88179a_unbind() does the power-down with a write:

	tmp8 = 0;
	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &tmp8);

Does the PHY stay powered after 'ip link set <dev> down', and does the new
resume code above then read a state that the stop path was supposed to have
cleared?  This came in with the earlier series patch adding the AX179A chip
support rather than with this patch, but this patch is the first code to
branch on that register's contents.

> +		reg8 = AX_PHY_POWER;
> +		ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
> +		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 ax88179_reset()
> +	 */

[Severity: Low]
The call below is ax88179a_reset(), while the comment names ax88179_reset(),
which is a different file-static function in ax88179_178a.c and is not on
this path.  Should the comment say ax88179a_reset()?

> +	reg8 = 0;
> +	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
> +	msleep(250);
> +
> +	ax88179a_reset(dev);

[Severity: Medium]
ax88179a_reset() rewrites both the cached and the hardware receive mode with
cold-start defaults:

	ax179_data->rxctl = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START |
			    AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB;
	...
	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->rxctl);

AX_RX_CTL_PRO is not part of that set, and nothing on the resume path calls
ndo_set_rx_mode/ax88179_set_multicast() again; usbnet_resume() does not
re-push the RX mode and ax88179a_mac_link_up() writes the same overwritten
ax179_data->rxctl cache.

Does a promiscuous interface, for example a bridge port or a running
capture, stop receiving foreign unicast frames after a suspend/resume cycle
while net->flags still has IFF_PROMISC?

> +
> +	if (netif_running(dev->net)) {
> +		rtnl_lock();

[Severity: High]
Can this take rtnl_mutex recursively?  The usb_driver .resume callback runs
synchronously in the caller's context:

__dev_open()			/* holds rtnl_mutex, sets __LINK_STATE_START */
  usbnet_open()
    usb_autopm_get_interface()
      pm_runtime_resume_and_get()
        usb_resume_interface()
          ax88179_resume_wrapper()
            ax88179a_resume()
              rtnl_lock()	<-- rtnl_mutex already held by this task

netif_running() is already true at that point because __dev_open() sets
__LINK_STATE_START before calling ndo_open, so bringing up a runtime
suspended adapter would hit this.

ax88179a_suspend() has the mirror exposure: a task holding RTNL blocks in
pm_runtime while the suspend callback waits for RTNL.

asix_devices.c documents this hazard for ax88772 and blocks autosuspend with
pm_runtime_get_noresume(&intf->dev) in ax88772_bind().  This driver keeps
.supports_autosuspend = 1 and there is no pm_runtime_*/usb_autopm_* call in
ax88179_178a.c, ax88179_lib.c or ax88179a_devices.c.  Should the same
approach be used here, or the phylink calls moved out of the PM callbacks?

> +		phylink_resume(ax179_data->phylink);
> +		rtnl_unlock();
> +	}
> +
> +	ax88179_set_pm_mode(dev, false);
> +
> +	return usbnet_resume(intf);
> +}

[Severity: Low]
None of the control transfers in this callback are checked, the
ax88179a_reset() return value is dropped, and usbnet_resume() always returns
0, so a failed firmware-mode or PHY power restore is reported to the USB
core as a successful resume, including on the .reset_resume path.

ax88179_write_cmd() does propagate -EPIPE/-EPROTO/-ESHUTDOWN from
usbnet_write_cmd(_nopm), and the msleep(250) delays do not confirm that the
transition happened.  The AX_MONITOR_MOD, AX_MEDIUM_STATUS_MODE and
AX88179A_WAKEUP_SETTING writes in ax88179a_suspend() are unchecked in the
same way while the callback claims WoL was armed.

Would checking these and returning the error be preferable?

>  
>  static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
>  {
>  	struct ax88179_data *ax179_data = dev->driver_priv;

[ ... ]

> @@ -1048,7 +1137,11 @@ static int ax88179a_reset(struct usbnet *dev)
>  			     1, 1, &tmp) > 0)
>  		ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY;
>  
> -	phylink_start(ax179_data->phylink);
> +	/* ax88179a_reset() may also be called from resume context, phylink
> +	 * is already started, then.
> +	 */

[Severity: Low]
Is phylink really already started when this runs from ax88179a_resume()?
ax88179a_suspend() passes mac_wol = !!priv->wolopts, and with WoL off
phylink_suspend() takes the else branch:

drivers/net/phy/phylink.c:phylink_suspend() {
	...
	} else {
		phylink_stop(pl);
	}
}

so phylink is stopped at this point and is only restarted by the
phylink_resume() call that follows the reset.  Would wording along the lines
of "phylink_resume() will start it" describe the guard more accurately?

> +	if (!ax179_data->in_pm)
> +		phylink_start(ax179_data->phylink);
>  
>  	usbnet_link_change(dev, 0, 0);
>

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo
  2026-09-16  3:24 ` [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 0 · Low: 2

- [Low] ax88179a_get_drvinfo()
  (drivers/net/usb/ax88179a_devices.c:221-233) formats the ethtool…
- [Low] When a bootloader-window read fails in ax88179a_bind()
  (ax88179_read_cmd() < 0 at…

--

ax88179_178a: Add ethtool get_drvinfo

This adds ax88179a_get_drvinfo() and wires it into ax88179a_ethtool_ops so
that ethtool -i reports the cached firmware version bytes for AX88179A and
newer chips. It calls usbnet_get_drvinfo() first and then overwrites
info->fw_version with the four cached bytes.

> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index ff41d20c219d4..6225b88681e62 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -218,6 +218,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]);
> +}

[Severity: Low]
Is the fourth component here ever read from the device?

The only writer of fw_version[] is the loop in ax88179a_bind():

	for (int i = 0; i < 3; i++) {
		ret = ax88179_read_cmd(dev, AX88179A_ACCESS_BL, (0xFD + i),
				       1, 1, &ax179_data->fw_version[i]);
		if (ret < 0)
			ax179_data->fw_version[i] = 0xff;
	}

That fills fw_version[0..2] only, and the container comes from
kzalloc_obj(*ax179_data), so fw_version[3] is always 0.

Grepping fw_version[ over drivers/net/usb/ finds no other assignment, so
the last component of the string exported through ETHTOOL_GDRVINFO is a
constant 0 rather than something read from the chip.

Should the loop read a fourth bootloader register, or should the format
string print three components?  The declaration in
drivers/net/usb/ax88179_lib.h:

	u8 fw_version[4];

the read loop, and this new format string currently disagree about how
many bytes the firmware version has, and this patch is what turns that
disagreement into a user-visible string.  The same mismatch is still
present at the end of the series (cfe9f4be1c4a5ee15cf890112026b94028566314).

[Severity: Low]
What does userspace see when the bootloader-window reads fail?

In ax88179a_bind(), a failed ax88179_read_cmd() stores a sentinel and the
error is not propagated, so bind continues:

		if (ret < 0)
			ax179_data->fw_version[i] = 0xff;

With this change, ethtool -i then reports "255.255.255.0" as if the chip
had answered the vendor request.  Before this patch the sentinel only
appeared in the netdev_info() line at bind time, where the surrounding log
gave context.

Would it be preferable to leave info->fw_version as set by
usbnet_get_drvinfo() when the reads failed, or to track a "version unknown"
state, rather than formatting the sentinel bytes?  This is unchanged at the
end of the series (cfe9f4be1c4a5ee15cf890112026b94028566314).

> +
>  static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
>  {
>  	struct ax88179_data *ax179_data = dev->driver_priv;

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information
  2026-09-16  3:24 ` [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider:

Critical: 1 · High: 0 · Medium: 1 · Low: 3

- [Critical] usb: net: sysfs ABI and module name breakage for ax88179
- [Medium] Undisclosed Kconfig symbol rename leaves two in-tree
  defconfigs orphaned.
- [Low] FALSE POSITIVE (retained only per validation policy): the concern
  claims `select AX88796B_PHY` is bogus because the synthetic PHY IDs…
- [Low] The Kconfig prompt/help and MODULE_DESCRIPTION updated by this
  patch still describe the module as Gigabit-only while the device list…
- [Low] Firmware-version producer/consumer mismatch: ax88179a_bind()
  (drivers/net/usb/ax88179a_devices.c:761-766) reads only three bytes…

--

ax88179_178a: Update driver name and information

This renames the Kconfig symbol for the ax88179 driver, extends the Kconfig
prompt and help text with the newly supported chips, and adds selects for
PHYLINK and AX88796B_PHY. It also renames the usb_driver structure and its
.name string and updates MODULE_DESCRIPTION.

> diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig
> index 115990b0d9537..4158455a2da76 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 3.0/2.0 to Gigabit Ethernet"
>  	depends on USB_USBNET
> -	select CRC32
>  	select PHYLINK
> +	select AX88796B_PHY
> +	select CRC32
>  	default y

[Severity: Medium]
Does this rename leave the two loongarch defconfigs behind?  Both
arch/loongarch/configs/loongson32_defconfig and
arch/loongarch/configs/loongson64_defconfig still carry:

    # CONFIG_USB_NET_AX88179_178A is not set

Since Kconfig silently ignores unknown symbols, and USB_NET_AX88179 keeps
"default y" while both defconfigs have CONFIG_USB_USBNET=m, doesn't
USB_NET_AX88179 come out as m there and the module get built on platforms
that explicitly disabled it?  The same silent flip would happen to any
existing .config with the old symbol disabled when run through
make oldconfig.

Should the two defconfig lines be renamed in the same patch?

The commit message says only:

    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.

The user-visible Kconfig symbol rename from USB_NET_AX88179_178A to
USB_NET_AX88179 is not mentioned at all.  Could the changelog call it out
so backporters know to look for defconfig fallout?

>  	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
>  

[Severity: Low]
This isn't a bug, but the prompt and help text still say Gigabit only,
while the list now includes a Fast Ethernet part and a 2.5G part.  The
prompt reads "ASIX AX88179/179A/178A USB 3.0/2.0 to Gigabit Ethernet" and
the help text says "to Gigabit Ethernet adapters", yet the added entries
are AX88279 and AX88772D/E.

The device table comments in ax88179_178a.c describe them as:

	/* ASIX AX88772D USB 2.0 100Mbit Ethernet */
	/* ASIX AX88279 USB 3.2 2500Mbit Ethernet */

and ax88179a_phylink_setup() in ax88179a_devices.c matches that:

	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;

Could the wording be widened to cover 10/100 and 2.5G as well?

> diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
> index 2ecead0181eaf..4026f19ecb826 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

[ ... ]

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 93031b9ee92bc..2601a8875b9e2 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c

[ ... ]

> @@ -1353,7 +1353,7 @@ 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 based USB 3.0/2.0 Gigabit Ethernet Devices");
>  MODULE_LICENSE("GPL");

[Severity: Low]
Same wording question here: the new MODULE_DESCRIPTION still says
"USB 3.0/2.0 Gigabit Ethernet Devices" although the driver now also binds
the 10/100 AX88772D and the 2.5G AX88279.  Would it be worth updating this
string too?

One more observation, in code this patch does not touch but which comes
from earlier patches in the same series and is still present at the end of
the series:

[Severity: Low]
Is the reported firmware version short one byte?  ax88179a_bind() in
drivers/net/usb/ax88179a_devices.c fills only three entries:

	for (int i = 0; i < 3; i++) {
		ret = ax88179_read_cmd(dev, AX88179A_ACCESS_BL, (0xFD + i),
				       1, 1, &ax179_data->fw_version[i]);
		if (ret < 0)
			ax179_data->fw_version[i] = 0xff;
	}
	netdev_info(dev->net, "AX88179A/279/772D Chip Version: %x, FW: %d.%d.%d.%d\n", ...

but both consumers print four, the probe message above and the ethtool
path in ax88179a_get_drvinfo():

	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]);

struct ax88179_data is allocated with kzalloc_obj(), so fw_version[3] is
always zero rather than firmware data, and ethtool -i reports a trailing
".0" that never came from the device.  Should the fourth byte be read from
its real source, or should the stored and displayed version consistently
use three components?

[ ... ]

> @@ -1341,8 +1341,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,

[Severity: Critical]
This renames things that are visible to userspace, and I don't see
any compatibility shim for them.

usb_register() uses struct usb_driver .name verbatim for the driver
directory, so /sys/bus/usb/drivers/ax88179_178a becomes
/sys/bus/usb/drivers/ax88179 with this patch.  Anything that pokes
bind, unbind or new_id under the old path - udev rules, suspend/resume
helper scripts, test harnesses - silently stops finding the directory
after a kernel upgrade.

The module name changes too: the Makefile hunk in this patch keeps
ax88179-y := ax88179_178a.o ax88179a_devices.o ax88179_lib.o, so the
object is now ax88179.ko where it used to be ax88179_178a.ko.  Explicit
modprobe ax88179_178a invocations and any /etc/modprobe.d/ entries
(blacklist ax88179_178a, options ax88179_178a ...) no longer resolve.
Hotplug autoloading should still work since it goes through the
MODULE_DEVICE_TABLE(usb, products) modaliases, but the by-name cases
do not.

Would you consider adding MODULE_ALIAS("ax88179_178a") next to the
updated MODULE_DESCRIPTION so the old module name keeps resolving, or
is the view that the old name is young enough that nothing depends on
it?  Either way, could the changelog state explicitly that the usb
driver .name and the module name change, since right now it only
mentions "Update driver name in usb_driver" without noting the
user-visible consequences?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
  2026-09-16  3:24 ` [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 3 · Low: 0

- [Medium] The patch smuggles an undisclosed RX-path repair into an
  "EEPROM access" commit: `ax88179_bind()` now sets…
- [Medium] `ax88179_eeprom_access_params()`
  (drivers/net/usb/ax88179_lib.c:197-214) picks the hardware address…
- [Medium] `ax88179_get_eeprom()` (drivers/net/usb/ax88179_lib.c:237-249)
  only rejects `ret < 0` from `__ax88179_read_cmd()`, but…

--

ax88179_178a: Add support for AX88179A/772D/279 EEPROM access

This adds ethtool EEPROM read support for the AX88179A/772D eFuse array
and the AX88279 flash, by making the read block size and the access
command per-device instead of hardcoded 2-byte EEPROM words.  Write
access is refused for the newer parts.

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 2601a8875b9e2..4c69568ec4a23 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c
> @@ -687,6 +687,19 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
>  	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)
> +		goto err_nodev;
> +
> +	ax179_data->chip_version = (ax179_data->chip_version & 0xf0) >> 4;
> +	ax179_data->is_ax88772d = 0;
> +	ax179_data->ip_align = 1;
                              ^^^

[Severity: Medium]
Is this ip_align assignment related to EEPROM access at all?  It looks
like an RX-path change that belongs to a different patch in the series.

ip_align has no EEPROM consumer.  Its only user is ax88179_set_multicast()
in ax88179_lib.c, which decides whether the hardware inserts the 2-byte
IP alignment pseudo header:

	data->rxctl = (AX_RX_CTL_START | AX_RX_CTL_AB | AX_RX_CTL_DROPCRCERR);
	if (data->ip_align)
		data->rxctl |= AX_RX_CTL_IPE;

That gating was introduced earlier in this series ("ax88179_178a: Add
AX179A/AX279 multicast configuration"), and until this commit nothing on
the ax88179_178a.c path ever set ip_align, so it stayed 0 from kzalloc.
Meanwhile ax88179_rx_fixup() strips the pad unconditionally:

	/* Skip IP alignment pseudo header */
	skb_pull(skb, 2);

Since ax88179_set_multicast() is wired as .ndo_set_rx_mode in
ax88179_netdev_ops, dev_open() -> __dev_open() -> dev_set_rx_mode() runs
it on every "ip link set up", and again on promisc/allmulti/multicast
changes.  Does that mean every frame received on classic AX88179/178A
adapters is shifted by 2 bytes in the intermediate commits of the series,
and that this line is the fix?

Would it make sense to move this initialization into the commit that added
the ip_align gating (or carry a Fixes: tag if that commit is already
applied), and mention it in the changelog?

>  	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;

[ ... ]

> diff --git a/drivers/net/usb/ax88179_lib.c b/drivers/net/usb/ax88179_lib.c
> index 3b498206c50d5..db77b7d4539ec 100644
> --- a/drivers/net/usb/ax88179_lib.c
> +++ b/drivers/net/usb/ax88179_lib.c
> @@ -194,36 +194,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;
> +	}
> +}

[Severity: Medium]
Should the address encoding be derived from the raw chip_version nibble
here, rather than from the eeprom_read_cmd/eeprom_block pair that the same
request already uses?

chip_version comes straight from the device in ax88179_bind():

	ret = ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_CHIP_STATUS,
			       1, 1, &ax179_data->chip_version);
	...
	ax179_data->chip_version = (ax179_data->chip_version & 0xf0) >> 4;

and it is never checked against enum ax_chip_version in ax88179_lib.h,
which only defines 0x0, 0x4, 0x6 and 0x7.  For a device bound by
ax88179_178a.c that reports any nibble >= 0x6 (rebadged clone,
unenumerated silicon, or a register that reads back 0xf), bind() has
already declared word-addressable EEPROM (AX_ACCESS_EEPROM, block 2), but
this helper hands out eFuse-style (i << 4) or flash-style addresses.  Does
ethtool -e then return bytes from unrelated EEPROM addresses on such a
device?

The same nibble is now used to refuse writes:

	ax179_data = dev->driver_priv;
	if (ax179_data->chip_version >= AX_VERSION_AX88179A)
		return -EOPNOTSUPP;

Is that gate better expressed in terms of eeprom_write_cmd/eeprom_wen?  As
written, a legacy device reporting >= 0x6 loses ethtool -E even though
bind() configured AX_ACCESS_EEPROM with eeprom_wen = 0, which worked at
the parent commit.

There is also a predicate mismatch between the three users of the nibble.
ax88179a_bind() picks flash parameters with an equality test:

	if (ax179_data->chip_version == AX_VERSION_AX88279) {
		ax179_data->ip_align = 1;
		ax179_data->eeprom_read_cmd = AX88179A_FLASH_READ;

while ax88179a_get_eeprom_len() and this helper both use >=:

	if (ax179_data->chip_version >= AX_VERSION_AX88279)
		return AX88279_EEPROM_LEN;
	else
		return AX88179A_EEPROM_LEN;

For a nibble of 0x8-0xf, doesn't that combine eFuse geometry from bind
(AX_ACCESS_EFUS, 20-byte blocks) with the 16 KiB length and flash-style
addressing, so ethtool issues hundreds of AX_ACCESS_EFUS transfers with
addresses well past the 32 x 20 byte eFuse array and reports the results
as EEPROM contents?

>  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]);
> +	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 < 0) {
>  			kfree(eeprom_buff);
>  			return -EIO;
>  		}
      ^^^^^

[Severity: Medium]
Should this test be ret != ax179_data->eeprom_block instead of ret < 0?

__usbnet_read_cmd() in usbnet.c returns the number of bytes the device
actually transferred, and copies only that many:

	err = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
			      cmd, reqtype, value, index, buf, size,
			      USB_CTRL_GET_TIMEOUT);
	if (err > 0 && err <= size) {
		if (data)
			memcpy(data, buf, err);

So a zero-length or short response is accepted here as a complete block,
and since the buffer is now kzalloc()ed, the untransferred remainder is
handed to userspace as 0x00.  Does that make a failed or partial device
read indistinguishable from an unprogrammed eFuse/flash region?

The old loop had the same ret < 0 test but only ever asked for a single
2-byte word; this patch now requests 20-byte AX_ACCESS_EFUS and 256-byte
AX88179A_FLASH_READ transfers per iteration, where a busy device or a
rejected address can legitimately short-read.

>  	}
>  
> -	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;
>  }
> @@ -231,12 +256,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;
> +

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279
  2026-09-16  3:24 ` [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
@ 2026-09-17 21:25   ` netdev-bot+sashiko
  0 siblings, 0 replies; 31+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 21:25 UTC (permalink / raw)
  To: mail
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux, andrew,
	hkallweit1, linux-usb, netdev, linux-kernel, neuromoments

Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 6 · Low: 0

- [Medium] asix_ax88279_config_aneg()
  (drivers/net/phy/ax88796b.c:146-166) programs the vendor 2.5G…
- [Medium] asix_ax88279_read_status()
  (drivers/net/phy/ax88796b.c:108-121) does not check the return value…
- [Medium] asix_ax88279_read_status() (drivers/net/phy/ax88796b.c:99-144)
  has no `if (!phydev->link) return 0;` guard.
- [Medium] asix_ax88279_read_status() hard-codes phydev->duplex =
  DUPLEX_FULL with the comment 'Only supports full duplex'…
- [Medium] In asix_ax88279_read_status() the link-partner 2.5G capability
  bit is only ever assigned inside `if (val >= 0 && val &…
- [Medium] net: phy: ax88796b: incomplete clearing of 1000baseT
  capabilities for AX88772D

--

ax88796b: Add support for AX88772D, AX88179A and AX88279

This adds phy_driver entries and matching MDIO ids for the AX88772D,
AX88179A and AX88279 PHYs found in ASIX USB-Ethernet controllers, plus
AX88279-specific get_features, read_status, config_aneg and config_init
callbacks.

A few questions about the new AX88279 callbacks below.

> diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
> index f20ddf6491499..62f6beb25f41e 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,103 @@ 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;
> +

[Severity: Medium]
Is clearing only 1000baseT_Full enough for a 100MBit part?

The comment says the AX88772D falsely reports 1000baseT, but
genphy_read_abilities() derives three gigabit modes from MII_ESTATUS
when BMSR_ESTATEN is set:

	linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
			 phydev->supported, val & ESTATUS_1000_TFULL);
	linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
			 phydev->supported, val & ESTATUS_1000_THALF);
	linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
			 phydev->supported, val & ESTATUS_1000_XFULL);

Only ETHTOOL_LINK_MODE_1000baseT_Full_BIT is put into mask and removed
from phydev->supported here, so if the hardware also sets
ESTATUS_1000_THALF or ESTATUS_1000_XFULL those bits survive in
phydev->supported. They are then copied into phydev->advertising by
phy_probe()/phy_advertise_supported(), reported to userspace by
phy_ethtool_get_link_ksettings(), and accepted by
phy_ethtool_ksettings_set() since it validates the request against
phydev->supported. genphy_config_advert() would put ADVERTISE_1000HALF
in MII_CTRL1000 for a PHY that cannot do gigabit at all.

If the ESTATUS block is bogus on this part, would it be clearer to drop
all three bits, or to mask off the whole of MII_ESTATUS? Also, the mask
plus linkmode_andnot() dance is a long way of writing what the sibling
asix_ax88279_get_features() does with linkmode_clear_bit() -- is there a
reason for the difference here?

[Severity: Medium]
Should asix_ax88279_read_status() bail out here when the link is down?

genphy_read_status() only takes its "nothing changed" early return when
autoneg is enabled and both old_link and phydev->link are set. On a
link-down poll it falls through and does:

	phydev->speed = SPEED_UNKNOWN;
	phydev->duplex = DUPLEX_UNKNOWN;

The rest of this callback then overwrites both unconditionally, so with the
cable unplugged ethtool reports a speed decoded from AX_CTRL_STATUS and
"Duplex: Full" instead of Unknown/Unknown, and phydev->interface is flipped
based on a register that carries no valid speed.

The sibling callback asix_ax88772a_read_status() in this same file has:

	if (!phydev->link)
		return 0;

> +	/* Read actual speed from vendor register */
> +	val = phy_read(phydev, AX_CTRL_STATUS);
> +	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;
> +	}

[Severity: Medium]
Can a failed MDIO read turn into a fabricated link speed here?

phy_read() can return a negative errno, and this PHY is reached over USB
control transfers (ax88179_mdiobus_read() in
drivers/net/usb/ax88179a_devices.c), so -ENODEV/-ETIMEDOUT/-EPIPE are
ordinary outcomes on unplug or transport errors.

The value is masked without being checked, and since all four values of the
two-bit field have a case and there is no default, every errno decodes as a
speed:

	-EIO       (0xfffffffb) & 0x0c == 0x08 -> SPEED_1000
	-ETIMEDOUT (0xffffff8e) & 0x0c == 0x0c -> SPEED_2500
	-ENODEV    (0xffffffed) & 0x0c == 0x0c -> SPEED_2500

That speed then feeds the lp_advertising computation and the
phydev->interface selection below, and the callback still returns 0, so
phy_check_link_status() never sees the I/O failure. The MMD read a few lines
down does check val >= 0, and asix_ax88772a_read_status() uses:

	val = phy_read(phydev, MII_BMCR);
	if (val < 0)
		return val;

Would the same check be appropriate for AX_CTRL_STATUS?

> +
> +	/* 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
> +	 */
> +	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);
> +	}

[Severity: Medium]
Can a stale 2500baseT/Full bit survive in phydev->lp_advertising?

This is the only assignment to that bit, and it is gated on both a
successful read and MDIO_AN_10GBT_STAT_REMOK. If the link then moves to a
1000M or 100M partner that does not set REMOK, or if the MMD read fails,
the bit set by the previous 2.5G partner is never cleared.

genphy_read_lpa() only touches the Clause 22 derived partner modes:

	mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
	mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);

so it cannot clear this driver-added bit either. Userspace would keep
reporting 2500baseT/Full for a partner that never advertised it, and
phy_resolve_aneg_linkmode() can resolve that stale mode as the highest
common one.

The read error is also swallowed rather than returned. Was that intentional?

> +	/* Only supports full duplex */
> +	phydev->duplex = DUPLEX_FULL;

[Severity: Medium]
If the part only supports full duplex, should all half-duplex modes be
removed from phydev->supported?

asix_ax88279_get_features() clears only one of them:

	/* AX88279 does not support reported 100baseT-half duplex mode */
	linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported);

but genphy_c45_pma_read_abilities() -> genphy_c45_pma_read_ext_abilities()
derives 10baseT_Half from the same hardware bit as 10baseT_Full:

	linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
			 phydev->supported,
			 val & MDIO_PMA_EXTABLE_10BT);

so 10baseT_Half stays in phydev->supported. phy_ethtool_ksettings_set()
masks the requested advertisement against phydev->supported rather than the
phylink MAC capabilities, so "ethtool -s ethX advertise ..." including
10baseT/Half reaches genphy_config_advert() and puts ADVERTISE_10HALF on the
wire. If the partner links half duplex, this line still reports DUPLEX_FULL
to the MAC.

Would clearing the remaining half-duplex bits in get_features, or resolving
duplex from hardware, be preferable?

> +
> +	/* 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;
> +	}
> +
> +	ret = genphy_config_aneg(phydev);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	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);
> +
> +	return ret;
> +}

[Severity: Medium]
Does the vendor 2.5G advertisement bit get negotiated after this write?

genphy_config_aneg() ends with the restart decision:

	return genphy_check_and_restart_aneg(phydev, changed);

and "changed" comes from genphy_config_advert(), whose mask does not include
bit 12:

	err = phy_modify_changed(phydev, MII_ADVERTISE,
				 ADVERTISE_ALL | ADVERTISE_100BASE4 |
				 ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
				 adv);

So for a request that differs only in 2500baseT/Full,
genphy_check_and_restart_aneg() sees no change, autoneg enabled and
isolation clear, and returns without writing BMCR_ANRESTART:

	if (restart)
		return genphy_restart_aneg(phydev);

	return 0;

AX_ADVERTISE_2500 is then written here with no restart following, and
_phy_start_aneg() does not issue one either, so the new advertisement is
accepted but never put on the wire.

In the opposite case, where a standard bit did change, the restart happens
inside genphy_config_aneg() before AX_ADVERTISE_2500 is programmed, so
negotiation can run with the previous 2.5G advertisement.

Would it work to program the vendor bit with phy_modify_changed() first and
pass that result into __genphy_config_aneg(phydev, changed)?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916-ax88179a-v12-0-60c04c9924a2%40birger-koblitz.de

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2026-09-17 21:25 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-16  3:24 ` [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®