All of lore.kernel.org
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: mail@birger-koblitz.de
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux@armlinux.org.uk,
	andrew@lunn.ch, hkallweit1@gmail.com, linux-usb@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	neuromoments@gmail.com
Subject: Re: [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips
Date: Thu, 17 Sep 2026 21:24:56 +0000	[thread overview]
Message-ID: <178968029668.22033.6092490845837885048@kernel.org> (raw)
In-Reply-To: <20260916-ax88179a-v12-6-60c04c9924a2@birger-koblitz.de>

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

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

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

--

ax88179_178a: Add HW support for AX179A-based chips

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

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

> diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile
> index ddd76fa71e2ee..2ecead0181eaf 100644
> --- a/drivers/net/usb/Makefile
> +++ b/drivers/net/usb/Makefile
> @@ -13,7 +13,7 @@ obj-$(CONFIG_USB_LAN78XX)	+= lan78xx.o
>  obj-$(CONFIG_USB_NET_AX8817X)	+= asix.o
>  asix-y := asix_devices.o asix_common.o ax88172a.o
>  obj-$(CONFIG_USB_NET_AX88179_178A)      += ax88179.o
> -ax88179-y := ax88179_178a.o ax88179_lib.o
> +ax88179-y := ax88179_178a.o ax88179a_devices.o ax88179_lib.o

[Severity: Low]
The commit message says:

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

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

> diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
> index 6aa35c8eb594f..a2c596305f798 100644
> --- a/drivers/net/usb/ax88179_178a.c
> +++ b/drivers/net/usb/ax88179_178a.c
> @@ -1270,6 +1270,18 @@ static const struct driver_info at_umc2000sp_info = {
>  
>  static const struct usb_device_id products[] = {
>  {
> +	/* ASIX AX88179A USB 3.2 1000Mbit Ethernet */
> +	USB_DEVICE_VER(0x0b95, 0x1790, 0x0200, 0x0200),
> +	.driver_info = (unsigned long)&ax88179a_info,
> +}, {
> +	/* ASIX AX88772D USB 2.0 100Mbit Ethernet */
> +	USB_DEVICE_VER(0x0b95, 0x1790, 0x0300, 0x0300),
> +	.driver_info = (unsigned long)&ax88772d_info,
> +}, {
> +	/* ASIX AX88279 USB 3.2 2500Mbit Ethernet */
> +	USB_DEVICE_VER(0x0b95, 0x1790, 0x0400, 0x0400),
> +	.driver_info = (unsigned long)&ax88279_info,
> +}, {

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

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

	mii_nway_restart(&dev->mii);

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

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

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

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

[ ... ]

> +static const struct ax_bulkin_settings AX88179A_BULKIN_SIZE[] = {
> +	[BULK_IN_SPEED_1G_SS]		= {5, 0x7B, 0x00, 0x17, 0x0F},
> +	[BULK_IN_SPEED_1G_HS]		= {5, 0xC0, 0x02, 0x06, 0x0F},
> +	[BULK_IN_SPEED_100_FULL_SS]	= {7, 0xF0, 0x00, 0x0C, 0x0F},
> +	[BULK_IN_SPEED_100_HALF_SS]	= {6, 0x00, 0x00, 0x06, 0x0F},
> +	[BULK_IN_SPEED_100_FULL_HS]	= {5, 0xC0, 0x04, 0x06, 0x0F},
> +	[BULK_IN_SPEED_100_HALF_HS]	= {7, 0xC0, 0x04, 0x06, 0x0F},
> +	[BULK_IN_SPEED_FS]		= {7, 0x00, 0x00, 0x03, 0x3F},
> +};
> +
> +static const struct ax_bulkin_settings AX88772D_BULKIN_SIZE[] = {
> +	[BULK_IN_SPEED_100_FULL_HS]	= {5, 0xC0, 0x04, 0x06, 0x0F},
> +	[BULK_IN_SPEED_100_HALF_HS]	= {7, 0xC0, 0x04, 0x06, 0x0F},
> +	[BULK_IN_SPEED_FS]		= {7, 0x00, 0x00, 0x03, 0x3F},
> +};

[ ... ]

> +static int ax88179_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum)
> +{

[ ... ]

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

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

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

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

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

[ ... ]

> +static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts, u8 speed, bool full_duplex)
> +{
> +	struct ax88179_data *ax179_data = dev->driver_priv;
> +	const struct ax_bulkin_settings *bulkin_data;
> +	int index = 0;
> +
> +	switch (speed) {

[ ... ]

> +	case ETHER_LINK_1000:	/* AX88279 & AX88178A */
> +		if (link_sts & AX_USB_SS)
> +			index = BULK_IN_SPEED_1G_SS;
> +		else if (link_sts & AX_USB_HS)
> +			index = BULK_IN_SPEED_1G_HS;
> +		break;
> +
> +	case ETHER_LINK_100:
> +		if (link_sts & AX_USB_SS)
> +			index = BULK_IN_SPEED_100_FULL_SS;
> +		else if (link_sts & AX_USB_HS)
> +			index = BULK_IN_SPEED_100_FULL_HS;
> +		if (!full_duplex)
> +			index++;
> +		break;

[ ... ]

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

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

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

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

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

[ ... ]

> +static int ax88179a_get_eeprom_len(struct net_device *net)
> +{
> +	struct ax88179_data *ax179_data = netdev2data(net);
> +
> +	if (ax179_data->chip_version >= AX_VERSION_AX88279)
> +		return AX88279_EEPROM_LEN;
> +	else
> +		return AX88179A_EEPROM_LEN;
> +}
> +
> +static const struct ethtool_ops ax88179a_ethtool_ops = {
> +	.get_link		= ethtool_op_get_link,
> +	.get_msglevel		= usbnet_get_msglevel,
> +	.set_msglevel		= usbnet_set_msglevel,
> +	.get_wol		= ax88179_get_wol,
> +	.set_wol		= ax88179_set_wol,
> +	.get_eeprom_len		= ax88179a_get_eeprom_len,
> +	.get_eeprom		= ax88179_get_eeprom,
> +	.set_eeprom		= ax88179_set_eeprom,

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

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

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

> +	.nway_reset		= usbnet_nway_reset,

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

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

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

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

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

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

[ ... ]

> +static void ax88179a_mac_link_up(struct phylink_config *config,
> +				 struct phy_device *phy,
> +				 unsigned int phy_mode, phy_interface_t interface,
> +				 int speed, int duplex,
> +				 bool tx_pause, bool rx_pause)
> +{
> +	struct usbnet *dev = netdev_priv(to_net_dev(config->dev));
> +	struct ax88179_data *ax179_data = dev->driver_priv;
> +	u8 tmp8, link_sts, reg8[3];

[ ... ]

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

[ ... ]

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

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

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

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

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

> +
> +		tmp8 = AX_XGMII_EN;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
> +
> +		tmp8 = 0x1C | AX_LSO_ENHANCE_EN;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_MAC_LSO_ENHANCE_CTRL, 1, 1, &tmp8);
> +
> +		mode |= AX_MEDIUM_GIGAMODE | AX_MEDIUM_FULL_DUPLEX;
> +		bulk_config_speed = ETHER_LINK_2500;
> +
> +		break;
> +
> +	case SPEED_1000:
> +		mode |= AX_MEDIUM_GIGAMODE;
> +		bulk_config_speed = ETHER_LINK_1000;
> +		fallthrough;
> +
> +	case SPEED_100:

[ ... ]

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

[ ... ]

> +	ax88179_read_cmd(dev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, 1, 1, &link_sts);
> +	ax88179a_bulkin_config(dev, link_sts, bulk_config_speed, !!duplex);
> +
> +	if (ax179_data->chip_version < AX_VERSION_AX88279) {
> +		tmp8 = 0;
> +		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
> +	}

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

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

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

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

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

drivers/net/usb/ax88179_lib.c:ax88179_change_mtu() {
	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16);
	tmp16 |= AX_MEDIUM_JUMBO_EN;
	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE, 2, 2, &tmp16);
	...
}

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

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

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

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

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

[ ... ]

> +static const struct net_device_ops ax88179a_netdev_ops = {
> +	.ndo_open		= usbnet_open,
> +	.ndo_stop		= usbnet_stop,
> +	.ndo_start_xmit		= usbnet_start_xmit,
> +	.ndo_tx_timeout		= usbnet_tx_timeout,
> +	.ndo_get_stats64	= dev_get_tstats64,
> +	.ndo_change_mtu		= ax88179_change_mtu,
> +	.ndo_set_mac_address	= ax88179_set_mac_addr,
> +	.ndo_validate_addr	= eth_validate_addr,
> +	.ndo_eth_ioctl		= ax88179a_mii_ioctl,
> +};

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

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

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

[ ... ]

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

[ ... ]

> +	if (!ax179_data->is_ax88772d)
> +		dev->mii.supports_gmii = 1;
> +
> +	dev->net->features |= NETIF_F_SG | NETIF_F_IP_CSUM |
> +			      NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO |
> +			      NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX |
> +			      NETIF_F_HW_VLAN_CTAG_FILTER;
> +
> +	dev->net->hw_features |= dev->net->features;

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

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

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

[ ... ]

> +	/* Power PHY for probing */
> +	reg8 = AX_PHY_POWER;
> +	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
> +	msleep(250);
> +
> +	ret = ax88179a_init_mdio(dev);
> +	if (ret)
> +		goto err_nodev;
> +
> +	ret = ax88179a_phylink_setup(dev);
> +	if (ret)
> +		goto phylink_err;
> +
> +	ret = ax88179a_init_phy(dev);
> +	if (ret)
> +		goto initphy_err;
> +
> +	return 0;
> +
> +initphy_err:
> +	phylink_destroy(ax179_data->phylink);
> +phylink_err:
> +	ax88179a_mdio_unregister(ax179_data);
> +err_nodev:
> +	kfree(ax179_data);
> +	ax179_data = NULL;
> +
> +	return ret;
> +}

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

[ ... ]

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

[ ... ]

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

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

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

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

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

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

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

[ ... ]

> +		if (pkt_desc & AX179A_RX_PD_DROP || !(pkt_desc & AX179A_RX_PD_RX_OK) ||
> +		    pkt_len > (dev->hard_mtu + AX179A_RX_HW_PAD)) {
> +			skb_pull(skb, pkt_len_plus_padd);
> +
> +			/* Next RX Packet Descriptor */
> +			pkt_desc_ptr++;
> +			continue;
> +		}
> +
> +		ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len);
> +		if (!ax_skb)
> +			goto err;
> +
> +		skb_put(ax_skb, pkt_len);
> +		memcpy(ax_skb->data, skb->data + (ax179_data->ip_align ? AX179A_RX_HW_PAD : 0),
> +		       pkt_len);

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

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

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

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

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

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

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

[ ... ]

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

[ ... ]

> +	if (!dev->can_dma_sg && (dev->net->features & NETIF_F_SG) && skb_linearize(skb)) {
> +		dev_kfree_skb_any(skb);
> +		return NULL;
> +	}
> +
> +	headroom = skb_headroom(skb);
> +	tailroom = skb_tailroom(skb);
> +
> +	if (!(headroom >= sizeof(tx_desc) && tailroom >= padding_size)) {
> +		ax_skb = skb_copy_expand(skb, sizeof(tx_desc), padding_size, flags);
> +		dev_kfree_skb_any(skb);
> +		skb = ax_skb;
> +		if (!skb)
> +			return NULL;
> +	}
> +	if (padding_size != 0)
> +		skb_put_zero(skb, padding_size);
> +	/* Copy TX header */
> +	tx_desc_ptr = skb_push(skb, sizeof(tx_desc));
> +	put_unaligned_le64(tx_desc, tx_desc_ptr);

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

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

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

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

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

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

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

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

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

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

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

[ ... ]

> +	/* Check if WoL is supported */
> +	ax179_data->wol_supported = 0;
> +	if (ax88179_read_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD,
> +			     1, 1, &tmp) > 0)
> +		ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY;

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

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

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

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

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

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

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

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

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

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

> +const struct driver_info ax88179a_info = {
> +	.description = "ASIX AX88179A USB 3.2 Gigabit Ethernet",
> +	.bind = ax88179a_bind,
> +	.unbind = ax88179a_unbind,
> +	.status = ax88179a_status,
> +	.reset = ax88179a_reset,
> +	.stop = ax88179a_stop,
> +	.flags = FLAG_ETHER | FLAG_FRAMING_AX | FLAG_MULTI_PACKET | FLAG_AVOID_UNLINK_URBS,
> +	.rx_fixup = ax88179a_rx_fixup,
> +	.tx_fixup = ax88179a_tx_fixup,
> +};

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

[ ... ]

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

  reply	other threads:[~2026-09-17 21:24 UTC|newest]

Thread overview: 63+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16  3:23 [PATCH net-next v12 00/15] ax88179_178a: Add support for AX88179A-based chips Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 01/15] phylink: Add phylink_mac_interrupt Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:03     ` Birger Koblitz
2026-09-27 14:18       ` Andrew Lunn
2026-09-28  4:48         ` Birger Koblitz
2026-09-28 15:27           ` Andrew Lunn
2026-09-16  3:24 ` [PATCH net-next v12 02/15] phylib: Add support for PHYs with broken forced mode Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:04     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 03/15] ax88179_178a: Fix endianness of pause watermark register Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:05     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 04/15] ax88179_178a: Split driver into library and device specific code Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-19 22:04     ` Jakub Kicinski
2026-09-27  9:01       ` Birger Koblitz
2026-09-27  9:08     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 05/15] ax88179_178a: Add netdev2data() convenience function Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:09     ` Birger Koblitz
2026-09-19 22:05   ` Jakub Kicinski
2026-09-27  8:17     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 06/15] ax88179_178a: Add HW support for AX179A-based chips Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko [this message]
2026-09-19 22:15     ` Jakub Kicinski
2026-09-19 23:22       ` Birger Koblitz
2026-09-19 23:32         ` Jakub Kicinski
2026-09-27  9:18     ` Birger Koblitz
2026-09-19 22:12   ` Jakub Kicinski
2026-09-27  8:20     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 07/15] ax88179_178a: Add EEE configuration support for AX88179A MACs Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:20     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 08/15] ax88179_178a: Add EEE configuration support for AX88179A PHYs Birger Koblitz
2026-09-17 21:24   ` netdev-bot+sashiko
2026-09-27  9:21     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 09/15] ax88179_178a: Add VLAN offload support for AX88179A Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:24     ` Birger Koblitz
2026-09-27 14:24       ` Andrew Lunn
2026-09-28  4:41         ` Birger Koblitz
2026-09-28 12:28           ` Andrew Lunn
2026-09-16  3:24 ` [PATCH net-next v12 10/15] ax88179_178a: Add AX179A/AX279 multicast configuration Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:25     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 11/15] ax88179_178a: Add Suspend/resume support for AX88179A/772D/279 Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:36     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 12/15] ax88179_178a: Add ethtool get_drvinfo Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:37     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 13/15] ax88179_178a: Update driver name and information Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:38     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 14/15] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:40     ` Birger Koblitz
2026-09-16  3:24 ` [PATCH net-next v12 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279 Birger Koblitz
2026-09-17 21:25   ` netdev-bot+sashiko
2026-09-27  9:42     ` Birger Koblitz
2026-09-19  5:28 ` [PATCH v12 0/15] ax88179_178a: Add support for AX88179A-based chips Jianhui Xu
2026-09-19 22:30 ` [PATCH net-next v12 00/15] " patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178968029668.22033.6092490845837885048@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=andrew@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=hkallweit1@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=mail@birger-koblitz.de \
    --cc=netdev@vger.kernel.org \
    --cc=neuromoments@gmail.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.