* [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips
@ 2026-09-14 12:46 Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
` (14 more replies)
0 siblings, 15 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 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 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 | 1213 ++++++++++++++++++++++++++++++++++++
include/linux/phy.h | 1 +
include/linux/phylink.h | 1 +
12 files changed, 2319 insertions(+), 624 deletions(-)
---
base-commit: 1bb784eb6e38fd73143f021608e4ef3095d0c0d7
change-id: 20260630-ax88179a-a1d89fe21730
Best regards,
--
Birger Koblitz <mail@birger-koblitz.de>
^ permalink raw reply [flat|nested] 42+ messages in thread
* [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:13 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
` (13 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 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>
---
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] 42+ messages in thread
* [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:14 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
` (12 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 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>
---
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] 42+ messages in thread
* [PATCH net-next v11 03/15] ax88179_178a: Fix endianness of pause watermark register
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
` (11 subsequent siblings)
14 siblings, 0 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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] 42+ messages in thread
* [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (2 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:15 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
` (10 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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
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>
---
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] 42+ messages in thread
* [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (3 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
` (9 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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] 42+ messages in thread
* [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (4 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:12 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
` (8 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 | 965 +++++++++++++++++++++++++++++++++++++
6 files changed, 1144 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..33be9ef73e73dd550402692e96605d22c481c7ec 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, 0, 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, 0, 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..21ca71cba52ca84883a05d8be869115395c82af1
--- /dev/null
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -0,0 +1,965 @@
+// 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 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 = usbnet_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, ®8);
+ 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 & VLAN_VID_MASK);
+ }
+
+ 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))
+ 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, ®16);
+ reg16 &= ~AX_MEDIUM_RECEIVE_EN;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
+
+ reg16 = 0;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
+
+ reg8 = 0;
+ ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
+
+ 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] 42+ messages in thread
* [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (5 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
` (7 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 21ca71cba52ca84883a05d8be869115395c82af1..80cabe3557cdef8267c365fccc0bc379936bb241 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] 42+ messages in thread
* [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (6 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
` (6 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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
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>
---
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 80cabe3557cdef8267c365fccc0bc379936bb241..97aad545daee1dd1f5c4d63a1276523589c9617f 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] 42+ messages in thread
* [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (7 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
` (5 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 97aad545daee1dd1f5c4d63a1276523589c9617f..4bca801236b08f5e361f49843602bb20e51ea3ed 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, ®8);
+ vlan_ctrl = reg8;
+
+ /* Address */
+ reg8 = (vid / 16);
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
+
+ /* Data */
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ reg16 &= ~(1 << (vid % 16));
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+
+ 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, ®8);
+ vlan_ctrl = reg8;
+
+ /* Address */
+ reg8 = (vid / 16);
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
+
+ /* Data */
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+
+ ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+ reg16 |= (1 << (vid % 16));
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
+
+ reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+ ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
+
+ return 0;
+}
+
static void ax88179a_mdio_unregister(struct ax88179_data *data)
{
mdiobus_unregister(data->mdio);
@@ -499,6 +555,49 @@ static int ax88179a_init_mdio(struct usbnet *dev)
return ret;
}
+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,
@@ -509,6 +608,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 = usbnet_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] 42+ messages in thread
* [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (8 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
` (4 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 4bca801236b08f5e361f49843602bb20e51ea3ed..24accbc22092ac3ab7ae0059e7b749c9f7f39530 100644
--- a/drivers/net/usb/ax88179a_devices.c
+++ b/drivers/net/usb/ax88179a_devices.c
@@ -608,6 +608,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 = usbnet_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] 42+ messages in thread
* [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (9 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
` (3 subsequent siblings)
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 33be9ef73e73dd550402692e96605d22c481c7ec..44a768c5808ac1f7071e186d9c86cd6149601971 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 24accbc22092ac3ab7ae0059e7b749c9f7f39530..27836c72ec14aaaedfaa97ef95730bc7980074bc 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, ®8);
+ if (!(reg8 & AX_PHY_POWER)) {
+ reg8 = AX_PHY_POWER;
+ ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
+ msleep(250);
+ }
+ ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
+
+ /* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle.
+ * PHY-power is re-enabled in ax88179_reset()
+ */
+ reg8 = 0;
+ ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
+ 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;
@@ -680,6 +766,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;
@@ -1040,7 +1129,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] 42+ messages in thread
* [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (10 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:16 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information Birger Koblitz
` (2 subsequent siblings)
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 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>
---
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 27836c72ec14aaaedfaa97ef95730bc7980074bc..817d542c4cc085d57837f2cd60d486efdb7f108e 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] 42+ messages in thread
* [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (11 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
14 siblings, 2 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 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>
---
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 44a768c5808ac1f7071e186d9c86cd6149601971..1d35ef90eceb77b27e8d96e64a008c63b40f4bd7 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] 42+ messages in thread
* [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (12 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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 1d35ef90eceb77b27e8d96e64a008c63b40f4bd7..55564aa0ca63f09d5f592415e15df20368c3238a 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] 42+ messages in thread
* [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
` (13 preceding siblings ...)
2026-09-14 12:46 ` [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-09-14 12:46 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
14 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-14 12:46 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] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 12:46 ` [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
@ 2026-09-14 14:12 ` Nicolai Buchwitz
2026-09-14 16:53 ` Andrew Lunn
2026-09-15 5:28 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 2 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:12 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
Hi Birger
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> [...]
> diff --git a/drivers/net/usb/ax88179_178a.c
> b/drivers/net/usb/ax88179_178a.c
> index
> 6aa35c8eb594fda38f7680f8801f0d82090c1efd..33be9ef73e73dd550402692e96605d22c481c7ec
> 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, 0, 0x0200),
USB_DEVICE_VER(0x0b95, 0x1790, 0x0200, 0x0200),
Otherwise it would select the wrong callback the existing AX88179
(0x0100)?
> + .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, 0, 0x0400),
USB_DEVICE_VER(0x0b95, 0x1790, 0x0400, 0x0400),
Same as with AX88179 above.
> [...]
> diff --git a/drivers/net/usb/ax88179a_devices.c
> b/drivers/net/usb/ax88179a_devices.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..21ca71cba52ca84883a05d8be869115395c82af1
> --- /dev/null
> +++ b/drivers/net/usb/ax88179a_devices.c
> [...]
> +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 = usbnet_mii_ioctl,
dev->mii.mdio_read is never initialized for ax88179a, so
dev->mii.mdio_read()
through usbnet_mii_ioctl() is a NULL function pointer.
Something like:
static int ax88179a_mii_ioctl(...)
{
struct ax88179_data *data = netdev2data(net);
return phylink_mii_ioctl(data->phylink, ifr, cmd);
}
> [...]
> +static int ax88179a_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
> +{
> [...]
> +
> + 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 & VLAN_VID_MASK);
Drop the mask and pass the complete vlan_tag, so priority is preserved?
> + }
> +
> + 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))
> + return NULL;
Call dev_kfree_skb_any() before returning, so the skb can't leak when
skb_linearize() fails.
> [...]
Thanks
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
@ 2026-09-14 14:13 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:13 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> 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)
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode
2026-09-14 12:46 ` [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
@ 2026-09-14 14:14 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:14 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> 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
>
> /**
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code
2026-09-14 12:46 ` [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
@ 2026-09-14 14:15 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:15 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo
2026-09-14 12:46 ` [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
@ 2026-09-14 14:16 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:16 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> 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
> 27836c72ec14aaaedfaa97ef95730bc7980074bc..817d542c4cc085d57837f2cd60d486efdb7f108e
> 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,
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information
2026-09-14 12:46 ` [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information Birger Koblitz
@ 2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:17 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> 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
> 44a768c5808ac1f7071e186d9c86cd6149601971..1d35ef90eceb77b27e8d96e64a008c63b40f4bd7
> 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");
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs
2026-09-14 12:46 ` [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
@ 2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: Nicolai Buchwitz @ 2026-09-14 14:17 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 14.9.2026 14:46, Birger Koblitz wrote:
> 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>
> ---
> 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
> 80cabe3557cdef8267c365fccc0bc379936bb241..97aad545daee1dd1f5c4d63a1276523589c9617f
> 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))
Reviewed-by: Nicolai Buchwitz <nb@tipi-net.de>
Thanks,
Nicolai
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 14:12 ` Nicolai Buchwitz
@ 2026-09-14 16:53 ` Andrew Lunn
2026-09-15 0:01 ` Birger Koblitz
2026-09-15 5:28 ` Birger Koblitz
1 sibling, 1 reply; 42+ messages in thread
From: Andrew Lunn @ 2026-09-14 16:53 UTC (permalink / raw)
To: Nicolai Buchwitz
Cc: Birger Koblitz, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
> > +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 = usbnet_mii_ioctl,
>
> dev->mii.mdio_read is never initialized for ax88179a, so
> dev->mii.mdio_read()
> through usbnet_mii_ioctl() is a NULL function pointer.
>
> Something like:
>
> static int ax88179a_mii_ioctl(...)
> {
> struct ax88179_data *data = netdev2data(net);
> return phylink_mii_ioctl(data->phylink, ifr, cmd);
> }
Is this transitory? Does a later patch fix this up?
The aim is to remove all dev->mii accesses.
Andrew
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 16:53 ` Andrew Lunn
@ 2026-09-15 0:01 ` Birger Koblitz
2026-09-15 12:07 ` Andrew Lunn
0 siblings, 1 reply; 42+ messages in thread
From: Birger Koblitz @ 2026-09-15 0:01 UTC (permalink / raw)
To: Andrew Lunn, Nicolai Buchwitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Heiner Kallweit, linux-usb, netdev,
linux-kernel, Jianhui Xu
On 9/14/26 18:53, Andrew Lunn wrote:
>>> +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 = usbnet_mii_ioctl,
>>
>> dev->mii.mdio_read is never initialized for ax88179a, so
>> dev->mii.mdio_read()
>> through usbnet_mii_ioctl() is a NULL function pointer.
>>
>> Something like:
>>
>> static int ax88179a_mii_ioctl(...)
>> {
>> struct ax88179_data *data = netdev2data(net);
>> return phylink_mii_ioctl(data->phylink, ifr, cmd);
>> }
>
> Is this transitory? Does a later patch fix this up?
> The aim is to remove all dev->mii accesses.
>
No, unfortunately, this is an mii-leftover. I thought that
usbnet was emulating the mii ioctl somehow, not that it was expecting
to be able to use a mii_mdio_read. I will remove the
>>> + .ndo_eth_ioctl = usbnet_mii_ioctl,
line, entirely.
Birger
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 14:12 ` Nicolai Buchwitz
2026-09-14 16:53 ` Andrew Lunn
@ 2026-09-15 5:28 ` Birger Koblitz
1 sibling, 0 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-15 5:28 UTC (permalink / raw)
To: Nicolai Buchwitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, Andrew Lunn, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
Hi Nicolai,
thanks so much for reviewing this patch-series!
On 9/14/26 16:12, Nicolai Buchwitz wrote:
>> static const struct usb_device_id products[] = {
>> {
>> + /* ASIX AX88179A USB 3.2 1000Mbit Ethernet */
>> + USB_DEVICE_VER(0x0b95, 0x1790, 0, 0x0200),
>
> USB_DEVICE_VER(0x0b95, 0x1790, 0x0200, 0x0200),
>
> Otherwise it would select the wrong callback the existing AX88179 (0x0100)?
Oops, that is a good catch! I took this from manufacturer code and
probably that range includes also prototypes. The older devices are
handled there with a different driver, however, but indeed we need to be
sure the 0x0100 versions are not caught. Will fix in the v12.
>
> Same as with AX88179 above.
Indeed. Will fix.
>> +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 = usbnet_mii_ioctl,
>
> dev->mii.mdio_read is never initialized for ax88179a, so dev-
> >mii.mdio_read()
> through usbnet_mii_ioctl() is a NULL function pointer.
>
> Something like:
>
> static int ax88179a_mii_ioctl(...)
> {
> struct ax88179_data *data = netdev2data(net);
> return phylink_mii_ioctl(data->phylink, ifr, cmd);
> }
>
I will remove the entire
+ .ndo_eth_ioctl = usbnet_mii_ioctl,
line. It should not be necessary.
>> +
>> + 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 & VLAN_VID_MASK);
>
> Drop the mask and pass the complete vlan_tag, so priority is preserved?
Indeed. Will fix.
>> + if (!dev->can_dma_sg && (dev->net->features & NETIF_F_SG) &&
>> skb_linearize(skb))
>> + return NULL;
>
> Call dev_kfree_skb_any() before returning, so the skb can't leak when
> skb_linearize() fails.
Will fix.
Birger
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-15 0:01 ` Birger Koblitz
@ 2026-09-15 12:07 ` Andrew Lunn
2026-09-16 0:12 ` Birger Koblitz
0 siblings, 1 reply; 42+ messages in thread
From: Andrew Lunn @ 2026-09-15 12:07 UTC (permalink / raw)
To: Birger Koblitz
Cc: Nicolai Buchwitz, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On Tue, Sep 15, 2026 at 02:01:48AM +0200, Birger Koblitz wrote:
> On 9/14/26 18:53, Andrew Lunn wrote:
> > > > +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 = usbnet_mii_ioctl,
> > >
> > > dev->mii.mdio_read is never initialized for ax88179a, so
> > > dev->mii.mdio_read()
> > > through usbnet_mii_ioctl() is a NULL function pointer.
> > >
> > > Something like:
> > >
> > > static int ax88179a_mii_ioctl(...)
> > > {
> > > struct ax88179_data *data = netdev2data(net);
> > > return phylink_mii_ioctl(data->phylink, ifr, cmd);
> > > }
> >
> > Is this transitory? Does a later patch fix this up?
> > The aim is to remove all dev->mii accesses.
> >
>
> No, unfortunately, this is an mii-leftover. I thought that
> usbnet was emulating the mii ioctl somehow, not that it was expecting
> to be able to use a mii_mdio_read. I will remove the
> >>> + .ndo_eth_ioctl = usbnet_mii_ioctl,
> line, entirely.
It would be nice to use what is proposed, so the ioctl keeps working.
You can test it with mii-tool.
Andrew
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-15 12:07 ` Andrew Lunn
@ 2026-09-16 0:12 ` Birger Koblitz
0 siblings, 0 replies; 42+ messages in thread
From: Birger Koblitz @ 2026-09-16 0:12 UTC (permalink / raw)
To: Andrew Lunn
Cc: Nicolai Buchwitz, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King, Heiner Kallweit,
linux-usb, netdev, linux-kernel, Jianhui Xu
On 9/15/26 14:07, Andrew Lunn wrote:
> On Tue, Sep 15, 2026 at 02:01:48AM +0200, Birger Koblitz wrote:
>> On 9/14/26 18:53, Andrew Lunn wrote:
>>>>> +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 = usbnet_mii_ioctl,
>>>>
>>>> dev->mii.mdio_read is never initialized for ax88179a, so
>>>> dev->mii.mdio_read()
>>>> through usbnet_mii_ioctl() is a NULL function pointer.
>>>>
>>>> Something like:
>>>>
>>>> static int ax88179a_mii_ioctl(...)
>>>> {
>>>> struct ax88179_data *data = netdev2data(net);
>>>> return phylink_mii_ioctl(data->phylink, ifr, cmd);
>>>> }
>>>
>>> Is this transitory? Does a later patch fix this up?
>>> The aim is to remove all dev->mii accesses.
>>>
>>
>> No, unfortunately, this is an mii-leftover. I thought that
>> usbnet was emulating the mii ioctl somehow, not that it was expecting
>> to be able to use a mii_mdio_read. I will remove the
>>>>> + .ndo_eth_ioctl = usbnet_mii_ioctl,
>> line, entirely.
>
> It would be nice to use what is proposed, so the ioctl keeps working.
>
> You can test it with mii-tool.
Thanks for the help, Andrew. I now implemented it as proposed by Nicolai
and will send it shortly, after a bit more testing.
Birger
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-09-14 14:13 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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] phylink_mac_interrupt() (drivers/net/phy/phylink.c:1621) reads
pl->phydev with a plain, unsynchronized load and immediately…
--
phylink: Add phylink_mac_interrupt
Adds an exported phylink_mac_interrupt() helper that forwards a link-change
interrupt received by the MAC to phylib via phy_mac_interrupt(), so that
drivers can avoid polling the PHY.
> 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]
How is the read of pl->phydev in phylink_mac_interrupt() synchronized
against the writers?
Every other pl->phydev accessor in phylink.c either holds
pl->phydev_mutex (phylink_bringup_phy(), phylink_disconnect_phy(),
phylink_resolve()) or calls ASSERT_RTNL() (phylink_rx_clk_stop_block(),
phylink_ethtool_*(), phylink_mii_ioctl(), phylink_speed_up/down(),
phylink_prepare_resume()). The field itself carries the comment:
/* Serialize updates to pl->phydev with phylink_resolve() */
struct mutex phydev_mutex;
Since the kernel-doc says the helper is for a link-change interrupt, the
caller cannot take that sleeping mutex, so is the documented rule for this
field reachable through this API at all?
Also, the load is a plain load rather than READ_ONCE(). Can the compiler
re-materialize pl->phydev for the phy_mac_interrupt(phy) use after the
NULL test, so that a concurrent clear in phylink_disconnect_phy() ends up
in phy_mac_interrupt(NULL)? The dereference happens here:
drivers/net/phy/phy.c:phy_queue_state_machine() {
mod_delayed_work(system_power_efficient_wq, &phydev->state_queue,
jiffies);
}
There is a second question about teardown ordering, reachable with the
consumer added later in this series (ax88179a_status() calls
phylink_mac_interrupt() from URB completion context):
drivers/net/usb/usbnet.c:usbnet_stop() {
...
if (info->stop) {
retval = info->stop(dev); /* -> phylink_stop() ->
phy_stop() ->
phy_stop_machine() */
...
usbnet_status_stop(dev); /* status URB killed only here */
}
phy_stop_machine() does cancel_delayed_work_sync(&phydev->state_queue).
If a status URB completion lands between info->stop and
usbnet_status_stop(), does phylink_mac_interrupt() re-arm
phydev->state_queue after that synchronous cancel and run the state
machine on an already stopped PHY?
Finally, phylink_disconnect_phy() clears pl->phydev under the mutex and
then calls phy_disconnect() outside it:
mutex_unlock(&pl->phydev_mutex);
if (phy) {
flush_work(&pl->resolve);
phy_disconnect(phy);
}
with no synchronize_irq(), refcount or RCU to exclude a reader that
already loaded the pointer. For a future caller that does not quiesce its
interrupt source before phylink_disconnect_phy(), can the lockless load
become a use-after-free on phydev->state_queue once phy_detach() ->
put_device() has dropped the last reference?
Would it help to document a calling-context and PHY-lifetime contract on
the new declaration in include/linux/phylink.h, i.e. that the caller must
mask and synchronize its interrupt before phylink_disconnect_phy(), and to
use READ_ONCE() for the pl->phydev load?
> diff --git a/include/linux/phylink.h b/include/linux/phylink.h
> index 1dda5c7ed5f14..f6c1db837fb50 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);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode
2026-09-14 12:46 ` [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
2026-09-14 14:14 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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: 1 · Medium: 1 · Low: 1
- [High] NULL pointer dereference of `phydev->drv` in
`phy_ethtool_ksettings_set()` (drivers/net/phy/phy.c:1193-1195).
- [Medium] Incomplete gate: the PHY_BROKEN_FORCED rejection is placed
only in `phy_ethtool_ksettings_set()`…
- [Low] At this commit, PHY_BROKEN_FORCED has no in-tree initializer: a
tree-wide search at 0a4f5bfb finds it only in its definition…
--
phylib: Add support for PHYs with broken forced mode
This adds a new phy_driver flag, PHY_BROKEN_FORCED, for PHYs that only
support auto-negotiated speed modes, and makes
phy_ethtool_ksettings_set() reject AUTONEG_DISABLE for such PHYs with
-EOPNOTSUPP instead of letting config_aneg() fail inside the state
machine.
> 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?
This is the first and only dereference of phydev->drv in
phy_ethtool_ksettings_set(), and it happens before
mutex_lock(&phydev->lock) and without the !phydev->drv test that the
other drv consumers in this file use. phy_remove() in phy_device.c
clears it while the phy_device stays alive and attached:
drivers/net/phy/phy_device.c:phy_remove() {
...
phydev->drv = NULL;
...
}
The entry point does not filter that state either:
drivers/net/phy/phy.c:phy_ethtool_set_link_ksettings() {
...
if (!phydev)
return -ENODEV;
return phy_ethtool_ksettings_set(phydev, cmd);
}
So after unbinding the PHY driver through
/sys/bus/mdio_bus/drivers/<drv>/unbind, does
"ethtool -s <dev> autoneg off speed 100 duplex full" reach the new
phydev->drv->flags read with drv == NULL? Before this patch the same
input was turned into -EIO further down the same call path:
drivers/net/phy/phy.c:_phy_start_aneg() {
lockdep_assert_held(&phydev->lock);
if (!phydev->drv)
return -EIO;
...
}
Would it make sense to add the same !phydev->drv check, or move the
test under phydev->lock? Note the store in phy_remove() is done with
neither phydev->lock nor rtnl held, so a concurrent unbind can also
make this read observe a stale driver pointer.
[Severity: Medium]
Does this cover all the paths that latch forced mode? The other core
phylib entry point still sets AUTONEG_DISABLE without consulting the
new flag:
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;
...
}
The raw BMCR write with ANENABLE cleared goes through, phy_start_aneg()
returns the driver's -EOPNOTSUPP to the ioctl caller, but nothing rolls
back phydev->autoneg, speed or duplex.
A later transition to PHY_UP (for example "ip link set dev X down"
followed by "ip link set dev X up") then runs
_phy_state_machine() -> _phy_start_aneg() -> phy_config_aneg() with
autoneg still AUTONEG_DISABLE, gets -EOPNOTSUPP again, and ends in:
drivers/net/phy/phy.c:phy_error_precise() {
WARN(1, "%pS: returned: %d\n", func, err);
...
}
Is that not the same warning and stack trace within the state machine
that the commit message says is prevented? Would placing the check at
a single choke point such as _phy_start_aneg() or phy_config_aneg(), or
rejecting the BMCR write, close both ABI paths?
For reference, the -EOPNOTSUPP producer added later in this series is
asix_ax88279_config_aneg() in drivers/net/phy/ax88796b.c:
if (phydev->autoneg == AUTONEG_DISABLE) {
phydev_warn(phydev, "Disabling autoneg is not supported\n");
return -EOPNOTSUPP;
}
That PHY is driven through phylink, where SIOCSMIIREG is handled by
phylink_mii_ioctl()/phylink_phy_write() rather than phy_mii_ioctl(), so
an end-to-end trigger with in-tree hardware was not demonstrated; MACs
using phylib directly (phy_do_ioctl*/phy_mii_ioctl) do reach it.
> mutex_lock(&phydev->lock);
> phydev->autoneg = autoneg;
>
> 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]
At this commit PHY_BROKEN_FORCED has no in-tree initializer, so
phydev->drv->flags & PHY_BROKEN_FORCED is always false and the new
early return is not reachable yet, while the commit message is worded
as a behavioural fix:
This prevents a kernel warning and stack-trace within the
phy_state_machine when PHYs return -EOPNOTSUPP from config_aneg()
The consumer arrives later in the same series, in "ax88796b: Add
support for AX88772D, AX88179A and AX88279", which sets
.flags = PHY_IS_INTERNAL | PHY_BROKEN_FORCED for the AX88279. Could
the wording say that the flag takes effect with that later patch?
Separately, and not something introduced here: two existing PHYs that
also reject forced mode still signal it only by returning an error from
config_aneg() rather than through a flag:
drivers/net/phy/air_en8811h.c:en8811h_config_aneg() {
if (phydev->autoneg == AUTONEG_DISABLE) {
phydev_warn(phydev, "Disabling autoneg is not supported\n");
return -EINVAL;
}
...
}
drivers/net/phy/bcm84881.c:bcm84881_config_aneg() {
/* disabled autoneg doesn't seem to work with this PHY */
if (phydev->autoneg == AUTONEG_DISABLE)
return -EINVAL;
...
}
Should these be converted to the new flag so there is one convention
for the same hardware property?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code
2026-09-14 12:46 ` [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
2026-09-14 14:15 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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] The patch renames the built module from ax88179_178a.ko to
ax88179.ko (drivers/net/usb/Makefile:…
--
ax88179_178a: Split driver into library and device specific code
This splits ax88179_178a.c into a new ax88179_lib.c/ax88179_lib.h holding
the register definitions and code common to the AX88179/178a/179a/279
family, leaving the device specific parts in ax88179_178a.c. The built
module is renamed from ax88179_178a to ax88179.
> 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 renames the built module from ax88179_178a.ko to ax88179.ko. Should a
MODULE_ALIAS("ax88179_178a") be added next to the usb_driver in
ax88179_178a.c so that the old name stays resolvable?
Device autoprobing is unaffected, since MODULE_DEVICE_TABLE and the
usb_driver .name are unchanged:
drivers/net/usb/ax88179_178a.c:
.name = "ax88179_178a",
But anything keyed on the module name stops matching after the rename:
- "blacklist ax88179_178a" in /etc/modprobe.d silently becomes a no-op,
so a host that deliberately kept this driver from binding will bind it
after the upgrade
- "modprobe ax88179_178a" fails with "Module ax88179_178a not found"
- initramfs/dracut force-load lists naming ax88179_178a become dead
entries
Grepping for MODULE_ALIAS in drivers/net/usb/ax88179*.c at the end of the
series (5c3c73e953b8) returns nothing, so the old name is never restored
later either.
Could the changelog also mention this user-visible consequence? The
current text only says:
Rename the module to ax88179 to reflect the broader scope of
controllers supported by the module.
which does not indicate that name-keyed modprobe configuration changes
behaviour.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function
2026-09-14 12:46 ` [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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] Naming-convention / global-namespace violation: the new helper is
defined non-static in drivers/net/usb/ax88179_lib.c:131 and declared…
- [Low] Reported for completeness, but verified NOT a defect: the concern
was that `netdev2data()` is added with zero callers and that the…
--
ax88179_178a: Add netdev2data() convenience function
This commit adds a small helper to the ax88179 library that returns the
ax88179_data structure belonging to a net_device by going through
netdev_priv() and usbnet->driver_priv. It is declared in
ax88179_lib.h alongside the other library entry points.
> 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 would a prefixed name be preferable here?
Every other cross-file symbol in this library carries the ax88179_
prefix: ax88179_read_cmd(), ax88179_write_cmd(), ax88179_mdio_read(),
ax88179_status(), ax88179_get_wol(), ax88179_set_multicast() and so on.
netdev2data() is defined non-static in ax88179_lib.c, so with
CONFIG_USB_NET_AX88179_178A=y the unprefixed name ends up in the
vmlinux/kallsyms global namespace and backtraces no longer say which
driver it belongs to.
Since drivers/net/usb/Makefile links ax88179_lib.o into the multi-object
ax88179 module, the function cannot simply be made static in the .c file.
Would either of these work instead?
ax88179_netdev2data()
or, given the body is two lines, a static inline in ax88179_lib.h.
I checked at the end of the series (5c3c73e9) and the symbol is still
global and unprefixed, so no later patch renames or localizes it.
[Severity: Low]
Also worth noting for completeness, and this turned out not to be a
problem: at this commit netdev2data() has no callers, and the existing
open-coded netdev_priv() + ->driver_priv sequences in the same file
(ax88179_get_wol(), ax88179_set_wol(), ax88179_set_multicast()) are left
untouched.
Checking the rest of the series shows the consumers arrive in the later
patches, with drivers/net/usb/ax88179a_devices.c calling netdev2data()
in six places at 5c3c73e9, so the intermediate commit builds cleanly and
changes no behaviour. Could the commit message mention which follow-up
patch first uses the helper?
> 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 b2ee1356727cf..9ff2a94a6fb2d 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);
The new declaration sits in the middle of the ax88179_-prefixed block,
which is where the naming inconsistency above is most visible.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips
2026-09-14 12:46 ` [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
2026-09-14 14:12 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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 20 potential
issue(s) to consider:
Critical: 1 · High: 4 · Medium: 12 · Low: 3
- [Critical] ax88179a_rx_fixup() validates the device-supplied per-packet
length only against an upper bound (`pkt_len > dev->hard_mtu +…
- [High] Two of the three new USB id-table entries use a bcdDevice lower
bound of 0 (`USB_DEVICE_VER(0x0b95, 0x1790, 0, 0x0200)` and…
- [High] ax88179a_netdev_ops sets `.ndo_eth_ioctl = usbnet_mii_ioctl`
while ax88179a_bind() never populates struct usbnet's embedded…
- [High] ax88179a_rx_fixup() reads the trailing 8-byte RX descriptor
header with `rx_hdr = *(u64 *)skb_tail_pointer(skb)` — no endianness…
- [High] Passing the address of a pointer to `ax88179_read_cmd`
overwrites the local pointer variable, causing stack corruption.
- [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] The commit message claims the device is by default handled by
CDC-NCM and that this driver only takes over "if the ax88179_178a…
- [Medium] ax88179a_ethtool_ops mixes ABIs for one link object:
`.get_link_ksettings = phy_ethtool_get_link_ksettings` /…
- [Medium] ax88179a_bulkin_config() applies the full-speed fallback only
when chip_version == AX_VERSION_AX88279.
- [Medium] In ax88179a_tx_fixup() the skb_linearize() failure path does
`return NULL` without freeing the skb.
- [Medium] ax88179_mdiobus_read() ignores the return value of
ax88179_read_cmd(), and ax88179_read_cmd() with size == 2 copies its…
- [Medium] phylink_start() is called from ax88179a_reset() (the
driver_info .reset hook, invoked early in usbnet_open()), but…
- [Medium] ax88179a_reset() calls phylink_start() (which queues the
asynchronous phylink resolve work and starts the PHY state machine)…
- [Medium] AX_MEDIUM_STATUS_MODE is read-modify-written by
ax88179_change_mtu() (process context, RTNL) and completely rewritten…
- [Medium] ax88179a_stop() disables the hardware (clears
AX_MEDIUM_RECEIVE_EN, writes AX_RX_CTL = 0) *before* calling…
- [Medium] ax88179a_tx_fixup() writes an 8-byte TX descriptor into the
SKB headroom (skb_push) and zero padding into the tailroom…
- [Medium] Undercounting TX packets for TSO super-packets.
- [Low] DISCARDED (fixed later in the same series): legacy
ax88179_suspend()/ax88179_resume() applied to the phylink-managed…
- [Low] DISCARDED (fixed later in the same series): ax88179a_netdev_ops
missing .ndo_vlan_rx_add_vid/.ndo_vlan_rx_kill_vid (which makes…
- [Low] ax88179a_bind() reads only three firmware-version bytes (`for
(int i = 0; i < 3; i++)` from AX88179A_ACCESS_BL registers 0xFD..0xFF…
--
ax88179_178a: Add HW support for AX179A-based chips
This adds a new ax88179a_devices.c with bind/reset/stop/rx_fixup/tx_fixup
handlers plus phylink and MDIO bus support for the AX88179A/AX88772D/AX88279
family, and three new USB id-table entries selected by bcdDevice. The
Kconfig select is switched from PHYLIB to PHYLINK.
A few questions below.
> 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
[ ... ]
> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 6aa35c8eb594f..33be9ef73e73d 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, 0, 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, 0, 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,
[Severity: High]
Can the first and third entries claim the classic AX88179? Two of the new
entries use a bcdDevice lower bound of 0, and they are placed ahead of the
pre-existing USB_DEVICE_AND_INTERFACE_INFO entry.
usb_match_device() only rejects when bcdDevice_lo > bcdDevice, and
USB_DEVICE_VER() sets no interface-class match flags, so
usb_match_one_id_intf() is never consulted. usb_match_id() returns the
first hit in products[], which would now be:
USB_DEVICE_VER(0x0b95, 0x1790, 0, 0x0200),
.driver_info = (unsigned long)&ax88179a_info,
An existing 0x0b95:0x1790 dongle with bcdDevice 1.00 would then be bound
with ax88179a_bind()/ax88179a_info, which registers an MDIO bus, creates a
phylink instance and programs AX88179A-only vendor registers
(AX88179A_PHY_POWER, AX88179A_MAC_PATH, AX88179A_BFM_DATA). ax88179a_bind()
reads AX_CHIP_STATUS but never rejects AX_VERSION_AX88179.
Would explicit lower bounds (0x0200 and 0x0400) match the commit message,
which states that bcdDevice 2.00/3.00/4.00 identify the three parts?
[Severity: Low]
This isn't a defect of the final series state, but at this commit the new
IDs share the existing usb_driver .suspend/.resume/.reset_resume callbacks,
so ax88179_resume() -> ax88179_reset() -> mii_nway_restart() runs against
dev->mii, whose mdio_read/mdio_write are never populated for these devices,
and phylink is neither suspended nor resumed. This is addressed later in
the same series by "ax88179_178a: Add Suspend/resume support for
AX88179A/772D/279", which introduces per-instance suspend/resume pointers
and ax88179a_suspend()/ax88179a_resume().
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> new file mode 100644
> index 0000000000000..21ca71cba52ca
> --- /dev/null
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -0,0 +1,965 @@
[ ... ]
> +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 the return value of ax88179_read_cmd() be checked here? For size 2,
ax88179_read_cmd() in ax88179_lib.c copies 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;
So a failed or timed-out control transfer is reported to phylib as the
valid register value 0, which for BMSR reads as "link down, no
capabilities" and for BMCR as "autoneg disabled". The Clause-45 sibling
ax179a_read_mmd() in this same file does propagate the error.
[ ... ]
> +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;
[ ... ]
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, &bulkin_data[index]);
> +}
[Severity: Medium]
Can an all-zero bulk-in profile be written here for the AX88179A/AX88772D?
The full-speed fallback is applied only for AX_VERSION_AX88279. For the
other parts, when link_sts has neither AX_USB_SS nor AX_USB_HS set, index
stays at 0 (BULK_IN_SPEED_2G5), or 1 for 100 Mbit half duplex.
AX88179A_BULKIN_SIZE[0] and AX88772D_BULKIN_SIZE[0..4] are not covered by
the designated initialisers, so {0, 0, 0, 0, 0} is written to
AX_RX_BULKIN_QCTRL. An AX88772D on a full-speed port reports AX_USB_FS
only.
Related, in ax88179a_mac_link_up():
ax88179_read_cmd(dev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, 1, 1, &link_sts);
ax88179a_bulkin_config(dev, link_sts, bulk_config_speed, !!duplex);
link_sts is an uninitialised stack byte and ax88179_read_cmd() only
zero-initialises its temporary for sizes 2 and 4, so a short or failed
transfer leaves stack garbage selecting the index. Should the read result
be checked?
[ ... ]
> +static const struct ethtool_ops ax88179a_ethtool_ops = {
> + .get_link = ethtool_op_get_link,
[ ... ]
> + .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,
> +};
[Severity: Medium]
Is mixing the two ABIs for one link object intentional here? The link
settings go through phylib directly on net->phydev, while pauseparam goes
through phylink.
phy_ethtool_set_link_ksettings() -> phy_ethtool_ksettings_set() validates
only against phydev->supported. phylink_bringup_phy() restricts
phy->advertising:
/* Restrict the phy advertisement according to the MAC support. */
linkmode_copy(phy->advertising, config.advertising);
but leaves phy->supported untouched, so the mac_capabilities and
supported_interfaces set up in ax88179a_phylink_setup() are not enforced on
a user set, and pl->link_config ends up out of sync with what the
pauseparam entries in the same table operate on.
Documentation/networking/sfp-phylink.rst asks for
phylink_ethtool_ksettings_get()/_set() here.
Also, usbnet_nway_reset() tests dev->mii.mdio_write and returns -EOPNOTSUPP
when it is NULL, which is the case for these devices; would
phylink_ethtool_nway_reset() be the right callback?
[ ... ]
> +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 = usbnet_mii_ioctl,
> +};
[Severity: High]
Can an MII ioctl on these interfaces hit a NULL function pointer?
ax88179a_bind() only sets dev->mii.supports_gmii, and the netdev private
area is zero-allocated, so mii.dev, mii.mdio_read and mii.mdio_write stay
NULL (unlike the legacy ax88179_bind(), which initialises all of them).
usbnet_mii_ioctl() has no guard:
return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL);
and generic_mii_ioctl() in drivers/net/mii.c calls the pointer directly:
mii_data->val_out =
mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
mii_data->reg_num);
So SIOCGMIIPHY/SIOCGMIIREG (for example mii-tool eth0, with CAP_NET_ADMIN)
would call through a NULL mdio_read while dev_ioctl() holds rtnl. Would
phylink_mii_ioctl() be the right handler for a phylink/mii_bus driver?
[Severity: Low]
This isn't a defect of the final series state, but at this commit the ops
table has no .ndo_vlan_rx_add_vid/.ndo_vlan_rx_kill_vid while
NETIF_F_HW_VLAN_CTAG_FILTER is advertised in ax88179a_bind(), which
register_netdevice() rejects with -EINVAL; .ndo_set_features and
.ndo_set_rx_mode are also absent. All four are added later in the same
series by the VLAN offload and multicast patches.
> +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;
> + }
[Severity: Medium]
The commit message says:
"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"
Is there anything in the patch that implements that opt-in?
The new USB_DEVICE_VER() entries carry no interface-class constraint, so
they also match the interfaces of the default CDC configuration. Both
USB_NET_AX88179_178A and USB_NET_CDC_NCM are "default y", and ax88179.o is
linked before cdc_ncm.o in drivers/net/usb/Makefile, so for a built-in
build whichever probe runs first wins. When this driver wins,
usb_driver_set_configuration(udev, 1) switches the device out of CDC-NCM
mode unconditionally.
Could the commit message describe the actual behaviour, or could the match
be restricted to the vendor configuration?
[ ... ]
> + 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]);
[Severity: Low]
The loop fills fw_version[0..2] but four components are printed, so the
last one is always the kzalloc-provided 0 and never comes from the device.
Is the loop bound or the register base off by one (four registers would be
0xFC..0xFF)? The same array is exported through ethtool later in the
series.
[ ... ]
> +static void ax88179a_mac_link_up(struct phylink_config *config,
[ ... ]
> + 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);
[Severity: Medium]
What serialises AX_MEDIUM_STATUS_MODE between this callback and
ax88179_change_mtu()?
ax88179_change_mtu() (now wired in as .ndo_change_mtu) does a
read-modify-write with sleeping control transfers in between:
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);
That runs under rtnl, while ax88179a_mac_link_up() runs from the phylink
resolve work under phylink's state_mutex and rewrites the whole register.
If change_mtu reads while the link is down and writes back after a
link-up, can the gigabit/duplex/flow-control bits be lost (or the jumbo bit
be dropped in the reverse order)?
[ ... ]
> +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]
Is the endianness handling of the descriptors incomplete here?
rx_hdr is read with a plain u64 dereference and no conversion at all, while
the first per-packet descriptor gets le64_to_cpus() and every later one is
dereferenced raw after pkt_desc_ptr++. The wire format looks
little-endian: ax88179a_tx_fixup() uses put_unaligned_le64() and the legacy
parser uses get_unaligned_le32() on the same trailing header.
On a big-endian host pkt_cnt and hdr_off would be byte-swapped, so the
consistency checks reject the URB; when a multi-packet URB does pass, every
descriptor after the first is decoded with the wrong length, RX_OK, DROP,
checksum and VLAN fields.
Both accesses are also unaligned: usbnet allocates RX buffers with
__netdev_alloc_skb_ip_align(), and hdr_off is a device-supplied 19-bit
value, so the 8-byte loads can land at 2-mod-8 addresses. Would
get_unaligned_le64() inside the loop be the right accessor?
[ ... ]
> + 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);
[Severity: Critical]
Should pkt_len also be checked against a lower bound? Only the upper bound
and the device-supplied DROP/RX_OK flags are tested, so a device-reported
pkt_len of 1..13 is accepted and passed up.
For pkt_len = 1, pkt_len_plus_padd = (1 + 7) & 0x7FFF8 = 8, so a
single-descriptor URB with hdr_off = 8 satisfies both pkt_end > hdr_off and
the final pkt_cnt == 0 && pkt_end != hdr_off check.
usbnet_skb_return() then calls eth_type_trans(), which reads a full struct
ethhdr from a 1-byte skb and does skb_pull_inline(skb, ETH_HLEN),
underflowing skb->len to roughly 4 GiB before netif_rx(). Every later
pskb_may_pull() then succeeds trivially.
The core backstop does not apply here, because rx_process() bails out
before the runt check for FLAG_MULTI_PACKET drivers, and all three new
driver_info entries set that flag:
drivers/net/usb/usbnet.c:rx_process() {
if (dev->driver_info->flags & FLAG_MULTI_PACKET)
return -EALREADY;
if (skb->len < ETH_HLEN) {
...
}
The legacy ax88179_rx_fixup() rejects short frames explicitly:
if ((*pkt_hdr & (AX_RXHDR_CRC_ERR | AX_RXHDR_DROP_ERR)) ||
pkt_len < 2 + ETH_HLEN) {
Was dropping that check intentional?
[ ... ]
> +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))
> + return NULL;
[Severity: Medium]
Does this path leak the skb? skb_linearize() does not free on failure, and
because all three driver_info entries set FLAG_MULTI_PACKET,
usbnet_start_xmit() treats the NULL return as "packet collected":
drivers/net/usb/usbnet.c:usbnet_start_xmit() {
skb = info->tx_fixup(dev, skb, GFP_ATOMIC);
if (!skb) {
if (info->flags & FLAG_MULTI_PACKET)
goto not_drop;
...
not_drop:
if (skb)
dev_kfree_skb_any(skb);
}
so nothing is freed and tx_dropped is not accounted. The skb_copy_expand()
failure path a few lines below does call dev_kfree_skb_any(skb) first.
> + 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]
Should this check skb_cloned()/skb_header_cloned() before writing into the
shared data buffer? The only gate is the numeric head/tailroom test, and
dev->net->needed_headroom is set to 8 in ax88179a_bind(), so a cloned but
roomy skb (TCP retransmit clone, or an AF_PACKET tap clone created by
dev_queue_xmit_nit()) takes the no-copy path. skb_push() then writes 8
bytes ahead of skb->data and skb_put_zero() writes past skb->tail inside
the buffer every clone still shares. skb_linearize() is a no-op for an
already-linear clone.
The legacy ax88179_tx_fixup() guards this:
if ((skb_header_cloned(skb) || headroom < 0) &&
pskb_expand_head(skb, headroom < 0 ? 8 : 0, 0, GFP_ATOMIC)) {
[ ... ]
> +static int ax88179a_reset(struct usbnet *dev)
> +{
[ ... ]
> + phylink_start(ax179_data->phylink);
> +
> + usbnet_link_change(dev, 0, 0);
> +
> + return 0;
> +}
[Severity: Medium]
Are phylink_start() and phylink_stop() balanced on the failed-open path?
phylink_start() runs from the .reset hook early in usbnet_open(), but
phylink_stop() only runs from ax88179a_stop(), which is reached via
ndo_stop. If usbnet_open() fails after info->reset() succeeded:
drivers/net/usb/usbnet.c:usbnet_open() {
if (dev->interrupt) {
retval = usbnet_status_start(dev, GFP_KERNEL);
if (retval < 0) {
...
goto done;
}
}
}
the done: label only does usb_autopm_put_interface() and returns, so
ndo_stop is never called. phylink and the PHY state machine then keep
running on a down interface, ax88179a_mac_link_up() can re-enable
AX88179A_MAC_PATH and rxctl, and a later successful open would call
phylink_start()/phy_start() a second time. These devices do have an
interrupt endpoint, and usbnet_status_start() can fail with
-ENOMEM/-ENODEV/-EBUSY.
[Severity: Medium]
Can the carrier state end up stuck down here? phylink_start() queues the
asynchronous resolve and starts the PHY, then usbnet_link_change(dev, 0, 0)
runs synchronously:
drivers/net/usb/usbnet.c:usbnet_link_change() {
clear_bit(EVENT_LINK_CARRIER_ON, &dev->flags);
netif_carrier_off(dev->net);
}
If phylink_resolve() has already called netif_carrier_on(), this clears the
carrier behind phylink's back, and phylink only touches the carrier on
state changes:
drivers/net/phy/phylink.c:phylink_resolve() {
if (link_state.link != cur_link_state) {
pl->old_link_state = link_state.link;
}
Since data->phydev->irq is PHY_MAC_INTERRUPT there is no polling, so
nothing re-runs the resolve until the next real link event. The two
writers share no lock (rtnl versus phylink state_mutex).
> +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, ®16);
> + reg16 &= ~AX_MEDIUM_RECEIVE_EN;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
> +
> + reg16 = 0;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
> +
> + reg8 = 0;
> + ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
[Severity: Medium]
Is this meant to be ax88179_write_cmd()? reg8 is set to 0 to power the PHY
down, but a read is issued, so nothing reaches the device and reg8 is
overwritten by the readback. Both ax88179a_reset() and ax88179a_unbind()
use a write for the same register:
tmp8 = 0;
ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &tmp8);
> +
> + phylink_stop(ax179_data->phylink);
> +
> + return 0;
> +}
[Severity: Medium]
Would phylink_stop() belong before the register writes? A resolve already
in flight can run ax88179a_mac_link_up() between the AX_RX_CTL = 0 write
and phylink_stop(), and its tail restores exactly what was just disabled:
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);
Since ax88179a_mac_link_down() is empty, the final resolve inside
phylink_stop() would not undo them, leaving the device RX/TX-enabled after
ndo_stop. Each step here is separated by a sleeping control transfer.
[ ... ]
> @@ -0,0 +1,965 @@
> +// 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 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 = usbnet_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, ®8);
> + 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 & VLAN_VID_MASK);
> + }
> +
> + 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))
> + 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);
[Severity: Medium]
Is a hardcoded packet count of 1 right for a TSO super-packet?
ax88179a_bind() advertises NETIF_F_TSO and calls
netif_set_tso_max_size(dev->net, 16384), and this function programs
skb_shinfo(skb)->gso_size into the MSS field of tx_desc, so the
hardware splits the buffer into skb_shinfo(skb)->gso_segs frames on
the wire. Accounting the whole super-packet as a single packet makes
tx_packets in rtnl_link_stats64 (via dev_get_tstats64) undercount by
the segment count for every large TCP flow -- with a 16 KiB
super-packet and a 1448 byte MSS that is one counted packet instead
of eleven. Byte accounting is unaffected only if the per-segment
headers are not expected to be counted; passing 0 as the byte delta
means those replicated headers are not counted either.
Would skb_shinfo(skb)->gso_segs ?: 1 (with a matching byte
adjustment for the duplicated headers) be what you want here, or is
there a reason the driver deliberately reports the pre-segmentation
count?
> +
> + 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, ®16);
> + reg16 &= ~AX_MEDIUM_RECEIVE_EN;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, ®16);
> +
> + reg16 = 0;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, ®16);
> +
> + reg8 = 0;
> + ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
> +
> + 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,
> +};
>
[Severity: High]
Should this be tmp rather than &tmp?
tmp is declared as u8 *tmp and set to point at the local buf[5]
array, so &tmp is a u8 ** -- the address of the pointer variable
itself, not of the one-byte buffer. ax88179_read_cmd() with size 1
falls through to __ax88179_read_cmd(), which hands that address to
the USB control transfer, so the device writes one byte straight
over the stack slot holding tmp, clobbering the pointer.
Nothing dereferences tmp after this point in ax88179a_reset() today,
so it does not crash, but it is still a stray write over a live local
and any future code added below this read (or a compiler that spills
and reloads tmp) would then follow a corrupted pointer. The rest of
the function consistently passes tmp/tmp16 by value for exactly this
reason.
While here: the condition only tests that the control transfer
succeeded, it never looks at the value read back, so
wol_supported is set to WAKE_MAGIC | WAKE_PHY unconditionally on any
responding device. Was the intent to test a bit in the returned
AX_MONITOR_MOD value? And would plain u8/u16 locals be clearer here
than the tmp/tmp16 aliasing over buf[5], which is what made this
mistake easy to miss?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs
2026-09-14 12:46 ` [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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: 0 · High: 0 · Medium: 0 · Low: 5
- [Low] Commit message of "ax88179_178a: Add EEE configuration support
for AX88179A MACs" ends in a truncated/garbled sentence describing…
- [Low] Concern (validated as resolved later in the series): registering
.mac_enable_tx_lpi/.mac_disable_tx_lpi makes phylink_create() set…
- [Low] Concern (validated as resolved later in the series):
ax88179a_ethtool_ops has no .get_eee/.set_eee entries at this commit,…
- [Low] Concern (validated as not a defect): the LPI ops are added
unconditionally to the single ax88179a_phylink_mac_ops table shared by…
- [Low] Concern (validated as speculative/non-actionable):
AX_GPHY_EEE_CTRL is written only from the two new phylink LPI…
--
ax88179_178a: Add EEE configuration support for AX88179A MACs
This adds two phylink LPI callbacks, ax88179a_mac_enable_tx_lpi() and
ax88179a_mac_disable_tx_lpi(), which toggle the AX_GPHY_EEE_CTRL register,
and registers them in ax88179a_phylink_mac_ops. The AX88179A has no LPI
timer registers, so the timer argument is ignored.
A few questions below, all minor.
> 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 truncated: "Add support for this EEE
enable/disable register and replace the EEE."
What is being replaced here? The diff is purely additive: it adds
ax88179a_mac_disable_tx_lpi(), ax88179a_mac_enable_tx_lpi() and two
members of ax88179a_phylink_mac_ops, and deletes nothing. The legacy
get_eee/set_eee and ax88179_enable_eee() code in ax88179_178a.c is left
untouched.
Could the sentence be completed so the changelog matches the change?
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 21ca71cba52ca..80cabe3557cde 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: Low]
AX_GPHY_EEE_CTRL is only ever written from these two new callbacks, so it is
never forced to a known value during ax88179a_bind()/ax88179a_reset(). If the
chip powers up with EEE enabled, does the hardware state match phylink's
bookkeeping until the first link up/down transition?
Would an explicit disable write in the reset path be worth adding?
On the ignored return value here: the phylink prototype is
void (*mac_disable_tx_lpi)(struct phylink_config *), so there is no way to
propagate a failed USB control transfer, and the resume path does re-resolve
the link (ax88179a_resume() -> ax88179a_reset() -> phylink_resume(), which
either calls phylink_start() or phylink_link_down() plus
phylink_mac_initial_config() and phylink_enable_and_run_resolve()), so the
register state is re-established on a link cycle. Mentioning it only for
completeness.
> +
> +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]
Registering both LPI ops makes phylink_create() set
pl->mac_supports_eee_ops = true, but ax88179a_phylink_setup() does not
populate phylink_config.lpi_interfaces at this commit, so phylink_create()
leaves mac_supports_eee false:
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);
}
In that state phylink_bringup_phy() takes the branch that calls
phy_disable_eee(phy), phylink_ethtool_get_eee()/set_eee() return
-EOPNOTSUPP, and the two new callbacks are never invoked, since
phylink_link_up() gates on mac_supports_eee and phylink_activate_lpi()
additionally tests config->lpi_interfaces.
This is resolved by the following patch in the series, "ax88179_178a: Add EEE
configuration support for AX88179A PHYs", which adds the memcpy of
supported_interfaces into lpi_interfaces before phylink_create(), so it only
affects the intermediate state. Would it be worth folding the
lpi_interfaces assignment into this patch so bisection points do not have EEE
forced off?
[Severity: Low]
Related to the above: at this commit ax88179a_ethtool_ops has no .get_eee or
.set_eee entries, and ax88179a_phylink_setup() sets
phylink_config.eee_enabled_default = false, so nothing can set
pl->eee_cfg.eee_enabled and ax88179a_mac_enable_tx_lpi() cannot be reached
from userspace.
The ethtool wrappers ax88179a_get_eee()/ax88179a_set_eee() arrive in the next
patch of the series, so this too is limited to the intermediate state.
[Severity: Low]
The ops are added unconditionally to the single mac_ops table shared by
AX88179A, AX88279 and AX88772D, while ax88179a_phylink_setup() sets
lpi_capabilities only for !is_ax88772d.
For AX88772D that combination looks intentional and is handled by phylink:
phylink_bringup_phy() documents ops present with empty lpi_capabilities as
"MAC supports phylink EEE, but wants EEE always disabled", and no
AX_GPHY_EEE_CTRL write happens for that chip.
Is the AX_GPHY_EEE_CTRL encoding the same on AX88279, or does that part need
a chip_version guard?
> };
>
> static int ax88179a_phylink_setup(struct usbnet *dev)
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs
2026-09-14 12:46 ` [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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: 1 · Medium: 2 · Low: 1
- [High] NULL function-pointer dereference reachable from userspace: the
AX88179A/279/772D path is entirely phylink/mdiobus based, yet…
- [Medium] This patch exposes .get_eee/.set_eee to userspace for the
AX88179A/772D/279, but the driver has no LPI timer support and never…
- [Medium] ax88179a_ethtool_ops
(drivers/net/usb/ax88179a_devices.c:224-241), the table this patch…
- [Low] Commit message inaccuracies.
--
ax88179_178a: Add EEE configuration support for AX88179A PHYs
This adds ethtool .get_eee/.set_eee handlers that forward to
phylink_ethtool_get_eee()/phylink_ethtool_set_eee(), and copies
phylink_config.supported_interfaces into phylink_config.lpi_interfaces so
that phylink considers the MAC capable of LPI. A few questions below.
> 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]
Is "AX88197A" a real part number? The chips handled by
drivers/net/usb/ax88179a_devices.c are AX88179A/AX88279/AX88772D, as the
probe message in ax88179a_bind() says:
"AX88179A/279/772D Chip Version: ..."
Should the subject prefix be updated too? After the file split earlier in
the series the module is ax88179:
drivers/net/usb/Makefile:
ax88179-y := ax88179_178a.o ax88179a_devices.o ax88179_lib.o
and the only file touched here is ax88179a_devices.c.
One more thing on the body: the description "Configure phylink to work with
the PHYs" does not mention the actual functional effect of the new memcpy.
phylink_create() computes
pl->mac_supports_eee = pl->mac_supports_eee_ops &&
pl->config->lpi_capabilities &&
!phy_interface_empty(pl->config->lpi_interfaces);
so filling lpi_interfaces is what makes the LPI MAC ops added by the
previous patch reachable at all, and it also enables EEE handling for
AX88279 (both PHY_INTERFACE_MODE_SGMII and PHY_INTERFACE_MODE_2500BASEX get
copied). Could the message state that?
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 80cabe3557cde..97aad545daee1 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);
> +}
> +
[Severity: Medium]
What does userspace see for the LPI timer through these two new ops?
ax88179a_phylink_setup() sets mac_capabilities, lpi_capabilities,
eee_enabled_default and now lpi_interfaces, but never assigns
phylink_config.lpi_timer_default, and phylink_create() does:
pl->eee_cfg.tx_lpi_timer = pl->config->lpi_timer_default;
phylink_bringup_phy() copies that into phydev->eee_cfg.tx_lpi_timer and
phy_ethtool_get_eee() reports it back via eeecfg_to_eee(), so
"ethtool --show-eee" prints a Tx LPI timer of 0 us.
Is the write side also a concern? "ethtool --set-eee <dev> tx-lpi-timer N"
returns success: phylink_ethtool_set_eee() -> phy_ethtool_set_eee() stores N
in phydev->eee_cfg and phylink caches it with
eee_to_eeecfg(&pl->eee_cfg, eee), so N is echoed back on subsequent reads
while ax88179a_mac_enable_tx_lpi() throws the value away:
static int ax88179a_mac_enable_tx_lpi(struct phylink_config *config,
u32 timer, bool tx_clk_stop)
{
/* AX88179A does not provide LPI timer registers */
return ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL,
true, 0, NULL);
}
The mac_enable_tx_lpi() kernel-doc in include/linux/phylink.h says
"@timer: LPI timeout in microseconds. ... Configure the LPI timeout
accordingly." Would it be better to advertise the hardware's fixed idle
timeout in lpi_timer_default so ethtool reports the truth, and/or reject a
mismatching timer in the MAC callback rather than silently accepting it?
> 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,
[Severity: High]
While looking at this table: can usbnet_nway_reset() ever work on this
hardware, and can the MII path here oops?
ax88179a_bind() sets only one mii field for the AX88179A family:
if (!ax179_data->is_ax88772d)
dev->mii.supports_gmii = 1;
mii.dev, mii.mdio_read, mii.mdio_write and mii.phy_id stay zero, unlike the
legacy sibling ax88179_bind() which fills them all in:
dev->mii.dev = dev->net;
dev->mii.mdio_read = ax88179_mdio_read;
dev->mii.mdio_write = ax88179_mdio_write;
dev->mii.phy_id_mask = 0xff;
usbnet_nway_reset() bails out with -EOPNOTSUPP when dev->mii.mdio_write is
NULL, so "ethtool -r" is a no-op here even though
phylink_ethtool_nway_reset() exists.
The same uninitialised dev->mii is reachable through
ax88179a_netdev_ops.ndo_eth_ioctl = usbnet_mii_ioctl, which calls
generic_mii_ioctl(&dev->mii, ...):
case SIOCGMIIPHY:
mii_data->phy_id = mii_if->phy_id;
fallthrough;
case SIOCGMIIREG:
mii_data->val_out =
mii_if->mdio_read(mii_if->dev, mii_data->phy_id,
mii_data->reg_num);
With mdio_read NULL, does "mii-tool eth0" (SIOCGMIIPHY) dereference a NULL
function pointer? Would phylink_mii_ioctl()/phy_do_ioctl_running() and
phylink_ethtool_nway_reset() be the right entries for a phylink driver?
> .get_link_ksettings = phy_ethtool_get_link_ksettings,
> .set_link_ksettings = phy_ethtool_set_link_ksettings,
[Severity: Medium]
Since this table now mixes phylink-based EEE ops with raw phylib link
settings helpers, should these two be phylink_ethtool_ksettings_get() and
phylink_ethtool_ksettings_set() instead?
The PHY is owned by phylink here:
ax88179a_init_phy()
ret = phylink_connect_phy(data->phylink, data->phydev);
but phy_ethtool_set_link_ksettings() operates directly on net->phydev and
calls phy_start_aneg(), so "ethtool -s eth0 advertise <mask>" bypasses
phylink entirely.
phylink_bringup_phy() only narrows phy->advertising once at connect time, so
the MAC limits declared in ax88179a_phylink_setup():
data->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_100;
if (data->is_ax88772d)
data->phylink_config.mac_capabilities |= MAC_10;
are not applied to later user changes, and phylink's cached
pl->link_config.advertising goes stale. That is the same state phylink
consults when computing the EEE advertisement for the new .set_eee path, so
does mixing the two interfaces make the EEE result depend on whether
ethtool -s was used before?
> @@ -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));
> +
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A
2026-09-14 12:46 ` [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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 8 potential
issue(s) to consider:
Critical: 0 · High: 1 · Medium: 7 · Low: 0
- [High] ax88179a_set_features() destroys the VLAN control register it
just programmed: the 256-iteration filter-table clearing loop writes…
- [Medium] Series ordering / bisect breakage that the commit message does
not disclose: ax88179a_bind() already sets dev->net->features |=…
- [Medium] Undisclosed, unrelated behaviour change in shared library
code: the hunk in drivers/net/usb/ax88179_lib.c adds…
- [Medium] The newly added `data->rx_checksum = !!(features &
NETIF_F_RXCSUM)` tracking in ax88179_set_features() is contradicted by…
- [Medium] The new VLAN ndo callbacks and the VLAN part of
ax88179a_set_features() discard the return value of every…
- [Medium] ax88179a_set_features() programs the VLAN mode bits by XOR-
toggling the value read from AX88179A_VLAN_ID_CONTROL (`tmp ^=…
- [Medium] The new VLAN helpers and ax88179a_set_features() perform
1-byte register reads into uninitialized stack variables (`u8 reg8;`…
- [Medium] ax88179_set_features() publishes the new RX-checksum state
with a plain store to data->rx_checksum from process context (RTNL…
--
ax88179_178a: Add VLAN offload support for AX88179A
This adds ndo_set_features, ndo_vlan_rx_add_vid and ndo_vlan_rx_kill_vid
for the AX88179A family so that CTAG TX/RX offload and the hardware VLAN
filter table can be configured. It also adds a line to the shared
ax88179_set_features() that tracks the NETIF_F_RXCSUM state.
No Link: tags were present in the commit header.
A few questions about the new code below.
> 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);
> }
[Severity: Medium]
This hunk changes RX checksum behaviour in the shared library used by both
ax88179_178a.c and ax88179a_devices.c, while the commit message only
mentions VLAN offload:
Features supported are:
NETIF_F_HW_VLAN_CTAG_TX, NETIF_F_HW_VLAN_CTAG_RX
and NETIF_F_HW_VLAN_CTAG_FILTER.
Previously ax88179_data.rx_checksum was only ever set to 1 in
ax88179a_reset() and never cleared, so ax88179a_rx_fixup() kept calling
ax88179a_rx_checksum() after the user disabled NETIF_F_RXCSUM. Could this
change be described in the commit message, or split into its own patch?
[Severity: Medium]
Does ax88179a_reset() undo this tracking? It programs the same register
absolutely and forces the flag back on without consulting net->features:
drivers/net/usb/ax88179a_devices.c:ax88179a_reset() {
...
/* 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;
...
}
After ethtool -K ethX rx off followed by ip link set ethX down; ip link set
ethX up, is hardware RX checksum offload re-enabled and rx_checksum forced
to 1 while net->features still has NETIF_F_RXCSUM cleared?
There is a second consequence of mixing an absolute write in reset() with
the XOR toggle here: a later ethtool -K ethX rx on reads the already-enabled
AX_RXCOE_* bits, XORs them to zero and disables the checksum engine, yet
sets rx_checksum = 1. Would ax88179a_rx_fixup() then interpret descriptor
checksum fields produced by a disabled engine?
This is still the case at the end of the series.
[Severity: Medium]
Is the store to data->rx_checksum here synchronized against the reader?
The writer runs in process context under RTNL, while the reader runs from
usbnet's bh work:
drivers/net/usb/ax88179a_devices.c:ax88179a_rx_fixup() {
...
if (ax179_data->rx_checksum)
ax88179a_rx_checksum(ax_skb, pkt_desc);
...
}
There is no lock, READ_ONCE()/WRITE_ONCE() or barrier on either side.
The ordering also looks inverted for the disable direction: AX_RXCOE_CTL is
reprogrammed first and the flag is cleared afterwards, so frames handled in
that window can still be marked CHECKSUM_UNNECESSARY from descriptor bits
the MAC no longer produces. Would clearing the flag before disabling the
hardware, and setting it after enabling it, be more correct?
>
> return 0;
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 97aad545daee1..4bca801236b08 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, ®8);
> + vlan_ctrl = reg8;
[Severity: Medium]
Can vlan_ctrl end up holding uninitialized stack data here? reg8 is
declared without an initializer and the read return value is not checked.
For size == 1, ax88179_read_cmd() passes the caller buffer straight through
without zeroing it:
drivers/net/usb/ax88179_lib.c:ax88179_read_cmd() {
...
} else {
ret = __ax88179_read_cmd(dev, cmd, value, index, size, data);
}
...
}
and nothing is copied on a failed transfer:
drivers/net/usb/usbnet.c:__usbnet_read_cmd() {
...
if (err > 0 && err <= size) {
if (data)
memcpy(data, buf, err);
...
}
So on an -ENODEV/-ETIMEDOUT/stalled control transfer, is one byte of kernel
stack written back into AX88179A_VLAN_ID_CONTROL, where the device
interprets it as VFE/VSO/RD/WE? The same pattern is in
ax88179a_vlan_rx_add_vid() and in ax88179a_set_features() (u8 tmp; read,
then tmp ^= AX_VLAN_CONTROL_VSO). aqc111 initializes its equivalent locals
(u8 reg8 = 0).
> +
> + /* Address */
> + reg8 = (vid / 16);
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
> +
> + /* Data */
> + reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
> +
> + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
> + reg16 &= ~(1 << (vid % 16));
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
> +
> + reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
> +
> + return 0;
> +}
[Severity: Medium]
Should these callbacks propagate the register access errors instead of
always returning 0? The 8021q core and __netdev_update_features() use the
return code to decide whether to record the new state.
For the 2-byte case ax88179_read_cmd() zeroes a temporary and copies it out
even when the transfer failed:
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 AX88179A_VLAN_ID_DATA0 read fails, reg16 becomes 0 and the
write-back clears the other 15 VIDs sharing that table word, while
ax88179a_vlan_rx_kill_vid() still reports success. ax88179a_vlan_rx_add_vid()
has the same issue with only the new bit surviving. A failed address or WE
write leaves the entry unprogrammed and also goes unreported.
ax88179a_set_features() does check ax88179_set_features(), but ignores all
of its own accesses, including the 256 loop iterations.
> +
> +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, ®8);
> + vlan_ctrl = reg8;
> +
[ ... ]
> + ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
> + reg16 |= (1 << (vid % 16));
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, ®16);
> +
> + reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
> + ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
> +
> + return 0;
> +}
> +
[ ... ]
> @@ -499,6 +555,49 @@ static int ax88179a_init_mdio(struct usbnet *dev)
> return ret;
> }
>
> +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 this bare WE strobe wipe the enable bits in the same register?
AX88179A_VLAN_ID_CONTROL is one byte holding all four bits:
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
ax88179_write_cmd(dev, ..., 1, 1, &tmp) writes the whole byte, so after
ethtool -K ethX rx-vlan-filter on, does the register end up as 0x01 with
VFE cleared (filter not enabled in hardware) and VSO cleared (RX tag
stripping silently turned off), while dev->features reports both enabled?
Every other site programs this register read-modify-write, including the two
callbacks added by this patch:
reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, ®8);
and ax88179a_reset() composes VFE/VSO from net->features. Should the loop
use vlan_ctrl | AX_VLAN_CONTROL_WE as well?
Note also that the core re-pushes the VIDs after the feature change, and
ax88179a_vlan_rx_add_vid() then ORs WE onto the now-zeroed control value,
so nothing appears to restore VFE/VSO. The same code is unchanged at the
end of the series.
> + }
> + }
> + }
> +
> + 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;
> +}
[Severity: Medium]
Both VLAN mode bits are programmed by XOR-toggling the value read back from
the device rather than being set or cleared from the requested features.
The changed mask only says the requested software bit differs from the
previous software bit, not that the hardware bit still mirrors the old
software state.
Two cases where the register does not mirror net->features:
- right after the clear loop above leaves the control register as 0x01,
so a following rx-vlan-filter off reads VFE=0 and XORs it to 1, arming
the hardware filter while userspace is told filtering is off
- before the first ax88179a_reset(), since feature changes are accepted
while the interface is down
In those cases the XOR inverts the request and the function still returns
success. Would assigning the bits from features be safer here?
>
> static const struct net_device_ops ax88179a_netdev_ops = {
> .ndo_open = usbnet_open,
> .ndo_stop = usbnet_stop,
> @@ -509,6 +608,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 = usbnet_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: Medium]
Is the series bisectable across this point? The earlier commit
"ax88179_178a: Add HW support for AX179A-based chips" already advertises the
filter capability 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 the two ndo callbacks only arrive here, and the core 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;
}
...
}
At the parent commit, grepping ax88179a_devices.c for ndo_vlan_rx returns no
match, so does usbnet_probe() -> register_netdev() fail for every AX88179A,
AX88772D and AX88279 device at the intermediate commits? Could the feature
bits be added in this patch instead, or the ordering noted in the commit
message?
>
> static int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration
2026-09-14 12:46 ` [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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] At this commit, ax88179_set_multicast()
(drivers/net/usb/ax88179_lib.c:313-315) only sets AX_RX_CTL_IPE when…
- [Medium] Adding '.ndo_set_rx_mode = ax88179_set_multicast' to
ax88179a_netdev_ops (drivers/net/usb/ax88179a_devices.c:611) creates a…
--
ax88179_178a: Add AX179A/AX279 multicast configuration
This makes AX_RX_CTL_IPE conditional on data->ip_align 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
netdev ops.
> 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 shift every received frame by two bytes on the legacy
AX88179/178A devices?
ax88179_set_multicast() is shared, and the legacy driver in
ax88179_178a.c registers it as .ndo_set_rx_mode in ax88179_netdev_ops.
At this commit, ->ip_align is never assigned anywhere in
ax88179_178a.c; ax88179_bind() zero-allocates the private data:
ax179_data = kzalloc_obj(*ax179_data);
if (!ax179_data)
return -ENOMEM;
dev->driver_priv = ax179_data;
The only assignments to ->ip_align in the tree at this commit are in
ax88179a_bind() in ax88179a_devices.c:
if (ax179_data->chip_version == AX_VERSION_AX88279) {
ax179_data->ip_align = 1;
...
} else {
ax179_data->ip_align = 0;
So on legacy chips the first rx-mode update after ifup
(dev_open() -> dev_set_rx_mode() -> __dev_set_rx_mode(), a multicast
join, or a promisc toggle) rewrites AX_RX_CTL as
START|AB|DROPCRCERR without AX_RX_CTL_IPE, undoing what
ax88179_reset() programmed:
*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;
Meanwhile ax88179_rx_fixup() still unconditionally accounts for the
2-byte alignment pseudo header:
/* Skip IP alignment pseudo header */
skb_pull(skb, 2);
...
memcpy(ax_skb->data, skb->data + 2, pkt_len);
Once the device stops inserting the pad, does each delivered frame lose
its first two bytes and pick up two bytes of trailing data? The state
also looks sticky, since ax88179_link_reset() re-programs the cached
rxctl.
This also seems to be the opposite of the commit message claim that the
change makes things "consistent with the initial configuration in
ax88179_reset()", at least for the legacy chips, since ax88179_reset()
sets AX_RX_CTL_IPE unconditionally there.
A later patch in the same series ("ax88179_178a: Add support for
AX88179A/772D/279 EEPROM access") adds "ax179_data->ip_align = 1;" to
ax88179_bind(), which restores AX_RX_CTL_IPE for the legacy path, so
the end state of the series is fine. Could that assignment be moved
into this patch instead, so legacy RX is not broken at intermediate
commits?
>
> 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 4bca801236b08..24accbc22092a 100644
> --- a/drivers/net/usb/ax88179a_devices.c
> +++ b/drivers/net/usb/ax88179a_devices.c
> @@ -608,6 +608,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 = usbnet_mii_ioctl,
> + .ndo_set_rx_mode = ax88179_set_multicast,
[Severity: Medium]
What serializes ax179_data->rxctl between this new callback and
ax88179a_mac_link_up()?
ax88179_set_multicast() builds rxctl in several separate stores (base
value, then |= AX_RX_CTL_IPE, then |= PRO/AMALL/AM) and then submits an
asynchronous register write:
ax88179_write_cmd_async(dev, AX_ACCESS_MAC, AX_RX_CTL,
2, 2, &data->rxctl);
It runs from __dev_set_rx_mode() with netif_addr_lock_bh() held only.
ax88179a_mac_link_up() reads the same field and programs the same
register with a blocking control transfer:
ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, &ax179_data->rxctl);
That path comes from the phylink resolve work, and phylink_resolve()
holds only:
mutex_lock(&pl->phydev_mutex);
phy = pl->phydev;
if (phy)
mutex_lock(&phy->lock);
mutex_lock(&pl->state_mutex);
so neither RTNL nor netif_addr_lock is held there. ax88179a_reset() is
a third writer of the same field:
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);
Two things look possible. Can ax88179a_mac_link_up() observe rxctl
between the base store and the "|= AX_RX_CTL_IPE" store and program
AX_RX_CTL without IPE on an AX88279 where ip_align == 1, while
ax88179a_rx_fixup() keeps deriving its offsets from ip_align:
pkt_len_plus_padd = ((pkt_len + 7 + (ax179_data->ip_align ? 2 : 0)) & 0x7FFF8);
...
memcpy(ax_skb->data, skb->data + (ax179_data->ip_align ? AX179A_RX_HW_PAD : 0),
And can the blocking write in ax88179a_mac_link_up() land after the
async URB from ax88179_set_multicast() completes, leaving the hardware
filter (promisc/AM bits) out of sync with data->rxctl until the next
link event or rx-mode change?
This still appears to be the case at the end of the series, where both
ax88179_set_multicast() and ax88179a_mac_link_up() remain unlocked.
> .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/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
2026-09-14 12:46 ` [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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 8 potential
issue(s) to consider:
Critical: 0 · High: 1 · Medium: 5 · Low: 2
- [High] Recursive rtnl_lock() deadlock: ax88179a_resume()
(drivers/net/usb/ax88179a_devices.c:210-214) takes rtnl_lock() from…
- [Medium] ax88179a_suspend()
(drivers/net/usb/ax88179a_devices.c:170-172) discards the return value…
- [Medium] The new PM callbacks read one-byte registers into
uninitialized stack variables and ignore the transfer status.
- [Medium] The PHY is deliberately power-cycled in ax88179a_resume()
(AX88179A_PHY_POWER written 0 at line 205, re-enabled inside…
- [Medium] ax88179a_resume() calls ax88179a_reset(), which
unconditionally reprograms AX_RX_CTL to the reset default…
- [Medium] ax88179_resume_wrapper() is installed as .reset_resume
(ax88179_178a.c:1350), so after a device reset that clears chip state,…
- [Low] ax88179a_resume() unconditionally powers the internal PHY and
calls ax88179a_reset(), which re-enables the full receive datapath…
- [Low] Both comments added by this patch describe behaviour the code
does not have.
--
ax88179_178a: Add Suspend/resume support for AX88179A/772D/279
The patch adds suspend/resume callbacks for the AX88179A/772D/279 family
by storing per-device function pointers in struct ax88179_data and
dispatching through new wrapper functions registered in the single
usb_driver instance.
> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 33be9ef73e73d..44a768c5808ac 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,
[Severity: Medium]
With ax88179_resume_wrapper() also installed as .reset_resume, what
restores the VLAN filter table after a device reset?
ax88179a_vlan_rx_add_vid() writes the VID bitmap straight into the chip:
drivers/net/usb/ax88179a_devices.c:ax88179a_vlan_rx_add_vid() {
reg8 = (vid / 16);
ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, ®8);
...
}
There is no software shadow of that table, and ax88179a_reset() only
re-enables filtering from the netdev features:
*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);
After a reset_resume (hub power loss and various system suspend paths),
does this leave VLAN filtering enabled with an empty VID table, so all
tagged frames are dropped while the kernel still lists the VIDs as
configured?
[Severity: High]
Keeping .supports_autosuspend = 1 while ax88179a_resume() takes
rtnl_lock(): can this deadlock on the first "ip link set up" after a
runtime suspend?
The driver never holds a PM usage reference (no usb_autopm_* or
pm_runtime_get_noresume() in ax88179_178a.c, ax88179_lib.c or
ax88179a_devices.c), so the interface can be runtime-suspended while the
netdev is down. Then:
net/core/dev.c:__dev_open() /* rtnl_mutex held */
set_bit(__LINK_STATE_START, &dev->state);
ops->ndo_open()
drivers/net/usb/usbnet.c:usbnet_open()
usb_autopm_get_interface(dev->intf) /* synchronous resume */
usb_resume_interface()
ax88179_resume_wrapper()
ax88179a_resume()
rtnl_lock(); /* already held */
The sibling driver documents exactly this hazard and blocks it by
holding a PM usage reference in ax88772_bind()
(drivers/net/usb/asix_devices.c). Should the same mitigation be applied
here?
On the same path, usbnet_open() calls info->reset after the resume
completes, so ax88179a_reset() then runs with in_pm == false and calls
phylink_start() on a phylink that phylink_resume() already started,
which trips the WARN in phy_start(). Is that intended?
> 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 24accbc22092a..27836c72ec14a 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);
> + 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]
tmp8 is declared uninitialized and the status of the 1-byte read is not
checked. For size 1, ax88179_read_cmd() passes the caller's pointer
straight through:
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);
}
}
and __usbnet_read_cmd() only copies on a positive length:
drivers/net/usb/usbnet.c:__usbnet_read_cmd() {
if (err > 0 && err <= size) {
if (data)
memcpy(data, buf, err);
}
If the vendor read fails or returns zero bytes, does this write an
uninitialized stack byte back to the device in AX_MONITOR_MOD, possibly
arming unintended bits? The driver's own ax88179a_stop() does
reg8 = 0; before the same 1-byte read.
> +
> + 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: Medium]
Should this propagate the usbnet_suspend() return value, as
asix_suspend() does with return usbnet_suspend(intf, message)?
usbnet_suspend() refuses an autosuspend with queued TX and undoes its
own accounting:
drivers/net/usb/usbnet.c:usbnet_suspend() {
if (!dev->suspend_count++) {
...
if (dev->txq.qlen && PMSG_IS_AUTO(message)) {
dev->suspend_count--;
spin_unlock_irq(&dev->txq.lock);
return -EBUSY;
}
Returning 0 there tells the USB core the interface is suspended even
though usbnet never detached the netdev or terminated URBs. The paired
resume then reaches:
drivers/net/usb/usbnet.c:usbnet_resume() {
if (!--dev->suspend_count) {
with suspend_count still 0, so the unsigned char wraps to 255 and the
URB resubmission, deferred-TX flush, EVENT_DEV_ASLEEP clearing and TX
wake are skipped from then on. Can that happen here?
Related: the phylink_suspend() call and all the WoL/low-power register
programming above happen before usbnet_suspend(), so on a refused
suspend the hardware is left in WoL/low-power state with no rollback.
Should the ordering be inverted, or the programming undone on error?
> 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, ®8);
> + if (!(reg8 & AX_PHY_POWER)) {
[Severity: Medium]
Same pattern here: reg8 is uninitialized and the read status is
discarded, yet the value gates powering the PHY and the 250 ms settle
time before the AX_FW_MODE_179A write. If the 1-byte read fails (STALL
or short response, plausible right after a bus reset), can the
AX_FW_MODE_179A write be issued against an unpowered PHY?
> + reg8 = AX_PHY_POWER;
> + ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
> + msleep(250);
> + }
> + ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
> +
> + /* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle.
> + * PHY-power is re-enabled in ax88179_reset()
> + */
[Severity: Low]
This isn't a bug, but the comment names ax88179_reset() while the code
below calls ax88179a_reset(). ax88179_reset() is a separate static
function in ax88179_178a.c that is not reached for AX88179A/772D/279.
> + reg8 = 0;
> + ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, ®8);
> + msleep(250);
[Severity: Medium]
After this deliberate PHY power-cycle, what re-applies the PHY
configuration?
ax88179a_init_phy() sets phydev->mac_managed_pm = true, which removes
phylib's mdio_bus_phy_resume() (the remaining caller of phy_init_hw() on
resume). With WoL enabled, phylink_suspend(pl, true) takes the MAC-WoL
branch and does not stop phylink, so on resume:
drivers/net/phy/phylink.c:phylink_resume() {
if (test_bit(PHYLINK_DISABLE_MAC_WOL, &pl->phylink_disable_state)) {
...
phylink_mac_initial_config(pl, true);
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_MAC_WOL);
} else {
phylink_start(pl);
}
}
The WoL branch touches only the MAC side, so phy_start() and the
PHY_UP -> _phy_start_aneg() path that reprograms MII_ADVERTISE never
run. Does the PHY then come back with reset-default advertisement and
EEE settings while phylib's cached state still claims the configured
values? For AX88279 the vendor 2.5G advertisement bit programmed by
asix_ax88279_config_aneg() in drivers/net/phy/ax88796b.c would be lost,
capping the link at 1G.
> +
> + ax88179a_reset(dev);
[Severity: Medium]
ax88179a_reset() unconditionally rewrites AX_RX_CTL to the reset
default:
ax179_data->rxctl = AX_RX_CTL_DROPCRCERR | AX_RX_CTL_START |
AX_RX_CTL_AP | AX_RX_CTL_AMALL | AX_RX_CTL_AB;
That drops AX_RX_CTL_PRO and does not re-apply the multicast hash table
programmed by ax88179_set_multicast(). Neither ax88179a_resume() nor
usbnet_resume() re-invokes ndo_set_rx_mode.
Does a promiscuous interface (bridge port, macvlan, packet capture) stop
receiving foreign-destination frames after every resume, while
dev->flags still reports promiscuity? Should the resume path replay
ax88179_set_multicast()?
[Severity: Low]
Should the reset be skipped, or the receiver left off, when the netdev
is down? ax88179a_stop() clears AX_MEDIUM_RECEIVE_EN, writes
AX_RX_CTL = 0 and calls phylink_stop(), but a resume with the interface
down re-arms AX_RX_CTL_START and AX_MEDIUM_RECEIVE_EN here while
usbnet_resume() does not resubmit RX URBs because EVENT_DEV_OPEN is
clear. Does that leave the MAC receiving into its bulk-in FIFO with no
host consumer and no software owner?
> +
> + if (netif_running(dev->net)) {
> + rtnl_lock();
> + phylink_resume(ax179_data->phylink);
> + rtnl_unlock();
> + }
> +
> + ax88179_set_pm_mode(dev, false);
> +
> + return usbnet_resume(intf);
> +}
> +
[ ... ]
> @@ -1040,7 +1129,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]
This isn't a bug, but does the comment match the code? When the netdev
is down, phylink is not started in resume context either, because
ax88179a_resume() only calls phylink_resume() under
if (netif_running(dev->net)). The gate itself behaves correctly in both
cases, but the stated rationale would mislead a later reader into
removing it.
Worth noting too that the gate reuses in_pm, whose only prior meaning
(ax88179_in_pm() in ax88179_lib.c) is selecting the *_nopm transfer
variants.
> + 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/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo
2026-09-14 12:46 ` [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
2026-09-14 14:16 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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) exports a four-component…
- [Low] Failed bootloader register reads are exported to userspace as a
genuine firmware version.
--
ax88179_178a: Add ethtool get_drvinfo
This adds ax88179a_get_drvinfo() as the get_drvinfo implementation for
ax88179a_ethtool_ops. It calls usbnet_get_drvinfo() first and then, for
chips at least as new as AX_VERSION_AX88179A, overwrites
info->fw_version with the four bytes cached in priv->fw_version.
> diff --git a/drivers/net/usb/ax88179a_devices.c b/drivers/net/usb/ax88179a_devices.c
> index 27836c72ec14a..817d542c4cc08 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;
> +
[Severity: Low]
Should a failed bootloader read be reported as a real firmware revision
here?
In ax88179a_bind(), a failing ax88179_read_cmd() on AX88179A_ACCESS_BL
stores a sentinel and binding continues:
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;
}
No validity flag is kept, and ax88179a_get_drvinfo() only gates on
priv->chip_version before overwriting the string that
usbnet_get_drvinfo() filled from dev->driver_info->description:
strscpy(info->fw_version, dev->driver_info->description,
sizeof(info->fw_version));
So after a transient control-transfer error on registers 0xFD..0xFF,
ethtool -i prints firmware "255.255.255.0" rather than falling back to
the description string. Would tracking a "firmware version unknown"
state in ax88179a_bind() and skipping the snprintf() in that case be
preferable?
> + 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]
Where does the fourth component of this version string come from?
The only producer of priv->fw_version[] is ax88179a_bind(), and its loop
only reads three bootloader registers:
for (int i = 0; i < 3; i++) {
ret = ax88179_read_cmd(dev, AX88179A_ACCESS_BL, (0xFD + i),
1, 1, &ax179_data->fw_version[i]);
struct ax88179_data is allocated with kzalloc_obj(*ax179_data) in the
same function, and u8 fw_version[4] in ax88179_lib.h has no other writer
in drivers/net/usb/, so fw_version[3] stays 0 forever.
That makes ethtool -i always report a trailing ".0" that the device never
supplied. Is the read loop short by one register, or should the format
string be "%d.%d.%d"?
> 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/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information
2026-09-14 12:46 ` [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
1 sibling, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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 patch renames the user-visible Kconfig symbol
USB_NET_AX88179_178A to USB_NET_AX88179 (drivers/net/usb/Kconfig:198,…
- [Low] The changelog states the patch adds 'further requirements such as
PHYLINK', but `select PHYLINK` is not added by this patch: it appears…
- [Low] Changing usb_driver.name from "ax88179_178a" to "ax88179" in
drivers/net/usb/ax88179_178a.c also renames the userspace-visible…
- [Low] The updated user-facing metadata classifies the whole family as
Gigabit Ethernet while the same patch documents two non-Gigabit parts.
--
ax88179_178a: Update driver name and information
This patch renames the Kconfig symbol for the ax88179 driver, extends the
Kconfig prompt and help text with the newly supported ASIX parts, and
renames the usb_driver structure, its .name string and MODULE_DESCRIPTION.
A few questions below about the symbol rename and the wording.
> 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"
[Severity: Medium]
Does renaming USB_NET_AX88179_178A to USB_NET_AX88179 silently flip the
driver on for the two in-tree defconfigs that still spell the old name?
Both arch/loongarch/configs/loongson32_defconfig and
arch/loongarch/configs/loongson64_defconfig still contain:
CONFIG_USB_USBNET=m
# CONFIG_USB_NET_AX8817X is not set
# CONFIG_USB_NET_AX88179_178A is not set
Kconfig has no alias for a renamed symbol, so that line now refers to an
unknown symbol and is dropped. With USB_USBNET=m and the new symbol
keeping "default y", USB_NET_AX88179 resolves to m on those platforms, so
drivers/net/usb/Makefile builds ax88179.o and the select lines below
force-enable PHYLINK (and thus PHYLIB) plus AX88796B_PHY where the
platform configs had explicitly disabled the driver.
The same loss of the stored value happens for any existing .config run
through make oldconfig or make olddefconfig.
Would it make sense to rename the two defconfig entries in this patch,
and to mention the symbol rename in the changelog? The changelog
currently only speaks of the Kconfig description, the usb_driver name and
MODULE_DESCRIPTION.
[Severity: Low]
The prompt still says "to Gigabit Ethernet" while the help text below
gains AX88279 and AX88772D/E. In drivers/net/usb/ax88179a_devices.c those
same parts are described as:
.description = "ASIX AX88772D/E USB 2.0 Fast Ethernet",
.description = "ASIX AX88279 USB 3.2 2.5Gigabit Ethernet",
so the family now spans 100 Mbit, 1 Gbit and 2.5 Gbit. Should the prompt,
the help text ("to Gigabit Ethernet adapters") and MODULE_DESCRIPTION use
speed-neutral wording, or list Fast/Gigabit/2.5-Gigabit explicitly?
> depends on USB_USBNET
> - select CRC32
> select PHYLINK
> + select AX88796B_PHY
> + select CRC32
[Severity: Low]
The changelog says the patch adds "further requirements such as PHYLINK".
Is that accurate? "select PHYLINK" appears here as an unchanged context
line; it was added by an earlier patch in the series (the pre-series tree
has "select PHYLIB"). The requirement this patch actually adds is "select
AX88796B_PHY", which force-enables the ASIX PHY driver for every config
that enables USB_NET_AX88179. Could the changelog name that one instead?
> 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 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 44a768c5808ac..1d35ef90eceb7 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",
[Severity: Low]
Is the userspace-visible side of this rename intentional? The .name
string is what shows up as /sys/bus/usb/drivers/ax88179_178a/ with its
bind, unbind and new_id attributes, so scripts and udev rules using that
path would need updating.
It is also reported by ethtool -i, via:
drivers/net/usb/usbnet.c:usbnet_probe() {
...
dev->driver_name = name;
...
}
drivers/net/usb/usbnet.c:usbnet_get_drvinfo() {
...
strscpy(info->driver, dev->driver_name, sizeof(info->driver));
...
}
and ax88179a_get_drvinfo() in drivers/net/usb/ax88179a_devices.c calls
usbnet_get_drvinfo() and only overwrites fw_version, so info->driver now
reads "ax88179". Could the changelog state this rename is userspace
visible, and that the patch is not a stable backport candidate?
> .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");
The Gigabit wording question above applies to this modinfo string as
well, since AX88772D/E is a Fast Ethernet part and AX88279 is 2.5 Gbit.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
2026-09-14 12:46 ` [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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: 3 · Low: 1
- [Medium] Undisclosed behaviour change to the already-supported
AX88179/AX88178A devices: ax88179_bind()…
- [Medium] The EEPROM access method is selected by three different,
mutually inconsistent predicates over the same unvalidated device-…
- [Medium] For the AX88179A/AX88772D efuse path the new
ax88179_eeprom_access_params() (drivers/net/usb/ax88179_lib.c:210-212)…
- [Low] Dead store on the new error path of ax88179_bind(): after
`kfree(ax179_data);` the code clears the local variable (`ax179_data =…
--
ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
This adds ethtool EEPROM read support for the AX88179A/772D efuses and the
AX88279 flash by generalising ax88179_get_eeprom() over a per-chip block
size and address encoding. ax88179_bind() now samples the chip version and
initialises the new eeprom_* fields, and ax88179_set_eeprom() refuses
writes on the newer parts.
A few questions below.
> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 1d35ef90eceb7..55564aa0ca63f 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;
[Severity: Medium]
Is the behaviour change for the already-supported parts intentional? Before
this patch ax88179_bind() had no register read that could abort the probe;
now a single failing control transfer for AX_CHIP_STATUS makes bind return
an error for every driver_info entry using ax88179_bind() (AX88179,
AX88178A, Cypress GX3, D-Link, Sitecom, Samsung, Lenovo, Belkin, Toshiba,
MCT, AT-UMC*), and usbnet_probe() then goes to out1 -> free_netdev(), so no
netdev is created at all.
The commit message only says:
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.
Could the message mention that the legacy probe path now depends on this
read succeeding, and that ethtool -E on the legacy parts becomes conditional
on the device-reported version nibble (see the new gate in
ax88179_set_eeprom() below)?
> +
> + 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;
^^^^^^^^^^^^^^^^^^
[Severity: Low]
Does this assignment do anything? ax179_data is a local that goes out of
scope immediately, while dev->driver_priv was published earlier in
ax88179_bind():
dev->driver_priv = ax179_data;
so dev->driver_priv keeps pointing at the freed allocation. Was
dev->driver_priv = NULL; intended here, or can the store simply be dropped?
There is no use-after-free today: usbnet_probe() jumps to out1 on bind
failure, which skips info->unbind and frees the netdev (and therefore the
private area) right away.
> +
> + 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 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]
Can the three predicates over chip_version disagree? The address encoding
is chosen here with range tests, but the command and block size are chosen
elsewhere with different tests.
ax88179a_bind() in ax88179a_devices.c uses an exact match:
if (ax179_data->chip_version == AX_VERSION_AX88279) {
ax179_data->eeprom_read_cmd = AX88179A_FLASH_READ;
ax179_data->eeprom_block = 256;
} else {
ax179_data->eeprom_read_cmd = AX_ACCESS_EFUS;
ax179_data->eeprom_block = 20;
}
ax88179a_get_eeprom_len() uses a range test:
if (ax179_data->chip_version >= AX_VERSION_AX88279)
return AX88279_EEPROM_LEN;
else
return AX88179A_EEPROM_LEN;
and the new code in ax88179_bind() hardcodes AX_ACCESS_EEPROM with
eeprom_block = 2 while still storing whatever nibble the chip reported.
The nibble from AX_CHIP_STATUS is not validated anywhere, so for a value
above AX_VERSION_AX88279 the helper picks the split flash addressing while
bind configured AX_ACCESS_EFUS with 20-byte transfers, and
get_eeprom_len() advertises 16 KiB. Symmetrically, a legacy device
reporting a nibble >= AX_VERSION_AX88179A gets AX_ACCESS_EEPROM issued with
efuse or flash addressing at a 2-byte length, and ethtool -E turns into
-EOPNOTSUPP. Would it be better to derive the addressing from
eeprom_read_cmd/eeprom_block, or to reject unknown version nibbles at bind
time?
[Severity: Medium]
For the efuse branch, are wValue and wIndex right? The comment in this same
function says the efuses are 20 bytes long, ax88179a_bind() sets
eeprom_block = 20, AX88179A_EEPROM_LEN is (32 * 20) = 640, and
ax88179_get_eeprom() below derives first/last, the per-row buffer offset and
the transfer length from eeprom_block. But the address here advances by 16
per row:
*value = i << 4;
*idx = 0;
The existing AX_ACCESS_EFUS user in this driver family passes a byte offset
in wValue and the access length in wIndex:
drivers/net/usb/ax88179_178a.c:ax88179_check_efuse() {
...
if (ax88179_read_cmd(dev, AX_ACCESS_EFUS, 0, 64, 64, efuse) < 0)
return -EINVAL;
...
}
Under that convention consecutive 20-byte reads spaced 16 bytes apart
overlap by 4 bytes per row and can only reach bytes 0..515 of the 640-byte
range ethtool is told about, and wIndex = 0 asks for a zero-length access
while 20 bytes are requested on the wire.
Does the failure stay silent? __usbnet_read_cmd() copies only the bytes the
device actually returned and reports a positive value, and
ax88179_get_eeprom() only tests ret < 0, so a short or rejected efuse access
is handed to userspace as a successful read of the kzalloc'ed zeros.
Has the AX88179A redefined AX_ACCESS_EFUS so that wValue is a shifted row
index and wIndex is unused? If so a comment would help, otherwise the
stride and the length field look inconsistent with eeprom_block = 20.
> +
> 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;
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279
2026-09-14 12:46 ` [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
@ 2026-09-16 6:47 ` netdev-bot+sashiko
0 siblings, 0 replies; 42+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 6:47 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: 1 · Medium: 5 · Low: 0
- [High] asix_ax88279_config_aneg() (drivers/net/phy/ax88796b.c:146-166)
programs the vendor 2.5G advertisement bit (MII_ADVERTISE bit 12,…
- [Medium] asix_ax88279_get_features() (ax88796b.c:168-186) clears only
ETHTOOL_LINK_MODE_100baseT_Half_BIT after…
- [Medium] asix_ax88279_read_status() (ax88796b.c:108) uses the return
value of `phy_read(phydev, AX_CTRL_STATUS)` without checking for a…
- [Medium] asix_ax88772D_get_features() (ax88796b.c:207-223) removes only
ETHTOOL_LINK_MODE_1000baseT_Full_BIT from the abilities produced by…
- [Medium] asix_ax88279_read_status() (ax88796b.c:99-144) has no `if
(!phydev->link) return 0;` guard after genphy_read_status(): it…
- [Medium] In asix_ax88279_read_status() (ax88796b.c:128-133) the link-
partner 2500baseT advertisement bit is only ever assigned inside the…
--
ax88796b: Add support for AX88772D, AX88179A and AX88279
The patch adds phy_driver entries and mdio device ids for the AX88772D,
AX88179A and AX88279 PHYs found in ASIX USB-Ethernet controllers, with
custom get_features, read_status, config_aneg and config_init callbacks
for the 2.5G part.
A few questions about the new 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]
Should there be a link check here before the vendor decoding, the way
asix_ax88772a_read_status() does it?
drivers/net/phy/ax88796b.c:asix_ax88772a_read_status() {
ret = genphy_update_link(phydev);
if (ret)
return ret;
if (!phydev->link)
return 0;
...
}
When the link is down (or autoneg has not completed) genphy_read_status()
deliberately publishes an unknown state:
drivers/net/phy/phy_device.c:genphy_read_status() {
...
phydev->speed = SPEED_UNKNOWN;
phydev->duplex = DUPLEX_UNKNOWN;
...
}
asix_ax88279_read_status() then overwrites both, plus phydev->interface,
from the vendor register. With no link AX_CTRL_STATUS reads 0, which
decodes to AX_CTRL_STATUS_SPEED_10, so does ethtool end up reporting
"10Mb/s, Full" on an unplugged port instead of Unknown?
> + /* 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]
Is the phy_read() return value missing an error check here?
These PHYs are internal to USB Ethernet controllers, so every MDIO access
is a USB control transfer that can fail with -EIO, -ETIMEDOUT or -ENODEV
on surprise removal. The errno is then masked as if it were register
content, and since the switch covers all four values of the two-bit field
with no default, every error decodes to a concrete speed:
(-EIO) & 0x0c == 0x08 -> AX_CTRL_STATUS_SPEED_1000 -> SPEED_1000
(-ETIMEDOUT) & 0x0c == 0x00 -> AX_CTRL_STATUS_SPEED_10 -> SPEED_10
The fabricated speed then selects phydev->interface further down, so the
MAC/PCS can be configured for 2500BASEX or SGMII based on an errno, and
the function still returns 0. The MMD read a few lines below does check,
and asix_ax88772a_read_status() in the same file does:
val = phy_read(phydev, MII_BMCR);
if (val < 0)
return val;
> +
> + /* 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 the 2500baseT bit in phydev->lp_advertising go stale here?
The bit is only ever assigned inside this branch. If the MMD read fails,
or the new partner reports REMOK == 0, the previous value survives.
genphy_read_lpa() does not clear it either, it only touches the clause-22
partner modes:
drivers/net/phy/phy_device.c:genphy_read_lpa() {
if (!phydev->autoneg_complete) {
mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
return 0;
}
...
}
So after a 2.5G partner has linked up once and the cable is then moved to
a 1G partner, does ethtool keep listing 2500baseT/Full under "Link partner
advertised link modes"? Would clearing the bit whenever the current read
does not positively establish 2.5G partner advertisement be better? The
phy_read_mmd() error is also swallowed rather than propagated.
> + /* 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;
> +}
[Severity: High]
Is the 2.5G advertisement bit ever taken into an auto-negotiation session
here? genphy_config_aneg() takes the restart decision before bit 12 of
MII_ADVERTISE is written.
Toggling ETHTOOL_LINK_MODE_2500baseT_Full_BIT does not change any register
that genphy_config_advert() writes, its MII_ADVERTISE mask does not contain
AX_ADVERTISE_2500 (0x1000):
drivers/net/phy/phy_device.c:genphy_config_advert() {
err = phy_modify_changed(phydev, MII_ADVERTISE,
ADVERTISE_ALL | ADVERTISE_100BASE4 |
ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
adv);
...
}
so changed stays 0 and no restart is issued for a running, non-isolated
PHY:
drivers/net/phy/phy_device.c:genphy_check_and_restart_aneg() {
if (!restart) {
ret = phy_read(phydev, MII_BMCR);
...
if (!(ret & BMCR_ANENABLE) || (ret & BMCR_ISOLATE))
restart = true;
}
if (restart)
return genphy_restart_aneg(phydev);
return 0;
}
phy_start_aneg() only calls phy_check_link_status() afterwards, and
phy_modify() discards the changed indication, so the new bit 12 value sits
in the register without BMCR_ANRESTART. In the opposite case, where
something else did set changed, the restart happens while bit 12 still
holds its old value, so does the session negotiate the stale 2.5G
capability?
Would writing bit 12 before genphy_config_aneg(), or using
phy_modify_changed() plus an explicit genphy_restart_aneg(), fix both
directions?
> +
> +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;
> +}
[Severity: Medium]
Should 10baseT_Half be cleared here too?
genphy_c45_pma_read_abilities() reaches the ext-abilities path (which is
what makes 100baseT_Half appear in the first place), and there the two
10 Mbit modes come from the same EXTABLE bit:
drivers/net/phy/phy-c45.c:genphy_c45_pma_read_ext_abilities() {
linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
phydev->supported,
val & MDIO_PMA_EXTABLE_10BT);
linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
phydev->supported,
val & MDIO_PMA_EXTABLE_10BT);
...
}
So 10baseT half duplex stays in phydev->supported and hence in
phydev->advertising and MII_ADVERTISE, while asix_ax88279_read_status()
states the opposite and forces the result:
/* Only supports full duplex */
phydev->duplex = DUPLEX_FULL;
If a partner resolves 10baseT half duplex, the resolved DUPLEX_HALF is
replaced by DUPLEX_FULL and the MAC is configured for full duplex. If the
PHY really cannot do half duplex, should the mode be dropped from
supported instead? Which of the two is correct for this part?
> +
> +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;
> +}
[Severity: Medium]
Should the other gigabit modes be masked out as well on this 100 Mbit
part? genphy_read_abilities() derives three modes from MII_ESTATUS:
drivers/net/phy/phy_device.c:genphy_read_abilities() {
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 1000baseT_Full is removed, so if the AX88772D also reports
ESTATUS_1000_THALF or ESTATUS_1000_XFULL, those modes remain in
phydev->supported and phydev->advertising and are written to MII_CTRL1000
by genphy_config_advert(). Are those two bits known to be clear on this
part, or would masking the whole gigabit class be safer?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260914-ax88179a-v11-0-5ea7a925ba6d%40birger-koblitz.de
^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2026-09-16 6:47 UTC | newest]
Thread overview: 42+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-14 12:46 [PATCH net-next v11 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-09-14 14:13 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
2026-09-14 14:14 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
2026-09-14 12:46 ` [PATCH net-next v11 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
2026-09-14 14:15 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
2026-09-14 14:12 ` Nicolai Buchwitz
2026-09-14 16:53 ` Andrew Lunn
2026-09-15 0:01 ` Birger Koblitz
2026-09-15 12:07 ` Andrew Lunn
2026-09-16 0:12 ` Birger Koblitz
2026-09-15 5:28 ` Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
2026-09-14 14:16 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 13/15] ax88179_178a: Update driver name and information Birger Koblitz
2026-09-14 14:17 ` Nicolai Buchwitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-09-16 6:47 ` netdev-bot+sashiko
2026-09-14 12:46 ` [PATCH net-next v11 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
2026-09-16 6:47 ` 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®