* [PATCH net] net: sgi: ioc3-eth: unregister netdev before freeing DMA rings
From: raoxu @ 2026-06-29 8:50 UTC (permalink / raw)
To: tsbogend
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-mips, netdev,
linux-kernel, raoxu, stable
From: Xu Rao <raoxu@uniontech.com>
ioc3eth_remove() frees the coherent RX and TX descriptor rings before
unregistering the netdev. If the interface is running,
unregister_netdev() invokes ioc3_close() through ndo_stop.
ioc3_close() stops the device and then calls ioc3_free_rx_bufs() and
ioc3_clean_tx_ring(). Both cleanup functions access descriptors in the
rings, so the current ordering causes CPU accesses to freed coherent
memory. Until ioc3_stop() disables RX and TX DMA, the device may also
continue using the freed ring addresses.
Unregister the netdev before releasing the rings. This lets the core
close a running interface and quiesce the device while the rings are
still valid. Keep the explicit timer deletion because ndo_stop is not
called when the interface is already down.
Fixes: c7b572747549 ("net: sgi: ioc3-eth: allocate space for desc rings only once")
Cc: stable@vger.kernel.org
Signed-off-by: Xu Rao <raoxu@uniontech.com>
---
drivers/net/ethernet/sgi/ioc3-eth.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c
index 261f2d35d579..009f37105eaf 100644
--- a/drivers/net/ethernet/sgi/ioc3-eth.c
+++ b/drivers/net/ethernet/sgi/ioc3-eth.c
@@ -967,11 +967,12 @@ static void ioc3eth_remove(struct platform_device *pdev)
struct net_device *dev = platform_get_drvdata(pdev);
struct ioc3_private *ip = netdev_priv(dev);
+ unregister_netdev(dev);
+ timer_delete_sync(&ip->ioc3_timer);
+
dma_free_coherent(ip->dma_dev, RX_RING_SIZE, ip->rxr, ip->rxr_dma);
dma_free_coherent(ip->dma_dev, TX_RING_SIZE + SZ_16K - 1, ip->tx_ring, ip->txr_dma);
- unregister_netdev(dev);
- timer_delete_sync(&ip->ioc3_timer);
free_netdev(dev);
}
--
2.50.1
^ permalink raw reply related
* [PATCH] net: fman: guard IRQ handlers against pre-init interrupt
From: ZhaoJinming @ 2026-06-29 8:45 UTC (permalink / raw)
To: horms, andrew, madalin.bucur
Cc: andrew+netdev, davem, edumazet, kuba, linux-kernel, netdev,
pabeni, sean.anderson, zhaojinming
read_dts_node() registers shared interrupt handlers via
devm_request_irq() with fman as the dev_id, passing it to fman_irq()
and fman_err_irq(). At registration time, fman is only partially
initialized -- kzalloc_obj() zero-initializes all fields, so
fman->cfg and fman->fpm_regs are NULL.
The handlers guard against incomplete initialization via:
if (!is_init_done(fman->cfg))
return IRQ_NONE;
However, is_init_done(NULL) returns true (intended to indicate that
fman_init() has completed and cfg has been freed). This means when
cfg is NULL before fman_config() allocates it, the guard does not
take effect:
is_init_done(NULL) -> returns true
!true -> false
guard skipped -> proceeds to dereference NULL fpm_regs
If another device on the same shared IRQ line fires during the window
between devm_request_irq() and fman_init(), the handler accesses
NULL fman->fpm_regs via ioread32be(), causing a crash. The window
includes of_platform_populate() which can be slow.
Add an irq_ready flag to struct fman that is set to true only after
fman_init() completes in fman_probe(). Check this flag at the start
of fman_irq() and fman_err_irq() before any register access.
Use READ_ONCE()/WRITE_ONCE() for the flag accesses since it is a
cross-context shared variable written in process context and read
in interrupt context.
This issue was identified in code review during the discussion of a
separate IRQF_SHARED UAF fix patch:
https://lore.kernel.org/netdev/20260626162323.GE1310988@horms.kernel.org/
Signed-off-by: ZhaoJinming <zhaojinming@uniontech.com>
---
drivers/net/ethernet/freescale/fman/fman.c | 5 +++++
drivers/net/ethernet/freescale/fman/fman.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c
index 013273a2de32..f14cb02d85a4 100644
--- a/drivers/net/ethernet/freescale/fman/fman.c
+++ b/drivers/net/ethernet/freescale/fman/fman.c
@@ -2510,6 +2510,8 @@ static irqreturn_t fman_err_irq(int irq, void *handle)
struct fman_fpm_regs __iomem *fpm_rg;
irqreturn_t single_ret, ret = IRQ_NONE;
+ if (!READ_ONCE(fman->irq_ready))
+ return IRQ_NONE;
if (!is_init_done(fman->cfg))
return IRQ_NONE;
@@ -2608,6 +2610,8 @@ static irqreturn_t fman_irq(int irq, void *handle)
struct fman_fpm_regs __iomem *fpm_rg;
irqreturn_t single_ret, ret = IRQ_NONE;
+ if (!READ_ONCE(fman->irq_ready))
+ return IRQ_NONE;
if (!is_init_done(fman->cfg))
return IRQ_NONE;
@@ -2845,6 +2849,7 @@ static int fman_probe(struct platform_device *of_dev)
dev_err(dev, "%s: FMan init failed\n", __func__);
return -EINVAL;
}
+ WRITE_ONCE(fman->irq_ready, true);
if (fman->dts_params.err_irq == 0) {
fman_set_exception(fman, FMAN_EX_DMA_BUS_ERROR, false);
diff --git a/drivers/net/ethernet/freescale/fman/fman.h b/drivers/net/ethernet/freescale/fman/fman.h
index 74eb62eba0d7..ce06d5867a50 100644
--- a/drivers/net/ethernet/freescale/fman/fman.h
+++ b/drivers/net/ethernet/freescale/fman/fman.h
@@ -314,6 +314,7 @@ struct fman {
struct fman_state_struct *state;
struct fman_cfg *cfg;
+ bool irq_ready; /* true after fman_init() completes */
struct muram_info *muram;
struct fman_keygen *keygen;
/* cam section in muram */
--
2.20.1
^ permalink raw reply related
* Re: [PATCH net v2] octeontx2-pf: check DMAC extraction support before filtering
From: Nitin Shetty J @ 2026-06-29 8:42 UTC (permalink / raw)
To: Harshitha Ramamurthy
Cc: netdev, linux-kernel, sgoutham, gakula, sbhatta, hkelam,
bbhushan2, andrew+netdev, davem, edumazet, kuba, pabeni, naveenm,
tduszynski, sumang
In-Reply-To: <20260626062329.871990-1-nshettyj@marvell.com>
On 2026-06-27 at 06:20:38, Harshitha Ramamurthy (hramamurthy@google.com) wrote:
> On Thu, Jun 25, 2026 at 11:24 PM <nshettyj@marvell.com> wrote:
> >
> > From: Suman Ghosh <sumang@marvell.com>
> >
> > Currently, configuring a VF MAC address via the PF (e.g., 'ip link
> > set <pf> vf 0 mac <mac>') blindly attempts to install a DMAC-based
> > hardware filter. However, the hardware parser profile might not
> > support DMAC extraction.
> >
> > Check if the hardware parsing profile supports DMAC extraction
> > before adding the filter. Additionally, emit a warning message
> > to inform the operator if the MAC filter installation fails due
> > to missing DMAC extraction support.
> >
> > Fixes: f0c2982aaf98 ("octeontx2-pf: Add support for SR-IOV management functions")
> > Signed-off-by: Suman Ghosh <sumang@marvell.com>
> > Signed-off-by: Nitin Shetty J <nshettyj@marvell.com>
> >
> > ---
> > v2:
> > - Move the DMAC extraction check from otx2_set_vf_mac() into
> > otx2_do_set_vf_mac() which already holds pf->mbox.lock, so all
> > mbox operations are under a single lock/unlock pair. All error
> > paths now use the existing goto-out pattern, eliminating the
> > scattered mutex_unlock() + return calls from v1.
> > - Return -EOPNOTSUPP instead of 0 when DMAC extraction is not
> > supported, so the caller gets an explicit error rather than a
> > silent success.
>
> Please ensure a minimum of 24 hr gap before posting a new revision and
> also don't post patches in reply to a previous posting as documented
> in:
>
> https://www.kernel.org/doc/html/next/process/maintainer-netdev.html
>
Apologies for the process oversight. I will ensure a 24-hour window
before posting v3, and I will submit it as a new thread.
> > ---
> > .../ethernet/marvell/octeontx2/nic/otx2_pf.c | 33 +++++++++++++++++++
> > 1 file changed, 33 insertions(+)
> >
> > diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
> > index b63df5737ff2..dc7e4a225dd0 100644
> > --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
> > +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_pf.c
> > @@ -2517,10 +2517,43 @@ EXPORT_SYMBOL(otx2_config_hwtstamp_set);
> >
> > static int otx2_do_set_vf_mac(struct otx2_nic *pf, int vf, const u8 *mac)
> > {
> > + struct npc_get_field_status_req *freq;
> > + struct npc_get_field_status_rsp *frsp;
> > struct npc_install_flow_req *req;
> > int err;
> >
> > mutex_lock(&pf->mbox.lock);
> > +
> > + /* Skip installing the DMAC filter if the hardware parser profile
> > + * does not support DMAC extraction.
> > + */
> > + freq = otx2_mbox_alloc_msg_npc_get_field_status(&pf->mbox);
> > + if (!freq) {
> > + err = -ENOMEM;
> > + goto out;
> > + }
>
> I noticed that otx2_set_vf_mac() copies the MAC address into the vf
> config structure before the programming is successful. Is that
> intended?
>
Good catch. This is not intended behavior. I will fix it in next V3.
> > +
> > + freq->field = NPC_DMAC;
> > + if (otx2_sync_mbox_msg(&pf->mbox)) {
> > + err = -EINVAL;
> > + goto out;
> > + }
> > +
> > + frsp = (struct npc_get_field_status_rsp *)otx2_mbox_get_rsp
> > + (&pf->mbox.mbox, 0, &freq->hdr);
> > + if (IS_ERR(frsp)) {
> > + err = PTR_ERR(frsp);
> > + goto out;
> > + }
> > +
> > + if (!frsp->enable) {
> > + netdev_warn(pf->netdev,
> > + "VF %d MAC filter not installed: DMAC extraction not supported by parser profile\n",
> > + vf);
>
> Would a netdev_warn_ratelimited() be better here to avoid spamming the log?
>
Since this block is only executed when we use the 'ip link' command
and does not sit on the runtime hot data path, the potential
for log spam is negligible. I prefer to retain netdev_warn()
so the operator receives an explicit warning when command fails.
> > + err = -EOPNOTSUPP;
> > + goto out;
> > + }
> > +
> > req = otx2_mbox_alloc_msg_npc_install_flow(&pf->mbox);
> > if (!req) {
> > err = -ENOMEM;
> > --
> > 2.48.1
> >
> >
>
^ permalink raw reply
* Re: [patch 09/24] timekeeping: Add CLOCK_AUX support for ktime_get_snapshot_id()
From: Thomas Gleixner @ 2026-06-29 8:39 UTC (permalink / raw)
To: Thomas Weißschuh
Cc: LKML, David Woodhouse, Miroslav Lichvar, John Stultz,
Stephen Boyd, Anna-Maria Behnsen, Frederic Weisbecker,
Arthur Kiyanovski, Rodolfo Giometti, Vincent Donnefort,
Marc Zyngier, Oliver Upton, kvmarm, Oliver Upton, Richard Cochran,
netdev, Takashi Iwai, Miri Korenblit, Johannes Berg, Jacob Keller,
Tony Nguyen, Saeed Mahameed, Peter Hilber, Michael S. Tsirkin,
virtualization, linux-wireless, linux-sound
In-Reply-To: <20260629055344-b74c43de-2a2b-4608-ae4e-ed0ac8e12fd1@linutronix.de>
On Mon, Jun 29 2026 at 05:55, Thomas Weißschuh wrote:
> On Fri, Jun 26, 2026 at 05:17:52PM +0200, Thomas Gleixner wrote:
>> On Fri, Jun 26 2026 at 13:03, Thomas Weißschuh wrote:
>> > On Fri, Jun 26, 2026 at 12:49:41PM +0200, Thomas Gleixner wrote:
>> >> On Fri, Jun 26 2026 at 10:48, Thomas Weißschuh wrote:
>> >> > On Tue, May 26, 2026 at 07:14:13PM +0200, Thomas Gleixner wrote:
>> >> > (...)
>> >> >
>> >> >> static inline void tk_update_aux_offs(struct timekeeper *tk, ktime_t offs)
>> >> >> @@ -1218,6 +1223,12 @@ bool ktime_get_snapshot_id(struct system
>> >> >> tkd = &tk_core;
>> >> >> offs = &tk_core.timekeeper.offs_boot;
>> >> >> break;
>> >> >> + case CLOCK_AUX ... CLOCK_AUX_LAST:
>> >> >> + tkd = aux_get_tk_data(clock_id);
>> >> >> + if (!tkd)
>> >> >> + return false;
>> >> >> + offs = &tkd->timekeeper.offs_aux;
>> >> >> + break;
>> >> >
>> >> > 'tkd' is also used to compute 'monoraw'. However 'tkr_raw' and 'tkr_mono'
>> >> > are the same for auxilary clocks, so this will compute a wrong 'monoraw'.
>> >>
>> >> AUX clocks are independent in the first place and the MONORAW part is
>> >> the "MONORAW" related to the AUX clock itself.
>> >>
>> >> > Instead 'monoraw' should be computed based on 'tk_core'.
>> >> > Which then also requires the sequence locking of 'tk_core'.
>> >>
>> >> No. From a PTP and steering point of view you want the "raw" value which
>> >> is related to the AUX clock itself and not the global one.
>> >
>> > Ack.
>> >
>> > However the kdocs call it 'CLOCK_MONOTONIC_RAW'. Can we clean this up?
>>
>> Yes. Something like the below?
>
> Looks good, thanks.
> The corresponding structure definitions are a also affected, though.
True.
^ permalink raw reply
* [PATCH net] net: sgi: ioc3-eth: fix split TX DMA mapping lengths
From: raoxu @ 2026-06-29 8:06 UTC (permalink / raw)
To: tsbogend
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-mips, netdev,
linux-kernel, raoxu, stable
From: Xu Rao <raoxu@uniontech.com>
When a linear skb crosses a 16 KiB boundary, ioc3_start_xmit()
splits it into two buffers of lengths s1 and s2. The descriptor
advertises those lengths through B1CNT and B2CNT.
The first buffer is mapped with s1, but the second buffer is also
mapped with s1 even though the device is told to fetch s2 bytes from
it. When the lengths differ, the DMA mapping does not cover the same
region as the second descriptor buffer, which can result in incorrect
cache maintenance or a DMA fault on implementations that enforce the
mapped range.
There is a separate mismatch in the error path. If mapping the second
buffer fails, only d1 needs to be unmapped. d1 was mapped for s1 bytes,
but the driver unmaps it using the full packet length. Streaming DMA
mappings must be unmapped with the same size used for the corresponding
map operation.
Map the second buffer with s2 and unmap the first buffer with s1 when
the second mapping fails.
Fixes: ed870f6a7aa2 ("net: sgi: ioc3-eth: use dma-direct for dma allocations")
Cc: stable@vger.kernel.org
Signed-off-by: Xu Rao <raoxu@uniontech.com>
---
drivers/net/ethernet/sgi/ioc3-eth.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/sgi/ioc3-eth.c b/drivers/net/ethernet/sgi/ioc3-eth.c
index 3973106..261f2d3 100644
--- a/drivers/net/ethernet/sgi/ioc3-eth.c
+++ b/drivers/net/ethernet/sgi/ioc3-eth.c
@@ -1061,9 +1061,9 @@ static netdev_tx_t ioc3_start_xmit(struct sk_buff *skb, struct net_device *dev)
d1 = dma_map_single(ip->dma_dev, skb->data, s1, DMA_TO_DEVICE);
if (dma_mapping_error(ip->dma_dev, d1))
goto drop_packet;
- d2 = dma_map_single(ip->dma_dev, (void *)b2, s1, DMA_TO_DEVICE);
+ d2 = dma_map_single(ip->dma_dev, (void *)b2, s2, DMA_TO_DEVICE);
if (dma_mapping_error(ip->dma_dev, d2)) {
- dma_unmap_single(ip->dma_dev, d1, len, DMA_TO_DEVICE);
+ dma_unmap_single(ip->dma_dev, d1, s1, DMA_TO_DEVICE);
goto drop_packet;
}
desc->p1 = cpu_to_be64(ioc3_map(d1, PCI64_ATTR_PREF));
--
2.47.3
^ permalink raw reply related
* Re: [PATCH net-next] net: neigh: avoid calling neigh_forced_gc on every alloc when table is full
From: Vimal Agrawal @ 2026-06-29 7:57 UTC (permalink / raw)
To: Kuniyuki Iwashima, kuba; +Cc: edumazet, netdev, vimal.agrawal
In-Reply-To: <20260625214659.1115819-1-kuniyu@google.com>
Hi Kuniyuki,
Thank you for the feedback.
However, the rate limiting issue exists independently of the threshold
values. If entries genuinely exceed gc_thresh3 — regardless of what it
is set to — neigh_forced_gc() is called on every allocation attempt
with no rate limiting. In my workload, most entries are
active/reachable with refcnt > 1, so the GC walk traverses the entire
table without reclaiming anything. Increasing gc_thresh3 would make
this worse, not better, as GC now has a larger table to scan on each
call.
Regarding neigh_hash_shift: in my workload, neigh_alloc() returns
ENOBUFS before reaching do_alloc() since GC cannot reclaim any
entries. kzalloc() is never called, so neigh_hash_grow() is not
involved in the latency I observed. The pre-lock time check in
neigh_forced_gc() is a low-cost safeguard that prevents repeated full
table scans regardless of gc_thresh3 value. It does not interfere with
correct GC behaviour — if entries are still above the threshold, GC
runs normally.
Hi Jakub,
I tested with different threshold values, filling the table completely
with 32k reachable entries and attempting 1000 additional allocations.
Exported neigh_forced_gc so that it can be profiled
no change 10ms 50ms 100ms
max cpu usage % 44% 11.8% 2.56% 1.42%
calls > 100us (of 1000) 101 31 13 7
At 10ms, max CPU usage is still 11.8% and 31 out of 1000 calls take
more than 100us. Given that 50ms reduces this to 2.56% and 13 calls
respectively, I would prefer 50ms as the threshold. However, I am open
to further discussion on the right value.
Thanks,
Vimal
On Fri, Jun 26, 2026 at 3:17 AM Kuniyuki Iwashima <kuniyu@google.com> wrote:
>
> From: Vimal Agrawal <avimalin@gmail.com>
> Date: Thu, 25 Jun 2026 10:20:20 +0000
> > Once the neighbour table exceeds gc_thresh3, neigh_forced_gc() is called
> > on every allocation attempt with no rate limiting. In workloads with mostly
> > active/reachable entries, the GC walk traverses a large portion of the
> > neighbour table without reclaiming entries, holding tbl->lock for an
> > extended period. This causes severe lock contention and allocation
> > latencies exceeding 16ms under sustained neighbour creation.
> >
> > Add a pre-lock check in neigh_forced_gc() to skip the GC run if one was
> > performed within the last second, avoiding repeated full table scans and
> > lock acquisitions on the hot allocation path.
> >
> > Profiling of neigh_create() shows ~3 orders of magnitude latency
> > improvement with this change.
> >
> > Link:https://lore.kernel.org/netdev/CALkUMdSCpx_ywYCx_ePLdm6yioO1nQWx7sSM=AEgsq0kywHxTw@mail.gmail.com/
>
> From the thread, these look misconfigured.
>
> ---8<---
> net.ipv6.neigh.default.gc_thresh2 = 32768
> net.ipv6.neigh.default.gc_thresh3 = 32768
> ---8<---
>
> If gc_thresh3 is larger enough, gc_thresh2 will give you 5s
> rate limiting.
>
> If the number of active neigh entries constantly exceeds
> gc_thresh3, it will be the correct gc_thresh2 for you.
>
> Also, I guess you want a new kernel param for the first
> neigh_hash_alloc(), which is currently fixed for 3, which
> is too small for some hosts.
>
> 50000 entries require neigh_hash_grow() 13 times.
>
> Can you test this on your real workload, starting from
> neigh_hash_shift=16 and appropriate gc_thresh2/3 ?
>
> ---8<---
> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index 1349c0eedb64..a75b3750eec9 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c
> @@ -1817,6 +1817,22 @@ EXPORT_SYMBOL(neigh_parms_release);
> static struct lock_class_key neigh_table_proxy_queue_class;
>
> static struct neigh_table __rcu *neigh_tables[NEIGH_NR_TABLES] __read_mostly;
> +static __initdata unsigned long neigh_hash_shift = 3;
> +
> +static int __init neigh_set_hash_shift(char *str)
> +{
> + ssize_t ret;
> +
> + if (!str)
> + return 0;
> +
> + ret = kstrtoul(str, 0, &neigh_hash_shift);
> + if (ret)
> + return 0;
> +
> + return 1;
> +}
> +__setup("neigh_hash_shift=", neigh_set_hash_shift);
>
> void neigh_table_init(int index, struct neigh_table *tbl)
> {
> @@ -1843,7 +1859,7 @@ void neigh_table_init(int index, struct neigh_table *tbl)
> panic("cannot create neighbour proc dir entry");
> #endif
>
> - RCU_INIT_POINTER(tbl->nht, neigh_hash_alloc(3));
> + RCU_INIT_POINTER(tbl->nht, neigh_hash_alloc(neigh_hash_shift));
>
> phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
> tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);
> ---8<---
>
>
>
> > Signed-off-by: Vimal Agrawal <vimal.agrawal@sophos.com>
> > ---
> > net/core/neighbour.c | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> > index 1349c0eedb64..078842db3c5f 100644
> > --- a/net/core/neighbour.c
> > +++ b/net/core/neighbour.c
> > @@ -260,6 +260,9 @@ static int neigh_forced_gc(struct neigh_table *tbl)
> > int shrunk = 0;
> > int loop = 0;
> >
> > + if (!time_after(jiffies, READ_ONCE(tbl->last_flush) + HZ))
> > + return 0;
> > +
> > NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs);
> >
> > spin_lock_bh(&tbl->lock);
> > --
> > 2.17.1
> > v
^ permalink raw reply
* RE: [PATCH] dt-bindings: Fix bracket
From: Jagielski, Jedrzej @ 2026-06-29 7:55 UTC (permalink / raw)
To: Manuel Ebner, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Oleksij Rempel,
open list:NETWORKING DRIVERS,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, Randy Dunlap
In-Reply-To: <20260627091936.29809-1-manuelebner@mailbox.org>
From: Manuel Ebner <manuelebner@mailbox.org>
Sent: Saturday, June 27, 2026 11:20 AM
>Add "(Alternate" to "ID)"
>
>Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
Hi
as the title states that's fix path we also need a fixes tag
here in the commit msg pointing out which patch is being fixed
^ permalink raw reply
* Re: [PATCH net v3] net: wwan: iosm: bound device offsets in the MUX downlink decoder
From: Loic Poulain @ 2026-06-29 7:49 UTC (permalink / raw)
To: Maoyi Xie
Cc: Sergey Ryazanov, Johannes Berg, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel,
stable
In-Reply-To: <178236824878.3259367.5389624724479864947@maoyixie.com>
On Thu, Jun 25, 2026 at 8:17 AM Maoyi Xie <maoyixie.tju@gmail.com> wrote:
>
> mux_dl_adb_decode() walks a chain of aggregated datagram tables using
> offsets and lengths taken from the modem. first_table_index,
> next_table_index, table_length, datagram_index and datagram_length are
> all device supplied le values. Only first_table_index was checked, and
> only for being non zero. The decoder then formed adth = block +
> adth_index and read the table header and the datagram entries with no
> bound against the received skb. A modem that reports an index or a
> length past the downlink buffer makes the decoder read out of bounds.
>
> The buffer is IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE and skb->len is at most
> that, so skb->len is the real limit, but none of these in band offsets
> were checked against it.
>
> The table chain is also followed with no forward progress check. The loop
> takes the next table from adth->next_table_index and stops only when that
> reaches zero. A modem can stage two tables that point at each other, so
> the loop never ends. It runs in softirq and clones the skb on every pass.
>
> Validate every device offset and length against skb->len before use.
> The block header must fit. Each table header, on entry and after every
> next_table_index, must lie inside the skb. The datagram table must fit.
> Each datagram index and length must stay inside the skb. The header
> padding must not exceed the datagram length so the receive length does
> not wrap. Require each next_table_index to move forward so the chain
> cannot cycle.
>
> This was reproduced under KASAN as a slab out of bounds read on a normal
> downlink receive once the iosm net device is up.
>
> Fixes: 1f52d7b62285 ("net: wwan: iosm: Enable M.2 7360 WWAN card support")
> Suggested-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
Reviewed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> ---
> Changes in v3:
> - Also require next_table_index to move strictly forward, so a modem
> cannot point two tables at each other and spin the decode loop in
> softirq. Raised in review of v2.
>
> Link to v1: https://lore.kernel.org/all/178185979029.4044562.9993615975949055530@maoyixie.com/
> Link to v2: https://lore.kernel.org/all/178196118045.462404.11069139160448641355@maoyixie.com/
>
> drivers/net/wwan/iosm/iosm_ipc_mux_codec.c | 40 +++++++++++++++++++++-------
> 1 file changed, 30 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c
> index bff46f7ca59f..0bbd41263cc2 100644
> --- a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c
> +++ b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c
> @@ -553,19 +553,21 @@ static int mux_dl_process_dg(struct iosm_mux *ipc_mux, struct mux_adbh *adbh,
> u32 packet_offset, i, rc, dg_len;
>
> for (i = 0; i < nr_of_dg; i++, dg++) {
> - if (le32_to_cpu(dg->datagram_index)
> - < sizeof(struct mux_adbh))
> + u32 dg_index = le32_to_cpu(dg->datagram_index);
> +
> + dg_len = le16_to_cpu(dg->datagram_length);
> +
> + if (dg_index < sizeof(struct mux_adbh))
> goto dg_error;
>
> - /* Is the packet inside of the ADB */
> - if (le32_to_cpu(dg->datagram_index) >=
> - le32_to_cpu(adbh->block_length)) {
> + /* Is the packet inside of the ADB and the received skb ? */
> + if (dg_index >= le32_to_cpu(adbh->block_length) ||
> + dg_index >= skb->len ||
> + dg_len > skb->len - dg_index ||
> + dl_head_pad_len >= dg_len) {
> goto dg_error;
> } else {
> - packet_offset =
> - le32_to_cpu(dg->datagram_index) +
> - dl_head_pad_len;
> - dg_len = le16_to_cpu(dg->datagram_length);
> + packet_offset = dg_index + dl_head_pad_len;
> /* Pass the packet to the netif layer. */
> rc = ipc_mux_net_receive(ipc_mux, if_id, ipc_mux->wwan,
> packet_offset,
> @@ -589,12 +591,16 @@ static void mux_dl_adb_decode(struct iosm_mux *ipc_mux,
> struct mux_adbh *adbh;
> struct mux_adth *adth;
> int nr_of_dg, if_id;
> - u32 adth_index;
> + u32 adth_index, prev_index = 0;
> u8 *block;
>
> block = skb->data;
> adbh = (struct mux_adbh *)block;
>
> + /* The block header itself must fit in the received skb. */
> + if (skb->len < sizeof(struct mux_adbh))
> + goto adb_decode_err;
> +
> /* Process the aggregated datagram tables. */
> adth_index = le32_to_cpu(adbh->first_table_index);
>
> @@ -606,6 +612,16 @@ static void mux_dl_adb_decode(struct iosm_mux *ipc_mux,
>
> /* Loop through mixed session tables. */
> while (adth_index) {
> + /* The table header must lie within the received skb, and the
> + * chain must move forward so a modem cannot make the loop
> + * cycle between two tables.
> + */
> + if (adth_index <= prev_index ||
> + adth_index < sizeof(struct mux_adbh) ||
> + adth_index > skb->len - sizeof(struct mux_adth))
> + goto adb_decode_err;
> + prev_index = adth_index;
> +
> /* Get the reference to the table header. */
> adth = (struct mux_adth *)(block + adth_index);
>
> @@ -629,6 +645,10 @@ static void mux_dl_adb_decode(struct iosm_mux *ipc_mux,
> if (le16_to_cpu(adth->table_length) < sizeof(struct mux_adth))
> goto adb_decode_err;
>
> + /* The whole datagram table must fit in the received skb. */
> + if (le16_to_cpu(adth->table_length) > skb->len - adth_index)
> + goto adb_decode_err;
> +
> /* Calculate the number of datagrams. */
> nr_of_dg = (le16_to_cpu(adth->table_length) -
> sizeof(struct mux_adth)) /
> --
> 2.34.1
^ permalink raw reply
* [PATCH net-next v10 2/5] hinic3: Add ethtool statistic ops
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1782718232.git.wudi234@huawei.com>
Add PF/VF statistics functions in TX and RX processing.
Implement following ethtool callback function:
.get_sset_count
.get_ethtool_stats
.get_strings
.get_eth_phy_stats
.get_eth_mac_stats
.get_eth_ctrl_stats
.get_rmon_stats
.get_pause_stats
These callbacks allow users to utilize ethtool for detailed
TX and RX netdev stats monitoring.
Add mgmt_msg_params_init_in_out() to support management
commands that require separate input and output buffers. This is
needed for retrieving the expanded PF/VF MAC statistics from HW,
which no longer fit in the input buffer used by the existing path.
Remove unused stats "restore_drop_sge" in struct hinic3_rxq_stats.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 483 ++++++++++++++++++
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 37 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 +++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 ++++
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 72 ++-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 15 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 62 ++-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
9 files changed, 844 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index e6dff46a0d00..0e1f83806933 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -498,6 +498,481 @@ static int hinic3_set_ringparam(struct net_device *netdev,
return 0;
}
+struct hinic3_stats {
+ char name[ETH_GSTRING_LEN];
+ u32 size;
+ int offset;
+};
+
+#define HINIC3_RXQ_STAT(_stat_item) { \
+ .name = "rxq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_rxq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_rxq_stats, _stat_item) \
+}
+
+#define HINIC3_TXQ_STAT(_stat_item) { \
+ .name = "txq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_txq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_txq_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_rx_queue_stats[] = {
+ HINIC3_RXQ_STAT(csum_errors),
+ HINIC3_RXQ_STAT(other_errors),
+ HINIC3_RXQ_STAT(rx_buf_empty),
+ HINIC3_RXQ_STAT(alloc_skb_err),
+ HINIC3_RXQ_STAT(alloc_rx_buf_err),
+};
+
+static struct hinic3_stats hinic3_tx_queue_stats[] = {
+ HINIC3_TXQ_STAT(busy),
+ HINIC3_TXQ_STAT(skb_pad_err),
+ HINIC3_TXQ_STAT(frag_len_overflow),
+ HINIC3_TXQ_STAT(offload_cow_skb_err),
+ HINIC3_TXQ_STAT(map_frag_err),
+ HINIC3_TXQ_STAT(unknown_tunnel_pkt),
+ HINIC3_TXQ_STAT(frag_size_err),
+};
+
+#define HINIC3_FUNC_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct l2nic_vport_stats, _stat_item), \
+ .offset = offsetof(struct l2nic_vport_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_function_stats[] = {
+ HINIC3_FUNC_STAT(tx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(rx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(tx_discard_vport),
+ HINIC3_FUNC_STAT(rx_discard_vport),
+ HINIC3_FUNC_STAT(tx_err_vport),
+ HINIC3_FUNC_STAT(rx_err_vport),
+};
+
+#define HINIC3_PORT_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct mag_cmd_port_stats, _stat_item), \
+ .offset = offsetof(struct mag_cmd_port_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_port_stats[] = {
+ HINIC3_PORT_STAT(mac_tx_fragment_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undersize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_oversize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_jabber_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_tx_good_oct_num),
+ HINIC3_PORT_STAT(mac_tx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_err_all_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_bad_pkt_num),
+
+ HINIC3_PORT_STAT(mac_rx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_rx_good_oct_num),
+ HINIC3_PORT_STAT(mac_rx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_unfilter_pkt_num),
+};
+
+static int hinic3_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int count, q_num;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ q_num = nic_dev->q_params.num_qps;
+ count = ARRAY_SIZE(hinic3_function_stats) +
+ (ARRAY_SIZE(hinic3_tx_queue_stats) +
+ ARRAY_SIZE(hinic3_rx_queue_stats)) *
+ q_num;
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ count += ARRAY_SIZE(hinic3_port_stats);
+
+ return count;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static u64 get_val_of_ptr(u32 size, const void *ptr)
+{
+ u64 ret = size == sizeof(u64) ? *(u64 *)ptr :
+ size == sizeof(u32) ? *(u32 *)ptr :
+ size == sizeof(u16) ? *(u16 *)ptr :
+ *(u8 *)ptr;
+
+ return ret;
+}
+
+static void hinic3_get_drv_queue_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_txq_stats txq_stats = {};
+ struct hinic3_rxq_stats rxq_stats = {};
+ u16 i = 0, j, qid;
+ char *p;
+
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->txqs)
+ break;
+
+ hinic3_txq_get_stats(&nic_dev->txqs[qid], &txq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++, i++) {
+ p = (char *)&txq_stats +
+ hinic3_tx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_tx_queue_stats[j].size,
+ p);
+ }
+ }
+
+ i = nic_dev->q_params.num_qps * ARRAY_SIZE(hinic3_tx_queue_stats);
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->rxqs)
+ break;
+
+ hinic3_rxq_get_stats(&nic_dev->rxqs[qid], &rxq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++, i++) {
+ p = (char *)&rxq_stats +
+ hinic3_rx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_rx_queue_stats[j].size,
+ p);
+ }
+ }
+}
+
+static u16 hinic3_get_ethtool_port_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ goto err_zero_stats;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get port stats from fw\n");
+ goto err_zero_stats;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_port_stats); j++, i++) {
+ p = (char *)ps + hinic3_port_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_port_stats[j].size, p);
+ }
+
+ kfree(ps);
+
+ return i;
+
+err_zero_stats:
+ memset(&data[i], 0, ARRAY_SIZE(hinic3_port_stats) * sizeof(*data));
+
+ return i + ARRAY_SIZE(hinic3_port_stats);
+}
+
+static void hinic3_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct l2nic_vport_stats vport_stats = {};
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ err = hinic3_get_vport_stats(nic_dev->hwdev,
+ hinic3_global_func_id(nic_dev->hwdev),
+ &vport_stats);
+ if (err)
+ netdev_err(netdev, "Failed to get function stats from fw\n");
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_function_stats); j++, i++) {
+ p = (char *)&vport_stats + hinic3_function_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_function_stats[j].size, p);
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ i += hinic3_get_ethtool_port_stats(netdev, data + i);
+
+ hinic3_get_drv_queue_stats(netdev, data + i);
+}
+
+static u16 hinic3_get_hw_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i, cnt = 0;
+
+ for (i = 0; i < ARRAY_SIZE(hinic3_function_stats); i++) {
+ memcpy(p, hinic3_function_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev)) {
+ for (i = 0; i < ARRAY_SIZE(hinic3_port_stats); i++) {
+ memcpy(p, hinic3_port_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+ }
+
+ return cnt;
+}
+
+static void hinic3_get_qp_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 *data = p;
+ u16 i, j;
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_tx_queue_stats[j].name, i);
+ }
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_rx_queue_stats[j].name, i);
+ }
+}
+
+static void hinic3_get_strings(struct net_device *netdev,
+ u32 stringset, u8 *data)
+{
+ char *p = (char *)data;
+ u16 offset;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ offset = hinic3_get_hw_stats_strings(netdev, p);
+ hinic3_get_qp_stats_strings(netdev,
+ p + offset * ETH_GSTRING_LEN);
+
+ return;
+ default:
+ netdev_err(netdev, "Invalid string set %u.\n", stringset);
+ return;
+ }
+}
+
+static void hinic3_get_eth_phy_stats(struct net_device *netdev,
+ struct ethtool_eth_phy_stats *phy_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth phy stats from fw\n");
+ return;
+ }
+
+ phy_stats->SymbolErrorDuringCarrier = ps->mac_rx_sym_err_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_mac_stats(struct net_device *netdev,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth mac stats from fw\n");
+ return;
+ }
+
+ mac_stats->FramesTransmittedOK = ps->mac_tx_good_pkt_num;
+ mac_stats->FramesReceivedOK = ps->mac_rx_good_pkt_num;
+ mac_stats->FrameCheckSequenceErrors = ps->mac_rx_fcs_err_pkt_num;
+ mac_stats->OctetsTransmittedOK = ps->mac_tx_total_oct_num;
+ mac_stats->OctetsReceivedOK = ps->mac_rx_total_oct_num;
+ mac_stats->MulticastFramesXmittedOK = ps->mac_tx_multi_pkt_num;
+ mac_stats->BroadcastFramesXmittedOK = ps->mac_tx_broad_pkt_num;
+ mac_stats->MulticastFramesReceivedOK = ps->mac_rx_multi_pkt_num;
+ mac_stats->BroadcastFramesReceivedOK = ps->mac_rx_broad_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_ctrl_stats(struct net_device *netdev,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth ctrl stats from fw\n");
+ return;
+ }
+
+ ctrl_stats->MACControlFramesTransmitted = ps->mac_tx_control_pkt_num;
+ ctrl_stats->MACControlFramesReceived = ps->mac_rx_control_pkt_num;
+
+ kfree(ps);
+}
+
+static const struct ethtool_rmon_hist_range hinic3_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1518 },
+ { 1519, 2047 },
+ { 2048, 4095 },
+ { 4096, 8191 },
+ { 8192, 9216 },
+ { 9217, 12287 },
+ {}
+};
+
+static void hinic3_get_rmon_stats(struct net_device *netdev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth rmon stats from fw\n");
+ return;
+ }
+
+ rmon_stats->undersize_pkts = ps->mac_rx_undersize_pkt_num;
+ rmon_stats->oversize_pkts = ps->mac_rx_oversize_pkt_num;
+ rmon_stats->fragments = ps->mac_rx_fragment_pkt_num;
+ rmon_stats->jabbers = ps->mac_rx_jabber_pkt_num;
+
+ rmon_stats->hist[0] = ps->mac_rx_64_oct_pkt_num;
+ rmon_stats->hist[1] = ps->mac_rx_65_127_oct_pkt_num;
+ rmon_stats->hist[2] = ps->mac_rx_128_255_oct_pkt_num;
+ rmon_stats->hist[3] = ps->mac_rx_256_511_oct_pkt_num;
+ rmon_stats->hist[4] = ps->mac_rx_512_1023_oct_pkt_num;
+ rmon_stats->hist[5] = ps->mac_rx_1024_1518_oct_pkt_num;
+ rmon_stats->hist[6] = ps->mac_rx_1519_2047_oct_pkt_num;
+ rmon_stats->hist[7] = ps->mac_rx_2048_4095_oct_pkt_num;
+ rmon_stats->hist[8] = ps->mac_rx_4096_8191_oct_pkt_num;
+ rmon_stats->hist[9] = ps->mac_rx_8192_9216_oct_pkt_num;
+ rmon_stats->hist[10] = ps->mac_rx_9217_12287_oct_pkt_num;
+
+ rmon_stats->hist_tx[0] = ps->mac_tx_64_oct_pkt_num;
+ rmon_stats->hist_tx[1] = ps->mac_tx_65_127_oct_pkt_num;
+ rmon_stats->hist_tx[2] = ps->mac_tx_128_255_oct_pkt_num;
+ rmon_stats->hist_tx[3] = ps->mac_tx_256_511_oct_pkt_num;
+ rmon_stats->hist_tx[4] = ps->mac_tx_512_1023_oct_pkt_num;
+ rmon_stats->hist_tx[5] = ps->mac_tx_1024_1518_oct_pkt_num;
+ rmon_stats->hist_tx[6] = ps->mac_tx_1519_2047_oct_pkt_num;
+ rmon_stats->hist_tx[7] = ps->mac_tx_2048_4095_oct_pkt_num;
+ rmon_stats->hist_tx[8] = ps->mac_tx_4096_8191_oct_pkt_num;
+ rmon_stats->hist_tx[9] = ps->mac_tx_8192_9216_oct_pkt_num;
+ rmon_stats->hist_tx[10] = ps->mac_tx_9217_12287_oct_pkt_num;
+
+ *ranges = hinic3_rmon_ranges;
+
+ kfree(ps);
+}
+
+static void hinic3_get_pause_stats(struct net_device *netdev,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth pause stats from fw\n");
+ return;
+ }
+
+ pause_stats->tx_pause_frames = ps->mac_tx_pause_num;
+ pause_stats->rx_pause_frames = ps->mac_rx_pause_num;
+
+ kfree(ps);
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -508,6 +983,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_link = ethtool_op_get_link,
.get_ringparam = hinic3_get_ringparam,
.set_ringparam = hinic3_set_ringparam,
+ .get_sset_count = hinic3_get_sset_count,
+ .get_ethtool_stats = hinic3_get_ethtool_stats,
+ .get_strings = hinic3_get_strings,
+ .get_eth_phy_stats = hinic3_get_eth_phy_stats,
+ .get_eth_mac_stats = hinic3_get_eth_mac_stats,
+ .get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
+ .get_rmon_stats = hinic3_get_rmon_stats,
+ .get_pause_stats = hinic3_get_pause_stats,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
index cfc9daa3034f..6b0f486ba590 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
@@ -53,6 +53,17 @@ static inline void mgmt_msg_params_init_default(struct mgmt_msg_params *msg_para
msg_params->timeout_ms = 0;
}
+static inline void
+mgmt_msg_params_init_in_out(struct mgmt_msg_params *msg_params, void *in_buf,
+ void *out_buf, u32 in_buf_size, u32 out_buf_size)
+{
+ msg_params->buf_in = in_buf;
+ msg_params->buf_out = out_buf;
+ msg_params->in_size = in_buf_size;
+ msg_params->expected_out_size = out_buf_size;
+ msg_params->timeout_ms = 0;
+}
+
enum cfg_cmd {
CFG_CMD_GET_DEV_CAP = 0,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index c5bca3c4af96..76c691f82703 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -143,6 +143,41 @@ struct l2nic_cmd_set_dcb_state {
u8 rsvd[7];
};
+struct l2nic_port_stats_info {
+ struct mgmt_msg_head msg_head;
+ u16 func_id;
+ u16 rsvd1;
+};
+
+struct l2nic_vport_stats {
+ u64 tx_unicast_pkts_vport;
+ u64 tx_unicast_bytes_vport;
+ u64 tx_multicast_pkts_vport;
+ u64 tx_multicast_bytes_vport;
+ u64 tx_broadcast_pkts_vport;
+ u64 tx_broadcast_bytes_vport;
+
+ u64 rx_unicast_pkts_vport;
+ u64 rx_unicast_bytes_vport;
+ u64 rx_multicast_pkts_vport;
+ u64 rx_multicast_bytes_vport;
+ u64 rx_broadcast_pkts_vport;
+ u64 rx_broadcast_bytes_vport;
+
+ u64 tx_discard_vport;
+ u64 rx_discard_vport;
+ u64 tx_err_vport;
+ u64 rx_err_vport;
+};
+
+struct l2nic_cmd_vport_stats {
+ struct mgmt_msg_head msg_head;
+ u32 stats_size;
+ u32 rsvd1;
+ struct l2nic_vport_stats stats;
+ u64 rsvd2[6];
+};
+
struct l2nic_cmd_lro_config {
struct mgmt_msg_head msg_head;
u16 func_id;
@@ -234,6 +269,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VPORT_ENABLE = 6,
L2NIC_CMD_SET_RX_MODE = 7,
L2NIC_CMD_SET_SQ_CI_ATTR = 8,
+ L2NIC_CMD_GET_VPORT_STAT = 9,
L2NIC_CMD_CLEAR_QP_RESOURCE = 11,
L2NIC_CMD_CFG_RX_LRO = 13,
L2NIC_CMD_CFG_LRO_TIMER = 14,
@@ -272,6 +308,7 @@ enum mag_cmd {
MAG_CMD_SET_PORT_ENABLE = 6,
MAG_CMD_GET_LINK_STATUS = 7,
+ MAG_CMD_GET_PORT_STAT = 151,
MAG_CMD_GET_PORT_INFO = 153,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
index de5a7984d2cb..1b14dc824ce1 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
@@ -639,6 +639,42 @@ int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up)
return 0;
}
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats)
+{
+ struct mag_cmd_port_stats_info stats_info = {};
+ struct mag_cmd_get_port_stat *ps;
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ps = kzalloc_obj(*ps);
+ if (!ps)
+ return -ENOMEM;
+
+ stats_info.port_id = hinic3_physical_port_id(hwdev);
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, ps,
+ sizeof(stats_info), sizeof(*ps));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
+ MAG_CMD_GET_PORT_STAT, &msg_params);
+
+ if (err || ps->head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get port statistics, err: %d, status: 0x%x\n",
+ err, ps->head.status);
+ err = -EFAULT;
+ goto out;
+ }
+
+ memcpy(stats, &ps->counter, sizeof(*stats));
+
+out:
+ kfree(ps);
+
+ return err;
+}
+
int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info)
{
@@ -738,3 +774,31 @@ int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
return hinic3_cfg_hw_pause(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
nic_pause);
}
+
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats)
+{
+ struct l2nic_cmd_vport_stats vport_stats = {};
+ struct l2nic_port_stats_info stats_info = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ stats_info.func_id = func_id;
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, &vport_stats,
+ sizeof(stats_info), sizeof(vport_stats));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_VPORT_STAT, &msg_params);
+
+ if (err || vport_stats.msg_head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get function statistics, err: %d, status: 0x%x\n",
+ err, vport_stats.msg_head.status);
+ return -EFAULT;
+ }
+
+ memcpy(stats, &vport_stats.stats, sizeof(*stats));
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
index 5d52202a8d4e..80573c121539 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
@@ -129,6 +129,110 @@ struct mag_cmd_get_xsfp_present {
u8 rsvd[2];
};
+struct mag_cmd_port_stats {
+ u64 mac_tx_fragment_pkt_num;
+ u64 mac_tx_undersize_pkt_num;
+ u64 mac_tx_undermin_pkt_num;
+ u64 mac_tx_64_oct_pkt_num;
+ u64 mac_tx_65_127_oct_pkt_num;
+ u64 mac_tx_128_255_oct_pkt_num;
+ u64 mac_tx_256_511_oct_pkt_num;
+ u64 mac_tx_512_1023_oct_pkt_num;
+ u64 mac_tx_1024_1518_oct_pkt_num;
+ u64 mac_tx_1519_2047_oct_pkt_num;
+ u64 mac_tx_2048_4095_oct_pkt_num;
+ u64 mac_tx_4096_8191_oct_pkt_num;
+ u64 mac_tx_8192_9216_oct_pkt_num;
+ u64 mac_tx_9217_12287_oct_pkt_num;
+ u64 mac_tx_12288_16383_oct_pkt_num;
+ u64 mac_tx_1519_max_bad_pkt_num;
+ u64 mac_tx_1519_max_good_pkt_num;
+ u64 mac_tx_oversize_pkt_num;
+ u64 mac_tx_jabber_pkt_num;
+ u64 mac_tx_bad_pkt_num;
+ u64 mac_tx_bad_oct_num;
+ u64 mac_tx_good_pkt_num;
+ u64 mac_tx_good_oct_num;
+ u64 mac_tx_total_pkt_num;
+ u64 mac_tx_total_oct_num;
+ u64 mac_tx_uni_pkt_num;
+ u64 mac_tx_multi_pkt_num;
+ u64 mac_tx_broad_pkt_num;
+ u64 mac_tx_pause_num;
+ u64 mac_tx_pfc_pkt_num;
+ u64 mac_tx_pfc_pri0_pkt_num;
+ u64 mac_tx_pfc_pri1_pkt_num;
+ u64 mac_tx_pfc_pri2_pkt_num;
+ u64 mac_tx_pfc_pri3_pkt_num;
+ u64 mac_tx_pfc_pri4_pkt_num;
+ u64 mac_tx_pfc_pri5_pkt_num;
+ u64 mac_tx_pfc_pri6_pkt_num;
+ u64 mac_tx_pfc_pri7_pkt_num;
+ u64 mac_tx_control_pkt_num;
+ u64 mac_tx_err_all_pkt_num;
+ u64 mac_tx_from_app_good_pkt_num;
+ u64 mac_tx_from_app_bad_pkt_num;
+
+ u64 mac_rx_fragment_pkt_num;
+ u64 mac_rx_undersize_pkt_num;
+ u64 mac_rx_undermin_pkt_num;
+ u64 mac_rx_64_oct_pkt_num;
+ u64 mac_rx_65_127_oct_pkt_num;
+ u64 mac_rx_128_255_oct_pkt_num;
+ u64 mac_rx_256_511_oct_pkt_num;
+ u64 mac_rx_512_1023_oct_pkt_num;
+ u64 mac_rx_1024_1518_oct_pkt_num;
+ u64 mac_rx_1519_2047_oct_pkt_num;
+ u64 mac_rx_2048_4095_oct_pkt_num;
+ u64 mac_rx_4096_8191_oct_pkt_num;
+ u64 mac_rx_8192_9216_oct_pkt_num;
+ u64 mac_rx_9217_12287_oct_pkt_num;
+ u64 mac_rx_12288_16383_oct_pkt_num;
+ u64 mac_rx_1519_max_bad_pkt_num;
+ u64 mac_rx_1519_max_good_pkt_num;
+ u64 mac_rx_oversize_pkt_num;
+ u64 mac_rx_jabber_pkt_num;
+ u64 mac_rx_bad_pkt_num;
+ u64 mac_rx_bad_oct_num;
+ u64 mac_rx_good_pkt_num;
+ u64 mac_rx_good_oct_num;
+ u64 mac_rx_total_pkt_num;
+ u64 mac_rx_total_oct_num;
+ u64 mac_rx_uni_pkt_num;
+ u64 mac_rx_multi_pkt_num;
+ u64 mac_rx_broad_pkt_num;
+ u64 mac_rx_pause_num;
+ u64 mac_rx_pfc_pkt_num;
+ u64 mac_rx_pfc_pri0_pkt_num;
+ u64 mac_rx_pfc_pri1_pkt_num;
+ u64 mac_rx_pfc_pri2_pkt_num;
+ u64 mac_rx_pfc_pri3_pkt_num;
+ u64 mac_rx_pfc_pri4_pkt_num;
+ u64 mac_rx_pfc_pri5_pkt_num;
+ u64 mac_rx_pfc_pri6_pkt_num;
+ u64 mac_rx_pfc_pri7_pkt_num;
+ u64 mac_rx_control_pkt_num;
+ u64 mac_rx_sym_err_pkt_num;
+ u64 mac_rx_fcs_err_pkt_num;
+ u64 mac_rx_send_app_good_pkt_num;
+ u64 mac_rx_send_app_bad_pkt_num;
+ u64 mac_rx_unfilter_pkt_num;
+};
+
+struct mag_cmd_port_stats_info {
+ struct mgmt_msg_head head;
+
+ u8 port_id;
+ u8 rsvd0[3];
+};
+
+struct mag_cmd_get_port_stat {
+ struct mgmt_msg_head head;
+
+ struct mag_cmd_port_stats counter;
+ u64 rsvd1[15];
+};
+
enum link_err_type {
LINK_ERR_MODULE_UNRECOGENIZED,
LINK_ERR_NUM,
@@ -209,6 +313,11 @@ int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info);
int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
bool enable);
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats);
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats);
+
int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 389b1c2158be..d304f1005831 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -28,10 +28,6 @@
#define HINIC3_LRO_PKT_HDR_LEN_IPV4 66
#define HINIC3_LRO_PKT_HDR_LEN_IPV6 86
-#define HINIC3_LRO_PKT_HDR_LEN(cqe) \
- (RQ_CQE_OFFOLAD_TYPE_GET((cqe)->offload_type, IP_TYPE) == \
- HINIC3_RX_IPV6_PKT ? HINIC3_LRO_PKT_HDR_LEN_IPV6 : \
- HINIC3_LRO_PKT_HDR_LEN_IPV4)
static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
{
@@ -46,7 +42,6 @@ static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
rxq_stats->alloc_skb_err = 0;
rxq_stats->alloc_rx_buf_err = 0;
- rxq_stats->restore_drop_sge = 0;
u64_stats_update_end(&rxq_stats->syncp);
}
@@ -155,8 +150,14 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
err = rx_alloc_mapped_page(rxq->page_pool, rx_info,
rxq->buf_len);
- if (unlikely(err))
+ if (unlikely(err)) {
+ preempt_disable();
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_rx_buf_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ preempt_enable();
break;
+ }
dma_addr = page_pool_get_dma_addr(rx_info->page) +
rx_info->page_offset;
@@ -170,6 +171,12 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
rxq->next_to_update << HINIC3_NORMAL_RQ_WQE);
rxq->delta -= i;
rxq->next_to_alloc = rxq->next_to_update;
+ } else if (free_wqebbs == rxq->q_depth - 1) {
+ preempt_disable();
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.rx_buf_empty++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ preempt_enable();
}
return i;
@@ -330,11 +337,23 @@ static void hinic3_rx_csum(struct hinic3_rxq *rxq, u32 offload_type,
struct net_device *netdev = rxq->netdev;
bool l2_tunnel;
+ if (unlikely(csum_err == HINIC3_RX_CSUM_IPSU_OTHER_ERR)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.other_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
+
if (!(netdev->features & NETIF_F_RXCSUM))
return;
if (unlikely(csum_err)) {
/* pkt type is recognized by HW, and csum is wrong */
+ if (!(csum_err & (HINIC3_RX_CSUM_HW_CHECK_NONE |
+ HINIC3_RX_CSUM_IPSU_OTHER_ERR))) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.csum_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
skb->ip_summed = CHECKSUM_NONE;
return;
}
@@ -387,8 +406,12 @@ static int recv_one_pkt(struct hinic3_rxq *rxq, struct hinic3_rq_cqe *rx_cqe,
u16 num_lro;
skb = hinic3_fetch_rx_buffer(rxq, pkt_len);
- if (unlikely(!skb))
+ if (unlikely(!skb)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_skb_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
return -ENOMEM;
+ }
/* place header in linear portion of buffer */
if (skb_is_nonlinear(skb))
@@ -550,11 +573,29 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
return 0;
}
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats)
+{
+ struct hinic3_rxq_stats *rxq_stats = &rxq->rxq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->syncp);
+ stats->csum_errors = rxq_stats->csum_errors;
+ stats->other_errors = rxq_stats->other_errors;
+ stats->rx_buf_empty = rxq_stats->rx_buf_empty;
+ stats->alloc_skb_err = rxq_stats->alloc_skb_err;
+ stats->alloc_rx_buf_err = rxq_stats->alloc_rx_buf_err;
+ } while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
+}
+
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(rxq->netdev);
+ u32 ip_type, offload_type, pkt_hdr_len;
u32 sw_ci, status, pkt_len, vlan_len;
struct hinic3_rq_cqe *rx_cqe;
+ u64 rx_bytes = 0;
u32 num_wqe = 0;
int nr_pkts = 0;
u16 num_lro;
@@ -574,10 +615,20 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (recv_one_pkt(rxq, rx_cqe, pkt_len, vlan_len, status))
break;
+ rx_bytes += pkt_len;
nr_pkts++;
num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
- if (num_lro)
+ if (num_lro) {
+ offload_type = le32_to_cpu(rx_cqe->offload_type);
+ ip_type = RQ_CQE_OFFOLAD_TYPE_GET(offload_type,
+ IP_TYPE);
+ pkt_hdr_len = ip_type == HINIC3_RX_IPV6_PKT ?
+ HINIC3_LRO_PKT_HDR_LEN_IPV6 :
+ HINIC3_LRO_PKT_HDR_LEN_IPV4;
+
+ rx_bytes += (num_lro - 1) * pkt_hdr_len;
num_wqe += hinic3_get_sge_num(rxq, pkt_len);
+ }
rx_cqe->status = 0;
@@ -588,5 +639,10 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (rxq->delta >= HINIC3_RX_BUFFER_WRITE)
hinic3_rx_fill_buffers(rxq);
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.packets += (u64)nr_pkts;
+ rxq->rxq_stats.bytes += rx_bytes;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+
return nr_pkts;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index 06d1b3299e7c..c11d080408a7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -8,6 +8,17 @@
#include <linux/dim.h>
#include <linux/netdevice.h>
+/* rx cqe checksum err */
+#define HINIC3_RX_CSUM_IP_CSUM_ERR BIT(0)
+#define HINIC3_RX_CSUM_TCP_CSUM_ERR BIT(1)
+#define HINIC3_RX_CSUM_UDP_CSUM_ERR BIT(2)
+#define HINIC3_RX_CSUM_IGMP_CSUM_ERR BIT(3)
+#define HINIC3_RX_CSUM_ICMPV4_CSUM_ERR BIT(4)
+#define HINIC3_RX_CSUM_ICMPV6_CSUM_ERR BIT(5)
+#define HINIC3_RX_CSUM_SCTP_CRC_ERR BIT(6)
+#define HINIC3_RX_CSUM_HW_CHECK_NONE BIT(7)
+#define HINIC3_RX_CSUM_IPSU_OTHER_ERR BIT(8)
+
#define RQ_CQE_OFFOLAD_TYPE_PKT_TYPE_MASK GENMASK(4, 0)
#define RQ_CQE_OFFOLAD_TYPE_IP_TYPE_MASK GENMASK(6, 5)
#define RQ_CQE_OFFOLAD_TYPE_TUNNEL_PKT_FORMAT_MASK GENMASK(11, 8)
@@ -39,7 +50,6 @@ struct hinic3_rxq_stats {
u64 rx_buf_empty;
u64 alloc_skb_err;
u64 alloc_rx_buf_err;
- u64 restore_drop_sge;
struct u64_stats_sync syncp;
};
@@ -123,6 +133,9 @@ void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
+
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats);
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget);
#endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 53c065194136..5b7075e9fe5d 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -97,8 +97,12 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
dma_info[0].dma = dma_map_single(&pdev->dev, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- if (dma_mapping_error(&pdev->dev, dma_info[0].dma))
+ if (dma_mapping_error(&pdev->dev, dma_info[0].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
return -EFAULT;
+ }
dma_info[0].len = skb_headlen(skb);
@@ -117,6 +121,9 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
skb_frag_size(frag),
DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_info[idx].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
err = -EFAULT;
goto err_unmap_page;
}
@@ -260,6 +267,9 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
if (l4_proto != IPPROTO_UDP ||
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.unknown_tunnel_pkt++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
/* Unsupported tunnel packet, disable csum offload */
return skb_checksum_help(skb);
}
@@ -436,6 +446,27 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
return offload;
}
+static void hinic3_get_pkt_stats(struct hinic3_txq *txq, struct sk_buff *skb)
+{
+ u32 hdr_len, tx_bytes;
+ unsigned short pkts;
+
+ if (skb_is_gso(skb)) {
+ hdr_len = (skb_shinfo(skb)->gso_segs - 1) *
+ skb_tcp_all_headers(skb);
+ tx_bytes = skb->len + hdr_len;
+ pkts = skb_shinfo(skb)->gso_segs;
+ } else {
+ tx_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
+ pkts = 1;
+ }
+
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.bytes += tx_bytes;
+ txq->txq_stats.packets += pkts;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+}
+
static u16 hinic3_get_and_update_sq_owner(struct hinic3_io_queue *sq,
u16 curr_pi, u16 wqebb_cnt)
{
@@ -542,8 +573,12 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
int err;
if (unlikely(skb->len < MIN_SKB_LEN)) {
- if (skb_pad(skb, MIN_SKB_LEN - skb->len))
+ if (skb_pad(skb, MIN_SKB_LEN - skb->len)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.skb_pad_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
goto err_out;
+ }
skb->len = MIN_SKB_LEN;
}
@@ -598,6 +633,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
txq->tx_stop_thrs,
txq->tx_start_thrs);
+ hinic3_get_pkt_stats(txq, skb);
hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner);
hinic3_write_db(txq->sq, 0, DB_CFLAG_DP_SQ,
hinic3_get_sq_local_pi(txq->sq));
@@ -607,6 +643,10 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
err_drop_pkt:
dev_kfree_skb_any(skb);
err_out:
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.dropped++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+
return NETDEV_TX_OK;
}
@@ -757,6 +797,24 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
return 0;
}
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats)
+{
+ struct hinic3_txq_stats *txq_stats = &txq->txq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->syncp);
+ stats->busy = txq_stats->busy;
+ stats->skb_pad_err = txq_stats->skb_pad_err;
+ stats->frag_len_overflow = txq_stats->frag_len_overflow;
+ stats->offload_cow_skb_err = txq_stats->offload_cow_skb_err;
+ stats->map_frag_err = txq_stats->map_frag_err;
+ stats->unknown_tunnel_pkt = txq_stats->unknown_tunnel_pkt;
+ stats->frag_size_err = txq_stats->frag_size_err;
+ } while (u64_stats_fetch_retry(&txq_stats->syncp, start));
+}
+
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget)
{
struct net_device *netdev = txq->netdev;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
index 00194f2a1bcc..0a21c423618f 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
@@ -157,6 +157,8 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats);
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget);
void hinic3_flush_txqs(struct net_device *netdev);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v10 5/5] hinic3: Remove unneeded coalesce parameters
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1782718232.git.wudi234@huawei.com>
Remove unneeded coalesce parameters in irq handling.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
drivers/net/ethernet/huawei/hinic3/hinic3_irq.c | 6 +-----
drivers/net/ethernet/huawei/hinic3/hinic3_rx.h | 3 ---
2 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index 4e3b8e3c31f9..cd83bce39d87 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -155,13 +155,9 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
nic_dev->intr_coalesce[q_id].resend_timer_cfg;
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
- if (err) {
+ if (err)
netdev_err(netdev,
"Failed to modify moderation for Queue: %u\n", q_id);
- } else {
- nic_dev->rxqs[q_id].last_coalesc_timer_cfg = coalesc_timer_cfg;
- nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
- }
err_unlock:
mutex_unlock(&nic_dev->state_lock);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index c11d080408a7..2ab691ed11a9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -111,9 +111,6 @@ struct hinic3_rxq {
dma_addr_t cqe_start_paddr;
struct dim dim;
-
- u8 last_coalesc_timer_cfg;
- u8 last_pending_limit;
} ____cacheline_aligned;
struct hinic3_dyna_rxq_res {
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v10 3/5] hinic3: Add ethtool coalesce ops
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1782718232.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_coalesce
.set_coalesce
These callbacks allow users to utilize ethtool for detailed
RX coalesce configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 286 +++++++++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 15 +-
2 files changed, 296 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 0e1f83806933..c329f1c62f0e 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -18,6 +18,11 @@
#include "hinic3_nic_cfg.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
+/* Coalesce time properties in microseconds */
+#define COALESCE_PENDING_LIMIT_UNIT 8
+#define COALESCE_TIMER_CFG_UNIT 5
+#define COALESCE_MAX_PENDING_LIMIT (255 * COALESCE_PENDING_LIMIT_UNIT)
+#define COALESCE_MAX_TIMER_CFG (255 * COALESCE_TIMER_CFG_UNIT)
static void hinic3_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
@@ -973,9 +978,284 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
kfree(ps);
}
+static int hinic3_set_queue_coalesce(struct net_device *netdev, u16 q_id,
+ struct hinic3_intr_coal_info *coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *intr_coal;
+ struct hinic3_intr_coal_info old_coal;
+ struct hinic3_interrupt_info info = {};
+ int err;
+
+ mutex_lock(&nic_dev->state_lock);
+ intr_coal = &nic_dev->intr_coalesce[q_id];
+ old_coal = *intr_coal;
+
+ intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
+ intr_coal->pending_limit = coal->pending_limit;
+ intr_coal->rx_pending_limit_low = coal->rx_pending_limit_low;
+ intr_coal->rx_pending_limit_high = coal->rx_pending_limit_high;
+
+ info.resend_timer_cfg = intr_coal->resend_timer_cfg;
+ mutex_unlock(&nic_dev->state_lock);
+
+ if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
+ q_id >= nic_dev->q_params.num_qps)
+ return 0;
+
+ info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
+ info.interrupt_coalesc_set = 1;
+
+ info.coalesc_timer_cfg = coal->coalesce_timer_cfg;
+ info.pending_limit = coal->pending_limit;
+
+ mutex_lock(&nic_dev->state_lock);
+ err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
+ mutex_unlock(&nic_dev->state_lock);
+
+ if (err) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, "Failed to set queue%u coalesce",
+ q_id);
+ mutex_lock(&nic_dev->state_lock);
+ *intr_coal = old_coal;
+ mutex_unlock(&nic_dev->state_lock);
+ return err;
+ }
+
+ return 0;
+}
+
+static int is_coalesce_exceed_limit(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ const struct {
+ const char *name;
+ u32 value;
+ u32 limit;
+ } coalesce_limits[] = {
+ {"rx_coalesce_usecs",
+ coal->rx_coalesce_usecs,
+ COALESCE_MAX_TIMER_CFG},
+ {"rx_max_coalesced_frames",
+ coal->rx_max_coalesced_frames,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_low",
+ coal->rx_max_coalesced_frames_low,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_high",
+ coal->rx_max_coalesced_frames_high,
+ COALESCE_MAX_PENDING_LIMIT},
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(coalesce_limits); i++) {
+ if (coalesce_limits[i].value > coalesce_limits[i].limit) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "%s out of range %d-%d",
+ coalesce_limits[i].name,
+ 0,
+ coalesce_limits[i].limit);
+ return -ERANGE;
+ }
+ }
+ return 0;
+}
+
+static int is_coalesce_legal(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ err = is_coalesce_exceed_limit(coal, extack);
+ if (err)
+ return err;
+
+ if (coal->rx_max_coalesced_frames_low >
+ coal->rx_max_coalesced_frames_high) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "invalid coalesce frame high %u, low %u",
+ coal->rx_max_coalesced_frames_high,
+ coal->rx_max_coalesced_frames_low);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static void check_coalesce_align(struct net_device *netdev,
+ u32 item, u32 unit, const char *str)
+{
+ if (item % unit)
+ netdev_warn(netdev, "%s in %d units, change to %u\n",
+ str, unit, item - item % unit);
+}
+
+#define CHECK_COALESCE_ALIGN(member, unit) \
+ check_coalesce_align(netdev, member, unit, #member)
+
+static void check_coalesce_changed(struct net_device *netdev,
+ u32 item, u32 unit, u32 ori_val,
+ const char *obj_str, const char *str)
+{
+ if ((item / unit) != ori_val)
+ netdev_dbg(netdev, "Change %s from %d to %u %s\n",
+ str, ori_val * unit, item - item % unit, obj_str);
+}
+
+#define CHECK_COALESCE_CHANGED(member, unit, ori_val, obj_str) \
+ check_coalesce_changed(netdev, member, unit, ori_val, obj_str, #member)
+
+static int hinic3_set_hw_coal_param(struct net_device *netdev,
+ struct hinic3_intr_coal_info *intr_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+ u16 i;
+
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ err = hinic3_set_queue_coalesce(netdev, i, intr_coal, extack);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *interrupt_info;
+
+ interrupt_info = &nic_dev->intr_coalesce[0];
+
+ coal->use_adaptive_rx_coalesce = nic_dev->adaptive_rx_coal;
+
+ mutex_lock(&nic_dev->state_lock);
+
+ coal->rx_max_coalesced_frames_low =
+ interrupt_info->rx_pending_limit_low *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ coal->rx_max_coalesced_frames_high =
+ interrupt_info->rx_pending_limit_high *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ /* TX/RX uses the same interrupt.
+ * So we only declare RX ethtool_coalesce parameters.
+ */
+ coal->rx_coalesce_usecs = interrupt_info->coalesce_timer_cfg *
+ COALESCE_TIMER_CFG_UNIT;
+
+ coal->rx_max_coalesced_frames = interrupt_info->pending_limit *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ mutex_unlock(&nic_dev->state_lock);
+
+ return 0;
+}
+
+static int hinic3_set_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *ori_intr_coal;
+ struct hinic3_intr_coal_info intr_coal = {};
+ const char *obj_str = "for netdev";
+ bool old_adaptive, new_adaptive;
+ bool dim_stopped = false;
+ int err;
+ int i;
+
+ err = is_coalesce_legal(coal, extack);
+ if (err)
+ return err;
+
+ CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ ori_intr_coal = &nic_dev->intr_coalesce[0];
+
+ CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT,
+ ori_intr_coal->coalesce_timer_cfg, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->pending_limit, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_high, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_low, obj_str);
+
+ old_adaptive = READ_ONCE(nic_dev->adaptive_rx_coal);
+ new_adaptive = !!coal->use_adaptive_rx_coalesce;
+
+ if (old_adaptive) {
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, false);
+
+ synchronize_net();
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ if (!nic_dev->q_params.irq_cfg[i].rxq)
+ continue;
+
+ cancel_work_sync(&nic_dev->q_params.irq_cfg[i].rxq
+ ->dim.work);
+ }
+
+ dim_stopped = true;
+ }
+
+ intr_coal.coalesce_timer_cfg =
+ (u8)(coal->rx_coalesce_usecs / COALESCE_TIMER_CFG_UNIT);
+
+ intr_coal.pending_limit =
+ (u8)(coal->rx_max_coalesced_frames /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ intr_coal.rx_pending_limit_high =
+ (u8)(coal->rx_max_coalesced_frames_high /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ intr_coal.rx_pending_limit_low =
+ (u8)(coal->rx_max_coalesced_frames_low /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ /* coalesce timer or pending set to zero will disable coalesce */
+ if (!new_adaptive &&
+ (!intr_coal.coalesce_timer_cfg || !intr_coal.pending_limit))
+ netdev_info(netdev, "Coalesce will be disabled\n");
+
+ err = hinic3_set_hw_coal_param(netdev, &intr_coal, extack);
+ if (err) {
+ if (dim_stopped)
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, old_adaptive);
+
+ return err;
+ }
+
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, new_adaptive);
+
+ return 0;
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
- ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES |
+ ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH,
.get_link_ksettings = hinic3_get_link_ksettings,
.get_drvinfo = hinic3_get_drvinfo,
.get_msglevel = hinic3_get_msglevel,
@@ -991,6 +1271,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
.get_rmon_stats = hinic3_get_rmon_stats,
.get_pause_stats = hinic3_get_pause_stats,
+ .get_coalesce = hinic3_get_coalesce,
+ .set_coalesce = hinic3_set_coalesce,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index 5479c235e3de..4e3b8e3c31f9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -101,10 +101,14 @@ static int hinic3_request_irq(struct hinic3_irq_cfg *irq_cfg, u16 q_id)
info.msix_index = irq_cfg->msix_entry_idx;
info.interrupt_coalesc_set = 1;
+
+ mutex_lock(&nic_dev->state_lock);
info.pending_limit = nic_dev->intr_coalesce[q_id].pending_limit;
info.coalesc_timer_cfg =
nic_dev->intr_coalesce[q_id].coalesce_timer_cfg;
info.resend_timer_cfg = nic_dev->intr_coalesce[q_id].resend_timer_cfg;
+ mutex_unlock(&nic_dev->state_lock);
+
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
if (err) {
netdev_err(netdev, "Failed to set RX interrupt coalescing attribute.\n");
@@ -169,17 +173,22 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
static void hinic3_update_queue_coal(struct net_device *netdev, u16 q_id,
u16 coal_timer, u16 coal_pkts)
{
+ u8 coalesc_timer_cfg, pending_limit, limit_low, limit_high;
struct hinic3_intr_coal_info *q_coal;
- u8 coalesc_timer_cfg, pending_limit;
struct hinic3_nic_dev *nic_dev;
nic_dev = netdev_priv(netdev);
q_coal = &nic_dev->intr_coalesce[q_id];
coalesc_timer_cfg = (u8)coal_timer;
+
+ mutex_lock(&nic_dev->state_lock);
+ limit_low = q_coal->rx_pending_limit_low;
+ limit_high = q_coal->rx_pending_limit_high;
+ mutex_unlock(&nic_dev->state_lock);
+
pending_limit = clamp_t(u8, coal_pkts >> HINIC3_COAL_PKT_SHIFT,
- q_coal->rx_pending_limit_low,
- q_coal->rx_pending_limit_high);
+ limit_low, limit_high);
hinic3_set_interrupt_moder(nic_dev->netdev, q_id,
coalesc_timer_cfg, pending_limit);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v10 4/5] hinic3: Add ethtool rss ops
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1782718232.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_rxnfc
.set_rxnfc
.get_channels
.set_channels
.get_rxfh_indir_size
.get_rxfh_key_size
.get_rxfh
.set_rxfh
These callbacks allow users to utilize ethtool for detailed
RSS parameters configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 10 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 2 +
.../huawei/hinic3/hinic3_netdev_ops.c | 2 -
| 555 +++++++++++++++++-
| 20 +
5 files changed, 585 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index c329f1c62f0e..62eda40867bb 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -16,6 +16,7 @@
#include "hinic3_hw_comm.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_cfg.h"
+#include "hinic3_rss.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
/* Coalesce time properties in microseconds */
@@ -1273,6 +1274,15 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_pause_stats = hinic3_get_pause_stats,
.get_coalesce = hinic3_get_coalesce,
.set_coalesce = hinic3_set_coalesce,
+ .get_rxnfc = hinic3_get_rxnfc,
+ .set_rxnfc = hinic3_set_rxnfc,
+ .get_rx_ring_count = hinic3_get_rx_ring_count,
+ .get_channels = hinic3_get_channels,
+ .set_channels = hinic3_set_channels,
+ .get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
+ .get_rxfh_key_size = hinic3_get_rxfh_key_size,
+ .get_rxfh = hinic3_get_rxfh,
+ .set_rxfh = hinic3_set_rxfh,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index 76c691f82703..3c1263ff99ff 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -282,6 +282,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VLAN_FILTER_EN = 26,
L2NIC_CMD_SET_RX_VLAN_OFFLOAD = 27,
L2NIC_CMD_CFG_RSS = 60,
+ L2NIC_CMD_GET_RSS_CTX_TBL = 62,
L2NIC_CMD_CFG_RSS_HASH_KEY = 63,
L2NIC_CMD_CFG_RSS_HASH_ENGINE = 64,
L2NIC_CMD_SET_RSS_CTX_TBL = 65,
@@ -301,6 +302,7 @@ enum l2nic_ucode_cmd {
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX = 0,
L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX = 1,
L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL = 4,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL = 6,
};
/* hilink mac group command */
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
index 2d527b0ecbbe..c28327a32c2d 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
@@ -356,9 +356,7 @@ static int hinic3_maybe_set_port_state(struct net_device *netdev, bool enable)
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
- mutex_lock(&nic_dev->state_lock);
err = hinic3_set_port_enable(nic_dev->hwdev, enable);
- mutex_unlock(&nic_dev->state_lock);
return err;
}
--git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
index 25db74d8c7dd..1d59fb106579 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
@@ -155,7 +155,7 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
L2NIC_CMD_SET_RSS_CTX_TBL, &msg_params);
if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
- return MGMT_STATUS_CMD_UNSUPPORTED;
+ return -EOPNOTSUPP;
} else if (err || ctx_tbl.msg_head.status) {
dev_err(hwdev->dev, "mgmt Failed to set rss context offload, err: %d, status: 0x%x\n",
err, ctx_tbl.msg_head.status);
@@ -165,6 +165,41 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
return 0;
}
+static int hinic3_get_rss_type(struct hinic3_hwdev *hwdev,
+ struct hinic3_rss_type *rss_type)
+{
+ struct l2nic_cmd_rss_ctx_tbl ctx_tbl = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ctx_tbl.func_id = hinic3_global_func_id(hwdev);
+
+ mgmt_msg_params_init_default(&msg_params, &ctx_tbl, sizeof(ctx_tbl));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_RSS_CTX_TBL,
+ &msg_params);
+ if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
+ return -EOPNOTSUPP;
+ } else if (err || ctx_tbl.msg_head.status) {
+ dev_err(hwdev->dev, "Failed to get hash type, err: %d, status: 0x%x\n",
+ err, ctx_tbl.msg_head.status);
+ return -EINVAL;
+ }
+
+ rss_type->ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
+ rss_type->ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
+ rss_type->ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
+ rss_type->tcp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
+ rss_type->tcp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
+ rss_type->tcp_ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context,
+ TCP_IPV6_EXT);
+ rss_type->udp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
+ rss_type->udp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);
+
+ return 0;
+}
+
static int hinic3_rss_cfg_hash_type(struct hinic3_hwdev *hwdev, u8 opcode,
enum hinic3_rss_hash_type *type)
{
@@ -264,7 +299,8 @@ static int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en)
if (err)
return err;
- hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+ if (!netif_is_rxfh_configured(netdev))
+ hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
err = hinic3_config_rss_hw_resource(netdev, nic_dev->rss_indir);
if (err)
@@ -334,3 +370,518 @@ void hinic3_try_to_enable_rss(struct net_device *netdev)
clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
nic_dev->q_params.num_qps = nic_dev->max_qps;
}
+
+static int hinic3_set_l4_rss_hash_ops(const struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ u8 rss_l4_en;
+
+ switch (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+ case 0:
+ rss_l4_en = 0;
+ break;
+ case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+ rss_l4_en = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ rss_type->tcp_ipv4 = rss_l4_en;
+ break;
+ case TCP_V6_FLOW:
+ rss_type->tcp_ipv6 = rss_l4_en;
+ break;
+ case UDP_V4_FLOW:
+ rss_type->udp_ipv4 = rss_l4_en;
+ break;
+ case UDP_V6_FLOW:
+ rss_type->udp_ipv6 = rss_l4_en;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_update_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ int err;
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ case TCP_V6_FLOW:
+ case UDP_V4_FLOW:
+ case UDP_V6_FLOW:
+ err = hinic3_set_l4_rss_hash_ops(cmd, rss_type);
+ if (err)
+ return err;
+
+ break;
+ case IPV4_FLOW:
+ rss_type->ipv4 = 1;
+ break;
+ case IPV6_FLOW:
+ rss_type->ipv6 = 1;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ cmd->data = 0;
+ netdev_err(netdev, "RSS is disable, not support to set flow-hash\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* RSS only supports hashing of IP addresses and L4 ports */
+ if (cmd->data & ~(RXH_IP_SRC | RXH_IP_DST |
+ RXH_L4_B_0_1 | RXH_L4_B_2_3))
+ return -EINVAL;
+
+ /* Both IP addresses must be part of the hash tuple */
+ if (!(cmd->data & RXH_IP_SRC) || !(cmd->data & RXH_IP_DST))
+ return -EINVAL;
+
+ /* L4 hash bits are not valid for pure L3 flow types */
+ if ((cmd->flow_type == IPV4_FLOW || cmd->flow_type == IPV6_FLOW) &&
+ (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)))
+ return -EINVAL;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ err = hinic3_update_rss_hash_opts(netdev, cmd, &rss_type);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_type(nic_dev->hwdev, rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss type\n");
+ return err;
+ }
+
+ nic_dev->rss_type = rss_type;
+
+ return 0;
+}
+
+static void convert_rss_l3_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (!rss_opt)
+ cmd->data &= ~(RXH_IP_SRC | RXH_IP_DST);
+}
+
+static void convert_rss_l4_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (rss_opt)
+ cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+}
+
+static int hinic3_convert_rss_type(struct net_device *netdev,
+ struct hinic3_rss_type *rss_type,
+ struct ethtool_rxnfc *cmd)
+{
+ cmd->data = RXH_IP_SRC | RXH_IP_DST;
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv4, cmd);
+ break;
+ case TCP_V6_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv6, cmd);
+ break;
+ case UDP_V4_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv4, cmd);
+ break;
+ case UDP_V6_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv6, cmd);
+ break;
+ case IPV4_FLOW:
+ convert_rss_l3_type(rss_type->ipv4, cmd);
+ break;
+ case IPV6_FLOW:
+ convert_rss_l3_type(rss_type->ipv6, cmd);
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ cmd->data = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ cmd->data = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
+ return 0;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ return hinic3_convert_rss_type(netdev, &rss_type, cmd);
+}
+
+u32 hinic3_get_rx_ring_count(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ return nic_dev->q_params.num_qps;
+}
+
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs)
+{
+ int err;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_GRXFH:
+ err = hinic3_get_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
+{
+ int err;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_SRXFH:
+ err = hinic3_set_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+static u16 hinic3_max_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 tcs = netdev_get_num_tc(netdev);
+
+ return tcs ? nic_dev->max_qps / tcs : nic_dev->max_qps;
+}
+
+static u16 hinic3_curr_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ if (netif_running(netdev))
+ return nic_dev->q_params.num_qps ?
+ nic_dev->q_params.num_qps : 1;
+ else
+ return min_t(u16, hinic3_max_channels(netdev),
+ nic_dev->q_params.num_qps);
+}
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ channels->max_rx = 0;
+ channels->max_tx = 0;
+ channels->max_other = 0;
+ /* report maximum channels */
+ channels->max_combined = hinic3_max_channels(netdev);
+ channels->rx_count = 0;
+ channels->tx_count = 0;
+ channels->other_count = 0;
+ /* report flow director queues as maximum channels */
+ channels->combined_count = hinic3_curr_channels(netdev);
+}
+
+static int
+hinic3_validate_channel_parameter(struct net_device *netdev,
+ const struct ethtool_channels *channels)
+{
+ u16 max_channel = hinic3_max_channels(netdev);
+ unsigned int count = channels->combined_count;
+
+ if (!count) {
+ netdev_err(netdev, "Unsupported combined_count=0\n");
+ return -EINVAL;
+ }
+
+ if (channels->tx_count || channels->rx_count || channels->other_count) {
+ netdev_err(netdev, "Setting rx/tx/other count not supported\n");
+ return -EINVAL;
+ }
+
+ if (count > max_channel) {
+ netdev_err(netdev, "Combined count %u exceed limit %u\n", count,
+ max_channel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_rss_update_num_qps_and_reprogram(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
+ if (!netif_is_rxfh_configured(netdev)) {
+ hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+ hinic3_rss_set_indir_tbl(nic_dev->hwdev, nic_dev->rss_indir);
+ }
+
+ if (!netif_running(netdev))
+ return 0;
+
+ err = hinic3_set_hw_rss_parameters(netdev, 1);
+ if (err)
+ netdev_err(netdev,
+ "Failed to update RSS parameters after changing channels\n");
+
+ return err;
+}
+
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ unsigned int count = channels->combined_count;
+ struct hinic3_dyna_txrxq_params q_params;
+ u16 old_num_qps;
+ int err;
+
+ err = hinic3_validate_channel_parameter(netdev, channels);
+ if (err)
+ return err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "This function doesn't support RSS, only support 1 queue pair\n");
+ return -EOPNOTSUPP;
+ }
+
+ old_num_qps = nic_dev->q_params.num_qps;
+
+ netdev_dbg(netdev, "Set max combined queue number from %u to %u\n",
+ old_num_qps, count);
+
+ if (netif_running(netdev)) {
+ q_params = nic_dev->q_params;
+ q_params.num_qps = (u16)count;
+ q_params.txqs_res = NULL;
+ q_params.rxqs_res = NULL;
+ q_params.irq_cfg = NULL;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err) {
+ netdev_err(netdev, "Failed to change channel settings\n");
+ return err;
+ }
+ } else {
+ nic_dev->q_params.num_qps = (u16)count;
+ }
+
+ err = hinic3_rss_update_num_qps_and_reprogram(netdev);
+ if (err) {
+ if (netif_running(netdev)) {
+ q_params.num_qps = old_num_qps;
+ if (hinic3_change_channel_settings(netdev, &q_params))
+ netdev_err(netdev, "Failed to rollback channel settings after RSS error\n");
+ } else {
+ nic_dev->q_params.num_qps = old_num_qps;
+ }
+ return err;
+ }
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_INDIR_SIZE;
+}
+
+static int hinic3_set_rss_rxfh(struct net_device *netdev,
+ const u32 *indir, u8 *key)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 temp_indir[L2NIC_RSS_INDIR_SIZE];
+ int err;
+ u32 i;
+
+ if (indir) {
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ temp_indir[i] = (u16)indir[i];
+
+ err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, temp_indir);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss indir table\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_indir, temp_indir, sizeof(temp_indir));
+ }
+
+ if (key) {
+ err = hinic3_rss_set_hash_key(nic_dev->hwdev, key);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss key\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_hkey, key, L2NIC_RSS_KEY_SIZE);
+ }
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_KEY_SIZE;
+}
+
+static int hinic3_rss_get_indir_tbl(struct hinic3_hwdev *hwdev,
+ u32 *indir_table)
+{
+ struct hinic3_cmd_buf_pair pair;
+ __le16 *indir_tbl = NULL;
+ int err, i;
+
+ err = hinic3_cmd_buf_pair_init(hwdev, &pair);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to allocate cmd_buf.\n");
+ return err;
+ }
+
+ memset(pair.in->buf, 0, le16_to_cpu(pair.in->size));
+
+ err = hinic3_cmdq_detail_resp(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL,
+ pair.in, pair.out, NULL);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to get rss indir table\n");
+ goto err_get_indir_tbl;
+ }
+
+ indir_tbl = (__force __le16 *)pair.out->buf;
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ indir_table[i] = le16_to_cpu(*(indir_tbl + i));
+
+err_get_indir_tbl:
+ hinic3_cmd_buf_pair_uninit(hwdev, &pair);
+
+ return err;
+}
+
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Rss is disabled\n");
+ return -EOPNOTSUPP;
+ }
+
+ rxfh->hfunc =
+ nic_dev->rss_hash_type == HINIC3_RSS_HASH_ENGINE_TYPE_XOR ?
+ ETH_RSS_HASH_XOR : ETH_RSS_HASH_TOP;
+
+ if (rxfh->indir) {
+ err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, rxfh->indir);
+ if (err)
+ return err;
+ }
+
+ if (rxfh->key)
+ memcpy(rxfh->key, nic_dev->rss_hkey, L2NIC_RSS_KEY_SIZE);
+
+ return err;
+}
+
+static int hinic3_update_hash_func_type(struct net_device *netdev, u8 hfunc)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ enum hinic3_rss_hash_type new_rss_hash_type;
+ int err;
+
+ switch (hfunc) {
+ case ETH_RSS_HASH_NO_CHANGE:
+ return 0;
+ case ETH_RSS_HASH_XOR:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
+ break;
+ case ETH_RSS_HASH_TOP:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_TOEP;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported hash func %u\n", hfunc);
+ return -EOPNOTSUPP;
+ }
+
+ if (new_rss_hash_type == nic_dev->rss_hash_type)
+ return 0;
+
+ err = hinic3_rss_set_hash_type(nic_dev->hwdev, new_rss_hash_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set RSS hash type to HW\n");
+ return err;
+ }
+
+ nic_dev->rss_hash_type = new_rss_hash_type;
+
+ return 0;
+}
+
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Not support to set rss parameters when rss is disable\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = hinic3_update_hash_func_type(netdev, rxfh->hfunc);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_rxfh(netdev, rxfh->indir, rxfh->key);
+
+ return err;
+}
--git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
index 78d82c2aca06..cf48a6449404 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
@@ -5,10 +5,30 @@
#define _HINIC3_RSS_H_
#include <linux/netdevice.h>
+#include <linux/ethtool.h>
int hinic3_rss_init(struct net_device *netdev);
void hinic3_rss_uninit(struct net_device *netdev);
void hinic3_try_to_enable_rss(struct net_device *netdev);
void hinic3_clear_rss_config(struct net_device *netdev);
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs);
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd);
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev);
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev);
+
+u32 hinic3_get_rx_ring_count(struct net_device *netdev);
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh);
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack);
+
#endif
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v10 0/5] net: hinic3: PF initialization
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
This is [3/3] part of hinic3 Ethernet driver second submission.
With this patch hinic3 becomes a complete Ethernet driver with
pf and vf.
Add 20 ethtool ops for information of queue, rss, coalesce and eth data.
Add MTU size validation
Config netdev watchdog timeout.
Remove unneed coalesce parameters.
Changes:
PATCH 03 V01: https://lore.kernel.org/netdev/cover.1773387649.git.zhuyikai1@h-partners.com/
* Add rmon/pause/phy/mac/ctrl stats (Ioana Ciornei)
PATCH 03 V02: https://lore.kernel.org/netdev/cover.1774684571.git.zhuyikai1@h-partners.com/
* Modify "return -EINVAL" intension problem (AI review)
* Use le16_to_cpu for rss_indir pair.out->buf (AI review)
* Use u32 instead of int in coalesce_limits to avoid overflow (AI review)
* Remove redundant u64_stats_update_begin/end when reading stats without
concurrent reader (AI review)
* Modify nic_dev->stats.syncp logic (AI review)
* Complete rxq/txq stats stats fileds in hinic3_rx/txq_get_stats (AI review)
* Remove statistics values in rtnl_link_stats64 from ethtool statistics
values (AI review)
* Add channel_cfg_lock & channel_res_lock to protect resources access (AI review)
* Remove OutOfRangeLengthField, FrameToolong and InRangeLengthErrors (Ioana Ciornei)
* Remove redundant mtu commit (Maxime Chevialler)
PATCH 03 V03: https://lore.kernel.org/netdev/cover.1774940117.git.zhuyikai1@h-partners.com/
* Change unnedd to unneeded (AI review)
* Remove packets,bytes,errors and dropped in hinic3_rx/tx_queue_stats (AI review)
* Remove duplicated entried in hinic3_port_stats[] (AI review)
* change stats_info.head.status to ps->head.status (AI review)
PATCH 03 V04: https://lore.kernel.org/netdev/cover.1775618797.git.zhuyikai1@h-partners.com/
* Remove restore_drop_sge in hinic3_rx_queue_stats (AI review)
* Remove hinic3_nic_stats (AI review)
* Use old_q_param to store old config and use it in error handling (Mohsin Bashir)
* Add netdev_info to inform the user that depth is trimmed (Mohsin Bashir)
* Remove const in hinic3_get_qp_stats_strings parameters (Mohsin Bashir)
* Change EOPNOTSUPP to ERANGE in is_coalesce_exceed_limit (Mohsin Bashir)
* Update nic_dev->rss_type after hinic3_set_rss_type (Mohsin Bashir)
* Modify MGMT_STATUS_CMD_UNSUPPORTED to EOPNOTSUPP for complying with the
error code specifications (Mohsin Bashir)
PATCH 03 V05: https://lore.kernel.org/netdev/cover.1775711066.git.zhuyikai1@h-partners.com/
* Clear HINIC3_CHANGE_RES_INVALID bit in error handling (AI review)
* Use low >= high to avoid low=high in is_coalesce_legal (AI review)
* As tx and rx share interrupts, we only use ETHTOOL_COALESCE_RX_USECS for
user setting to avoid user misunderstanding. So we do not add
ETHTOOL_COALESCE_TX_USECS. (Mohsin Bashir & AI review)
PATCH 03 V06: https://lore.kernel.org/netdev/cover.1779867397.git.zhuyikai1@h-partners.com/
* Remove redundant rx_jumbo_pending and rx_mini_pending judgement (Jakub Kicinski)
* Remove redundant max tx_pending judgement when .get_ringparam already got the
max value (Jakub Kicinski)
* Use extack instead of netdev_err/netdev_info/netdev_warning (Jakub Kicinski)
* Remove HINIC3_CHANNEL_RES_VALID and only use HINIC3_CHANGE_RES_INVALID
bit (Jakub Kicinski)
* Deference freed pointers in hinic3_change_channel_settings error
handling (Jakub Kicinski)
* Modify hinic3_open_channel (Jakub Kicinski)
PATCH 03 V07: https://lore.kernel.org/netdev/cover.1779940072.git.zhuyikai1@h-partners.com/
* Remove the trailing '\n' in NL_SET_ERR_MSG* (Jakub Kicinski)
PATCH 03 V08: https://lore.kernel.org/netdev/cover.1780907605.git.wudi234@huawei.com/
* Update patch commit to be more accurate for change information (AI review)
* Modify <net/devlink.h> to <linux/netlink.h> (AI review)
* Use sq/rq_depth instead of rx/txqs[0].q_depth (AI review)
* hinic3_change_channel_settings() runs under change_res_mutex
to protect dynamic channel updates (AI review)
* hinic3_close() serializes close and channel reconfiguration paths (AI review)
* Remove useless u64_stats_init in hinic3_get_drv_queue_stats() (AI review)
* hinic3_get_drv_queue_stats() fills rxq stats at correct idx
when txq is null (AI review)
* Remove unnecessary semicolons after the closing bracket of
static inline functions (AI review)
* Disable preemption in hinic3_rx_fill_buffers() (AI review)
* Add spinlock to protect per-queue coalesce parameters from
concurrent access (AI review)
* Reject static coalesce config when adaptive RX coalesce is
enabled (AI review)
* Validate interface state and queue ID before modifying coalesce
parameters (AI review)
* Differentiate get_coalesce output for adaptive/static modes and
zero-fill the struct (AI review)
* Return -ERANGE for invalid coalesce frame limits and simplify
error message (AI review)
* Remove unused watchdog timeout definition and assignment (AI review)
* Return -EOPNOTSUPP when RSS type query is unsupported by firmware (AI review)
* Reject L4 hash bits for pure L3 flow types (AI review)
* Split RSS type conversion to correctly handle L3-only flows (AI review)
* Reprogram RSS indir table and HW parameters after channel count
changes (AI review)
* Propagate exact error code from channel parameter validation (AI review)
* Update SW RSS indir copy only after HW programming succeeds (AI review)
* Program RSS hash type to HW before updating SW state to prevent
inconsistency (AI review)
PATCH 03 V09: https://lore.kernel.org/netdev/cover.1781062575.git.wudi234@huawei.com/
* Remove useless messages in hinic3_set_ringparam() (Jakub Kicinski)
* Solve the big-endian and little-endian issue (Jakub Kicinski)
PATCH 03 V10:
* Unify 3 mutex in struct hinic3_nic_dev: port_state_mutex, change_res_mutex,
coal_lock.
* Correct copy-paste error in hinic3_get_ringparam() (AI Review and
Harshitha Ramamurthy)
* hinic3_set_interrupt_moder() now compare q_id and num_qps inside mutex lock
(AI Review)
* Error branches in hinic3_link_status_change() goto unlock mutex before return
(AI Review)
* In hinic3_change_channel_settings(), hinic3_config_num_qps() and
hinic3_alloc_channel_resources() now run inside mutex lock. (AI Review)
* Call hinic3_free_qp_ctxts() in error paths of hinic3_open_channel(),
thus hinic3_change_channel_settings() don't call it in error path (AI Review)
* Unset HINIC3_INTF_UP during close and reopen in hinic3_change_channel_settings
* hinic3_change_channel_settings() restore previous resources before freeing
new_qp_params (AI Review)
* Delete unused macro HINIC3_LRO_PKT_HDR_LEN (AI Review)
* Drop packets when skb_checksum_help() return error (AI Review)
* Use rxq->q_mask instead of rxq->q_depth - 1 in hinic3_configure_rxqs()
(Dimitri Daskalakis)
* Clarify extack error message in hinic3_check_ringparam_valid()
(Harshitha Ramamurthy)
* Use rounddown_pow_of_two() instead of 1U << ilog2() in hinic3_set_ringparam()
for readability. (Harshitha Ramamurthy)
* Protect interrupt coalesce parameter reads with state_lock to prevent data
races(AI Review)
* Restore previous coalesce parameters on HW configuration failure (AI Review)
* Always report all coalesce parameters regardless of adaptive mode (AI Review)
* Safely stop DIM work when disabling adaptive RX coalescing (AI Review)
* Change coalesce disabled message from extack error to info log (AI Review)
* Implement dedicated get_rx_ring_count ethtool operation (Dimitri Daskalakis)
* Pass new RSS hash type to HW instead of the old one (AI Review)
* Initialize default RSS indirection table before HW programming when not
user-configured(AI Review)
* Revert queue count on RSS reprogramming failure (AI Review)
* Use sizeof for RSS indirection table copy to prevent size mismatch (AI Review)
Fan Gong (5):
hinic3: Add ethtool queue ops
hinic3: Add ethtool statistic ops
hinic3: Add ethtool coalesce ops
hinic3: Add ethtool rss ops
hinic3: Remove unneeded coalesce parameters
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 870 +++++++++++++++++-
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 32 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 13 +-
.../huawei/hinic3/hinic3_mgmt_interface.h | 39 +
.../huawei/hinic3/hinic3_netdev_ops.c | 108 ++-
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 +++
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 11 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rss.c | 555 ++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_rss.h | 20 +
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 74 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 18 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 69 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
17 files changed, 1960 insertions(+), 47 deletions(-)
base-commit: 903db046d5579bef0ea699eae4b279dd6455fc9f
--
2.43.0
^ permalink raw reply
* [PATCH net-next v10 1/5] hinic3: Add ethtool queue ops
From: Fan Gong @ 2026-06-29 7:37 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1782718232.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_ringparam
.set_ringparam
These callbacks allow users to utilize ethtool for detailed
queue depth configuration and monitoring.
Change port_state_mutex to state_lock as a unified mutex lock
in hinic3_nic_dev.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 91 +++++++++++++++
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 11 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 13 ++-
.../huawei/hinic3/hinic3_netdev_ops.c | 110 ++++++++++++++++--
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 11 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 2 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 7 +-
9 files changed, 236 insertions(+), 21 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 90fc16288de9..e6dff46a0d00 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
+#include <linux/netlink.h>
#include <linux/ethtool.h>
#include "hinic3_lld.h"
@@ -409,6 +410,94 @@ hinic3_get_link_ksettings(struct net_device *netdev,
return 0;
}
+static void hinic3_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ ring->rx_max_pending = HINIC3_MAX_RX_QUEUE_DEPTH;
+ ring->tx_max_pending = HINIC3_MAX_TX_QUEUE_DEPTH;
+ ring->rx_pending = nic_dev->q_params.rq_depth;
+ ring->tx_pending = nic_dev->q_params.sq_depth;
+}
+
+static void hinic3_update_qp_depth(struct net_device *netdev,
+ u32 sq_depth, u32 rq_depth)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i;
+
+ nic_dev->q_params.sq_depth = sq_depth;
+ nic_dev->q_params.rq_depth = rq_depth;
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ nic_dev->txqs[i].q_depth = sq_depth;
+ nic_dev->txqs[i].q_mask = sq_depth - 1;
+ nic_dev->rxqs[i].q_depth = rq_depth;
+ nic_dev->rxqs[i].q_mask = rq_depth - 1;
+ }
+}
+
+static int hinic3_check_ringparam_valid(struct net_device *netdev,
+ const struct ethtool_ringparam *ring,
+ struct netlink_ext_ack *extack)
+{
+ if (ring->tx_pending < HINIC3_MIN_QUEUE_DEPTH ||
+ ring->rx_pending < HINIC3_MIN_QUEUE_DEPTH) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Queue depth underflow[%d]: tx[%d], rx[%d]",
+ HINIC3_MIN_QUEUE_DEPTH,
+ ring->tx_pending, ring->rx_pending);
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params q_params = {};
+ u32 new_sq_depth, new_rq_depth;
+ int err;
+
+ err = hinic3_check_ringparam_valid(netdev, ring, extack);
+ if (err)
+ return err;
+
+ new_sq_depth = rounddown_pow_of_two(ring->tx_pending);
+ new_rq_depth = rounddown_pow_of_two(ring->rx_pending);
+ if (new_sq_depth == nic_dev->q_params.sq_depth &&
+ new_rq_depth == nic_dev->q_params.rq_depth)
+ return 0;
+
+ if (new_sq_depth != ring->tx_pending ||
+ new_rq_depth != ring->rx_pending)
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Requested Tx/Rx ring depth %u/%u trimmed to %u/%u",
+ ring->tx_pending, ring->rx_pending,
+ new_sq_depth, new_rq_depth);
+
+ if (!netif_running(netdev)) {
+ hinic3_update_qp_depth(netdev, new_sq_depth, new_rq_depth);
+ } else {
+ q_params = nic_dev->q_params;
+ q_params.sq_depth = new_sq_depth;
+ q_params.rq_depth = new_rq_depth;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -417,6 +506,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_msglevel = hinic3_get_msglevel,
.set_msglevel = hinic3_set_msglevel,
.get_link = ethtool_op_get_link,
+ .get_ringparam = hinic3_get_ringparam,
+ .set_ringparam = hinic3_set_ringparam,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index e7d6c2033b45..5479c235e3de 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -135,10 +135,13 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
struct hinic3_interrupt_info info = {};
- int err;
+ int err = 0;
+
+ if (!mutex_trylock(&nic_dev->state_lock))
+ goto out;
if (q_id >= nic_dev->q_params.num_qps)
- return 0;
+ goto err_unlock;
info.interrupt_coalesc_set = 1;
info.coalesc_timer_cfg = coalesc_timer_cfg;
@@ -156,6 +159,10 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
}
+err_unlock:
+ mutex_unlock(&nic_dev->state_lock);
+
+out:
return err;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 0a888fe4c975..bb2b000f86e8 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -178,7 +178,7 @@ static int hinic3_sw_init(struct net_device *netdev)
u8 mac_addr[ETH_ALEN];
int err;
- mutex_init(&nic_dev->port_state_mutex);
+ mutex_init(&nic_dev->state_lock);
nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
@@ -315,21 +315,28 @@ static void hinic3_link_status_change(struct net_device *netdev,
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ if (!mutex_trylock(&nic_dev->state_lock))
+ return;
+
if (link_status_up) {
if (netif_carrier_ok(netdev))
- return;
+ goto err_unlock;
nic_dev->link_status_up = true;
netif_carrier_on(netdev);
netdev_dbg(netdev, "Link is up\n");
} else {
if (!netif_carrier_ok(netdev))
- return;
+ goto err_unlock;
nic_dev->link_status_up = false;
netif_carrier_off(netdev);
netdev_dbg(netdev, "Link is down\n");
}
+
+err_unlock:
+ mutex_unlock(&nic_dev->state_lock);
+ return;
}
static void hinic3_port_module_event_handler(struct net_device *netdev,
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
index da73811641a9..2d527b0ecbbe 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
@@ -288,7 +288,8 @@ static void hinic3_free_channel_resources(struct net_device *netdev,
hinic3_free_qps(nic_dev, qp_params);
}
-static int hinic3_open_channel(struct net_device *netdev)
+static int hinic3_prepare_channel(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *qp_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
@@ -299,12 +300,25 @@ static int hinic3_open_channel(struct net_device *netdev)
return err;
}
- err = hinic3_configure_txrxqs(netdev, &nic_dev->q_params);
+ err = hinic3_configure_txrxqs(netdev, qp_params);
if (err) {
netdev_err(netdev, "Failed to configure txrxqs\n");
goto err_free_qp_ctxts;
}
+ return 0;
+
+err_free_qp_ctxts:
+ hinic3_free_qp_ctxts(nic_dev);
+
+ return err;
+}
+
+static int hinic3_open_channel(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
err = hinic3_qps_irq_init(netdev);
if (err) {
netdev_err(netdev, "Failed to init txrxq irq\n");
@@ -321,6 +335,7 @@ static int hinic3_open_channel(struct net_device *netdev)
err_uninit_qps_irq:
hinic3_qps_irq_uninit(netdev);
+
err_free_qp_ctxts:
hinic3_free_qp_ctxts(nic_dev);
@@ -341,9 +356,9 @@ static int hinic3_maybe_set_port_state(struct net_device *netdev, bool enable)
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
- mutex_lock(&nic_dev->port_state_mutex);
+ mutex_lock(&nic_dev->state_lock);
err = hinic3_set_port_enable(nic_dev->hwdev, enable);
- mutex_unlock(&nic_dev->port_state_mutex);
+ mutex_unlock(&nic_dev->state_lock);
return err;
}
@@ -428,6 +443,78 @@ static void hinic3_vport_down(struct net_device *netdev)
}
}
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params cur_trxq_params = {};
+ struct hinic3_dyna_qp_params new_qp_params = {};
+ struct hinic3_dyna_qp_params cur_qp_params = {};
+ int err;
+
+ cur_trxq_params = nic_dev->q_params;
+
+ mutex_lock(&nic_dev->state_lock);
+ hinic3_config_num_qps(netdev, trxq_params);
+
+ err = hinic3_alloc_channel_resources(netdev, &new_qp_params,
+ trxq_params);
+ if (err) {
+ netdev_err(netdev, "Failed to alloc channel resources\n");
+ mutex_unlock(&nic_dev->state_lock);
+ return err;
+ }
+
+ hinic3_vport_down(netdev);
+ hinic3_close_channel(netdev);
+ clear_bit(HINIC3_INTF_UP, &nic_dev->flags);
+ hinic3_get_cur_qps(nic_dev, &cur_qp_params);
+
+ hinic3_init_qps(nic_dev, &new_qp_params);
+
+ err = hinic3_prepare_channel(netdev, trxq_params);
+ if (err)
+ goto err_uninit_qps;
+
+ if (nic_dev->num_qp_irq > trxq_params->num_qps)
+ hinic3_qp_irq_change(netdev, trxq_params->num_qps);
+
+ nic_dev->q_params = *trxq_params;
+
+ err = hinic3_open_channel(netdev);
+ if (err)
+ goto err_qp_irq_reset;
+
+ err = hinic3_vport_up(netdev);
+ if (err)
+ goto err_close_channel;
+
+ set_bit(HINIC3_INTF_UP, &nic_dev->flags);
+
+ hinic3_free_channel_resources(netdev, &cur_qp_params, &cur_trxq_params);
+
+ mutex_unlock(&nic_dev->state_lock);
+
+ return 0;
+
+err_close_channel:
+ hinic3_close_channel(netdev);
+err_qp_irq_reset:
+ nic_dev->q_params = cur_trxq_params;
+
+ if (trxq_params->num_qps > cur_trxq_params.num_qps)
+ hinic3_qp_irq_change(netdev, cur_trxq_params.num_qps);
+err_uninit_qps:
+ hinic3_get_cur_qps(nic_dev, &new_qp_params);
+ hinic3_config_num_qps(netdev, &cur_trxq_params);
+ hinic3_init_qps(nic_dev, &cur_qp_params);
+ hinic3_free_channel_resources(netdev, &new_qp_params, trxq_params);
+ mutex_unlock(&nic_dev->state_lock);
+
+ return err;
+}
+
static int hinic3_open(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
@@ -458,6 +545,10 @@ static int hinic3_open(struct net_device *netdev)
hinic3_init_qps(nic_dev, &qp_params);
+ err = hinic3_prepare_channel(netdev, &nic_dev->q_params);
+ if (err)
+ goto err_uninit_qps;
+
err = hinic3_open_channel(netdev);
if (err)
goto err_uninit_qps;
@@ -473,7 +564,7 @@ static int hinic3_open(struct net_device *netdev)
err_close_channel:
hinic3_close_channel(netdev);
err_uninit_qps:
- hinic3_uninit_qps(nic_dev, &qp_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
err_destroy_num_qps:
hinic3_destroy_num_qps(netdev);
@@ -493,10 +584,15 @@ static int hinic3_close(struct net_device *netdev)
return 0;
}
+ mutex_lock(&nic_dev->state_lock);
hinic3_vport_down(netdev);
hinic3_close_channel(netdev);
- hinic3_uninit_qps(nic_dev, &qp_params);
- hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
+ hinic3_free_channel_resources(netdev, &qp_params,
+ &nic_dev->q_params);
+ hinic3_free_nicio_res(nic_dev);
+ hinic3_destroy_num_qps(netdev);
+ mutex_unlock(&nic_dev->state_lock);
return 0;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
index 9502293ff710..ccef82d57fdd 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
@@ -10,6 +10,9 @@
#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_mgmt_interface.h"
+#include "hinic3_nic_io.h"
+#include "hinic3_tx.h"
+#include "hinic3_rx.h"
#define HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev) (sizeof(*(nic_dev)->vlan_bitmap))
#define HINIC3_VLAN_BITMAP_SIZE(nic_dev) \
@@ -127,8 +130,8 @@ struct hinic3_nic_dev {
struct workqueue_struct *workq;
struct delayed_work periodic_work;
struct work_struct rx_mode_work;
- /* lock for enable/disable port */
- struct mutex port_state_mutex;
+ /* Protects Interface state */
+ struct mutex state_lock;
struct list_head uc_filter_list;
struct list_head mc_filter_list;
@@ -143,6 +146,10 @@ struct hinic3_nic_dev {
void hinic3_set_netdev_ops(struct net_device *netdev);
int hinic3_set_hw_features(struct net_device *netdev);
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params);
+
int hinic3_qps_irq_init(struct net_device *netdev);
void hinic3_qps_irq_uninit(struct net_device *netdev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
index 87e736adba02..0e7a0ccfba98 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
@@ -484,8 +484,8 @@ void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
}
}
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params)
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
index 12eefabcf1db..571b34d63950 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
@@ -14,6 +14,10 @@ struct hinic3_nic_dev;
#define HINIC3_RQ_WQEBB_SHIFT 3
#define HINIC3_SQ_WQEBB_SIZE BIT(HINIC3_SQ_WQEBB_SHIFT)
+#define HINIC3_MAX_TX_QUEUE_DEPTH 65536
+#define HINIC3_MAX_RX_QUEUE_DEPTH 16384
+#define HINIC3_MIN_QUEUE_DEPTH 128
+
/* ******************** RQ_CTRL ******************** */
enum hinic3_rq_wqe_type {
HINIC3_NORMAL_RQ_WQE = 1,
@@ -136,8 +140,8 @@ void hinic3_free_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params);
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params);
int hinic3_init_qp_ctxts(struct hinic3_nic_dev *nic_dev);
void hinic3_free_qp_ctxts(struct hinic3_nic_dev *nic_dev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 309ab5901379..389b1c2158be 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -541,7 +541,7 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
rq_associate_cqes(rxq);
pkts = hinic3_rx_fill_buffers(rxq);
- if (!pkts) {
+ if (pkts < rxq->q_mask) {
netdev_err(netdev, "Failed to fill Rx buffer\n");
return -ENOMEM;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 9306bf0020ca..53c065194136 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -261,8 +261,7 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
/* Unsupported tunnel packet, disable csum offload */
- skb_checksum_help(skb);
- return 0;
+ return skb_checksum_help(skb);
}
}
@@ -412,6 +411,10 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
offload |= HINIC3_TX_OFFLOAD_TSO;
} else {
tso_cs_en = hinic3_tx_csum(txq, task, skb);
+ if (tso_cs_en < 0) {
+ offload = HINIC3_TX_OFFLOAD_INVALID;
+ return offload;
+ }
if (tso_cs_en)
offload |= HINIC3_TX_OFFLOAD_CSUM;
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH bpf] bpf: tcp: Fix use-after-free in bpf_iter_tcp_established_batch()
From: Emil Tsalapatis @ 2026-06-29 7:37 UTC (permalink / raw)
To: Jose Fernandez (Anthropic), Eric Dumazet, Neal Cardwell,
Kuniyuki Iwashima, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Andrii Nakryiko, Yonghong Song, Martin KaFai Lau
Cc: netdev, linux-kernel, bpf, Ben Cressey
In-Reply-To: <20260620-bpf-iter-tcp-refcnt-v1-1-883bf9e69495@linux.dev>
On Fri Jun 19, 2026 at 8:32 PM EDT, Jose Fernandez (Anthropic) wrote:
> reqsk_queue_hash_req() publishes a TCP_NEW_SYN_RECV request_sock onto
> the ehash chain (via inet_ehash_insert(), which drops the bucket lock on
> return) and only afterwards refcount_set()s rsk_refcnt to 3.
>
> Lockless readers such as __inet_lookup_established() account for this by
> using refcount_inc_not_zero(), but bpf_iter_tcp_established_batch() uses
> plain sock_hold() while holding the bucket lock, on the assumption that
> the lock guarantees sk_refcnt > 0. That assumption does not hold for
> request_sock:
>
> CPU 0 CPU 1
> ----- -----
> tcp_conn_request()
> reqsk_queue_hash_req()
> inet_ehash_insert(req)
> spin_lock(bucket)
> __sk_nulls_add_node_rcu(req) // rsk_refcnt == 0
> spin_unlock(bucket)
> bpf_iter_tcp_established_batch()
> spin_lock(bucket)
> sock_hold(req) <-- addition on 0
> spin_unlock(bucket)
> refcount_set(&req->rsk_refcnt, 3) // clobbers saturated value
>
> which surfaces as:
>
> refcount_t: addition on 0; use-after-free.
> WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x48/0x90, CPU#1
> Call Trace:
> bpf_iter_tcp_established_batch+0x14e/0x170
> bpf_iter_tcp_batch+0x53/0x200
> bpf_iter_tcp_seq_next+0x27/0x70
> bpf_seq_read+0x107/0x410
> vfs_read+0xb9/0x380
>
> refcount_warn_saturate() then saturates the count, the publishing CPU's
> refcount_set() clobbers it, and the socket is left one reference short.
> When the last legitimate owner drops its reference the reqsk is freed
> while still reachable, leading to use-after-free panics in e.g.
> inet_csk_accept() or inet_csk_listen_stop().
>
> This reproduces in seconds with tcp_syncookies=0, a handful of threads
> doing connect()/close() to a local listener while others read an
> iter/tcp link in a tight loop.
>
> Use refcount_inc_not_zero() and skip the socket on failure, the same way
> every other ehash walker does. The listening hash is unaffected as
> listeners are always inserted into lhash2 with sk_refcnt >= 1, so
> bpf_iter_tcp_listening_batch() is left as-is.
>
> If every matching socket in a bucket is mid-init, end_sk can stay at 0;
> advance to the next bucket in that case rather than terminating the
> whole iteration on a stale batch[0].
>
> Fixes: 04c7820b776f ("bpf: tcp: Bpf iter batching and lock_sock")
> Reviewed-by: Ben Cressey <ben@cressey.dev>
> Assisted-by: Claude:unspecified
> Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> ---
> net/ipv4/tcp_ipv4.c | 35 ++++++++++++++++++++---------------
> 1 file changed, 20 insertions(+), 15 deletions(-)
>
> diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
> index fdc81150ff6c..92342dcc6892 100644
> --- a/net/ipv4/tcp_ipv4.c
> +++ b/net/ipv4/tcp_ipv4.c
> @@ -3074,25 +3074,25 @@ static unsigned int bpf_iter_tcp_established_batch(struct seq_file *seq,
> {
> struct bpf_tcp_iter_state *iter = seq->private;
> struct hlist_nulls_node *node;
> - unsigned int expected = 1;
> - struct sock *sk;
> + unsigned int expected = 0;
> + struct sock *sk = *start_sk;
>
> - sock_hold(*start_sk);
> - iter->batch[iter->end_sk++].sk = *start_sk;
> -
> - sk = sk_nulls_next(*start_sk);
> *start_sk = NULL;
> sk_nulls_for_each_from(sk, node) {
> - if (seq_sk_match(seq, sk)) {
> - if (iter->end_sk < iter->max_sk) {
> - sock_hold(sk);
> - iter->batch[iter->end_sk++].sk = sk;
> - } else if (!*start_sk) {
> - /* Remember where we left off. */
> - *start_sk = sk;
> - }
> - expected++;
> + if (!seq_sk_match(seq, sk))
> + continue;
> + if (iter->end_sk < iter->max_sk) {
> + /* reqsk_queue_hash_req() inserts with sk_refcnt == 0
> + * and refcount_set()s it after the bucket lock drops.
> + */
> + if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> + continue;
> + iter->batch[iter->end_sk++].sk = sk;
> + } else if (!*start_sk) {
> + /* Remember where we left off. */
> + *start_sk = sk;
> }
> + expected++;
> }
>
> return expected;
> @@ -3129,6 +3129,7 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
> struct sock *sk;
> int err;
>
> +again:
> sk = bpf_iter_tcp_resume(seq);
> if (!sk)
> return NULL; /* Done */
> @@ -3167,6 +3168,10 @@ static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
> WARN_ON_ONCE(iter->end_sk != expected);
> done:
> bpf_iter_tcp_unlock_bucket(seq);
> + if (unlikely(!iter->end_sk)) {
> + ++iter->state.bucket;
> + goto again;
> + }
> return iter->batch[0].sk;
> }
>
>
> ---
> base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
> change-id: 20260619-bpf-iter-tcp-refcnt-107d52b238da
>
> Best regards,
> --
> Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
^ permalink raw reply
* RE: [External Mail] Re: [PATCH v3 3/7] net: wwan: t9xx: Add control DMA interface
From: Wu. JackBB (GSM) @ 2026-06-29 7:29 UTC (permalink / raw)
To: Andrew Lunn
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <dc007021-9013-47b2-b06a-fe4a50d26722@lunn.ch>
Hi Andrew,
> > +static inline void mtk_cldma_clr_bd_dsc(struct cldma_drv_info *drv_info,
> > + struct bd_dsc *bd_dsc_pool, int nr_bds)
>
> No inline functions in C files. Please let the compiler decide.
Will remove inline from all static functions in .c files.
> > + bd_dsc->skb = __dev_alloc_skb(req->frag_size, GFP_KERNEL);
> > + if (!bd_dsc->skb) {
> > + dev_warn((mdev)->dev, "Failed to alloc SKB\n");
>
> You might want to rate limit this, and the other similar messages in
> the data path, otherwise it could be a DOS.
Will switch to dev_warn_ratelimited() for data path warnings.
> > + } while (++cnt < 10);
>
> Please use one of the helpers from iopoll.h.
Will replace with read_poll_timeout() in v4.
> > + devm_kfree(trans->mdev->dev, srv_que);
>
> It is unusual to see devm_kfree(). Why is it needed?
Will remove redundant devm_kfree() calls and let devres handle
cleanup on probe failure.
> > +static unsigned int ctrl_port_chl_mtu;
>
> Is this a global variable? Why is it not part of priv?
> > +module_param(ctrl_port_chl_mtu, uint, 0644);
>
> No modules parameters please. If this is an MTU, why not use the
> normal networking interfaces to set the MTU?
Will remove the module parameter entirely. The control port MTU
is determined by the device during handshake.
Thanks.
Jack Wu
^ permalink raw reply
* Re: [RFC PATCH net-next v8 03/12] net: phylink: add phylink_release_pcs() to externally release a PCS
From: Maxime Chevallier @ 2026-06-29 7:28 UTC (permalink / raw)
To: Christian Marangi
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan, Lorenzo Bianconi,
Heiner Kallweit, Russell King, Saravana Kannan, Philipp Zabel,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
netdev, devicetree, linux-kernel, linux-doc, linux-arm-kernel,
linux-mediatek, llvm
In-Reply-To: <6a421c2d.1e0bfc4f.b2eb6.06fb@mx.google.com>
On 6/29/26 09:18, Christian Marangi wrote:
> Nobody stops from implementing custom parser in fill_available_pcs and
> return a PCS pointer created directly by the MAC. (In such case late and
> release won't be needed as everything is present when phylink_create is
> called)
Ah alright :)
So my only request then would be to update the phylink porting guide to at
least point to the PCS documentation you're adding to say that the select_pcs()
op is deprecated :
https://docs.kernel.org/networking/sfp-phylink.html#rough-guide-to-converting-a-network-driver-to-sfp-phylink
Thanks,
Maxime
>
^ permalink raw reply
* RE: [External Mail] Re: [PATCH v3 2/7] net: wwan: t9xx: Add control plane transaction layer
From: Wu. JackBB (GSM) @ 2026-06-29 7:26 UTC (permalink / raw)
To: Andrew Lunn
Cc: Loic Poulain, Sergey Ryazanov, Johannes Berg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Wen-Zhi Huang, Shi-Wei Yeh, Minano Tseng, Matthias Brugger,
AngeloGioacchino Del Regno, Simon Horman, Jonathan Corbet,
Shuah Khan, linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, linux-doc@vger.kernel.org
In-Reply-To: <2a90ae6b-2b6d-4340-b557-915252cc3488@lunn.ch>
Hi Andrew,
> > +static int __init mtk_common_drv_init(void)
> > +{
> > + return 0;
> > +}
> > +module_init(mtk_common_drv_init);
> > +
> > +static void __exit mtk_common_drv_exit(void)
> > +{
> > +}
> > +module_exit(mtk_common_drv_exit);
>
> Since these don't do anything, they should not be needed.
Will move module_init/module_exit to the patch that first adds
content to them in v4.
> > + SET_HW_BITS(hw_bits, chs, MHCCIF_RC2EP_EVT_DEVICE_RESET,
> > + DEV_EVT_H2D_DEVICE_RESET);
> > +
> > + return LE32_TO_U32(cpu_to_le32(hw_bits));
>
> Please don't add white space like this. I assume a previous patch
> added this code, so move this to that patch.
Will remove the extra blank line in v4.
> > - devm_kfree(dev, mdev);
> > + mtk_dev_free(mdev);
>
> Why are you removing devm_ calls?
mtk_dev_alloc/mtk_dev_free are paired wrappers so the caller
doesn't need to know the underlying allocation mechanism.
The devm_kfree is still called inside mtk_dev_free.
Thanks.
Jack Wu.
^ permalink raw reply
* [PATCH v3] xsk: fix memory corruptions in net/core/xdp.c
From: Clement Lecigne @ 2026-06-29 7:22 UTC (permalink / raw)
To: aleksander.lobakin, edumazet, netdev
Cc: clecigne, bpf, linux-kernel, kuba, sdf, horms, john.fastabend,
ast, daniel
From: Clément Lecigne <clecigne@google.com>
Commit 560d958c6c68 ("xsk: add generic XSk &xdp_buff -> skb conversion")
introduced a vulnerability in the handling of XDP_PASS for AF_XDP zero-copy
frames.
Note: Currently, this specific AF_XDP zero-copy conversion path is only
reachable from the drivers/net/ethernet/intel/ice and
drivers/net/ethernet/intel/idpf drivers.
When building an skb, xdp_build_skb_from_zc() uses the chunk size
(xdp->frame_sz) for the allocation. However, napi_build_skb() automatically
reserves space at the end of the allocation for the skb_shared_info
structure.
Most high performance UMEM applications use 4K chunks, where the
corruption cannot happen. However, if the UMEM is configured with 2KB
chunks (a very common configuration to maximize packet density in memory),
a standard 1500 MTU packet will trigger the corruption because the required
space exceeds the 2048 byte chunk size:
Headroom (256) + Packet (1514) + skb_shared_info (320) = 2090 bytes
Because 2090 bytes > 2048 bytes and __skb_put() does not perform bounds
checking, the memcpy() writes past the available linear data area and
corrupts the skb_shared_info structure. This can lead to arbitrary code
execution if pointers like destructor_arg are overwritten.
Additionally, in xdp_copy_frags_from_zc(), the allocation size is set
strictly to the fragment size (len), but the subsequent memcpy() uses
LARGEST_ALIGN(len). This mismatch results in an out-of-bounds write of
up to 7 bytes, which triggers KASAN warnings and is unsafe despite typical
page pool allocator padding.
Fix the skb allocation in xdp_build_skb_from_zc() by dynamically
calculating the exact truesize required using SKB_HEAD_ALIGN() to
properly account for the headroom, the LARGEST_ALIGN(len), and the
skb_shared_info overhead.
Fix the out-of-bounds write in xdp_copy_frags_from_zc() by rounding up
the allocation request using LARGEST_ALIGN(len) to match the copy
operation.
Fixes: 560d958c6c68 ("xsk: add generic XSk &xdp_buff -> skb conversion")
CC: Alexander Lobakin <aleksander.lobakin@intel.com>
CC: Eric Dumazet <edumazet@google.com>
Signed-off-by: Clément Lecigne <clecigne@google.com>
---
Changes since v2:
- Used LARGEST_ALIGN to calculate the len to account for the aligned memcpy.
- Fixed the commit message to include the idpf driver.
Changes since v1:
- Used SKB_HEAD_ALIGN to properly calculate the required allocation size
including the skb_shared_info overhead.
- Re-ordered variable declarations.
---
diff --git a/net/core/xdp.c b/net/core/xdp.c
index 9890a30584ba..7e39f17ad407 100644
--- a/net/core/xdp.c
+++ b/net/core/xdp.c
@@ -698,8 +698,8 @@ static noinline bool xdp_copy_frags_from_zc(struct sk_buff *skb,
for (u32 i = 0; i < nr_frags; i++) {
const skb_frag_t *frag = &xinfo->frags[i];
- u32 len = skb_frag_size(frag);
- u32 offset, truesize = len;
+ u32 offset, len = skb_frag_size(frag);
+ u32 truesize = LARGEST_ALIGN(len);
struct page *page;
page = page_pool_dev_alloc(pp, &offset, &truesize);
@@ -738,9 +738,10 @@ static noinline bool xdp_copy_frags_from_zc(struct sk_buff *skb,
*/
struct sk_buff *xdp_build_skb_from_zc(struct xdp_buff *xdp)
{
+ u32 headroom = xdp->data_meta - xdp->data_hard_start;
const struct xdp_rxq_info *rxq = xdp->rxq;
- u32 len = xdp->data_end - xdp->data_meta;
- u32 truesize = xdp->frame_sz;
+ u32 len = LARGEST_ALIGN(xdp->data_end - xdp->data_meta);
+ u32 truesize = SKB_HEAD_ALIGN(headroom + len);
struct sk_buff *skb = NULL;
struct page_pool *pp;
int metalen;
@@ -762,7 +763,7 @@ struct sk_buff *xdp_build_skb_from_zc(struct xdp_buff *xdp)
}
skb_mark_for_recycle(skb);
- skb_reserve(skb, xdp->data_meta - xdp->data_hard_start);
+ skb_reserve(skb, headroom);
memcpy(__skb_put(skb, len), xdp->data_meta, LARGEST_ALIGN(len));
^ permalink raw reply related
* [PATCH net] bridge: stp: Fix a potential use-after-free when deleting a bridge
From: Ido Schimmel @ 2026-06-29 7:21 UTC (permalink / raw)
To: netdev, bridge; +Cc: davem, kuba, pabeni, edumazet, razor, horms, Ido Schimmel
The three STP timers are not supposed to be armed while the bridge is
administratively down. They are synchronously deactivated when the
bridge is put administratively down and the various call sites check for
'IFF_UP' before arming them.
This check is missing from br_topology_change_detection() and it is
possible to engineer a situation in which the topology change timer is
armed while the bridge is administratively down, resulting in a
use-after-free [1] when the bridge is deleted.
Fix by adding the missing check and for good measures synchronously
shutdown the three timers when the bridge is deleted.
[1]
ODEBUG: free active (active state 0) object: ffff88811662b9b0 object type: timer_list hint: br_topology_change_timer_expired (net/bridge/br_stp_timer.c:120)
WARNING: lib/debugobjects.c:629 at debug_print_object+0x1bc/0x450, CPU#9: ip/359
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Noam Rathaus <noamr@ssd-disclosure.com>
Reported-by: Neil Young <contact@ssd-disclosure.com>
Acked-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
---
net/bridge/br_if.c | 3 +++
net/bridge/br_stp.c | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
index 7ed19aa8ae59..c52613431f88 100644
--- a/net/bridge/br_if.c
+++ b/net/bridge/br_if.c
@@ -392,6 +392,9 @@ void br_dev_delete(struct net_device *dev, struct list_head *head)
br_fdb_delete_by_port(br, NULL, 0, 1);
+ timer_shutdown_sync(&br->hello_timer);
+ timer_shutdown_sync(&br->topology_change_timer);
+ timer_shutdown_sync(&br->tcn_timer);
cancel_delayed_work_sync(&br->gc_work);
br_sysfs_delbr(br->dev);
diff --git a/net/bridge/br_stp.c b/net/bridge/br_stp.c
index 46919d73d42f..c7e7e924f155 100644
--- a/net/bridge/br_stp.c
+++ b/net/bridge/br_stp.c
@@ -382,7 +382,8 @@ void br_topology_change_detection(struct net_bridge *br)
{
int isroot = br_is_root_bridge(br);
- if (br->stp_enabled != BR_KERNEL_STP)
+ if (br->stp_enabled != BR_KERNEL_STP ||
+ !(br->dev->flags & IFF_UP))
return;
br_info(br, "topology change detected, %s\n",
--
2.54.0
^ permalink raw reply related
* Re: [RFC PATCH net-next v8 03/12] net: phylink: add phylink_release_pcs() to externally release a PCS
From: Christian Marangi @ 2026-06-29 7:18 UTC (permalink / raw)
To: Maxime Chevallier
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Simon Horman, Jonathan Corbet, Shuah Khan, Lorenzo Bianconi,
Heiner Kallweit, Russell King, Saravana Kannan, Philipp Zabel,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
netdev, devicetree, linux-kernel, linux-doc, linux-arm-kernel,
linux-mediatek, llvm
In-Reply-To: <178defc6-8e60-4b0b-b3b0-f0f2a4003b68@bootlin.com>
On Mon, Jun 29, 2026 at 09:04:49AM +0200, Maxime Chevallier wrote:
> Hi Christian,
>
> On 6/27/26 14:33, Christian Marangi wrote:
> > On Thu, Jun 25, 2026 at 04:13:14PM +0200, Maxime Chevallier wrote:
> >> Hello Christian,
> >>
> >> On 6/18/26 14:57, Christian Marangi wrote:
> >>> Add phylink_release_pcs() to externally release a PCS from a phylink
> >>> instance. This can be used to handle case when a single PCS needs to be
> >>> removed and the phylink instance needs to be refreshed.
> >>>
> >>> On calling phylink_release_pcs(), the PCS will be removed from the
> >>> phylink internal PCS list and the phylink supported_interfaces value is
> >>> reparsed with the remaining PCS interfaces.
> >>>
> >>> Also a phylink resolve is triggered to handle the PCS removal.
> >>>
> >>> The flag force_major_config is set to make phylink resolve reconfigure
> >>> the interface (even if it didn't change).
> >>> This is needed to handle the special case when the current PCS used
> >>> by phylink is removed and a major_config is needed to propagae the
> >>> configuration change. With this option enabled we also force mac_config
> >>> even if the PHY link is not up for the in-band case.
> >>>
> >>> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> >>> ---
> >>> drivers/net/phy/phylink.c | 56 +++++++++++++++++++++++++++++++++++++++
> >>> include/linux/phylink.h | 2 ++
> >>> 2 files changed, 58 insertions(+)
> >>>
> >>> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> >>> index c38bcd43b8c8..064d6f5a06da 100644
> >>> --- a/drivers/net/phy/phylink.c
> >>> +++ b/drivers/net/phy/phylink.c
> >>> @@ -158,6 +158,8 @@ static const phy_interface_t phylink_sfp_interface_preference[] = {
> >>> static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
> >>>
> >>> static void phylink_run_resolve(struct phylink *pl);
> >>> +static void phylink_link_down(struct phylink *pl);
> >>> +static void phylink_pcs_disable(struct phylink_pcs *pcs);
> >>>
> >>> /**
> >>> * phylink_set_port_modes() - set the port type modes in the ethtool mask
> >>> @@ -918,6 +920,60 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
> >>> }
> >>> }
> >>>
> >>> +/**
> >>> + * phylink_release_pcs - Removes a PCS from the phylink PCS available list
> >>> + * @pcs: a pointer to the phylink_pcs struct to be released
> >>> + *
> >>> + * This function release a PCS from the phylink PCS available list if
> >>> + * actually in use. It also refreshes the supported interfaces of the
> >>> + * phylink instance by copying the supported interfaces from the phylink
> >>> + * conf and merging the supported interfaces of the remaining available PCS
> >>> + * in the list and trigger a resolve.
> >>> + */
> >>> +void phylink_release_pcs(struct phylink_pcs *pcs)
> >>> +{
> >>> + struct phylink *pl;
> >>> +
> >>> + ASSERT_RTNL();
> >>> +
> >>> + pl = pcs->phylink;
> >>> + if (!pl)
> >>> + return;
> >>> +
> >>> + mutex_lock(&pl->state_mutex);
> >>> +
> >>> + list_del(&pcs->list);
> >>> + pcs->phylink = NULL;
> >>> +
> >>> + /*
> >>> + * Check if we are removing the PCS currently
> >>> + * in use by phylink. If this is the case, tear down
> >>> + * the link, force phylink resolve to reconfigure the
> >>> + * interface mode, disable the current PCS and set the
> >>> + * phylink PCS to NULL.
> >>> + */
> >>> + if (pl->pcs == pcs) {
> >>> + phylink_link_down(pl);
> >>> + phylink_pcs_disable(pl->pcs);
> >>> +
> >>> + pl->force_major_config = true;
> >>> + pl->pcs = NULL;
> >>> + }
> >>> +
> >>> + mutex_unlock(&pl->state_mutex);
> >>> +
> >>> + /* Refresh supported interfaces */
> >>> + phy_interface_copy(pl->supported_interfaces,
> >>> + pl->config->supported_interfaces);
> >>> + list_for_each_entry(pcs, &pl->pcs_list, list)
> >>> + phy_interface_or(pl->supported_interfaces,
> >>> + pl->supported_interfaces,
> >>> + pcs->supported_interfaces);
> >>
> >> I've given more thought to that 'supported_interfaces' thing. This
> >> patchset redefines the meaning of
> >>
> >> pl->config->supported_interfaces
> >>
> >> Currently, it's filled by the MAC driver and means "Every interface
> >> we can support, including the ones provided by PCSs that we can use
> >> with this MAC".
> >>
> >> It now becomes "Every interface we support without needing a PCS", at
> >> least the way I understand that.
> >>
> >
> > Wait but with the current code using the OR logic, it still follows
> > "Every interface we can support...". The modes that needs a PCS are
> > specificed with the pcs_interfaces mask in phylink_config.
>
> you current code is correct, I was mostly concerned about the doc
> that goes along with it :)
Oh yep thanks, I will update also that entry. Also maybe check the .rst
introduced and tell me if something is confusing or badly described.
>
> So in the end, we'd have something like (simplified):
>
> pl->config.supported_interfaces = RGMII_xx | SGMII | 1000BaseX
> pl->config.pcs_interfaces = SGMII | 1000BaseX
>
> pcs->supported_interface = SGMII| 1000BaseX
>
> correct ?
>
Correct. phylink_config describing that for SGMII and 1000BaseX a PCS is
required and the related PCS declaring support for those modes.
Code will skip searching for a PCS for RGMII.
> >
> > The late add and release operates on the phylink supported_interfaces ONLY
> > when the MAC didn't specify support for it (by removing it as only the PCS
> > will declare support for it)
> >
> > The confusion is present because everything is validated later on
> > major_config so those supported_interfaces are just an HINT that are later
> > verified with get_caps and with the pcs_validate OPs.
> >
> > Adding the supported_interfaces to phylink is really to keep an original
> > reference of the value. This is to address a pattern I have notice where
> > the MAC driver always OR the interfaces with the one supported by the PCS.
> > (I remember it was pointed out by Russell)
> >
> > But I'm more than open to discussion as this is something marginal to the
> > whole implementation, I'm also questioning if this OR is actually useful to
> > anything on the nth tought on this.
> >
> > One thing that I notice is that parsing this early with AND might be
> > problematic at phylink_create, but I still have to evaluate that.
> >
> > My take is that would be good to have some review also on the other logic
> > as I think I reached a point where Sashiko starts to comments on more or
> > less unreal problem.
>
> True, TBH all the fwnode part is something I'm a bit less familiar with though
> so maybe someone else can browse through that.
>
> FWIW, I've tested that whole series on a board that has "legacy" PCS board
> that has mvpp2 and 2 possible PCSs, and it seems to work fine so no regressions
> there :)
>
> A side note with the "legacy" naming, I'd rather have it called "built-in" or
> something like that, I don't see a clear path to porting the existing code to
> fwnode without breaking DT compat, as it's likely we'll have to remove the PCS
> register ranges out of the MAC's range.
I think also built-in might be confusing as a SoC might have a PCS built-in
but just as a separate device on a different register map.
A better description might be "externally-managed" but that is very long...
Also the use of fwnode and DT is just to reference them and use the normal
helper but the implementation is liberal on custom implementation for the
current code.
Everything is around the pcs_interface mask, num_possible_pcs and the
fill_available_pcs.
Nobody stops from implementing custom parser in fill_available_pcs and
return a PCS pointer created directly by the MAC. (In such case late and
release won't be needed as everything is present when phylink_create is
called)
On OpenWrt we migrated every SoC that used ""exotic"" pcs reference (Qcom,
Realtek, MTK, Airoha) but I don't have a board that used something like the
'pcs' property.
>
> Thanks for this work anyway, this is great !
>
> Maxime
--
Ansuel
^ permalink raw reply
* [PATCH net-next v9 2/7] r8169: refactor RX path to prepare for multi-queue
From: javen @ 2026-06-29 7:13 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260629071339.1605-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
This patch is a preparatory refactoring of the RX path. It introduces
struct rtl8169_rx_ring and turns the previously embedded RX state in
rtl8169_private into a per-queue array.
While the netdev allocation is changed to devm_alloc_etherdev_mqs()
with up to 8 RX queues, the actual number of active RX rings
(num_rx_rings) is currently kept at 1. The actual multi-queue operation
and RSS enablement will be introduced in subsequent patches.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- sort some registers by its number
- remove some unused definitions, like RX_DESC_RING_TYPE_MAX
- change recheck_desc_ownbit type
- remove rdsar_reg in rx_ring struct
- opts1 are different in rx_desc and rx_desc_rss, move the judgement
to Patch 5/7
Changes in v3:
- remove ring->rx_desc_alloc_size, use constant instead
Changes in v4:
- change rdsar_reg type to unsigned int
- follow reverse xmas tree, in rtl_set_rx_tx_desc_registers(),
rtl8169_alloc_rx_data(), rtl8169_alloc_rx_desc(),
rtl8169_free_rx_desc()
- add comments on LED_CTRL, remove helper function
Changes in v5:
- modify rtl8169_init_ring(), do rx clear when failed
- add definition R8169_MAX_TX_QUEUES 1
Changes in v6:
- Restore the secondary Rx error filter when NETIF_F_RXFALL is enabled
in rtl_rx()
Changes in v7:
- remove code associated with recheck_desc_ownbit
Changes in v8:
- remove le64_to_cpu() for addr, rx get addr from rx_desc_phy_addr
Changes in v9:
- remove R8127_MAX_RX_QUEUES
- remvoe rx_desc_ring_type to the following patch
- Fix loop bound in init_ring_indexes
- Restore checksum API
---
drivers/net/ethernet/realtek/r8169_main.c | 232 +++++++++++++++++-----
1 file changed, 178 insertions(+), 54 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 8f3a5c50299f..13c56dbca230 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -74,9 +74,19 @@
#define NUM_TX_DESC 256 /* Number of Tx descriptor registers */
#define NUM_RX_DESC 256 /* Number of Rx descriptor registers */
#define R8169_TX_RING_BYTES (NUM_TX_DESC * sizeof(struct TxDesc))
-#define R8169_RX_RING_BYTES (NUM_RX_DESC * sizeof(struct RxDesc))
+
+/*
+ * Workaround for the hardware DMA prefetcher. The H/W might aggressively
+ * fetch one more descriptor even after hitting the RingEnd mark. We
+ * allocate this extra dummy space as padding to prevent out-of-bounds
+ * access and potential IOMMU faults.
+ */
+#define R8169_RX_RING_BYTES ((NUM_RX_DESC + 1) * sizeof(struct RxDesc))
#define R8169_TX_STOP_THRS (MAX_SKB_FRAGS + 1)
#define R8169_TX_START_THRS (2 * R8169_TX_STOP_THRS)
+#define R8169_MAX_RX_QUEUES 8
+#define R8169_DEFAULT_RX_QUEUES 1
+#define R8169_MAX_TX_QUEUES 1
#define OCP_STD_PHY_BASE 0xa400
@@ -441,6 +451,7 @@ enum rtl8125_registers {
TxPoll_8125 = 0x90,
LEDSEL3 = 0x96,
MAC0_BKP = 0x19e0,
+ RDSAR_Q1_LOW = 0x4000,
RSS_CTRL_8125 = 0x4500,
Q_NUM_CTRL_8125 = 0x4800,
EEE_TXIDLE_TIMER_8125 = 0x6048,
@@ -728,6 +739,16 @@ enum rtl_dash_type {
RTL_DASH_25_BP,
};
+struct rtl8169_rx_ring {
+ u32 index; /* Rx queue index */
+ u32 cur_rx; /* Index of next Rx pkt. */
+ u32 dirty_rx; /* Index for recycling. */
+ struct RxDesc *rx_desc_array; /* array of Rx Desc*/
+ dma_addr_t rx_desc_phy_addr[NUM_RX_DESC]; /* Rx data buffer physical dma address */
+ dma_addr_t rx_phy_addr; /* Rx desc physical address */
+ struct page *rx_databuff[NUM_RX_DESC]; /* Rx data buffers */
+};
+
struct rtl8169_private {
void __iomem *mmio_addr; /* memory map physical address */
struct pci_dev *pci_dev;
@@ -735,20 +756,18 @@ struct rtl8169_private {
struct phy_device *phydev;
enum mac_version mac_version;
enum rtl_dash_type dash_type;
- u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
u32 cur_tx; /* Index into the Tx descriptor buffer of next Rx pkt. */
u32 dirty_tx;
struct TxDesc *TxDescArray; /* 256-aligned Tx descriptor ring */
- struct RxDesc *RxDescArray; /* 256-aligned Rx descriptor ring */
dma_addr_t TxPhyAddr;
- dma_addr_t RxPhyAddr;
- struct page *Rx_databuff[NUM_RX_DESC]; /* Rx data buffers */
struct ring_info tx_skb[NUM_TX_DESC]; /* Tx data buffers */
struct napi_struct *rtl8169_napi;
+ struct rtl8169_rx_ring rx_ring[R8169_MAX_RX_QUEUES];
unsigned int num_rx_rings;
u16 cp_cmd;
u16 tx_lpi_timer;
u32 irq_mask;
+ unsigned int hw_supp_num_rx_queues;
unsigned int irq_nvecs;
struct clk *clk;
@@ -2620,9 +2639,27 @@ static void rtl_init_rxcfg(struct rtl8169_private *tp)
}
}
+static void rtl8169_rx_desc_init(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->num_rx_rings; i++) {
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[i];
+
+ memset(ring->rx_desc_array, 0x0, R8169_RX_RING_BYTES);
+ }
+}
+
static void rtl8169_init_ring_indexes(struct rtl8169_private *tp)
{
- tp->dirty_tx = tp->cur_tx = tp->cur_rx = 0;
+ tp->dirty_tx = 0;
+ tp->cur_tx = 0;
+
+ for (int i = 0; i < tp->num_rx_rings; i++) {
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[i];
+
+ ring->dirty_rx = 0;
+ ring->cur_rx = 0;
+ ring->index = i;
+ }
}
static void rtl_jumbo_config(struct rtl8169_private *tp)
@@ -2684,6 +2721,14 @@ static void rtl_hw_reset(struct rtl8169_private *tp)
static void rtl_setup_rx_params(struct rtl8169_private *tp)
{
tp->num_rx_rings = 1;
+ switch (tp->mac_version) {
+ case RTL_GIGA_MAC_VER_80:
+ tp->hw_supp_num_rx_queues = R8169_MAX_RX_QUEUES;
+ break;
+ default:
+ tp->hw_supp_num_rx_queues = R8169_DEFAULT_RX_QUEUES;
+ break;
+ }
}
static void rtl_request_firmware(struct rtl8169_private *tp)
@@ -2810,6 +2855,8 @@ static void rtl_set_rx_max_size(struct rtl8169_private *tp)
static void rtl_set_rx_tx_desc_registers(struct rtl8169_private *tp)
{
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[0];
+
/*
* Magic spell: some iop3xx ARM board needs the TxDescAddrHigh
* register to be written before TxDescAddrLow to work.
@@ -2817,8 +2864,16 @@ static void rtl_set_rx_tx_desc_registers(struct rtl8169_private *tp)
*/
RTL_W32(tp, TxDescStartAddrHigh, ((u64) tp->TxPhyAddr) >> 32);
RTL_W32(tp, TxDescStartAddrLow, ((u64) tp->TxPhyAddr) & DMA_BIT_MASK(32));
- RTL_W32(tp, RxDescAddrHigh, ((u64) tp->RxPhyAddr) >> 32);
- RTL_W32(tp, RxDescAddrLow, ((u64) tp->RxPhyAddr) & DMA_BIT_MASK(32));
+ RTL_W32(tp, RxDescAddrHigh, ((u64) ring->rx_phy_addr) >> 32);
+ RTL_W32(tp, RxDescAddrLow, ((u64) ring->rx_phy_addr) & DMA_BIT_MASK(32));
+
+ for (int i = 1; i < tp->num_rx_rings; i++) {
+ unsigned int rdsar_reg = RDSAR_Q1_LOW + (i - 1) * 8;
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[i];
+
+ RTL_W32(tp, rdsar_reg + 4, ((u64)ring->rx_phy_addr >> 32));
+ RTL_W32(tp, rdsar_reg, ((u64)ring->rx_phy_addr) & DMA_BIT_MASK(32));
+ }
}
static void rtl8169_set_magic_reg(struct rtl8169_private *tp)
@@ -4165,8 +4220,9 @@ static void rtl8169_mark_to_asic(struct RxDesc *desc)
}
static struct page *rtl8169_alloc_rx_data(struct rtl8169_private *tp,
- struct RxDesc *desc)
+ struct rtl8169_rx_ring *ring, unsigned int index)
{
+ struct RxDesc *desc = ring->rx_desc_array + index;
struct device *d = tp_to_dev(tp);
int node = dev_to_node(d);
dma_addr_t mapping;
@@ -4184,55 +4240,106 @@ static struct page *rtl8169_alloc_rx_data(struct rtl8169_private *tp,
}
desc->addr = cpu_to_le64(mapping);
+ ring->rx_desc_phy_addr[index] = mapping;
rtl8169_mark_to_asic(desc);
return data;
}
-static void rtl8169_rx_clear(struct rtl8169_private *tp)
+static void rtl8169_rx_clear(struct rtl8169_private *tp, struct rtl8169_rx_ring *ring)
{
int i;
- for (i = 0; i < NUM_RX_DESC && tp->Rx_databuff[i]; i++) {
+ for (i = 0; i < NUM_RX_DESC && ring->rx_databuff[i]; i++) {
dma_unmap_page(tp_to_dev(tp),
- le64_to_cpu(tp->RxDescArray[i].addr),
+ ring->rx_desc_phy_addr[i],
R8169_RX_BUF_SIZE, DMA_FROM_DEVICE);
- __free_pages(tp->Rx_databuff[i], get_order(R8169_RX_BUF_SIZE));
- tp->Rx_databuff[i] = NULL;
- tp->RxDescArray[i].addr = 0;
- tp->RxDescArray[i].opts1 = 0;
+ __free_pages(ring->rx_databuff[i], get_order(R8169_RX_BUF_SIZE));
+ ring->rx_databuff[i] = NULL;
+ ring->rx_desc_phy_addr[i] = 0;
+ ring->rx_desc_array[i].addr = 0;
+ ring->rx_desc_array[i].opts1 = 0;
}
}
-static int rtl8169_rx_fill(struct rtl8169_private *tp)
+static int rtl8169_rx_fill(struct rtl8169_private *tp, struct rtl8169_rx_ring *ring)
{
int i;
for (i = 0; i < NUM_RX_DESC; i++) {
struct page *data;
- data = rtl8169_alloc_rx_data(tp, tp->RxDescArray + i);
+ data = rtl8169_alloc_rx_data(tp, ring, i);
if (!data) {
- rtl8169_rx_clear(tp);
+ rtl8169_rx_clear(tp, ring);
return -ENOMEM;
}
- tp->Rx_databuff[i] = data;
+ ring->rx_databuff[i] = data;
}
/* mark as last descriptor in the ring */
- tp->RxDescArray[NUM_RX_DESC - 1].opts1 |= cpu_to_le32(RingEnd);
+ ring->rx_desc_array[NUM_RX_DESC - 1].opts1 |= cpu_to_le32(RingEnd);
return 0;
}
+static int rtl8169_alloc_rx_desc(struct rtl8169_private *tp)
+{
+ struct pci_dev *pdev = tp->pci_dev;
+ struct rtl8169_rx_ring *ring;
+
+ for (int i = 0; i < tp->num_rx_rings; i++) {
+ ring = &tp->rx_ring[i];
+ ring->rx_desc_array = dma_alloc_coherent(&pdev->dev,
+ R8169_RX_RING_BYTES,
+ &ring->rx_phy_addr,
+ GFP_KERNEL);
+ if (!ring->rx_desc_array)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void rtl8169_free_rx_desc(struct rtl8169_private *tp)
+{
+ struct pci_dev *pdev = tp->pci_dev;
+ struct rtl8169_rx_ring *ring;
+
+ for (int i = 0; i < tp->num_rx_rings; i++) {
+ ring = &tp->rx_ring[i];
+ if (ring->rx_desc_array) {
+ dma_free_coherent(&pdev->dev,
+ R8169_RX_RING_BYTES,
+ ring->rx_desc_array,
+ ring->rx_phy_addr);
+ ring->rx_desc_array = NULL;
+ }
+ }
+}
+
static int rtl8169_init_ring(struct rtl8169_private *tp)
{
+ int i, ret;
+
rtl8169_init_ring_indexes(tp);
+ rtl8169_rx_desc_init(tp);
memset(tp->tx_skb, 0, sizeof(tp->tx_skb));
- memset(tp->Rx_databuff, 0, sizeof(tp->Rx_databuff));
- return rtl8169_rx_fill(tp);
+ for (i = 0; i < tp->num_rx_rings; i++) {
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[i];
+
+ memset(ring->rx_databuff, 0, sizeof(ring->rx_databuff));
+ ret = rtl8169_rx_fill(tp, ring);
+ if (ret < 0)
+ goto err_clear;
+ }
+ return 0;
+
+err_clear:
+ while (--i >= 0)
+ rtl8169_rx_clear(tp, &tp->rx_ring[i]);
+ return ret;
}
static void rtl8169_unmap_tx_skb(struct rtl8169_private *tp, unsigned int entry)
@@ -4321,16 +4428,23 @@ static void rtl8169_cleanup(struct rtl8169_private *tp)
rtl8169_init_ring_indexes(tp);
}
-static void rtl_reset_work(struct rtl8169_private *tp)
+static void rtl8169_rx_desc_reset(struct rtl8169_private *tp)
{
- int i;
+ for (int i = 0; i < tp->num_rx_rings; i++) {
+ struct rtl8169_rx_ring *ring = &tp->rx_ring[i];
+ for (int j = 0; j < NUM_RX_DESC; j++)
+ rtl8169_mark_to_asic(ring->rx_desc_array + j);
+ }
+}
+
+static void rtl_reset_work(struct rtl8169_private *tp)
+{
netif_stop_queue(tp->dev);
rtl8169_cleanup(tp);
- for (i = 0; i < NUM_RX_DESC; i++)
- rtl8169_mark_to_asic(tp->RxDescArray + i);
+ rtl8169_rx_desc_reset(tp);
rtl8169_napi_enable(tp);
rtl_hw_start(tp);
@@ -4776,7 +4890,8 @@ static inline int rtl8169_fragmented_frame(u32 status)
return (status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag);
}
-static inline void rtl8169_rx_csum(struct sk_buff *skb, u32 opts1)
+static inline void rtl8169_rx_csum(struct sk_buff *skb,
+ u32 opts1)
{
u32 status = opts1 & (RxProtoMask | RxCSFailMask);
@@ -4786,15 +4901,29 @@ static inline void rtl8169_rx_csum(struct sk_buff *skb, u32 opts1)
skb_checksum_none_assert(skb);
}
+static bool rtl8169_check_rx_desc_error(struct net_device *dev,
+ struct rtl8169_private *tp,
+ u32 status)
+{
+ if (unlikely(status & RxRES)) {
+ if (status & (RxRWT | RxRUNT))
+ dev->stats.rx_length_errors++;
+ if (status & RxCRC)
+ dev->stats.rx_crc_errors++;
+ return true;
+ }
+ return false;
+}
+
static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp,
- int budget, struct napi_struct *napi)
+ struct rtl8169_rx_ring *ring, int budget, struct napi_struct *napi)
{
struct device *d = tp_to_dev(tp);
int count;
- for (count = 0; count < budget; count++, tp->cur_rx++) {
- unsigned int pkt_size, entry = tp->cur_rx % NUM_RX_DESC;
- struct RxDesc *desc = tp->RxDescArray + entry;
+ for (count = 0; count < budget; count++, ring->cur_rx++) {
+ unsigned int pkt_size, entry = ring->cur_rx % NUM_RX_DESC;
+ struct RxDesc *desc = ring->rx_desc_array + entry;
struct sk_buff *skb;
const void *rx_buf;
dma_addr_t addr;
@@ -4810,15 +4939,11 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp,
*/
dma_rmb();
- if (unlikely(status & RxRES)) {
+ if (rtl8169_check_rx_desc_error(dev, tp, status)) {
if (net_ratelimit())
netdev_warn(dev, "Rx ERROR. status = %08x\n",
status);
dev->stats.rx_errors++;
- if (status & (RxRWT | RxRUNT))
- dev->stats.rx_length_errors++;
- if (status & RxCRC)
- dev->stats.rx_crc_errors++;
if (!(dev->features & NETIF_F_RXALL))
goto release_descriptor;
@@ -4845,8 +4970,8 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp,
goto release_descriptor;
}
- addr = le64_to_cpu(desc->addr);
- rx_buf = page_address(tp->Rx_databuff[entry]);
+ addr = ring->rx_desc_phy_addr[entry];
+ rx_buf = page_address(ring->rx_databuff[entry]);
dma_sync_single_for_cpu(d, addr, pkt_size, DMA_FROM_DEVICE);
prefetch(rx_buf);
@@ -4973,7 +5098,8 @@ static int rtl8169_poll(struct napi_struct *napi, int budget)
rtl_tx(dev, tp, budget);
- work_done = rtl_rx(dev, tp, budget, napi);
+ /* rtl8169_poll() is used only when there is a single RX ring. */
+ work_done = rtl_rx(dev, tp, &tp->rx_ring[0], budget, napi);
if (work_done < budget && napi_complete_done(napi, work_done))
rtl_irq_enable(tp);
@@ -5104,18 +5230,17 @@ static int rtl8169_close(struct net_device *dev)
netif_stop_queue(dev);
rtl8169_down(tp);
- rtl8169_rx_clear(tp);
+ for (int i = 0; i < tp->num_rx_rings; i++)
+ rtl8169_rx_clear(tp, &tp->rx_ring[i]);
rtl8169_free_irq(tp);
phy_disconnect(tp->phydev);
- dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
- tp->RxPhyAddr);
dma_free_coherent(&pdev->dev, R8169_TX_RING_BYTES, tp->TxDescArray,
tp->TxPhyAddr);
tp->TxDescArray = NULL;
- tp->RxDescArray = NULL;
+ rtl8169_free_rx_desc(tp);
pm_runtime_put_sync(&pdev->dev);
@@ -5151,10 +5276,8 @@ static int rtl_open(struct net_device *dev)
if (!tp->TxDescArray)
goto out;
- tp->RxDescArray = dma_alloc_coherent(&pdev->dev, R8169_RX_RING_BYTES,
- &tp->RxPhyAddr, GFP_KERNEL);
- if (!tp->RxDescArray)
- goto err_free_tx_0;
+ if (rtl8169_alloc_rx_desc(tp) < 0)
+ goto err_free_rx_1;
retval = rtl8169_init_ring(tp);
if (retval < 0)
@@ -5182,12 +5305,10 @@ static int rtl_open(struct net_device *dev)
rtl8169_free_irq(tp);
err_release_fw_2:
rtl_release_firmware(tp);
- rtl8169_rx_clear(tp);
+ for (int i = 0; i < tp->num_rx_rings; i++)
+ rtl8169_rx_clear(tp, &tp->rx_ring[i]);
err_free_rx_1:
- dma_free_coherent(&pdev->dev, R8169_RX_RING_BYTES, tp->RxDescArray,
- tp->RxPhyAddr);
- tp->RxDescArray = NULL;
-err_free_tx_0:
+ rtl8169_free_rx_desc(tp);
dma_free_coherent(&pdev->dev, R8169_TX_RING_BYTES, tp->TxDescArray,
tp->TxPhyAddr);
tp->TxDescArray = NULL;
@@ -5688,7 +5809,10 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
u32 txconfig;
u32 xid;
- dev = devm_alloc_etherdev(&pdev->dev, sizeof (*tp));
+ dev = devm_alloc_etherdev_mqs(&pdev->dev, sizeof(*tp),
+ R8169_MAX_TX_QUEUES,
+ R8169_MAX_RX_QUEUES);
+
if (!dev)
return -ENOMEM;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v9 6/7] r8169: move struct ethtool_ops
From: javen @ 2026-06-29 7:13 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260629071339.1605-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
The patch moves the rtl8169_ethtool_ops definition further down in
r8169_main.c so that subsequent additions of rtl8169_get_channels and
rtl8169_set_channels can be referenced from the ops struct without
needing forward declarations.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes
Changes in v3:
- no changes
Changes in v4:
- no changes
Changes in v5:
- no changes
Changes in v6:
- modify commit message
Changes in v7:
- no changes
Changes in v8:
- no changes
Changes in v9:
- no changes
---
drivers/net/ethernet/realtek/r8169_main.c | 56 +++++++++++------------
1 file changed, 28 insertions(+), 28 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 9163cb31cebb..06b3782ccbb4 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -2535,34 +2535,6 @@ static int rtl8169_set_link_ksettings(struct net_device *ndev,
return 0;
}
-static const struct ethtool_ops rtl8169_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
- ETHTOOL_COALESCE_MAX_FRAMES,
- .get_drvinfo = rtl8169_get_drvinfo,
- .get_regs_len = rtl8169_get_regs_len,
- .get_link = ethtool_op_get_link,
- .get_coalesce = rtl_get_coalesce,
- .set_coalesce = rtl_set_coalesce,
- .get_regs = rtl8169_get_regs,
- .get_wol = rtl8169_get_wol,
- .set_wol = rtl8169_set_wol,
- .get_strings = rtl8169_get_strings,
- .get_sset_count = rtl8169_get_sset_count,
- .get_ethtool_stats = rtl8169_get_ethtool_stats,
- .get_ts_info = ethtool_op_get_ts_info,
- .nway_reset = phy_ethtool_nway_reset,
- .get_eee = rtl8169_get_eee,
- .set_eee = rtl8169_set_eee,
- .get_link_ksettings = phy_ethtool_get_link_ksettings,
- .set_link_ksettings = rtl8169_set_link_ksettings,
- .get_ringparam = rtl8169_get_ringparam,
- .get_pause_stats = rtl8169_get_pause_stats,
- .get_pauseparam = rtl8169_get_pauseparam,
- .set_pauseparam = rtl8169_set_pauseparam,
- .get_eth_mac_stats = rtl8169_get_eth_mac_stats,
- .get_eth_ctrl_stats = rtl8169_get_eth_ctrl_stats,
-};
-
static const struct rtl_chip_info *rtl8169_get_chip_version(u32 xid, bool gmii)
{
/* Chips combining a 1Gbps MAC with a 100Mbps PHY */
@@ -6243,6 +6215,34 @@ static void r8169_init_napi(struct rtl8169_private *tp)
}
}
+static const struct ethtool_ops rtl8169_ethtool_ops = {
+ .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
+ ETHTOOL_COALESCE_MAX_FRAMES,
+ .get_drvinfo = rtl8169_get_drvinfo,
+ .get_regs_len = rtl8169_get_regs_len,
+ .get_link = ethtool_op_get_link,
+ .get_coalesce = rtl_get_coalesce,
+ .set_coalesce = rtl_set_coalesce,
+ .get_regs = rtl8169_get_regs,
+ .get_wol = rtl8169_get_wol,
+ .set_wol = rtl8169_set_wol,
+ .get_strings = rtl8169_get_strings,
+ .get_sset_count = rtl8169_get_sset_count,
+ .get_ethtool_stats = rtl8169_get_ethtool_stats,
+ .get_ts_info = ethtool_op_get_ts_info,
+ .nway_reset = phy_ethtool_nway_reset,
+ .get_eee = rtl8169_get_eee,
+ .set_eee = rtl8169_set_eee,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = rtl8169_set_link_ksettings,
+ .get_ringparam = rtl8169_get_ringparam,
+ .get_pause_stats = rtl8169_get_pause_stats,
+ .get_pauseparam = rtl8169_get_pauseparam,
+ .set_pauseparam = rtl8169_set_pauseparam,
+ .get_eth_mac_stats = rtl8169_get_eth_mac_stats,
+ .get_eth_ctrl_stats = rtl8169_get_eth_ctrl_stats,
+};
+
static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
const struct rtl_chip_info *chip;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v9 1/7] r8169: add support for multi irqs
From: javen @ 2026-06-29 7:13 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260629071339.1605-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
RSS uses multi rx queues to receive packets, and each rx queue needs one
irq and napi. So this patch adds support for multi irqs and napi here.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- remove some unused definitions, such as index, name in rtl8169_irq
- remove array imr and isr
- remove min_irq_nvecs and max_irq_nvecs, replaced with help function
get_min_irq_nvecs and get_max_irq_nvecs
- alloc irq by flags, instead of PCI_IRQ_ALL_TYPES
Changes in v3:
- add enum rtl_isr_version to replace macro definition
- remove struct rtl8169_napi, use napi_struct array instead and alloc
memory for this array dynamically
- remove struct rtl8169_irq
Changes in v4:
- change retval to ret in rtl8169_set_real_num_queue()
- reverse xmas tree in rtl8169_poll() and rtl8169_interrupt()
- remove tp->hw_supp_isr_ver
Changes in v5:
- rtl8169_request_irq(), when failed, only free irqs which are
allocated
- remove rss_support, simplied napi init, call r8169_init_napi()
directly
- remove rtl_isr_version, INTR_VEC_MAP_MASK, INTR_VEC_MAP_STATUS,
R8169_MAX_MSIX_VEC, rss_enable, recheck_desc_ownbit
- rtl_software_parameter_initialize() this function will be expanded in
next patch, so i want to remain it here.
Changes in v6:
- Fix netpoll crash
- Fix use-after-free during driver unload by registering a devm action
for netif_napi_del()
- remove tp->irq
Changes in v7:
- pass NAPI as arg to rtl_rx()
- use netif_set_real_num_queues to replace rtl8169_set_real_num_queues
- replace rtl_software_parameter_initialize with rtl_setup_rx_params
Changes in v8:
- no changes
Changes in v9:
- no changes
---
drivers/net/ethernet/realtek/r8169_main.c | 150 +++++++++++++++++-----
1 file changed, 121 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index ec4fc21fa21f..8f3a5c50299f 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -733,7 +733,6 @@ struct rtl8169_private {
struct pci_dev *pci_dev;
struct net_device *dev;
struct phy_device *phydev;
- struct napi_struct napi;
enum mac_version mac_version;
enum rtl_dash_type dash_type;
u32 cur_rx; /* Index into the Rx descriptor buffer of next Rx pkt. */
@@ -745,10 +744,12 @@ struct rtl8169_private {
dma_addr_t RxPhyAddr;
struct page *Rx_databuff[NUM_RX_DESC]; /* Rx data buffers */
struct ring_info tx_skb[NUM_TX_DESC]; /* Tx data buffers */
+ struct napi_struct *rtl8169_napi;
+ unsigned int num_rx_rings;
u16 cp_cmd;
u16 tx_lpi_timer;
u32 irq_mask;
- int irq;
+ unsigned int irq_nvecs;
struct clk *clk;
struct {
@@ -2680,6 +2681,11 @@ static void rtl_hw_reset(struct rtl8169_private *tp)
rtl_loop_wait_low(tp, &rtl_chipcmd_cond, 100, 100);
}
+static void rtl_setup_rx_params(struct rtl8169_private *tp)
+{
+ tp->num_rx_rings = 1;
+}
+
static void rtl_request_firmware(struct rtl8169_private *tp)
{
struct rtl_fw *rtl_fw;
@@ -4266,9 +4272,21 @@ static void rtl8169_tx_clear(struct rtl8169_private *tp)
netdev_reset_queue(tp->dev);
}
+static void rtl8169_napi_disable(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->irq_nvecs; i++)
+ napi_disable(&tp->rtl8169_napi[i]);
+}
+
+static void rtl8169_napi_enable(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->irq_nvecs; i++)
+ napi_enable(&tp->rtl8169_napi[i]);
+}
+
static void rtl8169_cleanup(struct rtl8169_private *tp)
{
- napi_disable(&tp->napi);
+ rtl8169_napi_disable(tp);
/* Give a racing hard_start_xmit a few cycles to complete. */
synchronize_net();
@@ -4314,7 +4332,7 @@ static void rtl_reset_work(struct rtl8169_private *tp)
for (i = 0; i < NUM_RX_DESC; i++)
rtl8169_mark_to_asic(tp->RxDescArray + i);
- napi_enable(&tp->napi);
+ rtl8169_napi_enable(tp);
rtl_hw_start(tp);
}
@@ -4768,7 +4786,8 @@ static inline void rtl8169_rx_csum(struct sk_buff *skb, u32 opts1)
skb_checksum_none_assert(skb);
}
-static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget)
+static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp,
+ int budget, struct napi_struct *napi)
{
struct device *d = tp_to_dev(tp);
int count;
@@ -4820,7 +4839,7 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
goto release_descriptor;
}
- skb = napi_alloc_skb(&tp->napi, pkt_size);
+ skb = napi_alloc_skb(napi, pkt_size);
if (unlikely(!skb)) {
dev->stats.rx_dropped++;
goto release_descriptor;
@@ -4844,7 +4863,7 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
if (skb->pkt_type == PACKET_MULTICAST)
dev->stats.multicast++;
- napi_gro_receive(&tp->napi, skb);
+ napi_gro_receive(napi, skb);
dev_sw_netstats_rx_add(dev, pkt_size);
release_descriptor:
@@ -4856,8 +4875,12 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
{
- struct rtl8169_private *tp = dev_instance;
- u32 status = rtl_get_events(tp);
+ struct napi_struct *napi = dev_instance;
+ struct rtl8169_private *tp;
+ u32 status;
+
+ tp = netdev_priv(napi->dev);
+ status = rtl_get_events(tp);
if ((status & 0xffff) == 0xffff || !(status & tp->irq_mask))
return IRQ_NONE;
@@ -4873,13 +4896,43 @@ static irqreturn_t rtl8169_interrupt(int irq, void *dev_instance)
phy_mac_interrupt(tp->phydev);
rtl_irq_disable(tp);
- napi_schedule(&tp->napi);
+ napi_schedule(napi);
out:
rtl_ack_events(tp, status);
return IRQ_HANDLED;
}
+static void rtl8169_free_irq(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->irq_nvecs; i++) {
+ struct napi_struct *napi = &tp->rtl8169_napi[i];
+
+ pci_free_irq(tp->pci_dev, i, napi);
+ }
+}
+
+static int rtl8169_request_irq(struct rtl8169_private *tp)
+{
+ struct net_device *dev = tp->dev;
+ struct napi_struct *napi;
+ int i, rc;
+
+ for (i = 0; i < tp->irq_nvecs; i++) {
+ napi = &tp->rtl8169_napi[i];
+ rc = pci_request_irq(tp->pci_dev, i, rtl8169_interrupt,
+ NULL, napi, "%s-%d", dev->name, i);
+ if (rc)
+ goto free_irq;
+ }
+ return 0;
+
+free_irq:
+ while (--i >= 0)
+ pci_free_irq(tp->pci_dev, i, &tp->rtl8169_napi[i]);
+ return rc;
+}
+
static void rtl_task(struct work_struct *work)
{
struct rtl8169_private *tp =
@@ -4914,13 +4967,13 @@ static void rtl_task(struct work_struct *work)
static int rtl8169_poll(struct napi_struct *napi, int budget)
{
- struct rtl8169_private *tp = container_of(napi, struct rtl8169_private, napi);
- struct net_device *dev = tp->dev;
- int work_done;
+ struct rtl8169_private *tp = netdev_priv(napi->dev);
+ struct net_device *dev = napi->dev;
+ int work_done = 0;
rtl_tx(dev, tp, budget);
- work_done = rtl_rx(dev, tp, budget);
+ work_done = rtl_rx(dev, tp, budget, napi);
if (work_done < budget && napi_complete_done(napi, work_done))
rtl_irq_enable(tp);
@@ -5035,7 +5088,7 @@ static void rtl8169_up(struct rtl8169_private *tp)
phy_init_hw(tp->phydev);
phy_resume(tp->phydev);
rtl8169_init_phy(tp);
- napi_enable(&tp->napi);
+ rtl8169_napi_enable(tp);
enable_work(&tp->wk.work);
rtl_reset_work(tp);
@@ -5053,7 +5106,7 @@ static int rtl8169_close(struct net_device *dev)
rtl8169_down(tp);
rtl8169_rx_clear(tp);
- free_irq(tp->irq, tp);
+ rtl8169_free_irq(tp);
phy_disconnect(tp->phydev);
@@ -5074,7 +5127,10 @@ static void rtl8169_netpoll(struct net_device *dev)
{
struct rtl8169_private *tp = netdev_priv(dev);
- rtl8169_interrupt(tp->irq, tp);
+ for (int i = 0; i < tp->irq_nvecs; i++) {
+ rtl8169_interrupt(pci_irq_vector(tp->pci_dev, i),
+ &tp->rtl8169_napi[i]);
+ }
}
#endif
@@ -5082,7 +5138,6 @@ static int rtl_open(struct net_device *dev)
{
struct rtl8169_private *tp = netdev_priv(dev);
struct pci_dev *pdev = tp->pci_dev;
- unsigned long irqflags;
int retval = -ENOMEM;
pm_runtime_get_sync(&pdev->dev);
@@ -5107,8 +5162,7 @@ static int rtl_open(struct net_device *dev)
rtl_request_firmware(tp);
- irqflags = pci_dev_msi_enabled(pdev) ? IRQF_NO_THREAD : IRQF_SHARED;
- retval = request_irq(tp->irq, rtl8169_interrupt, irqflags, dev->name, tp);
+ retval = rtl8169_request_irq(tp);
if (retval < 0)
goto err_release_fw_2;
@@ -5125,7 +5179,7 @@ static int rtl_open(struct net_device *dev)
return retval;
err_free_irq:
- free_irq(tp->irq, tp);
+ rtl8169_free_irq(tp);
err_release_fw_2:
rtl_release_firmware(tp);
rtl8169_rx_clear(tp);
@@ -5275,6 +5329,14 @@ static void rtl_shutdown(struct pci_dev *pdev)
pci_prepare_to_sleep(pdev);
}
+static void r8169_free_napi(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->irq_nvecs; i++)
+ netif_napi_del(&tp->rtl8169_napi[i]);
+
+ kfree(tp->rtl8169_napi);
+}
+
static void rtl_remove_one(struct pci_dev *pdev)
{
struct rtl8169_private *tp = pci_get_drvdata(pdev);
@@ -5289,6 +5351,8 @@ static void rtl_remove_one(struct pci_dev *pdev)
unregister_netdev(tp->dev);
+ r8169_free_napi(tp);
+
if (tp->dash_type != RTL_DASH_NONE)
rtl8168_driver_stop(tp);
@@ -5328,7 +5392,9 @@ static void rtl_set_irq_mask(struct rtl8169_private *tp)
static int rtl_alloc_irq(struct rtl8169_private *tp)
{
+ struct pci_dev *pdev = tp->pci_dev;
unsigned int flags;
+ int nvecs;
switch (tp->mac_version) {
case RTL_GIGA_MAC_VER_02 ... RTL_GIGA_MAC_VER_06:
@@ -5344,7 +5410,14 @@ static int rtl_alloc_irq(struct rtl8169_private *tp)
break;
}
- return pci_alloc_irq_vectors(tp->pci_dev, 1, 1, flags);
+ nvecs = pci_alloc_irq_vectors(pdev, 1, 1, flags);
+
+ if (nvecs < 0)
+ return nvecs;
+
+ tp->irq_nvecs = nvecs;
+
+ return 0;
}
static void rtl_read_mac_address(struct rtl8169_private *tp,
@@ -5599,6 +5672,12 @@ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
return false;
}
+static void r8169_init_napi(struct rtl8169_private *tp)
+{
+ for (int i = 0; i < tp->irq_nvecs; i++)
+ netif_napi_add(tp->dev, &tp->rtl8169_napi[i], rtl8169_poll);
+}
+
static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
const struct rtl_chip_info *chip;
@@ -5703,12 +5782,12 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
rtl_hw_reset(tp);
+ rtl_setup_rx_params(tp);
+
rc = rtl_alloc_irq(tp);
if (rc < 0)
return dev_err_probe(&pdev->dev, rc, "Can't allocate interrupt\n");
- tp->irq = pci_irq_vector(pdev, 0);
-
INIT_WORK(&tp->wk.work, rtl_task);
disable_work(&tp->wk.work);
@@ -5716,8 +5795,6 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
dev->ethtool_ops = &rtl8169_ethtool_ops;
- netif_napi_add(dev, &tp->napi, rtl8169_poll);
-
dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX;
dev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO;
@@ -5778,6 +5855,10 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (jumbo_max)
dev->max_mtu = jumbo_max;
+ rc = netif_set_real_num_queues(tp->dev, 1, tp->num_rx_rings);
+ if (rc < 0)
+ return dev_err_probe(&pdev->dev, rc, "set tx/rx num failure\n");
+
rtl_set_irq_mask(tp);
tp->counters = dmam_alloc_coherent (&pdev->dev, sizeof(*tp->counters),
@@ -5792,9 +5873,15 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (rc)
return rc;
+ tp->rtl8169_napi = kcalloc(tp->irq_nvecs, sizeof(struct napi_struct), GFP_KERNEL);
+ if (!tp->rtl8169_napi)
+ return -ENOMEM;
+
+ r8169_init_napi(tp);
+
rc = register_netdev(dev);
if (rc)
- return rc;
+ goto err_free_napi;
if (IS_ENABLED(CONFIG_R8169_LEDS)) {
if (rtl_is_8125(tp))
@@ -5803,8 +5890,9 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
tp->leds = rtl8168_init_leds(dev);
}
- netdev_info(dev, "%s, %pM, %sXID %x, IRQ %d\n",
- chip->name, dev->dev_addr, ext_xid_str, xid, tp->irq);
+ netdev_info(dev, "%s, %pM, %sXID %x, IRQ %d (%d total)\n",
+ chip->name, dev->dev_addr, ext_xid_str, xid,
+ pci_irq_vector(pdev, 0), tp->irq_nvecs);
if (jumbo_max)
netdev_info(dev, "jumbo features [frames: %d bytes, tx checksumming: %s]\n",
@@ -5821,6 +5909,10 @@ static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
pm_runtime_put_sync(&pdev->dev);
return 0;
+
+err_free_napi:
+ r8169_free_napi(tp);
+ return rc;
}
static struct pci_driver rtl8169_pci_driver = {
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v9 3/7] r8169: add support for new interrupt mapping
From: javen @ 2026-06-29 7:13 UTC (permalink / raw)
To: hkallweit1, nic_swsd, andrew+netdev, davem, edumazet, kuba,
pabeni, horms
Cc: netdev, linux-kernel, Javen Xu
In-Reply-To: <20260629071339.1605-1-javen_xu@realsil.com.cn>
From: Javen Xu <javen_xu@realsil.com.cn>
To support RSS, the number of hardware interrupt bits should match the
interrupt of software. So we add support for new interrupt mapping here.
ISR_VEC_MAP_REG is the hardware register to indicate interrupt status.
IMR_SET_VEC_MAP_REG is interrupt mask which is set to enable irq.
Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
Changes in v2:
- no changes
Changes in v3:
- init index in napi_struct and get message_id from index
- move rtl8169_disable_hw_interrupt_msix directly before the call to
napi_schedule()
- change the condition in rtl8169_request_irq when RTL_VEC_MAP_ENABLE
enabled, use rtl8169_interrupt_msix
Changes in v4:
- remove flag tp->feature, replace tp->features & RTL_VEC_MAP_ENABLE
with tp->irq_nvecs > 1, they are equivalent.
- follow reverse xmas tree, in rtl8169_interrupt_msix(),
rtl8169_poll_msix_rx(), rtl8169_poll_msix_tx(),
rtl8169_poll_msix_other()
- use napi->index in rtl8169_poll_msix_other()
- add a comment to describe RTL8127 MSI-X vector layout
- simplify r8169_init_napi()
Changes in v5:
- replace magic number in rtl8169_poll_msix_tx()
Changes in v6:
- when irq_nvecs <= 1, use register IntrMask_8125, else using vec map
- fix irq sequence in rtl8169_interrupt_msix(), disable interrupts
before clean it
- remove dead code in rtl8169_poll_msix_tx()
Changes in v7:
- remove recheck_desc_ownbit
- change return value of rtl_tx
- remove message_id which only used once
Changes in v8:
- fix rtl8169_netpoll()
- remove tx_done
Changes in v9:
- change the way of getting message_id of napi
---
drivers/net/ethernet/realtek/r8169_main.c | 172 +++++++++++++++++++---
1 file changed, 154 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
index 13c56dbca230..2fb90dc9026c 100644
--- a/drivers/net/ethernet/realtek/r8169_main.c
+++ b/drivers/net/ethernet/realtek/r8169_main.c
@@ -85,6 +85,7 @@
#define R8169_TX_STOP_THRS (MAX_SKB_FRAGS + 1)
#define R8169_TX_START_THRS (2 * R8169_TX_STOP_THRS)
#define R8169_MAX_RX_QUEUES 8
+#define R8127_MAX_TX_QUEUES 8
#define R8169_DEFAULT_RX_QUEUES 1
#define R8169_MAX_TX_QUEUES 1
@@ -455,8 +456,12 @@ enum rtl8125_registers {
RSS_CTRL_8125 = 0x4500,
Q_NUM_CTRL_8125 = 0x4800,
EEE_TXIDLE_TIMER_8125 = 0x6048,
+ IMR_CLEAR_VEC_MAP_REG = 0x0d00,
+ ISR_VEC_MAP_REG = 0x0d04,
+ IMR_SET_VEC_MAP_REG = 0x0d0c,
};
+#define MSIX_ID_VEC_MAP_LINKCHG 29
#define LEDSEL_MASK_8125 0x23f
#define RX_VLAN_INNER_8125 BIT(22)
@@ -587,6 +592,9 @@ enum rtl_register_content {
/* magic enable v2 */
MagicPacket_v2 = (1 << 16), /* Wake up when receives a Magic Packet */
+#define ISRIMR_LINKCHG BIT(29)
+#define ISRIMR_TOK_Q0 BIT(8)
+#define ISRIMR_ROK_Q0 BIT(0)
};
enum rtl_desc_bit {
@@ -1664,26 +1672,38 @@ static u32 rtl_get_events(struct rtl8169_private *tp)
static void rtl_ack_events(struct rtl8169_private *tp, u32 bits)
{
- if (rtl_is_8125(tp))
- RTL_W32(tp, IntrStatus_8125, bits);
- else
+ if (rtl_is_8125(tp)) {
+ if (tp->irq_nvecs > 1)
+ RTL_W32(tp, ISR_VEC_MAP_REG, bits);
+ else
+ RTL_W32(tp, IntrStatus_8125, bits);
+ } else {
RTL_W16(tp, IntrStatus, bits);
+ }
}
static void rtl_irq_disable(struct rtl8169_private *tp)
{
- if (rtl_is_8125(tp))
- RTL_W32(tp, IntrMask_8125, 0);
- else
+ if (rtl_is_8125(tp)) {
+ if (tp->irq_nvecs > 1)
+ RTL_W32(tp, IMR_CLEAR_VEC_MAP_REG, 0xffffffff);
+ else
+ RTL_W32(tp, IntrMask_8125, 0);
+ } else {
RTL_W16(tp, IntrMask, 0);
+ }
}
static void rtl_irq_enable(struct rtl8169_private *tp)
{
- if (rtl_is_8125(tp))
- RTL_W32(tp, IntrMask_8125, tp->irq_mask);
- else
+ if (rtl_is_8125(tp)) {
+ if (tp->irq_nvecs > 1)
+ RTL_W32(tp, IMR_SET_VEC_MAP_REG, tp->irq_mask);
+ else
+ RTL_W32(tp, IntrMask_8125, tp->irq_mask);
+ } else {
RTL_W16(tp, IntrMask, tp->irq_mask);
+ }
}
static void rtl8169_irq_mask_and_ack(struct rtl8169_private *tp)
@@ -5037,6 +5057,45 @@ static void rtl8169_free_irq(struct rtl8169_private *tp)
}
}
+static void rtl8169_disable_hw_interrupt_msix(struct rtl8169_private *tp, int message_id)
+{
+ RTL_W32(tp, IMR_CLEAR_VEC_MAP_REG, BIT(message_id));
+}
+
+static void rtl8169_clear_hw_isr(struct rtl8169_private *tp, int message_id)
+{
+ RTL_W32(tp, ISR_VEC_MAP_REG, BIT(message_id));
+}
+
+static void rtl8169_enable_hw_interrupt_msix(struct rtl8169_private *tp, int message_id)
+{
+ RTL_W32(tp, IMR_SET_VEC_MAP_REG, BIT(message_id));
+}
+
+static irqreturn_t rtl8169_interrupt_msix(int irq, void *dev_instance)
+{
+ struct napi_struct *napi = dev_instance;
+ struct net_device *dev = napi->dev;
+ struct rtl8169_private *tp;
+ int message_id;
+
+ tp = netdev_priv(dev);
+ message_id = napi - tp->rtl8169_napi;
+
+ if (message_id == MSIX_ID_VEC_MAP_LINKCHG) {
+ rtl8169_clear_hw_isr(tp, message_id);
+ phy_mac_interrupt(tp->phydev);
+ return IRQ_HANDLED;
+ }
+
+ rtl8169_disable_hw_interrupt_msix(tp, message_id);
+ rtl8169_clear_hw_isr(tp, message_id);
+
+ napi_schedule(napi);
+
+ return IRQ_HANDLED;
+}
+
static int rtl8169_request_irq(struct rtl8169_private *tp)
{
struct net_device *dev = tp->dev;
@@ -5045,8 +5104,12 @@ static int rtl8169_request_irq(struct rtl8169_private *tp)
for (i = 0; i < tp->irq_nvecs; i++) {
napi = &tp->rtl8169_napi[i];
- rc = pci_request_irq(tp->pci_dev, i, rtl8169_interrupt,
- NULL, napi, "%s-%d", dev->name, i);
+ if (tp->irq_nvecs > 1)
+ rc = pci_request_irq(tp->pci_dev, i, rtl8169_interrupt_msix,
+ NULL, napi, "%s-%d", dev->name, i);
+ else
+ rc = pci_request_irq(tp->pci_dev, i, rtl8169_interrupt,
+ NULL, napi, "%s-%d", dev->name, i);
if (rc)
goto free_irq;
}
@@ -5253,8 +5316,12 @@ static void rtl8169_netpoll(struct net_device *dev)
struct rtl8169_private *tp = netdev_priv(dev);
for (int i = 0; i < tp->irq_nvecs; i++) {
- rtl8169_interrupt(pci_irq_vector(tp->pci_dev, i),
- &tp->rtl8169_napi[i]);
+ if (tp->irq_nvecs > 1)
+ rtl8169_interrupt_msix(pci_irq_vector(tp->pci_dev, i),
+ &tp->rtl8169_napi[i]);
+ else
+ rtl8169_interrupt(pci_irq_vector(tp->pci_dev, i),
+ &tp->rtl8169_napi[i]);
}
}
#endif
@@ -5505,10 +5572,16 @@ static const struct net_device_ops rtl_netdev_ops = {
static void rtl_set_irq_mask(struct rtl8169_private *tp)
{
- tp->irq_mask = RxOK | RxErr | TxOK | TxErr | LinkChg;
+ if (tp->irq_nvecs > 1) {
+ tp->irq_mask = ISRIMR_LINKCHG | ISRIMR_TOK_Q0;
+ for (int i = 0; i < tp->num_rx_rings; i++)
+ tp->irq_mask |= ISRIMR_ROK_Q0 << i;
+ } else {
+ tp->irq_mask = RxOK | RxErr | TxOK | TxErr | LinkChg;
- if (tp->mac_version <= RTL_GIGA_MAC_VER_06)
- tp->irq_mask |= SYSErr | RxFIFOOver;
+ if (tp->mac_version <= RTL_GIGA_MAC_VER_06)
+ tp->irq_mask |= SYSErr | RxFIFOOver;
+ }
}
static int rtl_alloc_irq(struct rtl8169_private *tp)
@@ -5793,10 +5866,73 @@ static bool rtl_aspm_is_safe(struct rtl8169_private *tp)
return false;
}
+static int rtl8169_poll_msix_rx(struct napi_struct *napi, int budget)
+{
+ struct net_device *dev = napi->dev;
+ struct rtl8169_private *tp;
+ int work_done = 0;
+ int message_id;
+
+ tp = netdev_priv(dev);
+ message_id = napi - tp->rtl8169_napi;
+
+ if (message_id < tp->num_rx_rings)
+ work_done += rtl_rx(dev, tp, &tp->rx_ring[message_id], budget, napi);
+
+ if (work_done < budget && napi_complete_done(napi, work_done))
+ rtl8169_enable_hw_interrupt_msix(tp, message_id);
+
+ return work_done;
+}
+
+static int rtl8169_poll_msix_tx(struct napi_struct *napi, int budget)
+{
+ struct net_device *dev = napi->dev;
+ struct rtl8169_private *tp;
+
+ tp = netdev_priv(dev);
+
+ rtl_tx(dev, tp, budget);
+
+ if (napi_complete_done(napi, 0))
+ rtl8169_enable_hw_interrupt_msix(tp, (int)(napi - tp->rtl8169_napi));
+
+ return 0;
+}
+
+static int rtl8169_poll_msix_other(struct napi_struct *napi, int budget)
+{
+ struct net_device *dev = napi->dev;
+ struct rtl8169_private *tp;
+
+ tp = netdev_priv(dev);
+
+ if (napi_complete_done(napi, 0))
+ rtl8169_enable_hw_interrupt_msix(tp, (int)(napi - tp->rtl8169_napi));
+
+ return 0;
+}
+
+/* RTL8127 MSI-X vector layout:
+ * Vectors 0 .. (RxQs - 1) : Rx Queues
+ * Vectors RxQs .. (RxQs + TxQs - 1) : Tx Queues
+ * Vector (RxQs + TxQs) and up : Other events (Link status(29), etc.)
+ */
static void r8169_init_napi(struct rtl8169_private *tp)
{
- for (int i = 0; i < tp->irq_nvecs; i++)
- netif_napi_add(tp->dev, &tp->rtl8169_napi[i], rtl8169_poll);
+ for (int i = 0; i < tp->irq_nvecs; i++) {
+ int (*poll_fn)(struct napi_struct *, int) = rtl8169_poll;
+
+ if (tp->irq_nvecs > 1) {
+ if (i < R8169_MAX_RX_QUEUES)
+ poll_fn = rtl8169_poll_msix_rx;
+ else if (i < R8169_MAX_RX_QUEUES + R8127_MAX_TX_QUEUES)
+ poll_fn = rtl8169_poll_msix_tx;
+ else
+ poll_fn = rtl8169_poll_msix_other;
+ }
+ netif_napi_add(tp->dev, &tp->rtl8169_napi[i], poll_fn);
+ }
}
static int rtl_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox