Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH 3/3] net: ipa: Add IPA v5.1 data
From: Alex Elder @ 2026-07-08 20:06 UTC (permalink / raw)
  To: esteuwu, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <20260622-sm8450-ipa-v1-3-532f0299f96e@proton.me>

On 6/22/26 8:44 PM, Esteban Urrutia via B4 Relay wrote:
> From: Esteban Urrutia <esteuwu@proton.me>
> 
> Add the required ipa_data-v5.1.c file for IPA v5.1 along with changes
> that declare IPA v5.1 support.
> This version of IPA is used in both SM8450 and SM8475 SoCs.
> 
> Signed-off-by: Esteban Urrutia <esteuwu@proton.me>

OK I'm finally reviewing this.  Thank you again for sharing links to
the resources you used and developed while doing this work.


For the most part this looks entirely correct.  There is one
pair of memory table entries that I think should not be there,
otherwise everything looks just about perfect.

I'm not totally sure that reducing the SMEM size will work
correctly.


I'm taking this opportunity to explain a LOT of things about
IPA and the driver code.  It's much more than what's typical
for a review, but I thought this provided a good chance to
explain some things in context.  You can add it to your notes
file if you like...

> ---
>   drivers/net/ipa/Makefile             |   2 +-
>   drivers/net/ipa/data/ipa_data-v5.1.c | 477 +++++++++++++++++++++++++++++++++++
>   drivers/net/ipa/gsi_reg.c            |   1 +
>   drivers/net/ipa/ipa_data.h           |   1 +
>   drivers/net/ipa/ipa_main.c           |   4 +
>   drivers/net/ipa/ipa_reg.c            |   1 +
>   6 files changed, 485 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/net/ipa/Makefile b/drivers/net/ipa/Makefile
> index e148ec3c1a10..d4995c2e8ca0 100644
> --- a/drivers/net/ipa/Makefile
> +++ b/drivers/net/ipa/Makefile
> @@ -7,7 +7,7 @@ IPA_REG_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.5
>   # Some IPA versions can reuse another set of GSI register definitions.
>   GSI_REG_VERSIONS	:=	3.1 3.5.1 4.0 4.5 4.9 4.11 5.0
>   
> -IPA_DATA_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.2 5.5
> +IPA_DATA_VERSIONS	:=	3.1 3.5.1 4.2 4.5 4.7 4.9 4.11 5.0 5.1 5.2 5.5
>   
>   obj-$(CONFIG_QCOM_IPA)	+=	ipa.o
>   
> diff --git a/drivers/net/ipa/data/ipa_data-v5.1.c b/drivers/net/ipa/data/ipa_data-v5.1.c
> new file mode 100644
> index 000000000000..85b21efa1224
> --- /dev/null
> +++ b/drivers/net/ipa/data/ipa_data-v5.1.c
> @@ -0,0 +1,477 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/* Copyright (C) 2023-2024 Linaro Ltd. */
> +/* Copyright (C) 2026 Esteban Urrutia <esteuwu@proton.me> */
> +
> +#include <linux/array_size.h>
> +#include <linux/log2.h>
> +
> +#include "../ipa_data.h"
> +#include "../ipa_endpoint.h"
> +#include "../ipa_mem.h"
> +#include "../ipa_version.h"
> +
> +/** enum ipa_resource_type - IPA resource types for an SoC having IPA v5.1 */
> +enum ipa_resource_type {
> +	/* Source resource types; first must have value 0 */
> +	IPA_RESOURCE_TYPE_SRC_PKT_CONTEXTS		= 0,
> +	IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_LISTS,
> +	IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_BUFF,
> +	IPA_RESOURCE_TYPE_SRC_HPS_DMARS,
> +	IPA_RESOURCE_TYPE_SRC_ACK_ENTRIES,
> +
> +	/* Destination resource types; first must have value 0 */
> +	IPA_RESOURCE_TYPE_DST_DATA_SECTORS		= 0,
> +	IPA_RESOURCE_TYPE_DST_DPS_DMARS,
> +	IPA_RESOURCE_TYPE_DST_ULSO_SEGMENTS,
> +};

The above looks correct to me.  They come from downstream
"ipa_utils.c", in the ipa3_rsrc_src_grp_config[IPA_5_1][][]
array and the ipa3_rsrc_dst_grp_config[IPA_5_1][][] array.

The *_SRC_* symbols are the index values used in the
ipa_resource_src[] array upstream, and the *_DST_* symbols
are indexes in the upstream ipa_resource_dst[] array.

> +/* Resource groups used for an SoC having IPA v5.1 */
> +enum ipa_rsrc_group_id {
> +	/* Source resource group identifiers */
> +	IPA_RSRC_GROUP_SRC_UL				= 0,
> +	IPA_RSRC_GROUP_SRC_DL,
> +	IPA_RSRC_GROUP_SRC_UNUSED_2,
> +	IPA_RSRC_GROUP_SRC_UNUSED_3,
> +	IPA_RSRC_GROUP_SRC_URLLC,
> +	IPA_RSRC_GROUP_SRC_U_RX_QC,
> +	IPA_RSRC_GROUP_SRC_COUNT,	/* Last in set; not a source group */
> +
> +	/* Destination resource group identifiers */
> +	IPA_RSRC_GROUP_DST_UL				= 0,
> +	IPA_RSRC_GROUP_DST_DL,
> +	IPA_RSRC_GROUP_DST_UNUSED_2,
> +	IPA_RSRC_GROUP_DST_UNUSED_3,
> +	IPA_RSRC_GROUP_DST_UNUSED_4,
> +	IPA_RSRC_GROUP_DST_UC,
> +	IPA_RSRC_GROUP_DST_DRB_IP,
> +	IPA_RSRC_GROUP_DST_COUNT,	/* Last; not a destination group */
> +};

These look correct.  They correspond to the second index values
in the downstream arrays mentioned earlier, and are used as
indexes into the limits[] array within an ipa_resource structure.

As you probably now know, the symbols correspond to these comments
in the downstream code:
                 /* UL  DL  unused  unused  URLLC UC_RX_Q N/A */
                 /* UL  DL  unused  unused unused  UC_RX_Q DRBIP N/A */
> +/* QSB configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_qsb_data ipa_qsb_data[] = {
> +	[IPA_QSB_MASTER_DDR] = {
> +		.max_writes		= 0,
> +		.max_reads		= 0,	/* no limit (hardware max) */
> +		.max_reads_beats	= 0,
> +	},
> +	[IPA_QSB_MASTER_PCIE] = {
> +		.max_writes		= 0,
> +		.max_reads		= 0,	/* no limit (hardware max) */
> +		.max_reads_beats	= 0,
> +	},
> +};

I think the DDR values might be wrong, but it's difficult to be
sure.  In some cases, in arrays like this in the downstream code,
if there is no entry found in an array, the *earlier* version
values should be used.  (Unless someone better informed states
that this is wrong, I think it's fine as-is.)

This information is found in the ipa3_qmb_outstanding[IPA_5_1][]
array in the downstream code.  However there is no entry for that
version.  Given that, all zeroes (as you have it) makes sense.
But it's possible this applies instead:

         [IPA_5_0][IPA_QMB_INSTANCE_DDR]         = {12, 12, 0},
         [IPA_5_0][IPA_QMB_INSTANCE_PCIE]        = {0, 0, 0},

I have no way of knowing; perhaps someone from Qualcomm can
get confirmation that all zeroes is correct.

(Note the order of values presented in the downstream code
differs from upstream.)



Most of the information in the structure below comes from the
ipa3_ep_mapping[IPA_V5_1][] array in the downstream code.
Many of the entries in that array are unused in the upstream
code, because we only use a small subset of the available
endpoints.

> +/* Endpoint configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_gsi_endpoint_data ipa_gsi_endpoint_data[] = {
> +	[IPA_ENDPOINT_AP_COMMAND_TX] = {

IPA_ENDPOINT_AP_COMMAND_TX corresponds to IPA_CLIENT_APPS_CMD_PROD
in the downstream code.  The downstream code doesn't label the
assignments within the ipa3_ep_mapping[][] array, so I think it's
a little harder to understand.  Anyway I'll show how they map
between downstream and upstream below.

The downstream structure is named ipa_ep_configuration.  The
upstream structure is named ipa_gsi_endpoint_data.

struct ipa_ep_configuration {
         bool valid;
         int group_num;
         bool support_flt;
         int sequencer_type;
         u8 qmb_master_sel;
         struct ipa_gsi_ep_config ipa_gsi_ep_info;
         u8 tx_instance;
};

And although ipa_gsi_ep_config is not defined in this code
base, here is what it looks like:

struct ipa_gsi_ep_config {
         int ipa_ep_num;
         int ipa_gsi_chan_num;
         int ipa_if_tlv;
         int ipa_if_aos;
         int ee;
         enum gsi_prefetch_mode prefetch_mode;
         uint8_t prefetch_threshold;
};

This might not be current; I'm using code found here:
   https://git.codelinaro.org/clo/la/kernel/msm-5.15.git


Here is the upstream structure, and I indicate where the
information comes from in the downstream code:

struct ipa_gsi_endpoint_data {
         u8 ee_id;	/* ipa_ep_configuration->ee */
         u8 channel_id;  /* ipa_ep_configuration->ipa_gsi_chan_num */
         u8 endpoint_id;	/* ipa_ep_configuration->ipa_ep_num */
         bool toward_ipa;

         struct gsi_channel_data channel;
         struct ipa_endpoint_data endpoint;
};

And here is the first sub-structure:

struct gsi_channel_data {
         u16 tre_count;	/* Computed based on other code (see below) */
         u16 event_count;
         u8 tlv_count;	/* ipa_ep_configuration->ipa_if_tlv */
};

> +		.ee_id		= GSI_EE_AP,

This is the "execution environment" that the endpoint is
associated with.  For upstream, that's either the AP or
the modem.  The "_AP_" sitting where it does in the
IPA_ENDPOINT_AP_COMMAND_TX endpoint ID also indicates
this is an AP endpoint.  It also matches what's seen
in the downstream ipa_gsi_ep_config->ee field.

> +		.channel_id	= 12,
> +		.endpoint_id	= 14,
> +		.toward_ipa	= true,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 20,

The tre_count number was derived from code in ipa3_setup_apps_pipes()
in downstream "ipa/ipa_v3/ipa.c".  There a ipa_sys_connect_params
structure contains a field desc_fifo_size, which is the size in bytes
of the transfer ring buffer.  The tre_count in upsteram code is in
units of a TRE (transfer ring element), i.e. it's the number of such
entries (that fit in that number of bytes).

The downstream IPA_CLIENT_APPS_CMD_PROD corresponds to upstream
IPA_ENDPOINT_AP_COMMAND_TX (the array entry we're in the middle
of here), and the downstream size is IPA_SYS_DESC_FIFO_SZ, or
0x800=2048 bytes.  Each TRE (struct gsi_tre) is 16 bytes.

In the downstream code--confusingly--ipa_gsi_setup_channel()
doubles the desc_fifo_sz value (for GSI, versus the older BAM
interface).  So the ring size becomes 4096 bytes, and that
works out to 256 16-byte GSI TRE entries.  I'm not sure why
512 is used for IPA v3.5.1, but it probably just means it's
bigger than it needs to be.

The event_count should be the same as the tre_count.  Again
I no longer know why that's not the case for IPA v3.5.1.


> +		},

Below is the second sub-structure in the upstream structure
ipa_gsi_endpoint_data, and the other structures it
incorporates.

struct ipa_endpoint_data {
         bool filter_support;
         struct ipa_endpoint_config config;
};

struct ipa_endpoint_config {
         u32 resource_group;
         bool checksum;
         bool qmap;
         bool aggregation;
         bool status_enable;
         bool dma_mode;
         enum ipa_endpoint_name dma_endpoint;
         union {
                 struct ipa_endpoint_tx tx;
                 struct ipa_endpoint_rx rx;
         };
};

struct ipa_endpoint_tx {
         enum ipa_seq_type seq_type;
         enum ipa_seq_rep_type seq_rep_type;
         enum ipa_endpoint_name status_endpoint;
};

struct ipa_endpoint_rx {
         u32 buffer_size;
         u32 pad_align;
         u32 aggr_time_limit;
         bool aggr_hard_limit;
         bool aggr_close_eof;
         bool holb_drop;
};

> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_SRC_UL,

This resource group corresponds to IPA_v5_0_GROUP_UL in
the downstream code.

> +				.dma_mode	= true,

The dma_mode is always true for the AP->IPA command TX
endpoint, false for others.

> +				.dma_endpoint	= IPA_ENDPOINT_AP_LAN_RX,

This is always the DMA endpoint id for the command
endpoint. I think it's where the status messages
related to transmitted commands get sent.  The AP<-LAN
(RX) endpoint is the "default" endpoint.

> +				.tx = {
> +					.seq_type = IPA_SEQ_DMA,

This is the sequencer type, always DMA for the command
endpoint.  The sequencer types are set based on what the
downstream code does.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_LAN_RX] = {

This is the default RX endpoint on the AP.  If a LAN
interface were supported it would also be the RX
endpoint for the LAN.  This corresponds to
IPA_CLIENT_APPS_LAN_CONS

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 13,
> +		.endpoint_id	= 16,
> +		.toward_ipa	= false,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 9,
> +		},
> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_DST_UL,
> +				.aggregation	= true,

Aggregation enabled means multiple received messages will
be placed by the IPA hardware into a single receive buffer
before forwarding the buffer to the host for processing.

> +				.status_enable	= true,

This setting means every transfer causes a status header to be
generated for each received message.  ipa_endpoint_status_parse()
splits them apart using information in the status header and
hands each de-aggregated message to the network stack.

> +				.rx = {
> +					.buffer_size	= 8192,

Each receive buffer is this big (in bytes).

> +					.pad_align	= ilog2(sizeof(u32)),

Before a received message is placed in the receive buffer,
IPA updates current buffer pointer to be aligned to this
boundary (in this case, 2^2 bytes).

> +					.aggr_time_limit = 500,

If aggregation hasn't exhausted the receive buffer in this many
microseconds, it forwards the buffer to the host anyway.

The time limit comes from IPA_GENERIC_AGGR_TIME_LIMIT in the
downstream code.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_MODEM_TX] = {

The AP_MODEM_TX here says that this is an AP endpoint,
whose destination is the modem (WAN in the downstream
code), and it is a TX endpoint (from the AP to the modem).
This corresponds to IPA_CLIENT_APPS_WAN_PROD.

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 11,
> +		.endpoint_id	= 2,
> +		.toward_ipa	= true,
> +		.channel = {
> +			.tre_count	= 512,
> +			.event_count	= 512,
> +			.tlv_count	= 25,
> +		},
> +		.endpoint = {
> +			.filter_support	= true,
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_SRC_UL,
> +				.checksum       = true,

The checksum true flag means IPA performs checksumming
on messages being sent (so the host doesn't have to).

> +				.qmap		= true,

The qmap true flag says that this channel uses QMAP
protocol (ETH_P_MAP).  A single message contains one
or more QMAP messages, which multiplexes multiple
logical channels over a single connection.

> +				.status_enable	= true,
> +				.tx = {
> +					.seq_type = IPA_SEQ_2_PASS_SKIP_LAST_UC,
> +					.status_endpoint =
> +						IPA_ENDPOINT_MODEM_AP_RX,

This says that status messages generated as a result
of messages received on this channel (i.e., using
this endpoint) are delivered to the *modem* endpoint
that recieves data from the AP.

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_AP_MODEM_RX] = {

AP endpoint, *from* the modem.  This corresponds to
IPA_CLIENT_APPS_WAN_CONS.

> +		.ee_id		= GSI_EE_AP,
> +		.channel_id	= 1,
> +		.endpoint_id	= 23,
> +		.toward_ipa	= false,
> +		.channel = {
> +			.tre_count	= 256,
> +			.event_count	= 256,
> +			.tlv_count	= 9,
> +		},
> +		.endpoint = {
> +			.config = {
> +				.resource_group	= IPA_RSRC_GROUP_DST_UL,
> +				.checksum       = true,
> +				.qmap		= true,
> +				.aggregation	= true,
> +				.rx = {
> +					.buffer_size	= 8192,
> +					.aggr_time_limit = 500,
> +					.aggr_close_eof	= true,

The aggr_close_eof flag determines which of two ways
aggregation in a receive buffer "closes".  (Closing
means th receive buffer is delivered to the host for
processing, and a new receive buffer begins to be
used.)

One policy closes aggregation when there is not enough
space left to hold an entire incoming message in the
buffer.  The other policy closes aggregation when the
data from a received message crosses a certain mark
(byte count) in the receive buffer.  (I no longer
recall which is which.)

> +				},
> +			},
> +		},
> +	},
> +	[IPA_ENDPOINT_MODEM_AP_TX] = {

Modem endpoint, transmitting (from the modem) *to* the AP.
Downstream calls the modem "Q6".  Configuring these endpoints
is the modem's responsibility, but the AP IPA driver needs
to be aware of these, so they're included in this data.
(I don't remember why; maybe it's to ensure endpoints and
channels are accounted for, and/or not reused?)

This endpoint id corresponds to IPA_CLIENT_Q6_WAN_CONS.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 0,
> +		.endpoint_id	= 12,
> +		.toward_ipa	= true,
> +		.endpoint = {
> +			.filter_support	= true,
> +		},
> +	},
> +	[IPA_ENDPOINT_MODEM_AP_RX] = {

This corresponds to IPA_CLIENT_Q6_WAN_CONS.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 7,
> +		.endpoint_id	= 21,
> +		.toward_ipa	= false,
> +	},
> +	[IPA_ENDPOINT_MODEM_DL_NLO_TX] = {

This has to do with a feature we don't use, but we still
need to configure it (I think so we take into account that
it implements filtering).  This endpoint corresponds to
IPA_CLIENT_Q6_DL_NLO_DATA_PROD.

> +		.ee_id		= GSI_EE_MODEM,
> +		.channel_id	= 2,
> +		.endpoint_id	= 15,
> +		.toward_ipa	= true,
> +		.endpoint = {
> +			.filter_support	= true,
> +		},
> +	},
> +};
"Resources" are data structures managed by the IPA/GSI
firmware.  We must configure these at initialization
time, and once configured, that firmware operates
using these resources.  I don't know much more than
that, and basically we just configure things the way
the downstream code does.

> +
> +/* Source resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource ipa_resource_src[] = {

Again, this array is filled with information that comes from the
ipa3_rsrc_src_grp_config[IPA_5_1][][] array in the downstream
code, in "ipa_utils.c".  Everything you have here looks correct.

> +	[IPA_RESOURCE_TYPE_SRC_PKT_CONTEXTS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 7,	.max = 12,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 1,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_U_RX_QC] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_LISTS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 21,	.max = 21,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 10,	.max = 10,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_DESCRIPTOR_BUFF] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 33,	.max = 33,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 20,	.max = 20,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_HPS_DMARS] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 0,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 1,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_U_RX_QC] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_SRC_ACK_ENTRIES] = {
> +		.limits[IPA_RSRC_GROUP_SRC_UL] = {
> +			.min = 38,	.max = 38,
> +		},
> +		.limits[IPA_RSRC_GROUP_SRC_URLLC] = {
> +			.min = 16,	.max = 16,
> +		},
> +	},
> +};
> +
> +/* Destination resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource ipa_resource_dst[] = {

And the content of this array comes from ipa3_rsrc_dst_grp_config[][].
Everything you have here looks correct as well.

> +	[IPA_RESOURCE_TYPE_DST_DATA_SECTORS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 6,	.max = 6,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 5,	.max = 5,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DRB_IP] = {
> +			.min = 39,	.max = 39,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_DST_DPS_DMARS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 0,	.max = 3,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 0,	.max = 3,
> +		},
> +	},
> +	[IPA_RESOURCE_TYPE_DST_ULSO_SEGMENTS] = {
> +		.limits[IPA_RSRC_GROUP_DST_UL] = {
> +			.min = 0,	.max = 63,
> +		},
> +		.limits[IPA_RSRC_GROUP_DST_DL] = {
> +			.min = 0,	.max = 63,
> +		},
> +	},
> +};
> +
> +/* Resource configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_resource_data ipa_resource_data = {
> +	.rsrc_group_dst_count	= IPA_RSRC_GROUP_DST_COUNT,
> +	.rsrc_group_src_count	= IPA_RSRC_GROUP_SRC_COUNT,
> +	.resource_src_count	= ARRAY_SIZE(ipa_resource_src),
> +	.resource_src		= ipa_resource_src,
> +	.resource_dst_count	= ARRAY_SIZE(ipa_resource_dst),
> +	.resource_dst		= ipa_resource_dst,
> +};
> +
> +/* IPA-resident memory region data for an SoC having IPA v5.1 */

Memory regions are sort of similar to resources, in that
there are ranges of available (IPA-local) memory that are
used by IPA for various purposes.  We need to configure
these, and this configuration (base and size of various
memory regions) is shared with the modem via a QMI message
exchange during initialization.

> +static const struct ipa_mem ipa_mem_local_data[] = {

IPA has local memory that is partitioned as defined by this
array.  The regions are used by IPA/GSI firmware and/or
hardware.  The configuration defined here is sent to
the modem in an ipa_init_modem_driver_req QMI message
so both the modem and AP have a consistent view of
how the memory is used.

Many memory regions are preceded by 0-2 "canaries", which
are 32-byte values initialized to IPA_MEM_CANARY_VAL.

In the downstream code there is structure ipa3_mem_partition
that defines these things, and structures of this type are
defined in "ipa_utils.c".  For IPA v5.1, ipa_5_1_mem_part
defines them all.  The mapping between downstream and
upstream is not trivial and direct, but it should be
obvious how they get translated.


With two exceptions, what I see here looks like you
correctly transferred everything.  (The two exceptions
are entries that from what I can tell, should not be
present.)

> +	{
> +		.id		= IPA_MEM_UC_EVENT_RING,
> +		.offset		= 0x0000,
> +		.size		= 0x1000,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_UC_SHARED,
> +		.offset		= 0x1000,
> +		.size		= 0x0080,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_UC_INFO,
> +		.offset		= 0x1080,
> +		.size		= 0x0200,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_FILTER_HASHED,
> +		.offset		= 0x1288,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_FILTER,
> +		.offset		= 0x1308,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_FILTER_HASHED,
> +		.offset		= 0x1388,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_FILTER,
> +		.offset		= 0x1408,
> +		.size		= 0x0078,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_ROUTE_HASHED,
> +		.offset		= 0x1488,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V4_ROUTE,
> +		.offset		= 0x1528,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_ROUTE_HASHED,
> +		.offset		= 0x15c8,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_V6_ROUTE,
> +		.offset		= 0x1668,
> +		.size		= 0x0098,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM_HEADER,
> +		.offset		= 0x1708,
> +		.size		= 0x0240,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_HEADER,
> +		.offset		= 0x1948,
> +		.size		= 0x01e0,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM_PROC_CTX,
> +		.offset		= 0x1b40,
> +		.size		= 0x0b20,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_PROC_CTX,
> +		.offset		= 0x2660,
> +		.size		= 0x0200,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_QUOTA_MODEM,
> +		.offset		= 0x2868,
> +		.size		= 0x0060,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_QUOTA_AP,
> +		.offset		= 0x28c8,
> +		.size		= 0x0048,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_TETHERING,
> +		.offset		= 0x2910,
> +		.size		= 0x03c0,
> +		.canary_count	= 0,
> +	},

The next two entries look wrong to me.  Can you explain where
you got these offsets and sizes?  Is it from "ipa_data-v5.0.c"?

Here are the relevant entries I see in ipa_5_1_mem_part
in the downstream code:
         .stats_flt_v4_ofst = 0,
         .stats_flt_v4_size = 0,
         .stats_flt_v6_ofst = 0,
         .stats_flt_v6_size = 0,
         .stats_rt_v4_ofst = 0,
         .stats_rt_v4_size = 0,
         .stats_rt_v6_ofst = 0,
         .stats_rt_v6_size = 0,
(Since their size is zero, their entries can be omitted.)

> +	{
> +		.id		= IPA_MEM_AP_V4_FILTER,
> +		.offset		= 0x29b8,
> +		.size		= 0x0188,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_AP_V6_FILTER,
> +		.offset		= 0x2b40,
> +		.size		= 0x0228,
> +		.canary_count	= 0,
> +	},

The remaining entries (below) look good.

> +	{
> +		.id		= IPA_MEM_STATS_FILTER_ROUTE,
> +		.offset		= 0x2cd0,
> +		.size		= 0x0ba0,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_STATS_DROP,
> +		.offset		= 0x3870,
> +		.size		= 0x0020,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_MODEM,
> +		.offset		= 0x3898,
> +		.size		= 0x0d48,
> +		.canary_count	= 2,
> +	},
> +	{
> +		.id		= IPA_MEM_NAT_TABLE,
> +		.offset		= 0x45e0,
> +		.size		= 0x0900,
> +		.canary_count	= 0,
> +	},
> +	{
> +		.id		= IPA_MEM_PDN_CONFIG,
> +		.offset		= 0x4ee8,
> +		.size		= 0x0100,
> +		.canary_count	= 2,
> +	},
> +};
> +
> +/* Memory configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_mem_data ipa_mem_data = {
> +	.local_count	= ARRAY_SIZE(ipa_mem_local_data),
> +	.local		= ipa_mem_local_data,
> +	.imem_addr	= 0x146a8000,

I think I needed to look up the imem offset value
in Qualcomm documentation I no longer have access
to.  Perhaps someone from there could confirm you
are using the right values here.

> +	.imem_size	= 0x00002000,
> +	/*
> +	 * While this value is 0xb000 on SM8450 and 0x9000 on SM8475,
> +	 * it has been left set to 0x9000 for compatibility with SM8475
> +	 */

As I said earlier, I'm not completely sure this will still
work on the SM8450.  Someone should confirm this, and it
really ought to be tested somehow.

> +	.smem_size	= 0x00009000,
> +};
> +
> +/* Interconnect rates are in 1000 byte/second units */
> +static const struct ipa_interconnect_data ipa_interconnect_data[] = {
> +	{
> +		.name			= "memory",
> +		.peak_bandwidth		= 1900000,	/* 1.9 GBps */
> +		.average_bandwidth	= 590000,	/* 590 MBps */

I no longer recall where to get these bandwidth values
for the interconnects.  Perhaps someone from Qualcomm
can find this out/confirm what you have.

Really nice work figuring out all this stuff...

					-Alex

> +	},
> +	/* Average rate is unused for the next interconnect */
> +	{
> +		.name			= "config",
> +		.peak_bandwidth		= 76800,	/* 76.8 MBps */
> +		.average_bandwidth	= 0,		/* unused */
> +	},
> +};
> +
> +/* Clock and interconnect configuration data for an SoC having IPA v5.1 */
> +static const struct ipa_power_data ipa_power_data = {
> +	.core_clock_rate	= 120 * 1000 * 1000,	/* Hz */
> +	.interconnect_count	= ARRAY_SIZE(ipa_interconnect_data),
> +	.interconnect_data	= ipa_interconnect_data,
> +};
> +
> +/* Configuration data for an SoC having IPA v5.1. */
> +const struct ipa_data ipa_data_v5_1 = {
> +	.version		= IPA_VERSION_5_1,
> +	.qsb_count		= ARRAY_SIZE(ipa_qsb_data),
> +	.qsb_data		= ipa_qsb_data,
> +	.modem_route_count	= 11,
> +	.endpoint_count		= ARRAY_SIZE(ipa_gsi_endpoint_data),
> +	.endpoint_data		= ipa_gsi_endpoint_data,
> +	.resource_data		= &ipa_resource_data,
> +	.mem_data		= &ipa_mem_data,
> +	.power_data		= &ipa_power_data,
> +};
> diff --git a/drivers/net/ipa/gsi_reg.c b/drivers/net/ipa/gsi_reg.c
> index e13cf835a013..a57072ba4bef 100644
> --- a/drivers/net/ipa/gsi_reg.c
> +++ b/drivers/net/ipa/gsi_reg.c
> @@ -110,6 +110,7 @@ static const struct regs *gsi_regs(struct gsi *gsi)
>   		return &gsi_regs_v4_11;
>   
>   	case IPA_VERSION_5_0:
> +	case IPA_VERSION_5_1:
>   	case IPA_VERSION_5_2:
>   	case IPA_VERSION_5_5:
>   		return &gsi_regs_v5_0;
> diff --git a/drivers/net/ipa/ipa_data.h b/drivers/net/ipa/ipa_data.h
> index 3eb9dc2ce339..fe6f7d5bfe88 100644
> --- a/drivers/net/ipa/ipa_data.h
> +++ b/drivers/net/ipa/ipa_data.h
> @@ -253,6 +253,7 @@ extern const struct ipa_data ipa_data_v4_7;
>   extern const struct ipa_data ipa_data_v4_9;
>   extern const struct ipa_data ipa_data_v4_11;
>   extern const struct ipa_data ipa_data_v5_0;
> +extern const struct ipa_data ipa_data_v5_1;
>   extern const struct ipa_data ipa_data_v5_2;
>   extern const struct ipa_data ipa_data_v5_5;
>   
> diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
> index 788dd99af2a4..6c449032ae45 100644
> --- a/drivers/net/ipa/ipa_main.c
> +++ b/drivers/net/ipa/ipa_main.c
> @@ -669,6 +669,10 @@ static const struct of_device_id ipa_match[] = {
>   		.compatible	= "qcom,sdx65-ipa",
>   		.data		= &ipa_data_v5_0,
>   	},
> +	{
> +		.compatible	= "qcom,sm8450-ipa",
> +		.data		= &ipa_data_v5_1,
> +	},
>   	{
>   		.compatible	= "qcom,milos-ipa",
>   		.data		= &ipa_data_v5_2,
> diff --git a/drivers/net/ipa/ipa_reg.c b/drivers/net/ipa/ipa_reg.c
> index 30bd69f4c147..5f22ca6295b1 100644
> --- a/drivers/net/ipa/ipa_reg.c
> +++ b/drivers/net/ipa/ipa_reg.c
> @@ -125,6 +125,7 @@ static const struct regs *ipa_regs(enum ipa_version version)
>   	case IPA_VERSION_4_11:
>   		return &ipa_regs_v4_11;
>   	case IPA_VERSION_5_0:
> +	case IPA_VERSION_5_1:
>   	case IPA_VERSION_5_2:
>   		return &ipa_regs_v5_0;
>   	case IPA_VERSION_5_5:
> 


^ permalink raw reply

* Re: [PATCH RFC net-next 3/3] net: dsa: mxl862xx: add devlink flash_update and info_get
From: Daniel Golle @ 2026-07-08 20:02 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <42792433-a95c-431f-ba0c-dc0644dcbdb5@lunn.ch>

On Wed, Jul 08, 2026 at 07:27:21PM +0200, Andrew Lunn wrote:
> > + * The flash process takes approximately 15 minutes. Progress is
> > + * reported via devlink status notifications. After a successful (or
> > + * failed) flash the driver reprobes the device automatically.
> 
> Have you tested the failed use case?
> 
> I assume if the firmware in the flash is invalid, the bootloader does
> not boot it, and it remains in the bootloader waiting for another
> attempt. Does this DSA driver still load, so devlink can be used to
> try again?

No. Without a running the firmware the driver doesn't probe and only
a special rescue tool allows to recover the hardware.
Having the DSA driver detect the presence of the switch stuck in
mcuboot mode and probe without registering any user or CPU ports
also isn't straight forward.

^ permalink raw reply

* Re: [PATCH 0/3] SM8450 IPA support
From: Alex Elder @ 2026-07-08 19:49 UTC (permalink / raw)
  To: Esteban Urrutia, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <48c624c3-5408-4862-b4ec-9321ad279bf9@proton.me>

On 6/23/26 8:57 PM, Esteban Urrutia wrote:
> On 6/23/26 11:56 AM, Alex Elder wrote:
>> I assume you have implemented this based on what you found in
>> some downstream code.  And if so, could you please indicate
>> where to find that (so I can do some cross-referencing myself).
>> I no longer have access to any Qualcomm internal documentation.
> 
> Hello. Yes, that would be the case. What I used goes as follows.

Thank you very much for sharing this information.

> 1. My personal findings regarding IPA:
> https://gist.github.com/esteuwu/bd49ed67ed9290f41612bdae1cacb5bc

This was interesting to see.  It's something I should probably
document better.  Most everything maps to the downstream code,
but it's not always completely obvious how, because the upstream
driver has evolved substantially.

> Note that these may be subject to errors since I mostly cross-checked
> values to get here.
> 
> 2. SM8450 downstream device tree:
> https://github.com/LineageOS/android_kernel_qcom_sm8450-devicetrees/blob/lineage-20/qcom/waipio.dtsi#L3304
> 
> 3. SM8475 downstream device tree:
> https://github.com/LineageOS/android_kernel_qcom_sm8450-devicetrees/blob/lineage-20/qcom/cape.dtsi#L2624
> 
> It's worth mentioning that between SM8450 and SM8475, IPA SRAM size is
> different, so I used the smaller SRAM size to support SM8475 as well. Hence
> the reason why I included SM8475's downstream device tree as well.

This means that the SRAM size (ipa_mem_data->smem_size) should
possibly be defined in devicetree (as the IMEM address and size
now are).

The SMEM region is used for "IPA filter tables", and access to
it is shared between the AP and the modem.  Unlike the other
(host) memory regions, the size used is *not* included in the
ipa_init_modem_driver_req message that communicates from the
AP to the modem where the regions are, and their sizes.

So it's possible that the size used must actually match what
is expected by both the AP and modem.  If that is the case,
using the smaller size might have problems on whichever
platform (SM8450?) expects the larger one.

So I'm not sure whether using the smaller size for both
platforms is OK; someone from Qualcomm might be able to
answer that question.

> 4. SM8450/SM8475 downstream IPA driver:
> https://github.com/LineageOS/android_kernel_qcom_sm8450-modules/tree/lineage-20/qcom/opensource/dataipa
> 
> Most of my cross-checking came from the source code in this folder.

Yes, "ipa_utils.c" contains a great deal of the information
needed.

> 
> Finally, for some values such as qmap, aggregation, tre_count and
> event_count, I had to cross-check on the same folder that all
> ipa_data-vX.Y.c files reside, since I couldn't find any reference to these
> values in downstream code.

I'll try to explain those things separately.

					-Alex

> Regards,
> Esteban
> 


^ permalink raw reply

* Re: [PATCH RFC net-next 2/3] net: dsa: mxl862xx: add SMDIO clause-22 register access
From: Daniel Golle @ 2026-07-08 19:45 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, linux-kernel, netdev
In-Reply-To: <96c362d3-59d2-43c4-b2aa-c12dfeb6fcca@lunn.ch>

On Wed, Jul 08, 2026 at 07:22:49PM +0200, Andrew Lunn wrote:
> On Tue, Jul 07, 2026 at 04:16:07PM +0200, Daniel Golle wrote:
> > Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
> > SMDIO register access. MCUboot rescue mode only exposes clause-22
> > registers; the existing clause-45 MMD interface is unavailable during
> > firmware transfer. The MDIO bus lock is held per-transaction (not
> > across polls) so that SB PDI polling during flash erase does not
> > starve other MDIO users.
> 
> What other MDIO users are there? It sounds like once the switch is in
> rescue mode, switch management is dead. So how can there be users?

The MDIO bus lock refers to the host bus which is used to connect
the switch management interface. The same bus can also be used to
connect other unrelated PHYs (eg. to provide a WAN or management
interface independent of the switch).
Especially because this "other" interface can be used to connect
to the host via SSH and perform the firmware update on the switch
holding the MDIO bus lock may impact PHY polling and impair the
link used to conduct the update.
At least that was my thought when implementing this.

^ permalink raw reply

* Re: [PATCH net-next v5 1/4] dt-bindings: net: pse-pd: add bindings for Realtek/Broadcom PSE MCU
From: Jonas Jelonek @ 2026-07-08 19:44 UTC (permalink / raw)
  To: Conor Dooley
  Cc: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, netdev, devicetree,
	linux-kernel, Daniel Golle, Bjørn Mork
In-Reply-To: <20260708-lyrically-footrest-10963a12145f@spud>

Hi Conor,

On 08.07.26 18:56, Conor Dooley wrote:
> On Tue, Jul 07, 2026 at 10:50:21PM +0200, Jonas Jelonek wrote:
>> [...]
>> Ok, I hope I put this together correctly. A concrete proposal:
>>
>> "realtek,pse-mcu-gen1"                        (Protocol Gen 1, UART)
>> "realtek,pse-mcu-gen1-smbus"            (Protocol Gen 1, SMBus)
>> "realtek,pse-mcu-gen2"                        (Protocol Gen 2, UART)
>> "realtek,pse-mcu-gen2-i2c"                  (Protocol Gen 2, raw I2C)
>> "realtek,pse-mcu-gen2-smbus"            (Protocol Gen 2, SMBus)
>>
>> This uniquely identifies the protocol used: first generation and second
>> generation. As Rob mentioned before [1], this also pulls in the raw I2C
>> vs. SMBus framing in contrast to having it in a property. The framing
>> suffix appears only on I2C attachments because it doesn't apply to
>> UART transport, and this is given by the parent serial@ node.
>>
>> Though I'm still open for suggestions regarding the protocol
>> identification if "-gen1"/"-gen2" is not acceptable.
> This seems reasonable enough.
>
>> [...]
>>
>> It would also be an exception to the other PSE-PD bindings. They describe
>> controllers used across many switches too, yet none encode the
> The difference is those cases (for what few pse-psd bindings there are)
> the compatibles correspond to individual devices. Here you have
> compatibles you're going to use to cover multiple devices (with device
> corresponding to a combination of mcu/firmware/hardware behind the mcu).
> That lack of a 1:1 mapping is why I'm asking for something different from
> you than you see with the existing pse-pd devices. The switch the device
> is integrated on seems to be the only thing that reasonably makes sense
> to use.
>
>
>> switch/enclosure. Board-specific compatibles might still be added later in
>> case a device really has a variation or quirk that genuinely needs its own
>> compatible.
> And in doing so, have to retrofit that compatible to all devicetrees
> that use it. This is one of the reasons that we generally demand
> device-specific compatibles.
>
> You could add switch-specific compatibles that fall back to the ones you
> provide above, with the driver only using the ones above unless
> something crops up in the future?

Thank you for the thorough explanation. I think I got your point now and
understand why it's required that way.

I'll rework this for the next version.

> Cheers,
> Conor.

Best regards,
Jonas

^ permalink raw reply

* Re: [PATCH net-next] net/mlx5e: bound TX CQ poll softirq residency with a time budget
From: Jose Fernandez (Anthropic) @ 2026-07-08 19:32 UTC (permalink / raw)
  To: Dragos Tatulea
  Cc: Saeed Mahameed, Tariq Toukan, Mark Bloch, Leon Romanovsky,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, netdev, linux-rdma, linux-kernel, Ben Cressey
In-Reply-To: <5557a08f-6d53-4425-8004-b7f0bbcd8a50@nvidia.com>

Hi Dragos,

Thanks for the review.

On Mon, Jul 06, 2026 at 01:48:41PM +0200, Dragos Tatulea wrote:
> This is interesting. I can think of 2 cases here which can overlap:
>
> 1) Packets are sent with xmit_more which trigger this many WQEs per
>   CQE scenario.
>
> 2) TSO packets can have many fragments so even a single WQE will have
>    more than one DMA unmap. For the default GSO size of 64K each TSO
>    skb will have 2 fragments on your platform. If the GSO is bumped to
>    256K then you usually get ~5 fragments.
>
> Let's first understand if this is a case of 1) (small packets sent
> with xmit_more) or 2) (many TSO packets) or 1+2) (many TSO packets sent
> with xmit_more).

Likely both, mostly 1). The workload is bursty bulk transfers, and
the stalls hit while processing a burst's completions: one CQE covers
a whole xmit_more batch, each TSO skb adds its 2 frag unmaps, so the
CQE budget bounds entries but not the unmap work behind them.

I don't have the tx_xmit_more / tx_tso_packets ratios handy. I'll
collect them across a burst window and report back.

> If xmit_more is used then you can actually tune the BQL to avoid this
> behavior which seems to wreak havoc on your configuration.

We considered that. BQL bounds bytes in flight, but bytes only
translate into poll time at some assumed per-unmap cost, and that
cost is the unstable part here: a few us normally, ~100x worse when
many CPUs contend on the shared SMMU command queue. A BQL setting
small enough for the worst case would hurt throughput the rest of
the time.

> The bounded poll is faster because it is interrupted earlier or is
> it faster because it results in less contention on the IOMMU?

Both, and we tried to separate them. Sweeping the yield cadence
barely moves the numbers, so it is not just doing less per poll. And
in A/B runs with the budget armed, the competing side's per-unmap
cost drops 15-20%, so there is a real contention reduction on top of
the transmit path getting the core back sooner.

> This change is too invasive in the driver for all the other cases. We have to figure
> another way to go about this issue.

Fair enough, and the module parameter should not have been there anyway.
The direction I would rather take: fix the cost instead of capping it,
by converting the mlx5e TX path to the tso_dma_map helpers (as bnxt
did), so a TSO skb maps as one IOVA range where the DMA IOVA API is
available, completing with one sync instead of one per frag.

Once the counters confirm the frag-unmap volume I will prototype that,
unless you or Tariq/Saeed see a problem with the direction. Happy to
test on the affected boxes. We have a repro harness.

Thanks,
Jose

^ permalink raw reply

* Re: [PATCH v12 nf-next 7/7] netfilter: nft_flow_offload: Add bridgeflow to nft_flow_offload_eval()
From: Eric Woudstra @ 2026-07-08 19:14 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
	Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
	Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
	Martin Karsten, netdev, netfilter-devel, bridge
In-Reply-To: <ak4d89hkh0Jvcp2W@chamomile>



On 7/8/26 11:52 AM, Pablo Neira Ayuso wrote:
> Hi,
> 
> On Tue, Jul 07, 2026 at 11:10:45AM +0200, Eric Woudstra wrote:
>> Edit nft_flow_offload_eval() to make it possible to handle a flowtable of
>> the nft bridge family.
>>
>> Use nft_flow_offload_bridge_init() to fill the flow tuples. It uses
>> nft_dev_fill_bridge_path() in each direction.
> 
> I decided to add a bit more boiler plate in my proposal to detach the
> inet and bridge flowtable dataplanes.
> 
> More comments below.
> 
>> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
>> ---
>>  include/net/netfilter/nf_flow_table.h |   5 +
>>  net/netfilter/nf_flow_table_path.c    | 126 ++++++++++++++++++++++++++
>>  net/netfilter/nft_flow_offload.c      |  20 +++-
>>  3 files changed, 146 insertions(+), 5 deletions(-)
>>
>> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
>> index 5c6e3b65ae85b..a109eda5250c7 100644
>> --- a/include/net/netfilter/nf_flow_table.h
>> +++ b/include/net/netfilter/nf_flow_table.h
>> @@ -305,6 +305,11 @@ nf_flow_table_offload_del_cb(struct nf_flowtable *flow_table,
>>  void flow_offload_route_init(struct flow_offload *flow,
>>  			     struct nf_flow_route *route);
>>  
>> +int flow_offload_bridge_init(struct flow_offload *flow,
>> +			     const struct nft_pktinfo *pkt,
>> +			     enum ip_conntrack_dir dir,
>> +			     struct nft_flowtable *ft);
>> +
>>  int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow);
>>  void flow_offload_refresh(struct nf_flowtable *flow_table,
>>  			  struct flow_offload *flow, bool force);
>> diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
>> index 2b6ebb594a9ee..cdd6a822cb811 100644
>> --- a/net/netfilter/nf_flow_table_path.c
>> +++ b/net/netfilter/nf_flow_table_path.c
>> @@ -1,6 +1,7 @@
>>  // SPDX-License-Identifier: GPL-2.0-only
>>  #include <linux/kernel.h>
>>  #include <linux/module.h>
>> +#include <linux/if_vlan.h>
>>  #include <linux/init.h>
>>  #include <linux/etherdevice.h>
>>  #include <linux/netlink.h>
>> @@ -365,3 +366,128 @@ int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct,
>>  	return -ENOENT;
>>  }
>>  EXPORT_SYMBOL_GPL(nft_flow_route);
>> +
>> +static int nft_dev_fill_bridge_path(struct flow_offload *flow,
>> +				    struct nft_flowtable *ft,
>> +				    enum ip_conntrack_dir dir,
>> +				    const struct net_device *src_dev,
>> +				    const struct net_device *dst_dev,
>> +				    unsigned char *src_ha,
>> +				    unsigned char *dst_ha)
>> +{
>> +	struct flow_offload_tuple_rhash *th = flow->tuplehash;
>> +	struct net_device_path_ctx ctx = {};
>> +	struct net_device_path_stack stack;
>> +	struct nft_forward_info info = {};
>> +	int i, j = 0;
>> +
>> +	for (i = th[dir].tuple.encap_num - 1; i >= 0 ; i--) {
>> +		if (info.num_encaps >= NF_FLOW_TABLE_ENCAP_MAX)
>> +			return -1;
>> +
>> +		if (th[dir].tuple.in_vlan_ingress & BIT(i))
>> +			continue;
>> +
>> +		info.encap[info.num_encaps].id = th[dir].tuple.encap[i].id;
>> +		info.encap[info.num_encaps].proto = th[dir].tuple.encap[i].proto;
>> +		info.num_encaps++;
>> +
>> +		if (th[dir].tuple.encap[i].proto == htons(ETH_P_PPP_SES))
>> +			continue;
>> +
>> +		if (ctx.num_vlans >= NET_DEVICE_PATH_VLAN_MAX)
>> +			return -1;
>> +		ctx.vlan[ctx.num_vlans].id = th[dir].tuple.encap[i].id;
>> +		ctx.vlan[ctx.num_vlans].proto = th[dir].tuple.encap[i].proto;
>> +		ctx.num_vlans++;
>> +	}
> 
> I am not sure why this is needed, in my approach I simplified this,
> but maybe I broke bridge vlan filtering. I am not sure what test
> coverage you made.
> 

This part is used for handling packets that are incoming with encaps.
Previously I got the encaps from the skb and stored them in
th[dir].tuple.encap. I understand you want to do this differently.
It is also why I'm starting in the opposite direction as in you patch-set.

The code above then reads th[dir].tuple.encap and starts filling info
and ctx with encaps. It also handles encaps marked with a
in_vlan_ingress bit. Once info and ctx are filled with the encaps from
the skb, it can continue with dev_fill_bridge_path().

My selftest script bridge_fastpath.sh tests this code thoroughly in many
different scenarios. Incoming vlan, q-in-q, ad, pppoe, pppoe-in-q. Also
on a dsa-port with the in_vlan_ingress bit set.

>> +	ctx.dev = src_dev;
>> +	ether_addr_copy(ctx.daddr, dst_ha);
>> +
>> +	if (dev_fill_bridge_path(&ctx, &stack) < 0)
>> +		return -1;
>> +
>> +	nft_dev_path_info(&stack, &info, dst_ha, &ft->data);
>> +
>> +	if (!info.indev || info.indev != dst_dev)
>> +		return -1;
>> +
>> +	th[!dir].tuple.iifidx = info.indev->ifindex;
>> +	for (i = info.num_encaps - 1; i >= 0; i--) {
>> +		th[!dir].tuple.encap[j].id = info.encap[i].id;
>> +		th[!dir].tuple.encap[j].proto = info.encap[i].proto;
>> +		if (info.ingress_vlans & BIT(i))
>> +			th[!dir].tuple.in_vlan_ingress |= BIT(j);
>> +		j++;
>> +	}
>> +	th[!dir].tuple.encap_num = info.num_encaps;
>> +
>> +	th[dir].tuple.mtu = dst_dev->mtu;
>> +	ether_addr_copy(th[dir].tuple.out.h_source, src_ha);
>> +	ether_addr_copy(th[dir].tuple.out.h_dest, dst_ha);
>> +	th[dir].tuple.out.ifidx = info.outdev->ifindex;
>> +	th[dir].tuple.xmit_type = FLOW_OFFLOAD_XMIT_DIRECT;
>> +
>> +	return 0;
>> +}
>> +
>> +int flow_offload_bridge_init(struct flow_offload *flow,
>> +			     const struct nft_pktinfo *pkt,
>> +			     enum ip_conntrack_dir dir,
>> +			     struct nft_flowtable *ft)
>> +{
>> +	const struct net_device *in_dev, *out_dev;
>> +	struct ethhdr *eth = eth_hdr(pkt->skb);
>> +	struct flow_offload_tuple *tuple;
>> +	int err, i = 0;
>> +
>> +	in_dev = nft_in(pkt);
>> +	if (!in_dev || !nft_flowtable_find_dev(in_dev, ft))
>> +		return -1;
>> +
>> +	out_dev = nft_out(pkt);
>> +	if (!out_dev || !nft_flowtable_find_dev(out_dev, ft))
>> +		return -1;
>> +
>> +	tuple =  &flow->tuplehash[!dir].tuple;
>> +
>> +	if (skb_vlan_tag_present(pkt->skb)) {
>> +		tuple->encap[i].id = skb_vlan_tag_get(pkt->skb);
>> +		tuple->encap[i].proto = pkt->skb->vlan_proto;
>> +		i++;
>> +	}
>> +
>> +	switch (eth_hdr(pkt->skb)->h_proto) {
>> +	case htons(ETH_P_8021Q): {
>> +		struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb_mac_header(pkt->skb)
>> +					 + sizeof(struct ethhdr));
>> +		tuple->encap[i].id = ntohs(vhdr->h_vlan_TCI);
>> +		tuple->encap[i].proto = htons(ETH_P_8021Q);
>> +		i++;
>> +		break;
>> +	}
>> +	case htons(ETH_P_PPP_SES): {
>> +		struct pppoe_hdr *phdr = (struct pppoe_hdr *)(skb_mac_header(pkt->skb)
>> +					  + sizeof(struct ethhdr));
>> +
>> +		tuple->encap[i].id = ntohs(phdr->sid);
>> +		tuple->encap[i].proto = htons(ETH_P_PPP_SES);
>> +		i++;
>> +		break;
>> +	}
>> +	}
>> +	tuple->encap_num = i;
> 
> I am not sure these lines above can work. The VLAN tag might be
> already gone by when the packet is observed from the bridge/forward
> hook. I think populating the encap fields of the tuple by using the
> observed packet is not good to go.
> 

I have not experienced this, but I understand that this is no guarantee
that it does not happen at all.

>> +	err = nft_dev_fill_bridge_path(flow, ft, !dir, out_dev, in_dev,
>> +				       eth->h_dest, eth->h_source);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	err = nft_dev_fill_bridge_path(flow, ft, dir, in_dev, out_dev,
>> +				       eth->h_source, eth->h_dest);
>> +	if (err < 0)
>> +		return err;
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(flow_offload_bridge_init);
>> diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
>> index 0be62841155b6..d0d63ef7cecd5 100644
>> --- a/net/netfilter/nft_flow_offload.c
>> +++ b/net/netfilter/nft_flow_offload.c
>> @@ -53,6 +53,7 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
>>  {
>>  	struct nft_flow_offload *priv = nft_expr_priv(expr);
>>  	struct nf_flowtable *flowtable = &priv->flowtable->data;
>> +	bool routing = flowtable->type->family != NFPROTO_BRIDGE;
>>  	struct tcphdr _tcph, *tcph = NULL;
>>  	struct nf_flow_route route = {};
>>  	enum ip_conntrack_info ctinfo;
>> @@ -109,14 +110,21 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
>>  		goto out;
>>  
>>  	dir = CTINFO2DIR(ctinfo);
>> -	if (nft_flow_route(pkt, ct, &route, dir, priv->flowtable) < 0)
>> -		goto err_flow_route;
>> +	if (routing) {
>> +		if (nft_flow_route(pkt, ct, &route, dir, priv->flowtable) < 0)
>> +			goto err_flow_route;
>> +	}
> 
> As said, I am leaning towards adding a bit more boilerplate code to
> separate the bridge and inet flowtable datapaths.
> 
>>  	flow = flow_offload_alloc(ct);
>>  	if (!flow)
>>  		goto err_flow_alloc;
>>  
>> -	flow_offload_route_init(flow, &route);
>> +	if (routing)
>> +		flow_offload_route_init(flow, &route);
>> +	else
>> +		if (flow_offload_bridge_init(flow, pkt, dir, priv->flowtable) < 0)
>> +			goto err_flow_add;
>> +
>>  	if (tcph)
>>  		flow_offload_ct_tcp(ct);
>>  
>> @@ -164,8 +172,10 @@ static void nft_flow_offload_eval(const struct nft_expr *expr,
>>  err_flow_add:
>>  	flow_offload_free(flow);
>>  err_flow_alloc:
>> -	dst_release(route.tuple[dir].dst);
>> -	dst_release(route.tuple[!dir].dst);
>> +	if (routing) {
>> +		dst_release(route.tuple[dir].dst);
>> +		dst_release(route.tuple[!dir].dst);
>> +	}
>>  err_flow_route:
>>  	clear_bit(IPS_OFFLOAD_BIT, &ct->status);
>>  out:
>> -- 
>> 2.53.0
>>


^ permalink raw reply

* Re: [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Simon Horman @ 2026-07-08 19:04 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: Pablo Neira Ayuso, Runyu Xiao, laforge, andrew+netdev, davem,
	edumazet, kuba, osmocom-net-gprs, netdev, linux-kernel,
	jianhao.xu
In-Reply-To: <20260708183252.GL1364329@horms.kernel.org>

On Wed, Jul 08, 2026 at 07:32:52PM +0100, Simon Horman wrote:
> On Wed, Jul 08, 2026 at 01:10:58PM +0200, Paolo Abeni wrote:
> > On 7/8/26 12:35 PM, Simon Horman wrote:
> > > On Tue, Jul 07, 2026 at 04:51:12PM +0200, Pablo Neira Ayuso wrote:

...

> > >> I think this patch is not correct.
> > > 
> > > Hi Pablo,
> > > 
> > > Of course you are correct.
> > > Sorry for not realising this earlier.
> > 
> > Human slop here made me wrongly apply this patch. Could either of you
> > please share a formal revert?
> 
> Sure, will do.

- [PATCH net-next] Revert "gtp: annotate PDP lookups under RTNL"
  https://lore.kernel.org/netdev/20260708-gtp-rtnl-v1-1-218091f171bc@kernel.org/T/

^ permalink raw reply

* [PATCH net-next] Revert "gtp: annotate PDP lookups under RTNL"
From: Simon Horman @ 2026-07-08 19:02 UTC (permalink / raw)
  To: Pablo Neira Ayuso, Harald Welte, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: osmocom-net-gprs, netdev, Simon Horman

This reverts commit 0be5c3f0fbef3679f50f345b9237b8f9ea5de4e9.

Commit 0be5c3f0fbef ("gtp: annotate PDP lookups under RTNL") added a
lockdep_rtnl_is_held condition to hlist_for_each_rcu() loops to help
insure that RTNL is held.

Unfortunately, as pointed out by Pablo Neira Ayuso, the PDP context list
is actually protected by the genetlink mutex. And so the condition
is incorrect.

Compile tested only.

Signed-off-by: Simon Horman <horms@kernel.org>
---
 drivers/net/gtp.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index 4ad9528322c45c4511a2cde02d3ca7c8faea3758..a60ef32b35b8255a17bed3fbc6ce19f26b3ba3a8 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -151,8 +151,7 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family)
 
 	head = &gtp->tid_hash[gtp0_hashfn(tid) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_tid,
-				 lockdep_rtnl_is_held()) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_tid) {
 		if (pdp->af == family &&
 		    pdp->gtp_version == GTP_V0 &&
 		    pdp->u.v0.tid == tid)
@@ -169,8 +168,7 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family)
 
 	head = &gtp->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_tid,
-				 lockdep_rtnl_is_held()) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_tid) {
 		if (pdp->af == family &&
 		    pdp->gtp_version == GTP_V1 &&
 		    pdp->u.v1.i_tei == tid)
@@ -187,8 +185,7 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr)
 
 	head = &gtp->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_addr,
-				 lockdep_rtnl_is_held()) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_addr) {
 		if (pdp->af == AF_INET &&
 		    pdp->ms.addr.s_addr == ms_addr)
 			return pdp;
@@ -223,8 +220,7 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp,
 
 	head = &gtp->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_addr,
-				 lockdep_rtnl_is_held()) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_addr) {
 		if (pdp->af == AF_INET6 &&
 		    ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr))
 			return pdp;




^ permalink raw reply related

* Re: [PATCH net v3] tipc: fix u16 MTU truncation in media and bearer MTU validation
From: Vadim Fedorenko @ 2026-07-08 19:00 UTC (permalink / raw)
  To: Cen Zhang (Microsoft), jmaloy, davem, edumazet, kuba, pabeni,
	horms
  Cc: netdev, tipc-discussion, linux-kernel, tung.quang.nguyen,
	AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <20260708180212.2898-1-blbllhy@gmail.com>

On 08/07/2026 19:02, Cen Zhang (Microsoft) wrote:
> Both TIPC_NL_MEDIA_SET and TIPC_NL_BEARER_SET accept user-supplied
> MTU values but only enforce a minimum bound, not a maximum. When a user
> sets the MTU to a value exceeding U16_MAX (65535), it passes validation
> but is silently truncated when assigned to u16 fields l->mtu and
> l->advertised_mtu in tipc_link_create(). Values like 65536 (0x10000)
> truncate to 0, causing a division by zero in tipc_link_set_queue_limits()
> which computes TIPC_MAX_PUBL / (l->mtu / ITEM_SIZE). Other overflowing
> values (e.g. 65537-131071) produce small incorrect MTU values, resulting
> in link malfunction behaviors.
> 
> Crash stack (triggered as unprivileged user via user namespace):
> 
>    tipc_link_set_queue_limits  net/tipc/link.c:2531
>    tipc_link_create            net/tipc/link.c:520
>    tipc_node_check_dest        net/tipc/node.c:1279
>    tipc_disc_rcv               net/tipc/discover.c:252
>    tipc_rcv                    net/tipc/node.c:2129
>    tipc_udp_recv               net/tipc/udp_media.c:392
> 
> Two independent paths lack the upper bound check:
> 1. tipc_udp_mtu_bad() -- called from __tipc_nl_media_set() (MEDIA_SET)
> 2. inline check in __tipc_nl_bearer_set() at bearer.c:1160 (BEARER_SET)
> 
> Fix both by rejecting MTU values above U16_MAX.
> 
> Fixes: 901271e0403a ("tipc: implement configuration of UDP media MTU")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Closes: https://lore.kernel.org/all/CAB8m9WgETt0AjmFwE=F-CKjGXsK6_WDv0=kbYRcC8-noo+amnA@mail.gmail.com
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
> ---
> v3: Use nla_policy check to limit MTU max value as suggested by Vadim
> v2: Solved format issue
> Link: https://lore.kernel.org/all/CAB8m9WgETt0AjmFwE=F-CKjGXsK6_WDv0=kbYRcC8-noo+amnA@mail.gmail.com
> 
>   net/tipc/netlink.c | 6 +++++-
>   1 file changed, 5 insertions(+), 1 deletion(-)
> 
> diff --git a/net/tipc/netlink.c b/net/tipc/netlink.c
> index 8336a9664703..1307dd1a9613 100644
> --- a/net/tipc/netlink.c
> +++ b/net/tipc/netlink.c
> @@ -113,12 +113,16 @@ const struct nla_policy tipc_nl_node_policy[TIPC_NLA_NODE_MAX + 1] = {
>   };
>   
>   /* Properties valid for media, bearer and link */
> +static const struct netlink_range_validation tipc_nl_mtu_range = {
> +	.max = U16_MAX,
> +};
> +
>   const struct nla_policy tipc_nl_prop_policy[TIPC_NLA_PROP_MAX + 1] = {
>   	[TIPC_NLA_PROP_UNSPEC]		= { .type = NLA_UNSPEC },
>   	[TIPC_NLA_PROP_PRIO]		= { .type = NLA_U32 },
>   	[TIPC_NLA_PROP_TOL]		= { .type = NLA_U32 },
>   	[TIPC_NLA_PROP_WIN]		= { .type = NLA_U32 },
> -	[TIPC_NLA_PROP_MTU]		= { .type = NLA_U32 },
> +	[TIPC_NLA_PROP_MTU]		= NLA_POLICY_FULL_RANGE(NLA_U32, &tipc_nl_mtu_range),

netlink_range_validation with only .max range doesn't look great, there
is simply NLA_POLICY_MAX() macro.

if you really want to use range check you can technically set .min to
TIPC_MIN_BEARER_MTU, but you still have to have both checks for min mtu
as there is unavoidable encap overhead...

>   	[TIPC_NLA_PROP_BROADCAST]	= { .type = NLA_U32 },
>   	[TIPC_NLA_PROP_BROADCAST_RATIO]	= { .type = NLA_U32 }
>   };


^ permalink raw reply

* [PATCH net-next v2 11/12] ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

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>
---
 drivers/net/usb/ax88179_178a.c | 75 +++++++++++++++++++++++++++++++++---------
 1 file changed, 59 insertions(+), 16 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 31e0ec1c15b455b8810f73d4d8ae9a5b5b05f9a2..3972eff910ca766cd6e2cca0431ca50a2b02dd2b 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -17,6 +17,8 @@
 
 #define AX88179_PHY_ID				0x03
 #define AX_EEPROM_LEN				0x100
+#define AX88279_EEPROM_LEN			0x4000
+#define AX88179A_EEPROM_LEN			(32 * 20)
 #define AX88179_EEPROM_MAGIC			0x17900b95
 #define AX_MCAST_FLTSIZE			8
 #define AX_MAX_MCAST				64
@@ -37,8 +39,11 @@
 #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
@@ -805,41 +810,74 @@ ax88179_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
 
 static int ax88179_get_eeprom_len(struct net_device *net)
 {
-	return AX_EEPROM_LEN;
+	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *ax179_data;
+
+	ax179_data = dev->driver_priv;
+
+	if (ax179_data->chip_version < AX_VERSION_AX88179A)
+		return AX_EEPROM_LEN;
+	else if (ax179_data->chip_version >= AX_VERSION_AX88279)
+		return AX88279_EEPROM_LEN;
+	else
+		return AX88179A_EEPROM_LEN;
 }
 
-static int
-ax88179_get_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom,
-		   u8 *data)
+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;
+	}
+}
+
+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;
+	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;
 }
@@ -849,12 +887,17 @@ ax88179_set_eeprom(struct net_device *net, struct ethtool_eeprom *eeprom,
 		   u8 *data)
 {
 	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *ax179_data;
 	u16 *eeprom_buff;
 	int first_word;
 	int last_word;
 	int ret;
 	int i;
 
+	ax179_data = dev->driver_priv;
+	if (ax179_data->chip_version >= AX_VERSION_AX88179A)
+		return -EOPNOTSUPP;
+
 	netdev_dbg(net, "write EEPROM len %d, offset %d, magic 0x%x\n",
 		   eeprom->len, eeprom->offset, eeprom->magic);
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 12/12] ax88179_178a: Add AX179A/AX279 multicast configuration
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

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>
---
 drivers/net/usb/ax88179_178a.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 3972eff910ca766cd6e2cca0431ca50a2b02dd2b..d7c021388979595b9c0773eea2126080ca7d2d86 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -1313,10 +1313,14 @@ static int ax179a_vlan_rx_add_vid(struct net_device *net, __be16 proto, u16 vid)
 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);
+	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;

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 10/12] ax88179_178a: Add ethtool get_drvinfo
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

Add ax88179_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>
---
 drivers/net/usb/ax88179_178a.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 519d01e80f938346b6c8742be36b70ae31bc2855..31e0ec1c15b455b8810f73d4d8ae9a5b5b05f9a2 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -764,6 +764,21 @@ static void ax88179_disconnect(struct usb_interface *intf)
 	usbnet_disconnect(intf);
 }
 
+static void ax88179_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *priv = dev->driver_priv;
+
+	/* 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
 ax88179_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo)
 {
@@ -1177,6 +1192,7 @@ static int ax88179_set_eee(struct net_device *net, struct ethtool_keee *edata)
 }
 
 static const struct ethtool_ops ax88179_ethtool_ops = {
+	.get_drvinfo		= ax88179_get_drvinfo,
 	.get_link		= ethtool_op_get_link,
 	.get_msglevel		= usbnet_get_msglevel,
 	.set_msglevel		= usbnet_set_msglevel,

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 09/12] ax88179_178a: Add VLAN offload support for AX88179A
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

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>
---
 drivers/net/usb/ax88179_178a.c | 87 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 925b66f8b8fff10578056bb13e15dc4ba0f88ecc..519d01e80f938346b6c8742be36b70ae31bc2855 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -1195,6 +1195,62 @@ static const struct ethtool_ops ax88179_ethtool_ops = {
 	.get_ts_info		= ethtool_op_get_ts_info,
 };
 
+static int ax179a_vlan_rx_kill_vid(struct net_device *net, __be16 proto, u16 vid)
+{
+	struct usbnet *dev = netdev_priv(net);
+	u8 vlan_ctrl;
+	u16 reg16;
+	u8 reg8;
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+	vlan_ctrl = reg8;
+
+	/* Address */
+	reg8 = (vid / 16);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, &reg8);
+
+	/* Data */
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+	reg16 &= ~(1 << (vid % 16));
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	return 0;
+}
+
+static int ax179a_vlan_rx_add_vid(struct net_device *net, __be16 proto, u16 vid)
+{
+	struct usbnet *dev = netdev_priv(net);
+	u8 vlan_ctrl;
+	u16 reg16;
+	u8 reg8;
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+	vlan_ctrl = reg8;
+
+	/* Address */
+	reg8 = (vid / 16);
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_ADDRESS, 1, 1, &reg8);
+
+	/* Data */
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_RD;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+	reg16 |= (1 << (vid % 16));
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_DATA0, 2, 2, &reg16);
+
+	reg8 = vlan_ctrl | AX_VLAN_CONTROL_WE;
+	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_VLAN_ID_CONTROL, 1, 1, &reg8);
+
+	return 0;
+}
+
 static void ax88179_set_multicast(struct net_device *net)
 {
 	struct usbnet *dev = netdev_priv(net);
@@ -1240,6 +1296,7 @@ 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) {
@@ -1259,6 +1316,34 @@ 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);
+	}
+
+	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;
@@ -1326,6 +1411,8 @@ static const struct net_device_ops ax88179_netdev_ops = {
 	.ndo_eth_ioctl		= usbnet_mii_ioctl,
 	.ndo_set_rx_mode	= ax88179_set_multicast,
 	.ndo_set_features	= ax88179_set_features,
+	.ndo_vlan_rx_add_vid	= ax179a_vlan_rx_add_vid,
+	.ndo_vlan_rx_kill_vid	= ax179a_vlan_rx_kill_vid,
 };
 
 static int ax88179_check_eeprom(struct usbnet *dev)

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 08/12] ax88179_178a: Add support for ethtool pause parameter configuration
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

The AX179A-based chips support pause parameter configuration.
Make it available through ethtool ops.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 48 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 5f3a6d85cc4054e43e085531fcd715a990d87c6a..925b66f8b8fff10578056bb13e15dc4ba0f88ecc 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -973,6 +973,52 @@ static int ax88179_set_link_ksettings(struct net_device *net,
 	return mii_ethtool_set_link_ksettings(&dev->mii, cmd);
 }
 
+static void ax88179a_get_pauseparam(struct net_device *net, struct ethtool_pauseparam *pause)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *data;
+
+	data = dev->driver_priv;
+	pause->autoneg = data->pause_autoneg;
+	pause->tx_pause = data->pause_tx;
+	pause->rx_pause = data->pause_rx;
+}
+
+static int ax88179a_set_pauseparam(struct net_device *net, struct ethtool_pauseparam *pause)
+{
+	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *data;
+	u16 old, new, bmcr;
+	u8 cap = 0;
+
+	data = dev->driver_priv;
+
+	if (data->chip_version < AX_VERSION_AX88179A)
+		return -EOPNOTSUPP;
+
+	bmcr = ax88179_mdio_read(net, dev->mii.phy_id, MII_BMCR);
+	if (pause->autoneg && !(bmcr & BMCR_ANENABLE))
+		return -EINVAL;
+
+	data->pause_autoneg = pause->autoneg;
+	data->pause_tx = pause->tx_pause;
+	data->pause_rx = pause->rx_pause;
+
+	if (pause->rx_pause)
+		cap |= FLOW_CTRL_RX;
+
+	if (pause->tx_pause)
+		cap |= FLOW_CTRL_TX;
+
+	old = ax88179_mdio_read(net, dev->mii.phy_id, MII_ADVERTISE);
+	new = (old & ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) |
+		mii_advertise_flowctrl(cap);
+	if (old != new)
+		ax88179_mdio_write(net, dev->mii.phy_id, MII_ADVERTISE, new);
+
+	return mii_nway_restart(&dev->mii);
+}
+
 static int
 ax88179_ethtool_get_eee(struct usbnet *dev, struct ethtool_keee *data)
 {
@@ -1144,6 +1190,8 @@ static const struct ethtool_ops ax88179_ethtool_ops = {
 	.nway_reset		= usbnet_nway_reset,
 	.get_link_ksettings	= ax88179_get_link_ksettings,
 	.set_link_ksettings	= ax88179_set_link_ksettings,
+	.get_pauseparam		= ax88179a_get_pauseparam,
+	.set_pauseparam		= ax88179a_set_pauseparam,
 	.get_ts_info		= ethtool_op_get_ts_info,
 };
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 07/12] ax88179_178a: Obtain speed and duplex from Interrupt URB
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

For newer AX179A/772D firmwares and the AX88279, the
interrupt URB response also contains information on the Ethernet
speed and duplex status. Read this in order to use it to configure
the link.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 94820dd1e7e4cbbcd2028783288615eef79b16e2..5f3a6d85cc4054e43e085531fcd715a990d87c6a 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -490,6 +490,7 @@ static int ax88179_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index,
 
 static void ax88179_status(struct usbnet *dev, struct urb *urb)
 {
+	struct ax88179_data *data = dev->driver_priv;
 	struct ax88179_int_data *event;
 	u32 link;
 
@@ -500,6 +501,9 @@ static void ax88179_status(struct usbnet *dev, struct urb *urb)
 	le32_to_cpus((void *)&event->intdata1);
 
 	link = (((__force u32)event->intdata1) & AX_INT_PPLS_LINK) >> 16;
+	data->speed = (((__force u32)event->intdata1) >> 8) & 0x7;
+	data->full_duplex = (((__force u32)event->intdata1) >> 12) & 0x1;
+	data->link = link;
 
 	if (netif_carrier_ok(dev->net) != link) {
 		usbnet_link_change(dev, link, 1);

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 06/12] ax88179_178a: EEE setup for AX88179A-based chips
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

Add EEE setup for AX88179A-based chips. For these
chips we enable EEE by default. Adapt the ax88179_chk_eee()
EEE status verification function to also support AX88179A-based
chips in order to make sure the initial setup leads to
a correctly identified EEE status.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 42 ++++++++++++++++++++++++++++--------------
 1 file changed, 28 insertions(+), 14 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 7d1d75d570a3de013b5eda11e0ce0eac988eab49..94820dd1e7e4cbbcd2028783288615eef79b16e2 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -972,6 +972,7 @@ static int ax88179_set_link_ksettings(struct net_device *net,
 static int
 ax88179_ethtool_get_eee(struct usbnet *dev, struct ethtool_keee *data)
 {
+	struct ax88179_data *priv = dev->driver_priv;
 	int val;
 
 	/* Get Supported EEE */
@@ -992,6 +993,13 @@ ax88179_ethtool_get_eee(struct usbnet *dev, struct ethtool_keee *data)
 		return val;
 	mii_eee_cap1_mod_linkmode_t(data->lp_advertised, val);
 
+	if (priv->chip_version >= AX_VERSION_AX88279) {
+		val = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE2);
+		if (val < 0)
+			return val;
+		mii_eee_cap2_mod_linkmode_adv_t(data->lp_advertised, val);
+	}
+
 	return 0;
 }
 
@@ -1010,7 +1018,9 @@ static int ax88179_chk_eee(struct usbnet *dev)
 
 	mii_ethtool_gset(&dev->mii, &ecmd);
 
-	if (ecmd.duplex & DUPLEX_FULL) {
+	priv->eee_active = 0;
+	if ((priv->chip_version < AX_VERSION_AX88279 && (ecmd.duplex & DUPLEX_FULL)) ||
+	    (ecmd.speed == SPEED_1000 && (ecmd.duplex & DUPLEX_FULL))) {
 		int eee_lp, eee_cap, eee_adv;
 		u32 lp, cap, adv, supported = 0;
 
@@ -1027,10 +1037,8 @@ static int ax88179_chk_eee(struct usbnet *dev)
 			return true;
 
 		eee_adv = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
-		if (eee_adv < 0) {
-			priv->eee_active = 0;
-			return false;
-		}
+		if (eee_adv < 0)
+			return true;
 
 		adv = mmd_eee_adv_to_ethtool_adv_t(eee_adv);
 		lp = mmd_eee_adv_to_ethtool_adv_t(eee_lp);
@@ -1038,16 +1046,13 @@ static int ax88179_chk_eee(struct usbnet *dev)
 			     SUPPORTED_1000baseT_Full :
 			     SUPPORTED_100baseT_Full;
 
-		if (!(lp & adv & supported)) {
-			priv->eee_active = 0;
-			return false;
-		}
+		if (!(lp & adv & supported))
+			return true;
 
 		priv->eee_active = 1;
 		return true;
 	}
 
-	priv->eee_active = 0;
 	return false;
 }
 
@@ -2535,11 +2540,20 @@ static int ax88179_reset(struct usbnet *dev)
 	ax179_data->eee_enabled = 0;
 	ax179_data->eee_active = 0;
 
-	ax88179_eee_config(dev, false);
+	if (ax179_data->chip_version < AX_VERSION_AX88179A) {
+		ax88179_eee_config(dev, false);
 
-	ax88179_ethtool_get_eee(dev, &eee_data);
-	linkmode_zero(eee_data.advertised);
-	ax88179_ethtool_set_eee(dev, &eee_data);
+		ax88179_ethtool_get_eee(dev, &eee_data);
+		linkmode_zero(eee_data.advertised);
+		ax88179_ethtool_set_eee(dev, &eee_data);
+	} else {
+		ax88179_eee_config(dev, true);
+		ax88179_ethtool_get_eee(dev, &eee_data);
+		linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, eee_data.advertised);
+		if (ax179_data->chip_version >= AX_VERSION_AX88279)
+			linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, eee_data.advertised);
+		ax88179_ethtool_set_eee(dev, &eee_data);
+	}
 
 	/* Restart autoneg */
 	mii_nway_restart(&dev->mii);

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 05/12] ax88179_178a: Add support for 2500 link speed of AX88279
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

Tha AX88279 support a link speed of 2500Mbit/s. Add support for
configuring this link speed in the ethtool link status getter/setter
functions.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 52 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index c2ebc8ae8d4ce72cbd04c2c16b9940416e9f427b..7d1d75d570a3de013b5eda11e0ce0eac988eab49 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -904,9 +904,36 @@ static int ax88179_get_link_ksettings(struct net_device *net,
 				      struct ethtool_link_ksettings *cmd)
 {
 	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *data;
+	int v;
+
+	data = dev->driver_priv;
 
 	mii_ethtool_get_link_ksettings(&dev->mii, cmd);
 
+	if (data->chip_version >= AX_VERSION_AX88279) {
+		linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+				 cmd->link_modes.supported);
+
+		v = ax88179_mdio_read(dev->net, dev->mii.phy_id, MII_ADVERTISE);
+		if (v >= 0 && v & AX_ADVERTISE_2500)
+			linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+					 cmd->link_modes.advertising);
+
+		v = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+		if (data->speed == ETHER_LINK_2500) {
+			cmd->base.speed = SPEED_2500;
+			/* 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
+			 */
+			if (v >= 0 && v & MDIO_AN_10GBT_STAT_REMOK)
+				linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+						 cmd->link_modes.lp_advertising);
+		}
+	}
+
 	return 0;
 }
 
@@ -914,6 +941,31 @@ static int ax88179_set_link_ksettings(struct net_device *net,
 				      const struct ethtool_link_ksettings *cmd)
 {
 	struct usbnet *dev = netdev_priv(net);
+	struct ax88179_data *data;
+	int v;
+
+	data = dev->driver_priv;
+
+	/* mii_ethtool_set_link_ksettings handles unknown bits in MII_ADVERTISE
+	 * transparently, so for the 2.5GBit link speed of the AX_VERSION_AX88279
+	 * we just set up AX_ADVERTISE_2500 before calling mii_ethtool_set_link_ksettings
+	 * at least for speeds < 2500
+	 */
+	if (data->chip_version == AX_VERSION_AX88279) {
+		v = ax88179_mdio_read(net, dev->mii.phy_id, MII_ADVERTISE);
+		if (v < 0)
+			return v;
+
+		if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT,
+				      cmd->link_modes.advertising))
+			v |= AX_ADVERTISE_2500;
+		else
+			v &= ~AX_ADVERTISE_2500;
+		ax88179_mdio_write(net, dev->mii.phy_id, MII_ADVERTISE, v);
+		if (cmd->base.speed == SPEED_2500)
+			return mii_nway_restart(&dev->mii);
+	}
+
 	return mii_ethtool_set_link_ksettings(&dev->mii, cmd);
 }
 

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 04/12] ax88179_178a: Add EEE HW configuration support for AX88179A
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

The AX88179A uses a much simpler HW configuration for EEE
via a single EEE configuration register. Add support for
this register and replace the EEE enable/disable functions
with a single EEE configuration function which enables/disables
EEE in the hardware.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 71 +++++++++++++++++++-----------------------
 1 file changed, 32 insertions(+), 39 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index c483b7b636e37d45455d46e1b0354fd63e0ef100..c2ebc8ae8d4ce72cbd04c2c16b9940416e9f427b 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -35,6 +35,7 @@
 #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_WRITE			0x24
 #define AX88179A_PHY_CLAUSE45			0x27
@@ -125,6 +126,9 @@
 #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
@@ -995,46 +999,35 @@ static int ax88179_chk_eee(struct usbnet *dev)
 	return false;
 }
 
-static void ax88179_disable_eee(struct usbnet *dev)
-{
-	u16 tmp16;
-
-	tmp16 = GMII_PHY_PGSEL_PAGE3;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  GMII_PHY_PAGE_SELECT, 2, &tmp16);
-
-	tmp16 = 0x3246;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  MII_PHYADDR, 2, &tmp16);
-
-	tmp16 = GMII_PHY_PGSEL_PAGE0;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  GMII_PHY_PAGE_SELECT, 2, &tmp16);
-}
-
-static void ax88179_enable_eee(struct usbnet *dev)
+static void ax88179_eee_config(struct usbnet *dev, bool enable)
 {
+	struct ax88179_data *priv = dev->driver_priv;
 	u16 tmp16;
 
-	tmp16 = GMII_PHY_PGSEL_PAGE3;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  GMII_PHY_PAGE_SELECT, 2, &tmp16);
-
-	tmp16 = 0x3247;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  MII_PHYADDR, 2, &tmp16);
-
-	tmp16 = GMII_PHY_PGSEL_PAGE5;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  GMII_PHY_PAGE_SELECT, 2, &tmp16);
-
-	tmp16 = 0x0680;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  MII_BMSR, 2, &tmp16);
+	if (priv->chip_version < AX_VERSION_AX88179A) {
+		tmp16 = GMII_PHY_PGSEL_PAGE3;
+		ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
+				  GMII_PHY_PAGE_SELECT, 2, &tmp16);
+
+		tmp16 = enable ? 0x3247 : 0x3246;
+		ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
+				  MII_PHYADDR, 2, &tmp16);
+		if (enable) {
+			tmp16 = GMII_PHY_PGSEL_PAGE5;
+			ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
+					  GMII_PHY_PAGE_SELECT, 2, &tmp16);
+
+			tmp16 = 0x0680;
+			ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
+					  MII_BMSR, 2, &tmp16);
+		}
 
-	tmp16 = GMII_PHY_PGSEL_PAGE0;
-	ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
-			  GMII_PHY_PAGE_SELECT, 2, &tmp16);
+		tmp16 = GMII_PHY_PGSEL_PAGE0;
+		ax88179_write_cmd(dev, AX_ACCESS_PHY, AX88179_PHY_ID,
+				  GMII_PHY_PAGE_SELECT, 2, &tmp16);
+	} else {
+		ax88179_write_cmd(dev, AX_GPHY_CTL, AX_GPHY_EEE_CTRL, enable, 0, NULL);
+	}
 }
 
 static int ax88179_get_eee(struct net_device *net, struct ethtool_keee *edata)
@@ -1056,13 +1049,13 @@ static int ax88179_set_eee(struct net_device *net, struct ethtool_keee *edata)
 
 	priv->eee_enabled = edata->eee_enabled;
 	if (!priv->eee_enabled) {
-		ax88179_disable_eee(dev);
+		ax88179_eee_config(dev, false);
 	} else {
 		priv->eee_enabled = ax88179_chk_eee(dev);
 		if (!priv->eee_enabled)
 			return -EOPNOTSUPP;
 
-		ax88179_enable_eee(dev);
+		ax88179_eee_config(dev, true);
 	}
 
 	ret = ax88179_ethtool_set_eee(dev, edata);
@@ -2490,7 +2483,7 @@ static int ax88179_reset(struct usbnet *dev)
 	ax179_data->eee_enabled = 0;
 	ax179_data->eee_active = 0;
 
-	ax88179_disable_eee(dev);
+	ax88179_eee_config(dev, false);
 
 	ax88179_ethtool_get_eee(dev, &eee_data);
 	linkmode_zero(eee_data.advertised);

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 02/12] ax88179_178a: Add HW support for AX179A-based chips
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

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>
---
 drivers/net/usb/ax88179_178a.c | 961 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 926 insertions(+), 35 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 945c071dfd1d2f0816c779e1a401ac158adc8d99..3aad69b5d4f8ad43527fa7cf51304af9a926ea78 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -13,6 +13,7 @@
 #include <linux/usb/usbnet.h>
 #include <uapi/linux/mdio.h>
 #include <linux/mdio.h>
+#include <linux/if_vlan.h>
 
 #define AX88179_PHY_ID				0x03
 #define AX_EEPROM_LEN				0x100
@@ -32,17 +33,28 @@
 #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 AX88179A_FLASH_READ			0x21
+#define AX88179A_FLASH_WRITE			0x24
+#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
@@ -62,6 +74,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
 
@@ -91,6 +112,17 @@
 	#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_CLK_SELECT				0x33
 	#define AX_CLK_SELECT_BCS	0x01
@@ -111,7 +143,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
@@ -164,8 +240,58 @@
 	#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
+
 static int ax88179_reset(struct usbnet *dev);
 
+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;
@@ -174,6 +300,19 @@ 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;
+	bool pause_autoneg;
+	bool pause_tx;
+	bool pause_rx;
+	u8 rx_checksum;
+	u8 eeprom_read_cmd;
+	u16 eeprom_block;
 };
 
 struct ax88179_int_data {
@@ -181,15 +320,48 @@ struct ax88179_int_data {
 	__le32 intdata2;
 };
 
-static const struct {
+struct ax_bulkin_settings {
 	unsigned char ctrl, timer_l, timer_h, size, ifg;
-} AX88179_BULKIN_SIZE[] =	{
+};
+
+static const struct ax_bulkin_settings AX88179_BULKIN_SIZE[] =	{
 	{7, 0x4f, 0,	0x12, 0xff},
 	{7, 0x20, 3,	0x16, 0xff},
 	{7, 0xae, 7,	0x18, 0xff},
 	{7, 0xcc, 0x4c, 0x18, 8},
 };
 
+static const struct ax_bulkin_settings AX88179A_BULKIN_SIZE[] = {
+	{5, 0x7B, 0x00,	0x17, 0x0F},	/* 1G, SS */
+	{5, 0xC0, 0x02,	0x06, 0x0F},	/* 1G, HS */
+	{7, 0xF0, 0x00,	0x0C, 0x0F},	/* 100M, Full, SS */
+	{6, 0x00, 0x00,	0x06, 0x0F},	/* 100M, Half, SS */
+	{5, 0xC0, 0x04,	0x06, 0x0F},	/* 100M, Full, HS */
+	{7, 0xC0, 0x04,	0x06, 0x0F},	/* 100M, Half, HS */
+	{7, 0x00, 0x00,	0x03, 0x3F},	/* FS */
+};
+
+static const struct ax_bulkin_settings AX88772D_BULKIN_SIZE[] = {
+	{0, 0x00, 0x00,	0x00, 0x00},	/* 1G, SS (unused) */
+	{0, 0x00, 0x00,	0x00, 0x00},	/* 1G, HS (unused) */
+	{0, 0x00, 0x00,	0x00, 0x00},	/* 100M, Full, SS (unused) */
+	{0, 0x00, 0x00,	0x00, 0x00},	/* 100M, Half, SS (unused) */
+	{5, 0xC0, 0x04,	0x06, 0x0F},	/* 100M, Full, HS */
+	{7, 0xC0, 0x04,	0x06, 0x0F},	/* 100M, Half, HS */
+	{7, 0x00, 0x00,	0x03, 0x3F},	/* FS */
+};
+
+static const struct ax_bulkin_settings AX88279_BULKIN_SIZE[] = {
+	{5, 0x10, 0x01,	0x11, 0x0F},	/* 2.5G */
+	{7, 0xB3, 0x01,	0x11, 0x0F},	/* 1G, SS */
+	{7, 0xC0, 0x02,	0x06, 0x0F},	/* 1G, HS */
+	{7, 0x80, 0x01,	0x03, 0x0F},	/* 100M, Full, SS */
+	{7, 0x80, 0x01,	0x03, 0x0F},	/* 100M, Half, SS */
+	{7, 0x80, 0x01,	0x03, 0x0F},	/* 100M, Full, HS */
+	{7, 0x80, 0x01,	0x03, 0x0F},	/* 100M, Half, HS */
+	{7, 0x00, 0x00,	0x03, 0x3F},	/* FS */
+};
+
 static void ax88179_set_pm_mode(struct usbnet *dev, bool pm_mode)
 {
 	struct ax88179_data *ax179_data = dev->driver_priv;
@@ -425,6 +597,26 @@ static int ax88179_suspend(struct usb_interface *intf, pm_message_t message)
 
 		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MONITOR_MOD,
 				  1, 1, &tmp8);
+
+		if (priv->chip_version >= AX_VERSION_AX88179A) {
+			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 if (priv->chip_version == AX_VERSION_AX88279) {
+		ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 8, 0x8000, 0, NULL);
+	}
+
+	if (priv->chip_version >= AX_VERSION_AX88179A) {
+		ax88179_write_cmd(dev, AX88179A_WAKEUP_SETTING, 0, EPHY_LOW_POWER_EN, 0, NULL);
+		ax88179_set_pm_mode(dev, false);
+		return 0;
 	}
 
 	/* Disable RX path */
@@ -456,12 +648,19 @@ static int ax88179_suspend(struct usb_interface *intf, pm_message_t message)
 }
 
 /* This function is used to enable the autodetach function. */
-/* This function is determined by offset 0x43 of EEPROM */
+/* This function is determined by offset 0x43 of EEPROM for the AX88179 */
 static int ax88179_auto_detach(struct usbnet *dev)
 {
+	struct ax88179_data *priv = dev->driver_priv;
 	u16 tmp16;
 	u8 tmp8;
 
+	if (priv->chip_version >= AX_VERSION_AX88179A) {
+		tmp16 = AX88179A_AUTODETACH_DELAY;
+		ax88179_write_cmd(dev, AX88179A_AUTODETACH, tmp16, 0, 0, NULL);
+		return 0;
+	}
+
 	if (ax88179_read_cmd(dev, AX_ACCESS_EEPROM, 0x43, 1, 2, &tmp16) < 0)
 		return 0;
 
@@ -484,11 +683,31 @@ static int ax88179_auto_detach(struct usbnet *dev)
 static int ax88179_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);
 
 	usbnet_link_change(dev, 0, 0);
 
+	if (ax179_data->chip_version >= AX_VERSION_AX88179A) {
+		ax88179_read_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+		if (!(reg8 & AX_PHY_POWER)) {
+			reg8 = AX_PHY_POWER;
+			ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+			msleep(250);
+		}
+		ax88179_write_cmd(dev, AX_FW_MODE, AX_FW_MODE_179A, 0, 0, NULL);
+
+		/* Now, that AX_FW_MODE_179A is enabled, the PHY needs a power-cycle.
+		 * PHY-power is re-enabled in ax88179_reset()
+		 */
+		reg8 = 0;
+		ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &reg8);
+		msleep(250);
+	}
+
 	ax88179_reset(dev);
 
 	ax88179_set_pm_mode(dev, false);
@@ -1293,6 +1512,17 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
 
 	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;
+	ax179_data->ip_align = 1;
+	ax179_data->eeprom_read_cmd = AX_ACCESS_EEPROM;
+	ax179_data->eeprom_block = 2;
+
 	dev->net->netdev_ops = &ax88179_netdev_ops;
 	dev->net->ethtool_ops = &ax88179_ethtool_ops;
 	dev->net->needed_headroom = 8;
@@ -1317,6 +1547,124 @@ 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 int ax88179a_bind(struct usbnet *dev, struct usb_interface *intf)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct ax88179_data *ax179_data;
+	int ret;
+
+	/* Check if vendor configuration */
+	if (udev->actconfig->desc.bConfigurationValue != 1) {
+		netdev_info(dev->net, "Switching to vendor mode\n");
+		usb_driver_set_configuration(udev, 1);
+		return -ENODEV;
+	}
+
+	ret = usbnet_get_endpoints(dev, intf);
+	if (ret < 0)
+		return ret;
+
+	ax179_data = kzalloc_obj(*ax179_data);
+	if (!ax179_data)
+		return -ENOMEM;
+
+	dev->driver_priv = ax179_data;
+
+	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 (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_block = 256;
+	} else {
+		ax179_data->ip_align = 0;
+		ax179_data->eeprom_read_cmd = AX_ACCESS_EFUS;
+		ax179_data->eeprom_block = 20;
+	}
+
+	dev->net->netdev_ops = &ax88179_netdev_ops;
+	dev->net->ethtool_ops = &ax88179_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;
+
+	/* Initialize MII structure */
+	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;
+	dev->mii.reg_num_mask = 0xff;
+	dev->mii.phy_id = 0x03;
+	if (!ax179_data->is_ax88772d)
+		dev->mii.supports_gmii = 1;
+
+	ax179_data->pause_autoneg = 1;
+	ax179_data->pause_tx = 1;
+	ax179_data->pause_rx = 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);
+
+	return 0;
+
+err_nodev:
+	kfree(ax179_data);
+	ax179_data = NULL;
+
+	return ret;
 }
 
 static void ax88179_unbind(struct usbnet *dev, struct usb_interface *intf)
@@ -1338,6 +1686,22 @@ static void ax88179_unbind(struct usbnet *dev, struct usb_interface *intf)
 	kfree(ax179_data);
 }
 
+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);
+
+	tmp8 = 0;
+	ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, &tmp8);
+
+	kfree(ax179_data);
+}
+
 static void
 ax88179_rx_checksum(struct sk_buff *skb, u32 *pkt_hdr)
 {
@@ -1354,6 +1718,21 @@ ax88179_rx_checksum(struct sk_buff *skb, u32 *pkt_hdr)
 		skb->ip_summed = CHECKSUM_UNNECESSARY;
 }
 
+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 ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 {
 	struct sk_buff *ax_skb;
@@ -1472,6 +1851,121 @@ static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 	return 0;
 }
 
+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 = le64_to_cpup((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);
+
+	pkt_end = 0;
+	while (pkt_cnt--) {
+		u64 pkt_desc = le64_to_cpup(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 *
 ax88179_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
 {
@@ -1505,6 +1999,59 @@ ax88179_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
 	return skb;
 }
 
+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));
+	*tx_desc_ptr = cpu_to_le64(tx_desc);
+
+	usbnet_set_skb_tx_stats(skb, 1, 0);
+
+	return skb;
+}
+
 static int ax88179_link_reset(struct usbnet *dev)
 {
 	struct ax88179_data *ax179_data = dev->driver_priv;
@@ -1580,72 +2127,343 @@ static int ax88179_link_reset(struct usbnet *dev)
 	return 0;
 }
 
+static void ax88179a_bulkin_config(struct usbnet *dev, u8 link_sts)
+{
+	struct ax88179_data *ax179_data = dev->driver_priv;
+	const struct ax_bulkin_settings *bulkin_data;
+	int index = 0;
+
+	switch (ax179_data->speed) {
+	case ETHER_LINK_2500:	/* AX88279 only */
+		index = 0;
+		break;
+
+	case ETHER_LINK_1000:	/* AX88279 & AX88178A */
+		if (ax179_data->chip_version == AX_VERSION_AX88279) {
+			if (link_sts & AX_USB_SS)
+				index = 1;
+			else if (link_sts & AX_USB_HS)
+				index = 2;
+		} else {
+			if (link_sts & AX_USB_SS)
+				index = 0;
+			else if (link_sts & AX_USB_HS)
+				index = 1;
+		}
+		break;
+
+	case ETHER_LINK_100:
+		if (ax179_data->chip_version == AX_VERSION_AX88279) {
+			if (link_sts & AX_USB_SS)
+				index = 3;
+			else if (link_sts & AX_USB_HS)
+				index = 5;
+			if (!ax179_data->full_duplex)
+				index++;
+		} else {
+			/* AX88279A & AX88277D */
+			if (link_sts & AX_USB_SS)
+				index = 2;
+			else if (link_sts & AX_USB_HS)
+				index = 4;
+			if (!ax179_data->full_duplex)
+				index++;
+		}
+		break;
+
+	case ETHER_LINK_10:
+		if (ax179_data->chip_version == AX_VERSION_AX88279)
+			index = 7;
+		else
+			index = 6;
+		break;
+
+	default:	/* No link */
+		index = 0;
+	}
+
+	if (ax179_data->chip_version == AX_VERSION_AX88279 && (link_sts & AX_USB_FS))
+		index = 7;
+
+	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 int ax88179a_link_reset(struct usbnet *dev)
+{
+	struct ax88179_data *ax179_data = dev->driver_priv;
+	u8 tmp8, link_sts, reg8[3];
+	u16 tmp16, mode, speed;
+
+	if (!ax179_data->link) {
+		netdev_info(dev->net, "ax88179a - Link status is: 0\n");
+		return 0;
+	}
+
+	/* 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 | AX_MEDIUM_TXFLOW_CTRLEN | AX_MEDIUM_RXFLOW_CTRLEN;
+
+	/* Link is up, but some older AX88179A FW versions do not send link speed
+	 * and duplex status in interrupt URB, so read it via MII
+	 */
+	if (!ax179_data->speed) {
+		struct ethtool_link_ksettings cmd;
+
+		mii_ethtool_get_link_ksettings(&dev->mii, &cmd);
+		ax179_data->full_duplex = cmd.base.duplex;
+		switch (cmd.base.speed) {
+		case SPEED_1000:
+			ax179_data->speed = ETHER_LINK_1000;
+			break;
+		case SPEED_100:
+			ax179_data->speed = ETHER_LINK_100;
+			break;
+		case SPEED_10:
+		default:
+			ax179_data->speed = ETHER_LINK_10;
+			break;
+		};
+	}
+
+	speed = 0;
+	switch (ax179_data->speed) {
+	case ETHER_LINK_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;
+
+		speed = 2500;
+		break;
+
+	case ETHER_LINK_1000:
+		mode |= AX_MEDIUM_GIGAMODE;
+		speed = 1000;
+		fallthrough;
+
+	case ETHER_LINK_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);
+
+		speed = speed ? speed : 100;
+		break;
+
+	case ETHER_LINK_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);
+
+		speed = 10;
+		break;
+	}
+
+	ax88179_read_cmd(dev, AX_ACCESS_MAC, PHYSICAL_LINK_STATUS, 1, 1, &link_sts);
+	ax88179a_bulkin_config(dev, link_sts);
+
+	if (ax179_data->chip_version < AX_VERSION_AX88279) {
+		tmp8 = 0;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX88179A_BFM_DATA, 1, 1, &tmp8);
+	}
+
+	if (ax179_data->full_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);
+
+	ax179_data->eee_enabled = ax88179_chk_eee(dev);
+
+	netif_carrier_on(dev->net);
+
+	netdev_info(dev->net, "ax88179a - Link status is: 1, Link speed: %d, Duplex: %d\n",
+		    speed, ax179_data->full_duplex);
+
+	return 0;
+}
+
 static int ax88179_reset(struct usbnet *dev)
 {
-	u8 buf[5];
-	u16 *tmp16;
-	u8 *tmp;
 	struct ax88179_data *ax179_data = dev->driver_priv;
 	struct ethtool_keee eee_data;
+	u16 *tmp16;
+	u8 buf[5];
+	u8 *tmp;
 
 	tmp16 = (u16 *)buf;
 	tmp = (u8 *)buf;
 
 	/* Power up ethernet PHY */
-	*tmp16 = 0;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16);
+	if (ax179_data->chip_version < AX_VERSION_AX88179A) {
+		*tmp16 = 0;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16);
 
-	*tmp16 = AX_PHYPWR_RSTCTL_IPRL;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16);
-	msleep(500);
+		*tmp16 = AX_PHYPWR_RSTCTL_IPRL;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PHYPWR_RSTCTL, 2, 2, tmp16);
+		msleep(500);
 
-	*tmp = AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, tmp);
-	msleep(200);
+		*tmp = AX_CLK_SELECT_ACS | AX_CLK_SELECT_BCS;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_CLK_SELECT, 1, 1, tmp);
+		msleep(200);
+	} else {
+		*tmp = AX_PHY_POWER;
+		ax88179_write_cmd(dev, AX88179A_PHY_POWER, 0, 0, 1, tmp);
+		msleep(250);
+	}
+
+	if (ax179_data->chip_version == AX_VERSION_AX88279) {
+		*tmp16 = ax88179_mdio_read(dev->net, dev->mii.phy_id, MII_ADVERTISE);
+		*tmp16 &= ~(ADVERTISE_10FULL | ADVERTISE_10HALF);
+		*tmp16 |= AX_ADVERTISE_2500;
+		ax88179_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, *tmp16);
+	}
 
 	/* Ethernet PHY Auto Detach*/
 	ax88179_auto_detach(dev);
 
+	if (ax179_data->chip_version >= AX_VERSION_AX88179A) {
+		*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);
 
 	/* RX bulk configuration */
-	memcpy(tmp, &AX88179_BULKIN_SIZE[0], 5);
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, tmp);
-
-	dev->rx_urb_size = 1024 * 20;
-
-	*tmp = 0x34;
-	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_LOW, 1, 1, tmp);
+	if (ax179_data->chip_version < AX_VERSION_AX88179A) {
+		memcpy(tmp, &AX88179_BULKIN_SIZE[0], 5);
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_BULKIN_QCTRL, 5, 5, tmp);
+		*tmp = 0x34;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_LOW, 1, 1, tmp);
+
+		*tmp = 0x52;
+		ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_PAUSE_WATERLVL_HIGH,
+				  1, 1, tmp);
+		dev->rx_urb_size = 1024 * 20;
+	} else {
+		/* The Bulk-Register configuration for the AX88179A is done in
+		 * ax88179a_link_reset(), 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 */
-	*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;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_RX_CTL, 2, 2, tmp16);
-
-	*tmp = AX_MONITOR_MODE_PMETYPE | AX_MONITOR_MODE_PMEPOL |
-	       AX_MONITOR_MODE_RWMP;
+	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 |
-		 AX_MEDIUM_GIGAMODE;
-	ax88179_write_cmd(dev, AX_ACCESS_MAC, AX_MEDIUM_STATUS_MODE,
-			  2, 2, tmp16);
+		 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;
@@ -1653,7 +2471,11 @@ static int ax88179_reset(struct usbnet *dev)
 			     1, 1, &tmp) > 0)
 		ax179_data->wol_supported = WAKE_MAGIC | WAKE_PHY;
 
-	ax88179_led_setting(dev);
+	/* For chips starting with AX88179A, LEDS are configured by the adapter
+	 * firmware directly from EEPROM/EFUSE values
+	 */
+	if (ax179_data->chip_version < AX_VERSION_AX88179A)
+		ax88179_led_setting(dev);
 
 	ax179_data->eee_enabled = 0;
 	ax179_data->eee_active = 0;
@@ -1706,6 +2528,24 @@ static int ax88179_stop(struct usbnet *dev)
 	return 0;
 }
 
+static int ax88179a_stop(struct usbnet *dev)
+{
+	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);
+
+	return 0;
+}
+
 static const struct driver_info ax88179_info = {
 	.description = "ASIX AX88179 USB 3.0 Gigabit Ethernet",
 	.bind = ax88179_bind,
@@ -1732,6 +2572,45 @@ static const struct driver_info ax88178a_info = {
 	.tx_fixup = ax88179_tx_fixup,
 };
 
+static const struct driver_info ax88179a_info = {
+	.description = "ASIX AX88179A USB 3.2 Gigabit Ethernet",
+	.bind = ax88179a_bind,
+	.unbind = ax88179a_unbind,
+	.status = ax88179_status,
+	.link_reset = ax88179a_link_reset,
+	.reset = ax88179_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,
+};
+
+static const struct driver_info ax88772d_info = {
+	.description = "ASIX AX88772D/E USB 2.0 Fast Ethernet",
+	.bind = ax88179a_bind,
+	.unbind = ax88179a_unbind,
+	.status = ax88179_status,
+	.link_reset = ax88179a_link_reset,
+	.reset = ax88179_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,
+};
+
+static const struct driver_info ax88279_info = {
+	.description = "ASIX AX88279 USB 3.2 2.5Gigabit Ethernet",
+	.bind = ax88179a_bind,
+	.unbind = ax88179a_unbind,
+	.status = ax88179_status,
+	.link_reset = ax88179a_link_reset,
+	.reset = ax88179_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,
+};
+
 static const struct driver_info cypress_GX3_info = {
 	.description = "Cypress GX3 SuperSpeed to Gigabit Ethernet Controller",
 	.bind = ax88179_bind,
@@ -1877,6 +2756,18 @@ static const struct driver_info at_umc2000sp_info = {
 
 static const struct usb_device_id products[] = {
 {
+	/* ASIX AX88179A/B USB 3.2 Gigabit 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 2.5GBit Ethernet */
+	USB_DEVICE_VER(0x0b95, 0x1790, 0x0400, 0x0400),
+	.driver_info = (unsigned long)&ax88279_info,
+}, {
 	/* ASIX AX88179 10/100/1000 */
 	USB_DEVICE_AND_INTERFACE_INFO(0x0b95, 0x1790, 0xff, 0xff, 0),
 	.driver_info = (unsigned long)&ax88179_info,

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 03/12] ax88179_178a: Add MMD accessor function for AX88179A
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

The AX88179A uses a much simpler Clause-45 MMD access interface,
make use of this interface and abstract MMD read/write operations
for the AX88179 and AX88179A architecture by introducing
ax_read_mmd() and ax_write_mmd(), which in turn call the chips'
respective implementation.

Signed-off-by: Birger Koblitz <mail@birger-koblitz.de>
---
 drivers/net/usb/ax88179_178a.c | 66 ++++++++++++++++++++++++------------------
 1 file changed, 38 insertions(+), 28 deletions(-)

diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
index 3aad69b5d4f8ad43527fa7cf51304af9a926ea78..c483b7b636e37d45455d46e1b0354fd63e0ef100 100644
--- a/drivers/net/usb/ax88179_178a.c
+++ b/drivers/net/usb/ax88179_178a.c
@@ -37,6 +37,7 @@
 #define AX_FW_MODE				0x08
 #define AX88179A_FLASH_READ			0x21
 #define AX88179A_FLASH_WRITE			0x24
+#define AX88179A_PHY_CLAUSE45			0x27
 #define AX88179A_ACCESS_BL			0x2A
 #define AX88179A_PHY_POWER			0x31
 #define AX88179A_AUTODETACH			0xC0
@@ -575,6 +576,32 @@ ax88179_phy_write_mmd_indirect(struct usbnet *dev, u16 prtad, u16 devad,
 	return 0;
 }
 
+static int ax_read_mmd(struct usbnet *dev, u16 dev_addr, u16 reg)
+{
+	struct ax88179_data *priv = dev->driver_priv;
+	u16 res;
+	int ret;
+
+	if (priv->chip_version >= AX_VERSION_AX88179A) {
+		ret = ax88179_read_cmd(dev, AX88179A_PHY_CLAUSE45, dev_addr, reg, 2, &res);
+		if (ret < 0)
+			return ret;
+		return res;
+	}
+
+	return ax88179_phy_read_mmd_indirect(dev, reg, dev_addr);
+}
+
+static int ax_write_mmd(struct usbnet *dev, u16 dev_addr, u16 reg, u16 data)
+{
+	struct ax88179_data *priv = dev->driver_priv;
+
+	if (priv->chip_version >= AX_VERSION_AX88179A)
+		return ax88179_write_cmd(dev, AX88179A_PHY_CLAUSE45, dev_addr, reg, 2, &data);
+
+	return ax88179_phy_write_mmd_indirect(dev, reg, dev_addr, data);
+}
+
 static int ax88179_suspend(struct usb_interface *intf, pm_message_t message)
 {
 	struct usbnet *dev = usb_get_intfdata(intf);
@@ -892,22 +919,19 @@ ax88179_ethtool_get_eee(struct usbnet *dev, struct ethtool_keee *data)
 	int val;
 
 	/* Get Supported EEE */
-	val = ax88179_phy_read_mmd_indirect(dev, MDIO_PCS_EEE_ABLE,
-					    MDIO_MMD_PCS);
+	val = ax_read_mmd(dev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
 	if (val < 0)
 		return val;
 	mii_eee_cap1_mod_linkmode_t(data->supported, val);
 
 	/* Get advertisement EEE */
-	val = ax88179_phy_read_mmd_indirect(dev, MDIO_AN_EEE_ADV,
-					    MDIO_MMD_AN);
+	val = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
 	if (val < 0)
 		return val;
 	mii_eee_cap1_mod_linkmode_t(data->advertised, val);
 
 	/* Get LP advertisement EEE */
-	val = ax88179_phy_read_mmd_indirect(dev, MDIO_AN_EEE_LPABLE,
-					    MDIO_MMD_AN);
+	val = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
 	if (val < 0)
 		return val;
 	mii_eee_cap1_mod_linkmode_t(data->lp_advertised, val);
@@ -920,8 +944,7 @@ ax88179_ethtool_set_eee(struct usbnet *dev, struct ethtool_keee *data)
 {
 	u16 tmp16 = linkmode_to_mii_eee_cap1_t(data->advertised);
 
-	return ax88179_phy_write_mmd_indirect(dev, MDIO_AN_EEE_ADV,
-					      MDIO_MMD_AN, tmp16);
+	return ax_write_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, tmp16);
 }
 
 static int ax88179_chk_eee(struct usbnet *dev)
@@ -935,32 +958,19 @@ static int ax88179_chk_eee(struct usbnet *dev)
 		int eee_lp, eee_cap, eee_adv;
 		u32 lp, cap, adv, supported = 0;
 
-		eee_cap = ax88179_phy_read_mmd_indirect(dev,
-							MDIO_PCS_EEE_ABLE,
-							MDIO_MMD_PCS);
-		if (eee_cap < 0) {
-			priv->eee_active = 0;
+		eee_cap = ax_read_mmd(dev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE);
+		if (eee_cap < 0)
 			return false;
-		}
 
 		cap = mmd_eee_cap_to_ethtool_sup_t(eee_cap);
-		if (!cap) {
-			priv->eee_active = 0;
-			return false;
-		}
-
-		eee_lp = ax88179_phy_read_mmd_indirect(dev,
-						       MDIO_AN_EEE_LPABLE,
-						       MDIO_MMD_AN);
-		if (eee_lp < 0) {
-			priv->eee_active = 0;
+		if (!cap)
 			return false;
-		}
 
-		eee_adv = ax88179_phy_read_mmd_indirect(dev,
-							MDIO_AN_EEE_ADV,
-							MDIO_MMD_AN);
+		eee_lp = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE);
+		if (eee_lp < 0)
+			return true;
 
+		eee_adv = ax_read_mmd(dev, MDIO_MMD_AN, MDIO_AN_EEE_ADV);
 		if (eee_adv < 0) {
 			priv->eee_active = 0;
 			return false;

-- 
2.47.3


^ permalink raw reply related

* [PATCH net-next v2 00/12] ax88179_178a: Add support for AX88179A-based chips
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz

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>
---
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 (12):
      ax88179_178a: Fix endianness of pause watermark register
      ax88179_178a: Add HW support for AX179A-based chips
      ax88179_178a: Add MMD accessor function for AX88179A
      ax88179_178a: Add EEE HW configuration support for AX88179A
      ax88179_178a: Add support for 2500 link speed of AX88279
      ax88179_178a: EEE setup for AX88179A-based chips
      ax88179_178a: Obtain speed and duplex from Interrupt URB
      ax88179_178a: Add support for ethtool pause parameter configuration
      ax88179_178a: Add VLAN offload support for AX88179A
      ax88179_178a: Add ethtool get_drvinfo
      ax88179_178a: Add support for AX88179A/772D/279 EEPROM access
      ax88179_178a: Add AX179A/AX279 multicast configuration

 drivers/net/usb/ax88179_178a.c | 1435 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 1298 insertions(+), 137 deletions(-)
---
base-commit: 1a9edf8be190decb17227e3cba540513d93ebb85
change-id: 20260630-ax88179a-a1d89fe21730

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


^ permalink raw reply

* [PATCH net-next v2 01/12] ax88179_178a: Fix endianness of pause watermark register
From: Birger Koblitz @ 2026-07-08 18:39 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: linux-usb, netdev, linux-kernel, Birger Koblitz
In-Reply-To: <20260708-ax88179a-v2-0-0800fedb2e16@birger-koblitz.de>

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>
---
 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 98f899ea2e9462f1ba99281a875385241745458b..945c071dfd1d2f0816c779e1a401ac158adc8d99 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
@@ -1617,11 +1617,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 related

* Re: [PATCH 2/9] ax88179_178a: Add HW support for AX179A-based chips
From: Birger Koblitz @ 2026-07-08 18:37 UTC (permalink / raw)
  To: Paolo Abeni, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski
  Cc: linux-usb, netdev, linux-kernel
In-Reply-To: <0b75cedb-691f-44ca-891c-d42d5cd24fd2@redhat.com>

Thanks for reviewing the patch-series, Paolo!

> 
> Does not build successfully:
> 
> ../drivers/net/usb/ax88179_178a.c: In function ‘ax88179_reset’:
> ../drivers/net/usb/ax88179_178a.c:2376:33: error: ‘AX_VLAN_CONTROL_VFE’
> undeclared (first use in this function)
>   2376 |                         *tmp |= AX_VLAN_CONTROL_VFE;
> 
> Full log here:
> 
> https://netdev-ctrl.bots.linux.dev/logs/build/1119419/14655119/build_32bit/stderr
Fixed in v2.

> 
> Side process note: the patch series shoudl include the target tree
> ('net-next' in this case).

Sorry about that. v2 will be sent out with correct prefix.

Birger

^ permalink raw reply

* Re: [PATCH v12 nf-next 3/7] netfilter: nf_flow_table_offload: Add nf_flow_rule_bridge()
From: Eric Woudstra @ 2026-07-08 18:36 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Florian Westphal, Phil Sutter,
	Nikolay Aleksandrov, Ido Schimmel, Kuniyuki Iwashima,
	Stanislav Fomichev, Samiullah Khawaja, Hangbin Liu, Krishna Kumar,
	Martin Karsten, netdev, netfilter-devel, bridge
In-Reply-To: <ak4dAXHDmTDRr7-b@chamomile>



On 7/8/26 11:48 AM, Pablo Neira Ayuso wrote:
> Hi,
> 
> On Tue, Jul 07, 2026 at 11:10:41AM +0200, Eric Woudstra wrote:
>> Add nf_flow_rule_bridge().
>>
>> It only calls the common rule and adds the redirect.
> 
> I decided to use the new _unsupp() function, so we don't pretend
> bridge hw offload is already supported. We will need a driver before
> we can add this, this stub does not provide much. I guess your goal
> was just to avoid a crash here.
> 

No, I am already using hw_offload between bridged interfaces
on the mt7986 succesfully for almost 2 years.
It works dsa-port to direct interface (lan1 to eth1 on Bananapi R3) and
between direct interfaces (eth0 to eth1 on Bananapi-R3-mini)

It can also be tested with my bridge_fastpath.sh selftest script.
This script uses veth-device pairs to test the software fastpath.
It can also use 2 real interfaces interconnected in a loop of copper,
when chosen with commandline arguments. Then it tests software- and
hardware-fastpath. It also tests many different scenarios.

So this is why I've added it, as it is already functional. If a software
fastpath is setup correctly, the hardware fastpath is also functional.

>> Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
>> Signed-off-by: Eric Woudstra <ericwouds@gmail.com>
>> ---
>>  include/net/netfilter/nf_flow_table.h |  3 +++
>>  net/netfilter/nf_flow_table_offload.c | 13 +++++++++++++
>>  2 files changed, 16 insertions(+)
>>
>> diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
>> index 7b23b245a5a86..5c6e3b65ae85b 100644
>> --- a/include/net/netfilter/nf_flow_table.h
>> +++ b/include/net/netfilter/nf_flow_table.h
>> @@ -368,6 +368,9 @@ void nf_flow_table_offload_flush_cleanup(struct nf_flowtable *flowtable);
>>  int nf_flow_table_offload_setup(struct nf_flowtable *flowtable,
>>  				struct net_device *dev,
>>  				enum flow_block_command cmd);
>> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
>> +			enum flow_offload_tuple_dir dir,
>> +			struct nf_flow_rule *flow_rule);
>>  int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
>>  			    enum flow_offload_tuple_dir dir,
>>  			    struct nf_flow_rule *flow_rule);
>> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
>> index 002ec15d988bd..5566ebda7b7d3 100644
>> --- a/net/netfilter/nf_flow_table_offload.c
>> +++ b/net/netfilter/nf_flow_table_offload.c
>> @@ -740,6 +740,19 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
>>  	return 0;
>>  }
>>  
>> +int nf_flow_rule_bridge(struct net *net, struct flow_offload *flow,
>> +			enum flow_offload_tuple_dir dir,
>> +			struct nf_flow_rule *flow_rule)
>> +{
>> +	if (nf_flow_rule_route_common(net, flow, dir, flow_rule) < 0)
>> +		return -1;
>> +
>> +	flow_offload_redirect(net, flow, dir, flow_rule);
>> +
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(nf_flow_rule_bridge);
>> +
>>  int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
>>  			    enum flow_offload_tuple_dir dir,
>>  			    struct nf_flow_rule *flow_rule)
>> -- 
>> 2.53.0
>>


^ permalink raw reply


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