* [PATCH net 0/2] net: macb: fix TXUBR interrupt storm on link flapping
From: Christian Taedcke via B4 Relay @ 2026-07-06 14:02 UTC (permalink / raw)
To: christian.taedcke-oss, Théo Lebrun, Conor Dooley,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Kevin Hao, Simon Horman, Sebastian Andrzej Siewior,
Clark Williams, Steven Rostedt, Robert Hancock
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke, stable
We observed a hard interrupt storm in the Cadence GEM (macb) driver on
a Xilinx ZynqMP based platform running a PREEMPT_RT kernel (6.6.142).
After several Ethernet link up/down transitions, the CPU that the MAC
IRQ is pinned to is pegged at 100% in the threaded MAC interrupt
handler and the kernel reports "sched: RT throttling activated",
killing the network interface. The MAC ISR keeps refiring with the
level-triggered TXUBR (TX used bit read) set, because the transmitter
is left pointing at a descriptor whose used bit is set. See the
individual commit messages for the full analysis.
Patch 1 fixes the root cause: gem_shuffle_tx_one_ring() resets tx_tail to
the ring base on link-up but never reprograms the hardware TBQP pointer.
Patch 2 fixes a second, independent bug: macb_interrupt() masks only
TCOMP (not TXUBR) when scheduling the TX NAPI, and macb_tx_poll()
re-enables only TCOMP. Because TXUBR is level-triggered, a persistent
used-descriptor condition keeps it asserted and re-fires immediately,
storming the MAC interrupt.
Both patches are required: on the affected platform the interrupt storm
still reproduces with patch 1 applied alone, so patch 2 is needed as
well to stop it.
The relevant code is essentially identical in mainline, so the bug is not
RT-specific. PREEMPT_RT merely turns the storm into a fatal failure via
RT throttling.
Tested on ZynqMP with PREEMPT_RT by repeatedly flapping the Ethernet
link. The interrupt storm and RT throttling no longer occur.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
---
Christian Taedcke (2):
net: macb: reprogram TBQP after shuffling the TX ring on link-up
net: macb: mask TXUBR during TX NAPI poll to prevent IRQ storms
drivers/net/ethernet/cadence/macb_main.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260703-upstreaming-macb-irq-storm-ebd290b1e832
Best regards,
--
Christian Taedcke <christian.taedcke@weidmueller.com>
^ permalink raw reply
* [PATCH net 1/2] net: macb: reprogram TBQP after shuffling the TX ring on link-up
From: Christian Taedcke via B4 Relay @ 2026-07-06 14:02 UTC (permalink / raw)
To: christian.taedcke-oss, Théo Lebrun, Conor Dooley,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Kevin Hao, Simon Horman, Sebastian Andrzej Siewior,
Clark Williams, Steven Rostedt, Robert Hancock
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke, stable
In-Reply-To: <20260706-upstreaming-macb-irq-storm-v1-0-ab3115b5a13a@weidmueller.com>
From: Christian Taedcke <christian.taedcke@weidmueller.com>
gem_shuffle_tx_one_ring() rotates the software TX ring so that the
tail sits at index 0 and resets queue->tx_tail to 0, but it never
reprograms the hardware transmit buffer queue pointer (TBQP). Other
paths that reset tx_tail to the ring base (macb_init_buffers() and
macb_tx_error_task()) also reprogram TBQP to queue->tx_ring_dma; this
path does not, leaving TBQP pointing at a stale descriptor.
gem_shuffle_tx_rings() runs on every link-up from
macb_mac_link_up(). After a few link up/down flaps that leave
un-completed descriptors in the ring, the stale TBQP keeps pointing at
a descriptor whose used bit is set. When TX is re-enabled on link-up,
the GEM reads that used descriptor and raises TXUBR. macb_interrupt()
schedules the TX NAPI, macb_tx_poll() makes no progress (work_done ==
0) and macb_tx_restart() re-issues TSTART, which makes the controller
read the same used descriptor again and re-assert TXUBR. As the MAC
interrupt is level-triggered, it never deasserts and one CPU is pegged
at 100% in the threaded handler, eventually triggering "sched: RT
throttling activated" and a dead network interface.
Fix it by reprogramming TBQP to the ring base on every path of
gem_shuffle_tx_one_ring() that resets tx_tail to 0, mirroring
macb_tx_error_task(). The early return for an already-aligned tail is
left untouched as TBQP is already consistent there. This is safe
because the shuffle runs from macb_mac_link_up() while TE is still
disabled, so the transmitter is halted.
Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
---
drivers/net/ethernet/cadence/macb_main.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index fd282a1700fb..b11cb8f068b7 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -820,7 +820,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
if (!count) {
queue->tx_head = 0;
queue->tx_tail = 0;
- goto unlock;
+ goto reset_hw_ptr;
}
shift = tail % ring_size;
@@ -869,6 +869,13 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
/* Make descriptor updates visible to hardware */
wmb();
+reset_hw_ptr:
+ /* tx_tail was reset to the ring base, so TBQP must be reprogrammed
+ * to match; otherwise it keeps pointing at a stale descriptor. Safe
+ * to write directly here as TX is still disabled (called from
+ * macb_mac_link_up() before TE is set).
+ */
+ queue_writel(queue, TBQP, lower_32_bits(queue->tx_ring_dma));
unlock:
spin_unlock_irqrestore(&queue->tx_ptr_lock, flags);
}
--
2.54.0
^ permalink raw reply related
* [PATCH net 2/2] net: macb: mask TXUBR during TX NAPI poll to prevent IRQ storms
From: Christian Taedcke via B4 Relay @ 2026-07-06 14:02 UTC (permalink / raw)
To: christian.taedcke-oss, Théo Lebrun, Conor Dooley,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Kevin Hao, Simon Horman, Sebastian Andrzej Siewior,
Clark Williams, Steven Rostedt, Robert Hancock
Cc: netdev, linux-kernel, linux-rt-devel, Christian Taedcke, stable
In-Reply-To: <20260706-upstreaming-macb-irq-storm-v1-0-ab3115b5a13a@weidmueller.com>
From: Christian Taedcke <christian.taedcke@weidmueller.com>
macb_interrupt() defers TX completion handling to NAPI, but when it
schedules the poll it only masks TCOMP, even though TXUBR is enabled
alongside it (both are part of MACB_TX_INT_FLAGS). macb_tx_poll() is
asymmetric in the same way and only re-enables TCOMP. TXUBR is thus
left unmasked while responsibility for handling it has been deferred
to NAPI.
Unlike an edge event, TXUBR is a persistent condition: the controller
keeps it asserted for as long as the transmitter reads a buffer
descriptor whose used bit is set. Leaving a level-triggered source
enabled while NAPI owns its processing means the interrupt refires
immediately after the handler returns, before the poll has had a
chance to clear the underlying condition. This turns into a hard
interrupt storm that pegs a CPU in the (threaded) MAC IRQ handler and,
on PREEMPT_RT, triggers RT throttling ("sched: RT throttling
activated"), taking the network interface down.
Several situations can keep the used-bit read asserted across a poll -
for example unreaped completed descriptors still sitting at tx_tail,
or a transmit restart racing with macb_start_xmit(). The specific
trigger does not matter: as long as the source stays unmasked, any
persistent assertion is enough to storm, so the interrupt handling
itself must be made self-limiting.
Mask TXUBR together with TCOMP in the IDR write when scheduling the TX
NAPI, and re-enable both from the napi_complete path in
macb_tx_poll(), making the TX interrupt mask/unmask symmetric and
consistent with how the driver already treats every other
NAPI-serviced source. The pending TXUBR is still recorded in
queue->txubr_pending before masking and acted on by macb_tx_restart(),
so no event is lost. A persistent TXUBR now degrades to NAPI-paced
polling instead of a CPU-pegging hard interrupt storm.
Fixes: 138badbc21a0 ("net: macb: use NAPI for TX completion path")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
---
drivers/net/ethernet/cadence/macb_main.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index b11cb8f068b7..f75cf2ffdf6f 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -1971,7 +1971,7 @@ static int macb_tx_poll(struct napi_struct *napi, int budget)
(unsigned int)(queue - bp->queues), work_done, budget);
if (work_done < budget && napi_complete_done(napi, work_done)) {
- queue_writel(queue, IER, MACB_BIT(TCOMP));
+ queue_writel(queue, IER, MACB_BIT(TCOMP) | MACB_BIT(TXUBR));
/* Packet completions only seem to propagate to raise
* interrupts when interrupts are enabled at the time, so if
@@ -2161,7 +2161,8 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
if (status & (MACB_BIT(TCOMP) |
MACB_BIT(TXUBR))) {
- queue_writel(queue, IDR, MACB_BIT(TCOMP));
+ queue_writel(queue, IDR, MACB_BIT(TCOMP) |
+ MACB_BIT(TXUBR));
macb_queue_isr_clear(bp, queue, MACB_BIT(TCOMP) |
MACB_BIT(TXUBR));
if (status & MACB_BIT(TXUBR)) {
--
2.54.0
^ permalink raw reply related
* [PATCH v3 1/9] ata: don't store pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
pci_device_id is not guaranteed to live longer than probe due to presence
of dynamic ID. All information apart from driver_data can be easily
retrieved from pci_dev, so just store driver_data.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/ata/ata_generic.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/ata/ata_generic.c b/drivers/ata/ata_generic.c
index e70b6c089cf1..18ea740ca582 100644
--- a/drivers/ata/ata_generic.c
+++ b/drivers/ata/ata_generic.c
@@ -51,11 +51,11 @@ enum {
static int generic_set_mode(struct ata_link *link, struct ata_device **unused)
{
struct ata_port *ap = link->ap;
- const struct pci_device_id *id = ap->host->private_data;
+ unsigned long driver_data = (unsigned long)ap->host->private_data;
int dma_enabled = 0;
struct ata_device *dev;
- if (id->driver_data & ATA_GEN_FORCE_DMA) {
+ if (driver_data & ATA_GEN_FORCE_DMA) {
dma_enabled = 0xff;
} else if (ap->ioaddr.bmdma_addr) {
/* Bits 5 and 6 indicate if DMA is active on master/slave */
@@ -206,7 +206,7 @@ static int ata_generic_init_one(struct pci_dev *dev, const struct pci_device_id
return rc;
pcim_pin_device(dev);
}
- return ata_pci_bmdma_init_one(dev, ppi, &generic_sht, (void *)id, 0);
+ return ata_pci_bmdma_init_one(dev, ppi, &generic_sht, (void *)id->driver_data, 0);
}
static const struct pci_device_id ata_generic[] = {
--
2.54.0
^ permalink raw reply related
* [PATCH v3 5/9] agp/via: don't rely on address of pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
Address of pci_device_id cannot be relied on due to presence of dynamic ID
and driver_override. Use driver_data instead.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/char/agp/via-agp.c | 308 +++++++++++----------------------------------
1 file changed, 72 insertions(+), 236 deletions(-)
diff --git a/drivers/char/agp/via-agp.c b/drivers/char/agp/via-agp.c
index 8b19a5d1a09b..ab3b73dd080a 100644
--- a/drivers/char/agp/via-agp.c
+++ b/drivers/char/agp/via-agp.c
@@ -221,204 +221,6 @@ static const struct agp_bridge_driver via_driver = {
.agp_type_to_mask_type = agp_generic_type_to_mask_type,
};
-static struct agp_device_ids via_agp_device_ids[] =
-{
- {
- .device_id = PCI_DEVICE_ID_VIA_82C597_0,
- .chipset_name = "Apollo VP3",
- },
-
- {
- .device_id = PCI_DEVICE_ID_VIA_82C598_0,
- .chipset_name = "Apollo MVP3",
- },
-
- {
- .device_id = PCI_DEVICE_ID_VIA_8501_0,
- .chipset_name = "Apollo MVP4",
- },
-
- /* VT8601 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8601_0,
- .chipset_name = "Apollo ProMedia/PLE133Ta",
- },
-
- /* VT82C693A / VT28C694T */
- {
- .device_id = PCI_DEVICE_ID_VIA_82C691_0,
- .chipset_name = "Apollo Pro 133",
- },
-
- {
- .device_id = PCI_DEVICE_ID_VIA_8371_0,
- .chipset_name = "KX133",
- },
-
- /* VT8633 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8633_0,
- .chipset_name = "Pro 266",
- },
-
- {
- .device_id = PCI_DEVICE_ID_VIA_XN266,
- .chipset_name = "Apollo Pro266",
- },
-
- /* VT8361 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8361,
- .chipset_name = "KLE133",
- },
-
- /* VT8365 / VT8362 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8363_0,
- .chipset_name = "Twister-K/KT133x/KM133",
- },
-
- /* VT8753A */
- {
- .device_id = PCI_DEVICE_ID_VIA_8753_0,
- .chipset_name = "P4X266",
- },
-
- /* VT8366 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8367_0,
- .chipset_name = "KT266/KY266x/KT333",
- },
-
- /* VT8633 (for CuMine/ Celeron) */
- {
- .device_id = PCI_DEVICE_ID_VIA_8653_0,
- .chipset_name = "Pro266T",
- },
-
- /* KM266 / PM266 */
- {
- .device_id = PCI_DEVICE_ID_VIA_XM266,
- .chipset_name = "PM266/KM266",
- },
-
- /* CLE266 */
- {
- .device_id = PCI_DEVICE_ID_VIA_862X_0,
- .chipset_name = "CLE266",
- },
-
- {
- .device_id = PCI_DEVICE_ID_VIA_8377_0,
- .chipset_name = "KT400/KT400A/KT600",
- },
-
- /* VT8604 / VT8605 / VT8603
- * (Apollo Pro133A chipset with S3 Savage4) */
- {
- .device_id = PCI_DEVICE_ID_VIA_8605_0,
- .chipset_name = "ProSavage PM133/PL133/PN133"
- },
-
- /* P4M266x/P4N266 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8703_51_0,
- .chipset_name = "P4M266x/P4N266",
- },
-
- /* VT8754 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8754C_0,
- .chipset_name = "PT800",
- },
-
- /* P4X600 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8763_0,
- .chipset_name = "P4X600"
- },
-
- /* KM400 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8378_0,
- .chipset_name = "KM400/KM400A",
- },
-
- /* PT880 */
- {
- .device_id = PCI_DEVICE_ID_VIA_PT880,
- .chipset_name = "PT880",
- },
-
- /* PT880 Ultra */
- {
- .device_id = PCI_DEVICE_ID_VIA_PT880ULTRA,
- .chipset_name = "PT880 Ultra",
- },
-
- /* PT890 */
- {
- .device_id = PCI_DEVICE_ID_VIA_8783_0,
- .chipset_name = "PT890",
- },
-
- /* PM800/PN800/PM880/PN880 */
- {
- .device_id = PCI_DEVICE_ID_VIA_PX8X0_0,
- .chipset_name = "PM800/PN800/PM880/PN880",
- },
- /* KT880 */
- {
- .device_id = PCI_DEVICE_ID_VIA_3269_0,
- .chipset_name = "KT880",
- },
- /* KTxxx/Px8xx */
- {
- .device_id = PCI_DEVICE_ID_VIA_83_87XX_1,
- .chipset_name = "VT83xx/VT87xx/KTxxx/Px8xx",
- },
- /* P4M800 */
- {
- .device_id = PCI_DEVICE_ID_VIA_3296_0,
- .chipset_name = "P4M800",
- },
- /* P4M800CE */
- {
- .device_id = PCI_DEVICE_ID_VIA_P4M800CE,
- .chipset_name = "VT3314",
- },
- /* VT3324 / CX700 */
- {
- .device_id = PCI_DEVICE_ID_VIA_VT3324,
- .chipset_name = "CX700",
- },
- /* VT3336 - this is a chipset for AMD Athlon/K8 CPU. Due to K8's unique
- * architecture, the AGP resource and behavior are different from
- * the traditional AGP which resides only in chipset. AGP is used
- * by 3D driver which wasn't available for the VT3336 and VT3364
- * generation until now. Unfortunately, by testing, VT3364 works
- * but VT3336 doesn't. - explanation from via, just leave this as
- * as a placeholder to avoid future patches adding it back in.
- */
-#if 0
- {
- .device_id = PCI_DEVICE_ID_VIA_VT3336,
- .chipset_name = "VT3336",
- },
-#endif
- /* P4M890 */
- {
- .device_id = PCI_DEVICE_ID_VIA_P4M890,
- .chipset_name = "P4M890",
- },
- /* P4M900 */
- {
- .device_id = PCI_DEVICE_ID_VIA_VT3364,
- .chipset_name = "P4M900",
- },
- { }, /* dummy final entry, always present */
-};
-
/*
* VIA's AGP3 chipsets do magick to put the AGP bridge compliant
@@ -437,17 +239,14 @@ static void check_via_agp3 (struct agp_bridge_data *bridge)
static int agp_via_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
- struct agp_device_ids *devs = via_agp_device_ids;
struct agp_bridge_data *bridge;
- int j = 0;
u8 cap_ptr;
cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
if (!cap_ptr)
return -ENODEV;
- j = ent - agp_via_pci_table;
- printk (KERN_INFO PFX "Detected VIA %s chipset\n", devs[j].chipset_name);
+ dev_info(&pdev->dev, "Detected VIA %s chipset\n", (const char *)ent->driver_data);
bridge = agp_alloc_bridge();
if (!bridge)
@@ -501,9 +300,8 @@ static int agp_via_resume(struct device *dev)
return 0;
}
-/* must be the same order as name table above */
static const struct pci_device_id agp_via_pci_table[] = {
-#define ID(x) \
+#define ID(x, name) \
{ \
.class = (PCI_CLASS_BRIDGE_HOST << 8), \
.class_mask = ~0, \
@@ -511,39 +309,77 @@ static const struct pci_device_id agp_via_pci_table[] = {
.device = x, \
.subvendor = PCI_ANY_ID, \
.subdevice = PCI_ANY_ID, \
+ .driver_data = (kernel_ulong_t)name, \
}
- ID(PCI_DEVICE_ID_VIA_82C597_0),
- ID(PCI_DEVICE_ID_VIA_82C598_0),
- ID(PCI_DEVICE_ID_VIA_8501_0),
- ID(PCI_DEVICE_ID_VIA_8601_0),
- ID(PCI_DEVICE_ID_VIA_82C691_0),
- ID(PCI_DEVICE_ID_VIA_8371_0),
- ID(PCI_DEVICE_ID_VIA_8633_0),
- ID(PCI_DEVICE_ID_VIA_XN266),
- ID(PCI_DEVICE_ID_VIA_8361),
- ID(PCI_DEVICE_ID_VIA_8363_0),
- ID(PCI_DEVICE_ID_VIA_8753_0),
- ID(PCI_DEVICE_ID_VIA_8367_0),
- ID(PCI_DEVICE_ID_VIA_8653_0),
- ID(PCI_DEVICE_ID_VIA_XM266),
- ID(PCI_DEVICE_ID_VIA_862X_0),
- ID(PCI_DEVICE_ID_VIA_8377_0),
- ID(PCI_DEVICE_ID_VIA_8605_0),
- ID(PCI_DEVICE_ID_VIA_8703_51_0),
- ID(PCI_DEVICE_ID_VIA_8754C_0),
- ID(PCI_DEVICE_ID_VIA_8763_0),
- ID(PCI_DEVICE_ID_VIA_8378_0),
- ID(PCI_DEVICE_ID_VIA_PT880),
- ID(PCI_DEVICE_ID_VIA_PT880ULTRA),
- ID(PCI_DEVICE_ID_VIA_8783_0),
- ID(PCI_DEVICE_ID_VIA_PX8X0_0),
- ID(PCI_DEVICE_ID_VIA_3269_0),
- ID(PCI_DEVICE_ID_VIA_83_87XX_1),
- ID(PCI_DEVICE_ID_VIA_3296_0),
- ID(PCI_DEVICE_ID_VIA_P4M800CE),
- ID(PCI_DEVICE_ID_VIA_VT3324),
- ID(PCI_DEVICE_ID_VIA_P4M890),
- ID(PCI_DEVICE_ID_VIA_VT3364),
+ ID(PCI_DEVICE_ID_VIA_82C597_0, "Apollo VP3"),
+ ID(PCI_DEVICE_ID_VIA_82C598_0, "Apollo MVP3"),
+ ID(PCI_DEVICE_ID_VIA_8501_0, "Apollo MVP4"),
+ /* VT8601 */
+ ID(PCI_DEVICE_ID_VIA_8601_0, "Apollo ProMedia/PLE133Ta"),
+ /* VT82C693A / VT28C694T */
+ ID(PCI_DEVICE_ID_VIA_82C691_0, "Apollo Pro 133"),
+ ID(PCI_DEVICE_ID_VIA_8371_0, "KX133"),
+ /* VT8633 */
+ ID(PCI_DEVICE_ID_VIA_8633_0, "Pro 266"),
+ ID(PCI_DEVICE_ID_VIA_XN266, "Apollo Pro266"),
+ /* VT8361 */
+ ID(PCI_DEVICE_ID_VIA_8361, "KLE133"),
+ /* VT8365 / VT8362 */
+ ID(PCI_DEVICE_ID_VIA_8363_0, "Twister-K/KT133x/KM133"),
+ /* VT8753A */
+ ID(PCI_DEVICE_ID_VIA_8753_0, "P4X266"),
+ /* VT8366 */
+ ID(PCI_DEVICE_ID_VIA_8367_0, "KT266/KY266x/KT333"),
+ /* VT8633 (for CuMine/ Celeron) */
+ ID(PCI_DEVICE_ID_VIA_8653_0, "Pro266T"),
+ /* KM266 / PM266 */
+ ID(PCI_DEVICE_ID_VIA_XM266, "PM266/KM266"),
+ /* CLE266 */
+ ID(PCI_DEVICE_ID_VIA_862X_0, "CLE266"),
+ ID(PCI_DEVICE_ID_VIA_8377_0, "KT400/KT400A/KT600"),
+ /* VT8604 / VT8605 / VT8603 (Apollo Pro133A chipset with S3 Savage4) */
+ ID(PCI_DEVICE_ID_VIA_8605_0, "ProSavage PM133/PL133/PN133"),
+ /* P4M266x/P4N266 */
+ ID(PCI_DEVICE_ID_VIA_8703_51_0, "P4M266x/P4N266"),
+ /* VT8754 */
+ ID(PCI_DEVICE_ID_VIA_8754C_0, "PT800"),
+ /* P4X600 */
+ ID(PCI_DEVICE_ID_VIA_8763_0, "P4X600"),
+ /* KM400 */
+ ID(PCI_DEVICE_ID_VIA_8378_0, "KM400/KM400A"),
+ /* PT880 */
+ ID(PCI_DEVICE_ID_VIA_PT880, "PT880"),
+ /* PT880 Ultra */
+ ID(PCI_DEVICE_ID_VIA_PT880ULTRA, "PT880 Ultra"),
+ /* PT890 */
+ ID(PCI_DEVICE_ID_VIA_8783_0, "PT890"),
+ /* PM800/PN800/PM880/PN880 */
+ ID(PCI_DEVICE_ID_VIA_PX8X0_0, "PM800/PN800/PM880/PN880"),
+ /* KT880 */
+ ID(PCI_DEVICE_ID_VIA_3269_0, "KT880"),
+ /* KTxxx/Px8xx */
+ ID(PCI_DEVICE_ID_VIA_83_87XX_1, "VT83xx/VT87xx/KTxxx/Px8xx"),
+ /* P4M800 */
+ ID(PCI_DEVICE_ID_VIA_3296_0, "P4M800"),
+ /* P4M800CE */
+ ID(PCI_DEVICE_ID_VIA_P4M800CE, "VT3314"),
+ /* VT3324 / CX700 */
+ ID(PCI_DEVICE_ID_VIA_VT3324, "CX700"),
+ /* VT3336 - this is a chipset for AMD Athlon/K8 CPU. Due to K8's unique
+ * architecture, the AGP resource and behavior are different from
+ * the traditional AGP which resides only in chipset. AGP is used
+ * by 3D driver which wasn't available for the VT3336 and VT3364
+ * generation until now. Unfortunately, by testing, VT3364 works
+ * but VT3336 doesn't. - explanation from via, just leave this as
+ * a placeholder to avoid future patches adding it back in.
+ */
+#if 0
+ ID(PCI_DEVICE_ID_VIA_VT3336, "VT3336"),
+#endif
+ /* P4M890 */
+ ID(PCI_DEVICE_ID_VIA_P4M890, "P4M890"),
+ /* P4M900 */
+ ID(PCI_DEVICE_ID_VIA_VT3364, "P4M900"),
{ }
};
--
2.54.0
^ permalink raw reply related
* [PATCH v3 0/9] pci: fix UAF and TOCTOU related to dynamic ID
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo, Sashiko
While working on improving the Rust abstractions [1], Sashiko reported that
an existing UAF issue related to dynamic ID, which I find to be genuine.
When taking a look at the code I also find a TOCTOU issue where the
existence check of dynamic ID happens in a separate critical section as the
actual insertion. This series fix both issues.
There are two exported functions "pci_match_id" and "pci_add_dynid" which I
have to tweak to implement this cleanly; I created separate "do_xxx"
functions to keep the existing APIs because they all have multiple users.
There're a few existing users which stores their pci_device_id argument in
probe callback. This is a bad pattern because nothing except driver_data
inside pci_device_id is what they want; actual ID information can be
retrieved from pci_dev instead.
There are two users that performs pointer arithmetic on the pci_device_id;
these are also problematic with dynamic ID and driver_override, so fix them
as well.
I've used the following coccinelle script to flag all cases where the
pci_device_id is used other than reading its fields.
@usage@
identifier fn, id;
position p;
@@
fn(..., struct pci_device_id *id, ...)
{
...
id@p
...
}
// Due to cocci isomorphism this needs to be explicit
@bad@
identifier fn, id;
type T;
position usage.p;
@@
fn(..., struct pci_device_id *id, ...)
{
...
(T*)id@p
...
}
// Good use cases
@good@
identifier fn, id, fld;
expression E;
position usage.p;
@@
fn(..., struct pci_device_id *id, ...)
{
...
(
id@p->fld
|
E(..., id@p, ...)
|
// Redundant checks, but ignore
!id@p
|
// Redundant checks, but ignore
id ? ... : ...
)
...
}
@script:python depends on usage && (bad || !good)@
p << usage.p;
@@
coccilib.report.print_report(p[0], "suspicious use of pci_device_id")
Link: https://lore.kernel.org/all/20260618-id_info-v1-0-96af1e559ef9@garyguo.net/ [1]
Link: https://lore.kernel.org/all/20260619170503.518F61F00A3A@smtp.kernel.org/ [2]
---
Changes in v3:
- Fix users which uses pci_device_id for pointer arithmetic. (Sashiko)
- Convert to scoped_guard. (Danilo)
- For static IDs, still give out static pointers and avoid making a copy.
- Link to v2: https://patch.msgid.link/20260630-pci_id_fix-v2-0-b834a98c0af2@garyguo.net
Changes in v2:
- Fix users which store pci_device_id.
- Clarify in probe documentation about the lifetime of pci_device_id
parameter.
- Dynamic ID conflict check now ignores override_only. (Sashiko)
- Link to v1: https://patch.msgid.link/20260626-pci_id_fix-v1-0-a35c803f1b95@garyguo.net
---
Gary Guo (9):
ata: don't store pci_device_id
nsp32: don't store pci_device_id
ipack: tpci200: don't store pci_device_id
mlxsw: don't store pci_device_id
agp/via: don't rely on address of pci_device_id
agp/amd-k7: don't rely on address of pci_device_id
pci: make pci_match_one_device match on ID instead of device
pci: fix dyn_id add TOCTOU
pci: fix UAF when probe runs concurrent to dyn ID removal
drivers/ata/ata_generic.c | 6 +-
drivers/char/agp/amd-k7-agp.c | 26 +--
drivers/char/agp/via-agp.c | 308 +++++++-----------------------
drivers/ipack/carriers/tpci200.c | 1 -
drivers/ipack/carriers/tpci200.h | 1 -
drivers/net/ethernet/mellanox/mlxsw/pci.c | 11 +-
drivers/pci/pci-driver.c | 193 ++++++++++---------
drivers/pci/pci.h | 36 +++-
drivers/pci/search.c | 6 +-
drivers/scsi/nsp32.c | 8 +-
drivers/scsi/nsp32.h | 8 +-
include/linux/pci.h | 1 +
12 files changed, 230 insertions(+), 375 deletions(-)
---
base-commit: 2b763db0c2763d6bf73d7d3e69665222d1f377cf
change-id: 20260626-pci_id_fix-83eaec007674
Best regards,
--
Gary Guo <gary@garyguo.net>
^ permalink raw reply
* [PATCH v3 6/9] agp/amd-k7: don't rely on address of pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
Address of pci_device_id cannot be relied on due to presence of dynamic ID
and driver_override. Use driver_data instead.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/char/agp/amd-k7-agp.c | 26 ++++----------------------
1 file changed, 4 insertions(+), 22 deletions(-)
diff --git a/drivers/char/agp/amd-k7-agp.c b/drivers/char/agp/amd-k7-agp.c
index 898ff30ffd46..4d201e71c517 100644
--- a/drivers/char/agp/amd-k7-agp.c
+++ b/drivers/char/agp/amd-k7-agp.c
@@ -387,37 +387,17 @@ static const struct agp_bridge_driver amd_irongate_driver = {
.agp_type_to_mask_type = agp_generic_type_to_mask_type,
};
-static struct agp_device_ids amd_agp_device_ids[] =
-{
- {
- .device_id = PCI_DEVICE_ID_AMD_FE_GATE_7006,
- .chipset_name = "Irongate",
- },
- {
- .device_id = PCI_DEVICE_ID_AMD_FE_GATE_700E,
- .chipset_name = "761",
- },
- {
- .device_id = PCI_DEVICE_ID_AMD_FE_GATE_700C,
- .chipset_name = "760MP",
- },
- { }, /* dummy final entry, always present */
-};
-
static int agp_amdk7_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct agp_bridge_data *bridge;
u8 cap_ptr;
- int j;
cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
if (!cap_ptr)
return -ENODEV;
- j = ent - agp_amdk7_pci_table;
- dev_info(&pdev->dev, "AMD %s chipset\n",
- amd_agp_device_ids[j].chipset_name);
+ dev_info(&pdev->dev, "AMD %s chipset\n", (const char *)ent->driver_data);
bridge = agp_alloc_bridge();
if (!bridge)
@@ -492,7 +472,6 @@ static int agp_amdk7_resume(struct device *dev)
return amd_irongate_driver.configure();
}
-/* must be the same order as name table above */
static const struct pci_device_id agp_amdk7_pci_table[] = {
{
.class = (PCI_CLASS_BRIDGE_HOST << 8),
@@ -501,6 +480,7 @@ static const struct pci_device_id agp_amdk7_pci_table[] = {
.device = PCI_DEVICE_ID_AMD_FE_GATE_7006,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)"Irongate",
},
{
.class = (PCI_CLASS_BRIDGE_HOST << 8),
@@ -509,6 +489,7 @@ static const struct pci_device_id agp_amdk7_pci_table[] = {
.device = PCI_DEVICE_ID_AMD_FE_GATE_700E,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)"761",
},
{
.class = (PCI_CLASS_BRIDGE_HOST << 8),
@@ -517,6 +498,7 @@ static const struct pci_device_id agp_amdk7_pci_table[] = {
.device = PCI_DEVICE_ID_AMD_FE_GATE_700C,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)"760MP",
},
{ }
};
--
2.54.0
^ permalink raw reply related
* [PATCH v3 2/9] nsp32: don't store pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
pci_device_id is not guaranteed to live longer than probe due to presence
of dynamic ID. All information apart from driver_data can be easily
retrieved from pci_dev, so just store driver_data.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/scsi/nsp32.c | 8 ++++----
drivers/scsi/nsp32.h | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/scsi/nsp32.c b/drivers/scsi/nsp32.c
index e893d5677241..9c9281222a0a 100644
--- a/drivers/scsi/nsp32.c
+++ b/drivers/scsi/nsp32.c
@@ -1470,7 +1470,7 @@ static int nsp32_show_info(struct seq_file *m, struct Scsi_Host *host)
(nsp32_read2(base, INDEX_REG) >> 8) & 0xff);
mode_reg = nsp32_index_read1(base, CHIP_MODE);
- model = data->pci_devid->driver_data;
+ model = data->model;
#ifdef CONFIG_PM
seq_printf(m, "Power Management: %s\n",
@@ -2907,8 +2907,8 @@ static int nsp32_eh_host_reset(struct scsi_cmnd *SCpnt)
*/
static int nsp32_getprom_param(nsp32_hw_data *data)
{
- int vendor = data->pci_devid->vendor;
- int device = data->pci_devid->device;
+ int vendor = data->Pci->vendor;
+ int device = data->Pci->device;
int ret, i;
int __maybe_unused val;
@@ -3340,7 +3340,7 @@ static int nsp32_probe(struct pci_dev *pdev, const struct pci_device_id *id)
}
data->Pci = pdev;
- data->pci_devid = id;
+ data->model = id->driver_data;
data->IrqNumber = pdev->irq;
data->BaseAddress = pci_resource_start(pdev, 0);
data->NumAddress = pci_resource_len (pdev, 0);
diff --git a/drivers/scsi/nsp32.h b/drivers/scsi/nsp32.h
index 924889f8bd37..9e65771cb592 100644
--- a/drivers/scsi/nsp32.h
+++ b/drivers/scsi/nsp32.h
@@ -564,10 +564,10 @@ typedef struct _nsp32_hw_data {
struct scsi_cmnd *CurrentSC;
- struct pci_dev *Pci;
- const struct pci_device_id *pci_devid;
- struct Scsi_Host *Host;
- spinlock_t Lock;
+ struct pci_dev *Pci;
+ int model;
+ struct Scsi_Host *Host;
+ spinlock_t Lock;
char info_str[100];
--
2.54.0
^ permalink raw reply related
* [PATCH v3 7/9] pci: make pci_match_one_device match on ID instead of device
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
There is a need to match just IDs instead of against devices. Thus rename
this function to pci_match_one_id, and add a pci_id_from_device helper to
make it easy to convert users.
Similar convert pci_match_id to do_pci_match_id, however the existing API
is kept due to quite a few users.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/pci/pci-driver.c | 38 ++++++++++++++++++++++++++++----------
drivers/pci/pci.h | 36 ++++++++++++++++++++++++++----------
drivers/pci/search.c | 6 ++++--
3 files changed, 58 insertions(+), 22 deletions(-)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index f36778e62ac1..0507cb801310 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -90,6 +90,27 @@ static void pci_free_dynids(struct pci_driver *drv)
spin_unlock(&drv->dynids.lock);
}
+/**
+ * do_pci_match_id - See if a PCI ID matches a given pci_id table
+ * @ids: array of PCI device ID structures to search in
+ * @dev_id: the actual PCI device ID structure to match against.
+ *
+ * Returns the matching pci_device_id structure or
+ * %NULL if there is no match.
+ */
+static const struct pci_device_id *do_pci_match_id(const struct pci_device_id *ids,
+ const struct pci_device_id *dev_id)
+{
+ if (ids) {
+ while (ids->vendor || ids->subvendor || ids->class_mask) {
+ if (pci_match_one_id(ids, dev_id))
+ return ids;
+ ids++;
+ }
+ }
+ return NULL;
+}
+
/**
* pci_match_id - See if a PCI device matches a given pci_id table
* @ids: array of PCI device ID structures to search in
@@ -105,14 +126,9 @@ static void pci_free_dynids(struct pci_driver *drv)
const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
struct pci_dev *dev)
{
- if (ids) {
- while (ids->vendor || ids->subvendor || ids->class_mask) {
- if (pci_match_one_device(ids, dev))
- return ids;
- ids++;
- }
- }
- return NULL;
+ struct pci_device_id dev_id = pci_id_from_device(dev);
+
+ return do_pci_match_id(ids, &dev_id);
}
EXPORT_SYMBOL(pci_match_id);
@@ -138,6 +154,7 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
{
struct pci_dynid *dynid;
const struct pci_device_id *found_id = NULL, *ids;
+ struct pci_device_id dev_id;
int ret;
/* When driver_override is set, only bind to the matching driver */
@@ -145,10 +162,11 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
if (ret == 0)
return NULL;
+ dev_id = pci_id_from_device(dev);
/* Look at the dynamic ids first, before the static ones */
spin_lock(&drv->dynids.lock);
list_for_each_entry(dynid, &drv->dynids.list, node) {
- if (pci_match_one_device(&dynid->id, dev)) {
+ if (pci_match_one_id(&dynid->id, &dev_id)) {
found_id = &dynid->id;
break;
}
@@ -158,7 +176,7 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
if (found_id)
return found_id;
- for (ids = drv->id_table; (found_id = pci_match_id(ids, dev));
+ for (ids = drv->id_table; (found_id = do_pci_match_id(ids, &dev_id));
ids = found_id + 1) {
/*
* The match table is split based on driver_override.
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4469e1a77f3c..0567a8762baa 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -442,21 +442,37 @@ static inline int pci_setup_cardbus(char *str) { return -ENOENT; }
#endif /* CONFIG_CARDBUS */
/**
- * pci_match_one_device - Tell if a PCI device structure has a matching
- * PCI device id structure
- * @id: single PCI device id structure to match
- * @dev: the PCI device structure to match against
+ * pci_id_from_device - Obtain a pci_device_id from a PCI device
+ * @dev: the PCI device
+ *
+ * Returns a pci_device_id filled.
+ */
+static inline struct pci_device_id pci_id_from_device(const struct pci_dev *dev)
+{
+ return (struct pci_device_id) {
+ .vendor = dev->vendor,
+ .device = dev->device,
+ .subvendor = dev->subsystem_vendor,
+ .subdevice = dev->subsystem_device,
+ .class = dev->class,
+ };
+}
+
+/**
+ * pci_match_one_id - Tell if a PCI device ID matches a needle PCI device id
+ * @id: single PCI device id structure to match against (needle)
+ * @dev_id: the actual ID from the PCI device (can be created via pci_id_from_device)
*
* Returns the matching pci_device_id structure or %NULL if there is no match.
*/
static inline const struct pci_device_id *
-pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
+pci_match_one_id(const struct pci_device_id *id, const struct pci_device_id *dev_id)
{
- if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
- (id->device == PCI_ANY_ID || id->device == dev->device) &&
- (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
- (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
- !((id->class ^ dev->class) & id->class_mask))
+ if ((id->vendor == PCI_ANY_ID || id->vendor == dev_id->vendor) &&
+ (id->device == PCI_ANY_ID || id->device == dev_id->device) &&
+ (id->subvendor == PCI_ANY_ID || id->subvendor == dev_id->subvendor) &&
+ (id->subdevice == PCI_ANY_ID || id->subdevice == dev_id->subdevice) &&
+ !((id->class ^ dev_id->class) & id->class_mask))
return id;
return NULL;
}
diff --git a/drivers/pci/search.c b/drivers/pci/search.c
index e3d3177fce54..c8c4bfe7817b 100644
--- a/drivers/pci/search.c
+++ b/drivers/pci/search.c
@@ -245,8 +245,10 @@ static int match_pci_dev_by_id(struct device *dev, const void *data)
{
struct pci_dev *pdev = to_pci_dev(dev);
const struct pci_device_id *id = data;
+ struct pci_device_id dev_id;
- if (pci_match_one_device(id, pdev))
+ dev_id = pci_id_from_device(pdev);
+ if (pci_match_one_id(id, &dev_id))
return 1;
return 0;
}
@@ -418,7 +420,7 @@ EXPORT_SYMBOL(pci_get_class);
*
* Iterates through the list of known PCI devices. If a PCI device is found
* with a matching base class code, the reference count to the device is
- * incremented. See pci_match_one_device() to figure out how does this works.
+ * incremented. See pci_match_one_id() to figure out how does this works.
* A new search is initiated by passing %NULL as the @from argument.
* Otherwise if @from is not %NULL, searches continue from next device on the
* global list. The reference count for @from is always decremented if it is
--
2.54.0
^ permalink raw reply related
* [PATCH v3 3/9] ipack: tpci200: don't store pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
pci_device_id is not guaranteed to live longer than probe due to presence
of dynamic ID. This stored ID is unused so remove it.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/ipack/carriers/tpci200.c | 1 -
drivers/ipack/carriers/tpci200.h | 1 -
2 files changed, 2 deletions(-)
diff --git a/drivers/ipack/carriers/tpci200.c b/drivers/ipack/carriers/tpci200.c
index 05dcb6675cd6..1cf51f763293 100644
--- a/drivers/ipack/carriers/tpci200.c
+++ b/drivers/ipack/carriers/tpci200.c
@@ -562,7 +562,6 @@ static int tpci200_pci_probe(struct pci_dev *pdev,
/* Save struct pci_dev pointer */
tpci200->info->pdev = pdev;
- tpci200->info->id_table = (struct pci_device_id *)id;
/* register the device and initialize it */
ret = tpci200_install(tpci200);
diff --git a/drivers/ipack/carriers/tpci200.h b/drivers/ipack/carriers/tpci200.h
index e79ac64abcff..a2bf3125794b 100644
--- a/drivers/ipack/carriers/tpci200.h
+++ b/drivers/ipack/carriers/tpci200.h
@@ -145,7 +145,6 @@ struct tpci200_slot {
*/
struct tpci200_infos {
struct pci_dev *pdev;
- struct pci_device_id *id_table;
struct tpci200_regs __iomem *interface_regs;
void __iomem *cfg_regs;
struct ipack_bus_device *ipack_bus;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 4/9] mlxsw: don't store pci_device_id
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
pci_device_id is not guaranteed to live longer than probe due to presence
of dynamic ID. This stored ID is unused so remove it.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Reviewed-by: Petr Machata <petrm@nvidia.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/net/ethernet/mellanox/mlxsw/pci.c | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlxsw/pci.c b/drivers/net/ethernet/mellanox/mlxsw/pci.c
index 0da85d36647d..bfe3268dfdc1 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/pci.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/pci.c
@@ -130,7 +130,6 @@ struct mlxsw_pci {
} comp;
} cmd;
struct mlxsw_bus_info bus_info;
- const struct pci_device_id *id;
enum mlxsw_pci_cqe_v max_cqe_ver; /* Maximal supported CQE version */
u8 num_cqs; /* Number of CQs */
u8 num_sdqs; /* Number of SDQs */
@@ -1768,7 +1767,6 @@ static void mlxsw_pci_mbox_free(struct mlxsw_pci *mlxsw_pci,
}
static int mlxsw_pci_sys_ready_wait(struct mlxsw_pci *mlxsw_pci,
- const struct pci_device_id *id,
u32 *p_sys_status)
{
unsigned long end;
@@ -1839,7 +1837,7 @@ static int mlxsw_pci_reset_sw(struct mlxsw_pci *mlxsw_pci)
}
static int
-mlxsw_pci_reset(struct mlxsw_pci *mlxsw_pci, const struct pci_device_id *id)
+mlxsw_pci_reset(struct mlxsw_pci *mlxsw_pci)
{
struct pci_dev *pdev = mlxsw_pci->pdev;
bool pci_reset_sbr_supported = false;
@@ -1848,7 +1846,7 @@ mlxsw_pci_reset(struct mlxsw_pci *mlxsw_pci, const struct pci_device_id *id)
u32 sys_status;
int err;
- err = mlxsw_pci_sys_ready_wait(mlxsw_pci, id, &sys_status);
+ err = mlxsw_pci_sys_ready_wait(mlxsw_pci, &sys_status);
if (err) {
dev_err(&pdev->dev, "Failed to reach system ready status before reset. Status is 0x%x\n",
sys_status);
@@ -1880,7 +1878,7 @@ mlxsw_pci_reset(struct mlxsw_pci *mlxsw_pci, const struct pci_device_id *id)
if (err)
return err;
- err = mlxsw_pci_sys_ready_wait(mlxsw_pci, id, &sys_status);
+ err = mlxsw_pci_sys_ready_wait(mlxsw_pci, &sys_status);
if (err) {
dev_err(&pdev->dev, "Failed to reach system ready status after reset. Status is 0x%x\n",
sys_status);
@@ -1932,7 +1930,7 @@ static int mlxsw_pci_init(void *bus_priv, struct mlxsw_core *mlxsw_core,
if (!mbox)
return -ENOMEM;
- err = mlxsw_pci_reset(mlxsw_pci, mlxsw_pci->id);
+ err = mlxsw_pci_reset(mlxsw_pci);
if (err)
goto err_reset;
@@ -2464,7 +2462,6 @@ static int mlxsw_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
mlxsw_pci->bus_info.device_name = pci_name(mlxsw_pci->pdev);
mlxsw_pci->bus_info.dev = &pdev->dev;
mlxsw_pci->bus_info.read_clock_capable = true;
- mlxsw_pci->id = id;
err = mlxsw_core_bus_device_register(&mlxsw_pci->bus_info,
&mlxsw_pci_bus, mlxsw_pci, false,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 8/9] pci: fix dyn_id add TOCTOU
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
Currently there is a TOCTOU issue in new_id_store as the dyn ID insertion
in pci_add_dynid and the pci_match_device are in separate critical
sections.
Fix this by moving the existing ID check to inside pci_add_dynid and only
check against the static ID table outside the critical section.
Fixes: 3853f9123c18 ("PCI: Avoid duplicate IDs in driver dynamic IDs list")
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/pci/pci-driver.c | 139 ++++++++++++++++++++++++-----------------------
1 file changed, 71 insertions(+), 68 deletions(-)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 0507cb801310..2e80ae150ff4 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -29,6 +29,47 @@ struct pci_dynid {
struct pci_device_id id;
};
+/**
+ * do_pci_add_dynid - add a new PCI device ID to this driver and re-probe devices
+ * @drv: target pci driver
+ * @id: ID to be added
+ * @check_dup: whether to check if matching ID is already present
+ *
+ * Adds a new dynamic pci device ID to this driver and causes the
+ * driver to probe for all devices again. @drv must have been
+ * registered prior to calling this function.
+ *
+ * CONTEXT:
+ * Does GFP_KERNEL allocation.
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+static int do_pci_add_dynid(struct pci_driver *drv, const struct pci_device_id *id, bool check_dup)
+{
+ struct pci_dynid *dynid, *existing_dynid;
+
+ dynid = kzalloc_obj(*dynid);
+ if (!dynid)
+ return -ENOMEM;
+
+ dynid->id = *id;
+
+ scoped_guard(spinlock, &drv->dynids.lock) {
+ if (check_dup) {
+ list_for_each_entry(existing_dynid, &drv->dynids.list, node) {
+ if (pci_match_one_id(&existing_dynid->id, id)) {
+ kfree(dynid);
+ return -EEXIST;
+ }
+ }
+ }
+ list_add_tail(&dynid->node, &drv->dynids.list);
+ }
+
+ return driver_attach(&drv->driver);
+}
+
/**
* pci_add_dynid - add a new PCI device ID to this driver and re-probe devices
* @drv: target pci driver
@@ -56,25 +97,17 @@ int pci_add_dynid(struct pci_driver *drv,
unsigned int class, unsigned int class_mask,
unsigned long driver_data)
{
- struct pci_dynid *dynid;
+ struct pci_device_id id = {
+ .vendor = vendor,
+ .device = device,
+ .subvendor = subvendor,
+ .subdevice = subdevice,
+ .class = class,
+ .class_mask = class_mask,
+ .driver_data = driver_data,
+ };
- dynid = kzalloc_obj(*dynid);
- if (!dynid)
- return -ENOMEM;
-
- dynid->id.vendor = vendor;
- dynid->id.device = device;
- dynid->id.subvendor = subvendor;
- dynid->id.subdevice = subdevice;
- dynid->id.class = class;
- dynid->id.class_mask = class_mask;
- dynid->id.driver_data = driver_data;
-
- spin_lock(&drv->dynids.lock);
- list_add_tail(&dynid->node, &drv->dynids.list);
- spin_unlock(&drv->dynids.lock);
-
- return driver_attach(&drv->driver);
+ return do_pci_add_dynid(drv, &id, false);
}
EXPORT_SYMBOL_GPL(pci_add_dynid);
@@ -94,16 +127,19 @@ static void pci_free_dynids(struct pci_driver *drv)
* do_pci_match_id - See if a PCI ID matches a given pci_id table
* @ids: array of PCI device ID structures to search in
* @dev_id: the actual PCI device ID structure to match against.
+ * @include_override_only: also match against device ID entries marked as override only.
*
* Returns the matching pci_device_id structure or
* %NULL if there is no match.
*/
static const struct pci_device_id *do_pci_match_id(const struct pci_device_id *ids,
- const struct pci_device_id *dev_id)
+ const struct pci_device_id *dev_id,
+ bool include_override_only)
{
if (ids) {
while (ids->vendor || ids->subvendor || ids->class_mask) {
- if (pci_match_one_id(ids, dev_id))
+ if ((!ids->override_only || include_override_only) &&
+ pci_match_one_id(ids, dev_id))
return ids;
ids++;
}
@@ -128,7 +164,7 @@ const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
{
struct pci_device_id dev_id = pci_id_from_device(dev);
- return do_pci_match_id(ids, &dev_id);
+ return do_pci_match_id(ids, &dev_id, true);
}
EXPORT_SYMBOL(pci_match_id);
@@ -153,7 +189,7 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
struct pci_dev *dev)
{
struct pci_dynid *dynid;
- const struct pci_device_id *found_id = NULL, *ids;
+ const struct pci_device_id *found_id = NULL;
struct pci_device_id dev_id;
int ret;
@@ -176,20 +212,9 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
if (found_id)
return found_id;
- for (ids = drv->id_table; (found_id = do_pci_match_id(ids, &dev_id));
- ids = found_id + 1) {
- /*
- * The match table is split based on driver_override.
- * In case override_only was set, enforce driver_override
- * matching.
- */
- if (found_id->override_only) {
- if (ret > 0)
- return found_id;
- } else {
- return found_id;
- }
- }
+ found_id = do_pci_match_id(drv->id_table, &dev_id, ret > 0);
+ if (found_id)
+ return found_id;
/* driver_override will always match, send a dummy id */
if (ret > 0)
@@ -197,11 +222,6 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
return NULL;
}
-static void _pci_free_device(struct device *dev)
-{
- kfree(to_pci_dev(dev));
-}
-
/**
* new_id_store - sysfs frontend to pci_add_dynid()
* @driver: target device driver
@@ -215,38 +235,22 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
{
struct pci_driver *pdrv = to_pci_driver(driver);
const struct pci_device_id *ids = pdrv->id_table;
- u32 vendor, device, subvendor = PCI_ANY_ID,
- subdevice = PCI_ANY_ID, class = 0, class_mask = 0;
- unsigned long driver_data = 0;
+ struct pci_device_id id = {
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID
+ };
int fields;
int retval = 0;
fields = sscanf(buf, "%x %x %x %x %x %x %lx",
- &vendor, &device, &subvendor, &subdevice,
- &class, &class_mask, &driver_data);
+ &id.vendor, &id.device, &id.subvendor, &id.subdevice,
+ &id.class, &id.class_mask, &id.driver_data);
if (fields < 2)
return -EINVAL;
if (fields != 7) {
- struct pci_dev *pdev = kzalloc_obj(*pdev);
- if (!pdev)
- return -ENOMEM;
-
- pdev->vendor = vendor;
- pdev->device = device;
- pdev->subsystem_vendor = subvendor;
- pdev->subsystem_device = subdevice;
- pdev->class = class;
- pdev->dev.release = _pci_free_device;
-
- device_initialize(&pdev->dev);
- if (pci_match_device(pdrv, pdev))
- retval = -EEXIST;
-
- put_device(&pdev->dev);
-
- if (retval)
- return retval;
+ if (do_pci_match_id(pdrv->id_table, &id, false))
+ return -EEXIST;
}
/* Only accept driver_data values that match an existing id_table
@@ -254,7 +258,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
if (ids) {
retval = -EINVAL;
while (ids->vendor || ids->subvendor || ids->class_mask) {
- if (driver_data == ids->driver_data) {
+ if (id.driver_data == ids->driver_data) {
retval = 0;
break;
}
@@ -264,8 +268,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
return retval;
}
- retval = pci_add_dynid(pdrv, vendor, device, subvendor, subdevice,
- class, class_mask, driver_data);
+ retval = do_pci_add_dynid(pdrv, &id, fields != 7);
if (retval)
return retval;
return count;
--
2.54.0
^ permalink raw reply related
* [PATCH v3 9/9] pci: fix UAF when probe runs concurrent to dyn ID removal
From: Gary Guo @ 2026-07-06 14:11 UTC (permalink / raw)
To: Bjorn Helgaas, Zhenzhong Duan, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Damien Le Moal,
Niklas Cassel, GOTO Masanori, YOKOTA Hiroshi,
James E.J. Bottomley, Martin K. Petersen, Vaibhav Gupta,
Jens Taprogge, Ido Schimmel, Petr Machata, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Airlie
Cc: linux-pci, driver-core, linux-kernel, linux-ide, linux-scsi,
industrypack-devel, netdev, dri-devel, Sashiko, Gary Guo
In-Reply-To: <20260706-pci_id_fix-v3-0-2d48fc025acc@garyguo.net>
Dynamic IDs are only guaranteed to be valid when dynids.lock is held,
as remove_id_store can free the node. Thus, make a copy in
pci_match_device. Also, clarify that the id parameter is only valid during
probe.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/all/20260619170503.518F61F00A3A@smtp.kernel.org/
Fixes: 0994375e9614 ("PCI: add remove_id sysfs entry")
Signed-off-by: Gary Guo <gary@garyguo.net>
---
drivers/pci/pci-driver.c | 28 +++++++++++++++-------------
include/linux/pci.h | 1 +
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 2e80ae150ff4..4851061babcb 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -179,6 +179,7 @@ static const struct pci_device_id pci_device_id_any = {
* pci_match_device - See if a device matches a driver's list of IDs
* @drv: the PCI driver to match against
* @dev: the PCI device structure to match against
+ * @id_copy: Place to store copy of pci_device_id for dynamic ID
*
* Used by a driver to check whether a PCI device is in its list of
* supported devices or in the dynids list, which may have been augmented
@@ -186,9 +187,9 @@ static const struct pci_device_id pci_device_id_any = {
* structure or %NULL if there is no match.
*/
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
- struct pci_dev *dev)
+ struct pci_dev *dev,
+ struct pci_device_id *id_copy)
{
- struct pci_dynid *dynid;
const struct pci_device_id *found_id = NULL;
struct pci_device_id dev_id;
int ret;
@@ -200,17 +201,16 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
dev_id = pci_id_from_device(dev);
/* Look at the dynamic ids first, before the static ones */
- spin_lock(&drv->dynids.lock);
- list_for_each_entry(dynid, &drv->dynids.list, node) {
- if (pci_match_one_id(&dynid->id, &dev_id)) {
- found_id = &dynid->id;
- break;
+ scoped_guard(spinlock, &drv->dynids.lock) {
+ struct pci_dynid *dynid;
+
+ list_for_each_entry(dynid, &drv->dynids.list, node) {
+ if (pci_match_one_id(&dynid->id, &dev_id)) {
+ *id_copy = dynid->id;
+ return id_copy;
+ }
}
}
- spin_unlock(&drv->dynids.lock);
-
- if (found_id)
- return found_id;
found_id = do_pci_match_id(drv->id_table, &dev_id, ret > 0);
if (found_id)
@@ -466,12 +466,13 @@ void pci_probe_flush_workqueue(void)
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{
const struct pci_device_id *id;
+ struct pci_device_id id_copy;
int error = 0;
if (drv->probe) {
error = -ENODEV;
- id = pci_match_device(drv, pci_dev);
+ id = pci_match_device(drv, pci_dev, &id_copy);
if (id)
error = pci_call_probe(drv, pci_dev, id);
}
@@ -1559,12 +1560,13 @@ static int pci_bus_match(struct device *dev, const struct device_driver *drv)
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *pci_drv;
const struct pci_device_id *found_id;
+ struct pci_device_id id_copy;
if (pci_dev_binding_disallowed(pci_dev))
return 0;
pci_drv = (struct pci_driver *)to_pci_driver(drv);
- found_id = pci_match_device(pci_drv, pci_dev);
+ found_id = pci_match_device(pci_drv, pci_dev, &id_copy);
if (found_id)
return 1;
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 64b308b6e61c..92c17c116de6 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -979,6 +979,7 @@ struct module;
* function returns zero when the driver chooses to
* take "ownership" of the device or an error code
* (negative number) otherwise.
+ * The pci_device_id parameter is only valid during probe.
* The probe function always gets called from process
* context, so it can sleep.
* @remove: The remove() function gets called whenever a device
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v1 net-next] net: phy: Drop #inclusion of <linux/mod_devicetable.h> from <linux/mdio.h>
From: Andrew Lunn @ 2026-07-06 14:17 UTC (permalink / raw)
To: Uwe Kleine-König (The Capable Hub)
Cc: Heiner Kallweit, Russell King, netdev, linux-kernel
In-Reply-To: <ca270a534d0f230a939a3fb4a661808b35d6436d.1783329817.git.u.kleine-koenig@baylibre.com>
On Mon, Jul 06, 2026 at 11:29:19AM +0200, Uwe Kleine-König (The Capable Hub) wrote:
> <linux/mdio.h> itself doesn't use any of the device id structures. The
> files #including <linux/mdio.h> use a variety of them:
>
> $ git grep -l '<linux/mdio.h>' | xargs grep --color -E '\<(acpi_device_id|amba_id|ap_device_id|apr_device_id|auxiliary_device_id|bcma_device_id|ccw_device_id|cdx_device_id|coreboot_device_id|css_device_id|dfl_device_id|dmi_(device|system)_id|eisa_device_id|fsl_mc_device_id|hda_device_id|hid_device_id|hv_vmbus_device_id|i2c_device_id|i3c_device_id|ieee1394_device_id|input_device_id|ipack_device_id|isapnp_device_id|ishtp_device_id|mcb_device_id|mdio_device_id|mei_cl_device_id|mhi_device_id|mips_cdmm_device_id|of_device_id|parisc_device_id|pci_device_id|pci_epf_device_id|pcmcia_device_id|platform_device_id|pnp_(card_)?device_id|rio_device_id|rpmsg_device_id|sdio_device_id|sdw_device_id|serio_device_id|slim_device_id|spi_device_id|spmi_device_id|ssam_device_id|ssb_device_id|tb_service_id|tee_client_device_id|typec_device_id|ulpi_device_id|usb_device_id|vchiq_device_id|virtio_device_id|wmi_device_id|x86_(cpu|device)_id|zorro_device_id|cpu_feature)\>'
> ...
>
> but none of them relies on <linux/mdio.h>'s #include.
>
> <linux/mod_devicetable.h> is a bad header mixing many different device
> id structures and thus is an unfortunate dependency because if e.g.
> struct wmi_device_id is modified all users of <linux/mdio.h> need to be
> recompiled despite none of them using struct wmi_device_id.
>
> So drop the unused header.
While i agree that there are lots of unneeded dependencies here, does
this change actually solve anything? Pretty much every PHY driver
needs mdio_device_id, which is part of mod_devicetable.h. So it must
be getting included somehow.
I assume the long term plan is to move all the structure definitions
out of mod_devicetable.h, so a driver can include just the ones it
needs? Is this patch somehow a step along that path? Why not move
mdio_device_id into mod_devicetable_mdio.h and include that? It seems
like the more obvious fix.
Andrew
^ permalink raw reply
* Re: [PATCH nf] ipvs: skip IPv6 extension headers in SCTP state lookup
From: Julian Anastasov @ 2026-07-06 14:17 UTC (permalink / raw)
To: Yizhou Zhao
Cc: netdev, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, lvs-devel, netfilter-devel, coreteam, linux-kernel,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu, stable
In-Reply-To: <87E04893-B2B2-485F-B242-9CACF2F176D6@mails.tsinghua.edu.cn>
Hello,
On Mon, 6 Jul 2026, Yizhou Zhao wrote:
> > On Jul 6, 2026, at 01:35, Julian Anastasov <ja@ssi.bg> wrote:
> >
> > May be it is better starting from ip_vs_set_state()
> > to provide new arg 'int iph_len/offset' (set to iph.len), down to
> > state_transition(), sctp_state_transition() and set_sctp_state().
> > Same for all protos. It should cost less stack and ipv6_find_hdr()
> > calls and what matters most, correct iph context in case we
> > have IP+ICMP+TCP (with just two ports or even with TCP flags)
> > and are scheduling ICMP, i.e. not IP+TCP as usually.
>
> I agree that the already parsed transport-header offset should be
> passed from ip_vs_set_state() down to the protocol state_transition()
> callbacks, instead of reparsing the skb in set_sctp_state(). We will
> send a v2 that does this for SCTP, TCP and the other IPVS protocols
> in one combined fix.
>
> > But what I see is that ip_vs_in_icmp*() are missing
> > the ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd) call just
> > after ip_vs_in_stats() and before ip_vs_icmp_xmit() where
> > we should provide ciph.len. That is why we don't reach the
> > set_tcp_state() calls to set correct cp->state and timeout
> > when scheduling related ICMP. So, this should be fixed too.
>
> For the ICMP path, I agree that the missing ip_vs_set_state() call is
> worth looking at, but using ICMP errors to drive the upper L4 state
> needs some care, because spoofed ICMP packets can match an
> existing embedded tuple before the endpoint TCP/SCTP stack
> performs its own validation. Maybe this change needs further
> discussion?
May be only for the schedule_icmp case while the other
ICMP replies should not change it:
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 7f93239898ff..05fdcf4ce2c0 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -1971,6 +1971,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
+ if (new_cp)
+ ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, offset);
if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol ||
IPPROTO_SCTP == cih->protocol)
offset += 2 * sizeof(__u16);
Here is why schedule_icmp was added:
https://archive.linuxvirtualserver.org/html/lvs-devel/2015-08/msg00015.html
But the inner TCP header should be generated by the
real server, not from the client, so things can go wrong. We can
leave it as it is now - we will forward the ICMP to the right real
server by using short conn timeout...
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply related
* Re: [PATCH net-next] net: chelsio: cxgb4: Use str_plural() in mem_intr_handler()
From: Potnuri Bharat Teja @ 2026-07-06 14:10 UTC (permalink / raw)
To: Thorsten Blum
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, netdev@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260704121010.201016-3-thorsten.blum@linux.dev>
On Saturday, July 07/04/26, 2026 at 17:40:11 +0530, Thorsten Blum wrote:
> Replace the manual ternary "s" pluralization with str_plural() to
> simplify the code.
>
> Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
> ---
Thank you.
Reviewed-by: Potnuri Bharat Teja <bharat@chelsio.com>
> drivers/net/ethernet/chelsio/cxgb4/t4_hw.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
> index 6871127427fa..29d7b786e51e 100644
> --- a/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
> +++ b/drivers/net/ethernet/chelsio/cxgb4/t4_hw.c
> @@ -33,6 +33,7 @@
> */
>
> #include <linux/delay.h>
> +#include <linux/string_choices.h>
> #include "cxgb4.h"
> #include "t4_regs.h"
> #include "t4_values.h"
> @@ -4867,7 +4868,7 @@ static void mem_intr_handler(struct adapter *adapter, int idx)
> if (printk_ratelimit())
> dev_warn(adapter->pdev_dev,
> "%u %s correctable ECC data error%s\n",
> - cnt, name[idx], cnt > 1 ? "s" : "");
> + cnt, name[idx], str_plural(cnt));
> }
> if (v & ECC_UE_INT_CAUSE_F)
> dev_alert(adapter->pdev_dev,
^ permalink raw reply
* Re: [PATCH v2 net-next] net: neigh: avoid calling neigh_forced_gc on every alloc when table is full
From: Paolo Abeni @ 2026-07-06 14:19 UTC (permalink / raw)
To: Vimal Agrawal, netdev; +Cc: kuba, kuniyu, edumazet, vimal.agrawal
In-Reply-To: <20260706065813.66116-1-vimal.agrawal@sophos.com>
On 7/6/26 8:58 AM, Vimal Agrawal wrote:
> 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 50 ms, 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/
> Signed-off-by: Vimal Agrawal <vimal.agrawal@sophos.com>
This apparently breaks neigh self-tests:
# 29.38 [+5.12] TEST: IPv4 "extern_valid" flag: Forced garbage
collection [FAIL]
//...
# 85.91 [+5.11] TEST: IPv6 "extern_valid" flag: Forced garbage
collection [FAIL]
full log at:
https://netdev-ctrl.bots.linux.dev/logs/vmksft/net/results/722145/13-test-neigh-sh/
/P
^ permalink raw reply
* Re: [Security] Vulnerability in: Linux kernel, openvswitch zerocopy underflow
From: Aaron Conole @ 2026-07-06 14:21 UTC (permalink / raw)
To: 侯敏熙
Cc: netdev, Ilya Maximets, Kyle Zeng, Eelco Chaudron,
Outbound Disclosures, security
In-Reply-To: <CAJ0BgHeKoF7nnwA1_V0PSVETA+T9z9EX=OtSM4vMxQJv=nZAPg@mail.gmail.com>
侯敏熙 <houminxi@gmail.com> writes:
>> Just an FYI - this vulnerability is now public after Sashiko scanned
>> a proposed patch:
>
> Thanks for the heads-up, Aaron.
>
> My test_trunc() only exercises the non-GSO forwarding path (veth +
> ping), so neither the GSO segmentation underflow nor the POP_ETH +
> TRUNC combination is covered.
>
> Once a fix lands on net-next, I'll extend the TRUNC test to verify
> the new cutlen semantics.
No need. Let's keep it as is. We can look at doing complex 'fuzzing'
stuff in the future, but for now just giving a heads up.
> happy to help with testing if needed.
>
> Minxi
>
> Aaron Conole <aconole@redhat.com> 于2026年7月6日周一 08:05写道:
>
> Ilya Maximets <i.maximets@ovn.org> writes:
>
> > On 6/26/26 5:12 AM, Kyle Zeng wrote:
> >> Hi Ilya,
> >>
> >> I just tested the patch. It prevents underflow. However, it leads to a
> >> BUG_ON crash, the PoC and crash splash are attached.
> >>
> >> The patch changes `OVS_CB(skb)->cutlen` from "number of bytes to
> >> remove" to "number of bytes to preserve." That also changes the
> >> no-truncation sentinel from 0 to U32_MAX.
> >> Most of the action paths were updated to use U32_MAX, but the
> >> miss-upcall path was not. It still does `error = ovs_dp_upcall(dp,
> >> skb, key, &upcall, 0);` in `ovs_dp_process_packet()`.
> >
> > This is a good point, I missed that part.
>
> Just an FYI - this vulnerability is now public after Sashiko scanned a
> proposed patch:
>
> https://sashiko.dev/#/patchset/20260702074926.1174810-1-houminxi%40gmail.com
>
> > This is a pre-existing issue, but while looking at how TRUNC is handled,
> > there appears to be an integer underflow vulnerability...
>
> Minxi has been adding tests for different actions, and proposed a TRUNC
> action test. When reviewing it, I looked at the AI comments from the two
> different Sashiko instances and saw this. I've CC'd Minxi as well,
> since he proposed the selftest.
>
> Further discussion probably doesn't need to be on the security only
> list, and I think it is best if we go to the public mailing list.
>
> >>
> >> Under the new semantics, that 0 is no longer "no truncation" It means
> >> "preserve zero bytes". As a result, `queue_userspace_packet()`
> >> computes:
> >> `skb_len = min(skb->len, cutlen); /* becomes 0 */` and then: `hlen =
> >> skb_len; /* also becomes 0 */` before calling `skb_zerocopy(user_skb,
> >> skb, skb_len, hlen);`.
> >>
> >> I adapted your patch and the new patch is attached.
> >
> > See some comments below.
> >
> >> From 5b2b6f328b339a22c8a96bdacda4b62884640696 Mon Sep 17 00:00:00 2001
> >> From: Kyle Zeng <kylebot@openai.com>
> >> Date: Fri, 26 Jun 2026 02:48:14 +0000
> >> Subject: [PATCH] openvswitch: fix GSO userspace truncation underflow
> >>
> >> OVS_ACTION_ATTR_TRUNC currently stores a delta from the original skb
> >> length in OVS_CB(skb)->cutlen. When a later userspace action segments a
> >> GSO skb, queue_gso_packets() reuses that delta for each smaller segment.
> >> A segment can then reach queue_userspace_packet() with cutlen greater
> >> than skb->len, underflowing the length passed to skb_zerocopy().
> >>
> >> Store the maximum preserved length instead and derive the effective
> >> length from each current skb with min(). Use U32_MAX as the
> >> no-truncation sentinel so the value remains valid if skb geometry
> >> changes before a consumer handles it.
> >>
> >> Fixes: f2a4d086ed4c ("openvswitch: Add packet truncation support.")
> >> Cc: stable@vger.kernel.org
> >> Assisted-by: Codex:gpt-5.5
> >> Signed-off-by: Kyle Zeng <kylebot@openai.com>
> >> ---
> >> net/openvswitch/actions.c | 20 ++++++++------------
> >> net/openvswitch/datapath.c | 19 +++++++++++--------
> >> net/openvswitch/datapath.h | 3 ++-
> >> net/openvswitch/vport.c | 2 +-
> >> 4 files changed, 22 insertions(+), 22 deletions(-)
> >>
> >> diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
> >> index 140388a18ae0..596ad1be8b6b 100644
> >> --- a/net/openvswitch/actions.c
> >> +++ b/net/openvswitch/actions.c
> >> @@ -837,12 +837,9 @@ static void do_output(struct datapath *dp, struct sk_buff
> *skb, int out_port,
> >> u16 mru = OVS_CB(skb)->mru;
> >> u32 cutlen = OVS_CB(skb)->cutlen;
> >>
> >> - if (unlikely(cutlen > 0)) {
> >> - if (skb->len - cutlen > ovs_mac_header_len(key))
> >> - pskb_trim(skb, skb->len - cutlen);
> >> - else
> >> - pskb_trim(skb, ovs_mac_header_len(key));
> >> - }
> >> + if (unlikely(cutlen != U32_MAX))
> >
> > There is no need to trim if cutlen >= skb->len. Is there some problem with
> > the approach I had in my diff:
> >
> > + if (unlikely(cutlen < skb->len))
> > + pskb_trim(skb, max(cutlen, ovs_mac_header_len(key)));
> >
> > ?
> >
> >> + pskb_trim(skb, max(min(skb->len, cutlen),
> >> + ovs_mac_header_len(key)));
> >>
> >> if (likely(!mru ||
> >> (skb->len <= mru + vport->dev->hard_header_len))) {
> >> @@ -1234,7 +1231,7 @@ static void execute_psample(struct datapath *dp, struct
> sk_buff *skb,
> >>
> >> psample_group.net⚠️ = ovs_dp_get_net(dp);
> >> md.in_ifindex = OVS_CB(skb)->input_vport->dev->ifindex;
> >> - md.trunc_size = skb->len - OVS_CB(skb)->cutlen;
> >> + md.trunc_size = min(skb->len, OVS_CB(skb)->cutlen);
> >> md.rate_as_probability = 1;
> >>
> >> rate = OVS_CB(skb)->probability ? OVS_CB(skb)->probability : U32_MAX;
> >> @@ -1284,22 +1281,21 @@ static int do_execute_actions(struct datapath *dp, struct
> sk_buff *skb,
> >> clone = skb_clone(skb, GFP_ATOMIC);
> >> if (clone)
> >> do_output(dp, clone, port, key);
> >> - OVS_CB(skb)->cutlen = 0;
> >> + OVS_CB(skb)->cutlen = U32_MAX;
> >> break;
> >> }
> >>
> >> case OVS_ACTION_ATTR_TRUNC: {
> >> struct ovs_action_trunc *trunc = nla_data(a);
> >>
> >> - if (skb->len > trunc->max_len)
> >> - OVS_CB(skb)->cutlen = skb->len - trunc->max_len;
> >> + OVS_CB(skb)->cutlen = trunc->max_len;
> >> break;
> >> }
> >>
> >> case OVS_ACTION_ATTR_USERSPACE:
> >> output_userspace(dp, skb, key, a, attr,
> >> len, OVS_CB(skb)->cutlen);
> >> - OVS_CB(skb)->cutlen = 0;
> >> + OVS_CB(skb)->cutlen = U32_MAX;
> >> if (nla_is_last(a, rem)) {
> >> consume_skb(skb);
> >> return 0;
> >> @@ -1453,7 +1449,7 @@ static int do_execute_actions(struct datapath *dp, struct
> sk_buff *skb,
> >>
> >> case OVS_ACTION_ATTR_PSAMPLE:
> >> execute_psample(dp, skb, a);
> >> - OVS_CB(skb)->cutlen = 0;
> >> + OVS_CB(skb)->cutlen = U32_MAX;
> >> if (nla_is_last(a, rem)) {
> >> consume_skb(skb);
> >> return 0;
> >> diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
> >> index f0164817d9b7..66c621a611b7 100644
> >> --- a/net/openvswitch/datapath.c
> >> +++ b/net/openvswitch/datapath.c
> >> @@ -276,7 +276,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct
> sw_flow_key *key)
> >> upcall.portid = ovs_vport_find_upcall_portid(p, skb);
> >>
> >> upcall.mru = OVS_CB(skb)->mru;
> >> - error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
> >> + error = ovs_dp_upcall(dp, skb, key, &upcall, U32_MAX);
> >> switch (error) {
> >> case 0:
> >> case -EAGAIN:
> >> @@ -458,6 +458,7 @@ static int queue_userspace_packet(struct datapath *dp, struct
> sk_buff *skb,
> >> struct sk_buff *user_skb = NULL; /* to be queued to userspace */
> >> struct nlattr *nla;
> >> size_t len;
> >
> > I would still rename this into msg_size. Having 'len' and 'skb_len'
> > in the same scope is confusing.
> >
> >> + unsigned int skb_len;
> >> unsigned int hlen;
> >> int err, dp_ifindex;
> >> u64 hash;
> >> @@ -478,7 +479,8 @@ static int queue_userspace_packet(struct datapath *dp, struct
> sk_buff *skb,
> >> skb = nskb;
> >> }
> >>
> >> - if (nla_attr_size(skb->len) > USHRT_MAX) {
> >> + skb_len = min(skb->len, cutlen);
> >> + if (nla_attr_size(skb_len) > USHRT_MAX) {
> >> err = -EFBIG;
> >> goto out;
> >> }
> >> @@ -493,11 +495,11 @@ static int queue_userspace_packet(struct datapath *dp,
> struct sk_buff *skb,
> >> * padding logic. Only perform zerocopy if padding is not required.
> >> */
> >> if (dp->user_features & OVS_DP_F_UNALIGNED)
> >> - hlen = skb_zerocopy_headlen(skb);
> >> + hlen = min(skb_zerocopy_headlen(skb), skb_len);
> >> else
> >> - hlen = skb->len;
> >> + hlen = skb_len;
> >>
> >> - len = upcall_msg_size(upcall_info, hlen - cutlen,
> >> + len = upcall_msg_size(upcall_info, hlen,
> >> OVS_CB(skb)->acts_origlen);
> >> user_skb = genlmsg_new(len, GFP_ATOMIC);
> >> if (!user_skb) {
> >> @@ -560,7 +562,7 @@ static int queue_userspace_packet(struct datapath *dp, struct
> sk_buff *skb,
> >> }
> >>
> >> /* Add OVS_PACKET_ATTR_LEN when packet is truncated */
> >> - if (cutlen > 0 &&
> >> + if (cutlen != U32_MAX &&
> >
> > This changes the logic. The attribute should be included only if the
> > packet was actually truncated, i.e., if skb_len < skb->len.
> >
> >> nla_put_u32(user_skb, OVS_PACKET_ATTR_LEN, skb->len)) {
> >> err = -ENOBUFS;
> >> goto out;
> >> @@ -585,9 +587,9 @@ static int queue_userspace_packet(struct datapath *dp, struct
> sk_buff *skb,
> >> err = -ENOBUFS;
> >> goto out;
> >> }
> >> - nla->nla_len = nla_attr_size(skb->len - cutlen);
> >> + nla->nla_len = nla_attr_size(skb_len);
> >>
> >> - err = skb_zerocopy(user_skb, skb, skb->len - cutlen, hlen);
> >> + err = skb_zerocopy(user_skb, skb, skb_len, hlen);
> >> if (err)
> >> goto out;
> >>
> >> @@ -644,6 +646,7 @@ static int ovs_packet_cmd_execute(struct sk_buff *skb, struct
> genl_info *info)
> >> packet->ignore_df = 1;
> >> }
> >> OVS_CB(packet)->mru = mru;
> >> + OVS_CB(packet)->cutlen = U32_MAX;
> >>
> >> if (a[OVS_PACKET_ATTR_HASH]) {
> >> hash = nla_get_u64(a[OVS_PACKET_ATTR_HASH]);
> >> diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
> >> index db0c3e69d66c..11fa104b6172 100644
> >> --- a/net/openvswitch/datapath.h
> >> +++ b/net/openvswitch/datapath.h
> >> @@ -118,7 +118,8 @@ struct datapath {
> >> * @mru: The maximum received fragement size; 0 if the packet is not
> >> * fragmented.
> >> * @acts_origlen: The netlink size of the flow actions applied to this skb.
> >> - * @cutlen: The number of bytes from the packet end to be removed.
> >> + * @cutlen: The maximum number of bytes to preserve on output. U32_MAX means
> >> + * no truncation.
> >> * @probability: The sampling probability that was applied to this skb; 0 means
> >> * no sampling has occurred; U32_MAX means 100% probability.
> >> * @upcall_pid: Netlink socket PID to use for sending this packet to userspace;
> >> diff --git a/net/openvswitch/vport.c b/net/openvswitch/vport.c
> >> index 56b2e2d1a749..12741485c939 100644
> >> --- a/net/openvswitch/vport.c
> >> +++ b/net/openvswitch/vport.c
> >> @@ -502,7 +502,7 @@ int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
> >>
> >> OVS_CB(skb)->input_vport = vport;
> >> OVS_CB(skb)->mru = 0;
> >> - OVS_CB(skb)->cutlen = 0;
> >> + OVS_CB(skb)->cutlen = U32_MAX;
> >> OVS_CB(skb)->probability = 0;
> >> OVS_CB(skb)->upcall_pid = 0;
> >> if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
> >> --
> >> 2.54.0
> >>
^ permalink raw reply
* Re: [PATCH v3 02/20] driver core: platform: provide platform_device_set_of_node()
From: Manuel Ebner @ 2026-07-06 14:39 UTC (permalink / raw)
To: Bartosz Golaszewski, Lee Jones, Thierry Reding,
Sebastian Hesselbarth, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Srinivas Kandagatla,
Greg Kroah-Hartman, Vinod Koul, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Saravana Kannan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andi Shyti, Andy Shevchenko,
Joerg Roedel, Will Deacon, Robin Murphy, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Matthew Brost, Thomas Hellström, Rodrigo Vivi,
David Airlie, Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu,
Philipp Zabel, Maximilian Luz, Hans de Goede, Ilpo Järvinen,
Krzysztof Kozlowski, Benjamin Herrenschmidt
Cc: brgl, linux-kernel, netdev, linux-arm-msm, linux-sound,
driver-core, devicetree, linuxppc-dev, linux-i2c, iommu, linux-pm,
imx, linux-arm-kernel, intel-xe, dri-devel, linux-usb, linux-mips,
platform-driver-x86, mfd
In-Reply-To: <20260706-pdev-fwnode-ref-v3-2-1ff028e33779@oss.qualcomm.com>
Hi Bartosz,
On Mon, 2026-07-06 at 14:44 +0200, Bartosz Golaszewski wrote:
> Encapsulate the reference counting logic for OF nodes assigned to
> platform devices created with platform_device_alloc() in a helper
> function. Make the kerneldoc state that this is the proper interface for
> assigning OF nodes to dynamically allocated platform devices. This will
> allow us to switch to counting the references of the device's firmware
> nodes, not only the OF nodes.
>
> Reviewed-by: Manuel Ebner <manuelebner@mailbox.org>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> ---
> drivers/base/platform.c | 18 ++++++++++++++++++
> include/linux/platform_device.h | 4 ++++
> 2 files changed, 22 insertions(+)
>
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index fb9120b0bcfe0e7dd9dfc0d29b91e0ad40a01440..3188d5aba5f90622f821c695049cacda030204fb
> 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -693,6 +693,24 @@ int platform_device_add_data(struct platform_device *pdev, const
> void *data,
> }
> EXPORT_SYMBOL_GPL(platform_device_add_data);
>
> +/**
> + * platform_device_set_of_node - assign an OF node to device
> + * @pdev: platform device to add the node for
> + * @np: new device node
> + *
> + * Assign an OF node to this platform device. Internally keep track of the
> + * reference count. Devices created with platform_device_alloc() must use this
> + * function instead of assigning the node manually.
I did some more pondering about this patch and concluded the right place
for a warning would be the lines been read right before or after assigning
a node manually.
I think this would be in platform_device_alloc() (as I suggested in v2).
BUT my knowledge in C isn't sound. So if it's good as is keep it. If it isn't
add or move the remark.
Either way Thanks and
Reviewed-by Manuel Ebner.
---
Note:
I removed Mark Brown <broonie@opensource.wolfsonmicro.com> from recipients because:
“RCPT TO <broonie@opensource.wolfsonmicro.com> failed:
<broonie@opensource.wolfsonmicro.com>: Recipient address rejected: Domain not found”.
> + */
> +void platform_device_set_of_node(struct platform_device *pdev,
> + struct device_node *np)
> +{
> + of_node_put(pdev->dev.of_node);
> + pdev->dev.of_node = of_node_get(np);
> + pdev->dev.fwnode = of_fwnode_handle(np);
> +}
> +EXPORT_SYMBOL_GPL(platform_device_set_of_node);
> +
> /**
> * platform_device_add - add a platform device to device hierarchy
> * @pdev: platform device we're adding
> diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h
> index 8c566f09d04efe420d85ffa046f92c44c6d08526..e9f7baceeb4c8269dbc0143c4d8fc9d73ba024ca
> 100644
> --- a/include/linux/platform_device.h
> +++ b/include/linux/platform_device.h
> @@ -19,6 +19,8 @@
> struct irq_affinity;
> struct mfd_cell;
> struct property_entry;
> +struct platform_device_id;
> +struct device_node;
>
> struct platform_device {
> const char *name;
> @@ -262,6 +264,8 @@ extern int platform_device_add_resources(struct platform_device
> *pdev,
> unsigned int num);
> extern int platform_device_add_data(struct platform_device *pdev,
> const void *data, size_t size);
> +void platform_device_set_of_node(struct platform_device *pdev,
> + struct device_node *np);
> extern int platform_device_add(struct platform_device *pdev);
> extern void platform_device_del(struct platform_device *pdev);
> extern void platform_device_put(struct platform_device *pdev);
^ permalink raw reply
* [PATCH net v2] ice: propagate ETH56G deskew read errors
From: Pengpeng Hou @ 2026-07-06 14:43 UTC (permalink / raw)
To: Tony Nguyen, Przemek Kitszel
Cc: Pengpeng Hou, Jedrzej Jagielski, Aleksandr Loktionov, Andrew Lunn,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Richard Cochran, Jacob Keller, Arkadiusz Kubalewski,
Karol Kolacinski, Sergey Temerkhanov, Michal Michalik,
intel-wired-lan, netdev, linux-kernel
ice_ptp_calc_deskew_eth56g() returns a u32 deskew value, but it also
returns the negative read_poll_timeout() error when the DESKEW valid bit
never appears. That converts the negative error into a large unsigned
deskew contribution, which can then be folded into the RX timestamp
offset and programmed into hardware.
Return the deskew value through an output parameter and propagate the
read error from ice_phy_set_offsets_eth56g() instead of using it as
offset data.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
Changes since v1:
- add the Fixes tag requested by Jedrzej and Aleksandr
- clarify that @deskew is an output parameter
- target the patch at net as suggested by Aleksandr
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 28 +++++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 8e5f97835954..cc424518a2a6 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -1736,17 +1736,21 @@ static u32 ice_ptp_calc_bitslip_eth56g(struct ice_hw *hw, u8 port, u32 bs,
* @ds: deskew multiplier
* @rs: RS-FEC enabled
* @spd: link speed
+ * @deskew: output parameter for the calculated deskew value
*
- * Return: calculated deskew value
+ * Return: 0 on success, negative error code otherwise
*/
-static u32 ice_ptp_calc_deskew_eth56g(struct ice_hw *hw, u8 port, u32 ds,
- bool rs, enum ice_eth56g_link_spd spd)
+static int ice_ptp_calc_deskew_eth56g(struct ice_hw *hw, u8 port, u32 ds,
+ bool rs, enum ice_eth56g_link_spd spd,
+ u32 *deskew)
{
u32 deskew_i, deskew_f;
int err;
- if (!ds)
+ if (!ds) {
+ *deskew = 0;
return 0;
+ }
read_poll_timeout(ice_read_ptp_reg_eth56g, err,
FIELD_GET(PHY_REG_DESKEW_0_VALID, deskew_i), 500,
@@ -1766,7 +1770,9 @@ static u32 ice_ptp_calc_deskew_eth56g(struct ice_hw *hw, u8 port, u32 ds,
deskew_i = FIELD_PREP(ICE_ETH56G_MAC_CFG_RX_OFFSET_INT, deskew_i);
/* Shift 3 fractional bits to the end of the integer part */
deskew_f <<= ICE_ETH56G_MAC_CFG_FRAC_W - PHY_REG_DESKEW_0_RLEVEL_FRAC_W;
- return mul_u32_u32_fx_q9(deskew_i | deskew_f, ds);
+ *deskew = mul_u32_u32_fx_q9(deskew_i | deskew_f, ds);
+
+ return 0;
}
/**
@@ -1789,6 +1795,7 @@ static int ice_phy_set_offsets_eth56g(struct ice_hw *hw, u8 port,
{
u32 rx_offset, tx_offset, bs_ds;
bool onestep, sfd;
+ int err;
onestep = hw->ptp.phy.eth56g.onestep_ena;
sfd = hw->ptp.phy.eth56g.sfd_ena;
@@ -1805,11 +1812,16 @@ static int ice_phy_set_offsets_eth56g(struct ice_hw *hw, u8 port,
if (sfd)
rx_offset = add_u32_u32_fx(rx_offset, cfg->rx_offset.sfd);
- if (spd < ICE_ETH56G_LNK_SPD_40G)
+ if (spd < ICE_ETH56G_LNK_SPD_40G) {
bs_ds = ice_ptp_calc_bitslip_eth56g(hw, port, bs_ds, fc, rs,
spd);
- else
- bs_ds = ice_ptp_calc_deskew_eth56g(hw, port, bs_ds, rs, spd);
+ } else {
+ err = ice_ptp_calc_deskew_eth56g(hw, port, bs_ds, rs, spd,
+ &bs_ds);
+ if (err)
+ return err;
+ }
+
rx_offset = add_u32_u32_fx(rx_offset, bs_ds);
rx_offset &= ICE_ETH56G_MAC_CFG_RX_OFFSET_INT |
ICE_ETH56G_MAC_CFG_RX_OFFSET_FRAC;
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v3 02/20] driver core: platform: provide platform_device_set_of_node()
From: Manuel Ebner @ 2026-07-06 14:48 UTC (permalink / raw)
To: Bartosz Golaszewski, Lee Jones, Thierry Reding,
Sebastian Hesselbarth, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Srinivas Kandagatla,
Greg Kroah-Hartman, Vinod Koul, Rafael J. Wysocki,
Danilo Krummrich, Rob Herring, Saravana Kannan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andi Shyti, Andy Shevchenko,
Joerg Roedel, Will Deacon, Robin Murphy, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Matthew Brost, Thomas Hellström, Rodrigo Vivi,
David Airlie, Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu,
Philipp Zabel, Maximilian Luz, Hans de Goede, Ilpo Järvinen,
Krzysztof Kozlowski, Benjamin Herrenschmidt
Cc: brgl, linux-kernel, netdev, linux-arm-msm, linux-sound,
driver-core, devicetree, linuxppc-dev, linux-i2c, iommu, linux-pm,
imx, linux-arm-kernel, intel-xe, dri-devel, linux-usb, linux-mips,
platform-driver-x86, mfd
In-Reply-To: <fbc50f89e0c3bb148656a3b8d96974f591576dec.camel@mailbox.org>
On Mon, 2026-07-06 at 16:39 +0200, Manuel Ebner wrote:
> Hi Bartosz,
>
> On Mon, 2026-07-06 at 14:44 +0200, Bartosz Golaszewski wrote:
> >
> > @@ -693,6 +693,24 @@ int platform_device_add_data(struct platform_device *pdev, const
Nevermind, I got confused because of @@ int platform_device_add_data() and
> @@ -619,6 +619,13 @@ static void platform_device_release_full(struct device *dev)
in [PATCH v3 05/20] driver core: update kerneldoc for platform_device_alloc()
Sorry for the noise
Manuel
^ permalink raw reply
* Re: [PATCH net-next v3] selftests/net/openvswitch: add output truncation test
From: Aaron Conole @ 2026-07-06 14:49 UTC (permalink / raw)
To: Minxi Hou
Cc: netdev, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
horms, shuah, dev, linux-kselftest, kylebot
In-Reply-To: <20260702074926.1174810-1-houminxi@gmail.com>
Minxi Hou <houminxi@gmail.com> writes:
> Add test_trunc exercising the OVS_ACTION_ATTR_TRUNC action. The test
> verifies truncation limits in four steps: reject trunc(1) and
> trunc(13) which are below ETH_HLEN, confirm normal forwarding works,
> apply trunc(14) which truncates packets to the Ethernet header and
> verify ping fails, then restore normal forwarding and verify recovery.
>
> The kernel requires max_len >= ETH_HLEN (14 bytes). trunc(14) sets
> OVS_CB(skb)->cutlen so pskb_trim strips the IP payload at output
> time; the receiver drops the runt frame and no echo reply is
> generated.
>
> Signed-off-by: Minxi Hou <houminxi@gmail.com>
> ---
Reviewed-by: Aaron Conole <aconole@redhat.com>
NB: There is a note from Sashiko on this about a pre-existing issue with
trunc() action. That issue was discussed a bit offline, and I think
someone/someprocess is going to submit a patch upstream "soon." That
said, as with all actions related security issues in OVS, they require
giving a process CAP_NET_ADMIN, which is quite a powerful capability and
generally not advised for the average user.
^ permalink raw reply
* Re: [PATCH v3 09/20] iommu/fsl: use platform_device_set_of_node()
From: Frank Li @ 2026-07-06 14:51 UTC (permalink / raw)
To: Bartosz Golaszewski
Cc: Lee Jones, Mark Brown, Thierry Reding, Sebastian Hesselbarth,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Srinivas Kandagatla, Greg Kroah-Hartman, Vinod Koul,
Rafael J. Wysocki, Danilo Krummrich, Rob Herring, Saravana Kannan,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Andi Shyti, Andy Shevchenko,
Joerg Roedel, Will Deacon, Robin Murphy, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Ulf Hansson, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Matthew Brost, Thomas Hellström, Rodrigo Vivi,
David Airlie, Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu,
Philipp Zabel, Maximilian Luz, Hans de Goede, Ilpo Järvinen,
Krzysztof Kozlowski, Benjamin Herrenschmidt, brgl, linux-kernel,
netdev, linux-arm-msm, linux-sound, driver-core, devicetree,
linuxppc-dev, linux-i2c, iommu, linux-pm, imx, linux-arm-kernel,
intel-xe, dri-devel, linux-usb, linux-mips, platform-driver-x86,
mfd
In-Reply-To: <20260706-pdev-fwnode-ref-v3-9-1ff028e33779@oss.qualcomm.com>
On Mon, Jul 06, 2026 at 02:44:21PM +0200, Bartosz Golaszewski wrote:
> Ahead of reworking the reference counting logic for platform devices,
> encapsulate the assignment of the OF node for dynamically allocated
> platform devices with the provided helper.
>
> Acked-by: Robin Murphy <robin.murphy@arm.com>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/iommu/fsl_pamu.c | 16 ++++++----------
> 1 file changed, 6 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/iommu/fsl_pamu.c b/drivers/iommu/fsl_pamu.c
> index 25aa477a95a95cb4fa4e132727cde0a936750ee2..c83bbc3faad56d6ee1c89b0a7f74028af02c81e9 100644
> --- a/drivers/iommu/fsl_pamu.c
> +++ b/drivers/iommu/fsl_pamu.c
> @@ -8,6 +8,7 @@
>
> #include "fsl_pamu.h"
>
> +#include <linux/cleanup.h>
> #include <linux/fsl/guts.h>
> #include <linux/interrupt.h>
> #include <linux/genalloc.h>
> @@ -933,7 +934,6 @@ static struct platform_driver fsl_of_pamu_driver = {
> static __init int fsl_pamu_init(void)
> {
> struct platform_device *pdev = NULL;
> - struct device_node *np;
> int ret;
>
> /*
> @@ -955,7 +955,8 @@ static __init int fsl_pamu_init(void)
> * PAMU node would require significant changes to a lot of code.
> */
>
> - np = of_find_compatible_node(NULL, NULL, "fsl,pamu");
> + struct device_node *np __free(device_node) =
> + of_find_compatible_node(NULL, NULL, "fsl,pamu");
> if (!np) {
> pr_err("could not find a PAMU node\n");
> return -ENODEV;
> @@ -964,7 +965,7 @@ static __init int fsl_pamu_init(void)
> ret = platform_driver_register(&fsl_of_pamu_driver);
> if (ret) {
> pr_err("could not register driver (err=%i)\n", ret);
> - goto error_driver_register;
> + return ret;
> }
>
> pdev = platform_device_alloc("fsl-of-pamu", 0);
> @@ -973,7 +974,8 @@ static __init int fsl_pamu_init(void)
> ret = -ENOMEM;
> goto error_device_alloc;
> }
> - pdev->dev.of_node = of_node_get(np);
> +
> + platform_device_set_of_node(pdev, np);
>
> ret = pamu_domain_init();
> if (ret)
> @@ -988,17 +990,11 @@ static __init int fsl_pamu_init(void)
> return 0;
>
> error_device_add:
> - of_node_put(pdev->dev.of_node);
> - pdev->dev.of_node = NULL;
> -
> platform_device_put(pdev);
>
> error_device_alloc:
> platform_driver_unregister(&fsl_of_pamu_driver);
>
> -error_driver_register:
> - of_node_put(np);
> -
> return ret;
> }
> arch_initcall(fsl_pamu_init);
>
> --
> 2.47.3
>
>
^ permalink raw reply
* [GIT PULL] bluetooth 2026-07-06
From: Luiz Augusto von Dentz @ 2026-07-06 14:52 UTC (permalink / raw)
To: davem, kuba; +Cc: linux-bluetooth, netdev
The following changes since commit 9e05e91a9a847ed57926414bd7c2c5e54d6c56c6:
amt: fix size calculation in amt_get_size() (2026-07-06 13:01:24 +0200)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth.git tags/for-net-2026-07-06
for you to fetch changes up to 6e1930ece855a4c256f1c7e6632d634cfb9888b5:
Bluetooth: L2CAP: fix tx ident leak for commands without a response (2026-07-06 10:46:58 -0400)
----------------------------------------------------------------
bluetooth pull request for net:
- hci_conn: Fix null ptr deref in hci_abort_conn()
- af_bluetooth: fix UAF in bt_accept_dequeue()
- L2CAP: validate option length before reading conf opt value
- L2CAP: cancel pending_rx_work before taking conn->lock
- L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
- L2CAP: fix tx ident leak for commands without a response
- SCO: Fix a race condition in sco_sock_timeout()
- ISO: avoid NULL deref of conn in iso_conn_big_sync()
- ISO: fix malformed ISO_END/CONT handling
- ISO: exclude RFU bits from ISO_SDU_Length
- MGMT: Fix adv monitor add failure cleanup
- MGMT: Fix UAF of hci_conn_params in add_device_complete
- bnep: pin L2CAP connection during netdev registration
- 6lowpan: avoid untracked enable work
- 6lowpan: hold L2CAP conn across debugfs control
- 6lowpan: Fix using chan->conn as indication to no remote netdev
- btintel_pcie: Refactor FLR to use device_reprobe()
- btnxpuart: Fix out-of-bounds firmware read in nxp_recv_fw_req_v3()
- hci_uart: clear HCI_UART_SENDING when write_work is canceled
- bpa10x: avoid OOB read of revision string in bpa10x_setup()
----------------------------------------------------------------
Cen Zhang (3):
Bluetooth: 6lowpan: avoid untracked enable work
Bluetooth: 6lowpan: hold L2CAP conn across debugfs control
Bluetooth: MGMT: Fix adv monitor add failure cleanup
Kiran K (1):
Bluetooth: btintel_pcie: Refactor FLR to use device_reprobe()
Luiz Augusto von Dentz (1):
Bluetooth: 6lowpan: Fix using chan->conn as indication to no remote netdev
Maoyi Xie (1):
Bluetooth: btnxpuart: Fix out-of-bounds firmware read in nxp_recv_fw_req_v3()
Muhammad Bilal (2):
Bluetooth: ISO: avoid NULL deref of conn in iso_conn_big_sync()
Bluetooth: L2CAP: validate option length before reading conf opt value
Pauli Virtanen (3):
Bluetooth: hci_uart: clear HCI_UART_SENDING when write_work is canceled
Bluetooth: ISO: fix malformed ISO_END/CONT handling
Bluetooth: ISO: exclude RFU bits from ISO_SDU_Length
Runyu Xiao (1):
Bluetooth: L2CAP: cancel pending_rx_work before taking conn->lock
Samuel Page (1):
Bluetooth: MGMT: Fix UAF of hci_conn_params in add_device_complete
Siwei Zhang (2):
Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
Stig Hornang (1):
Bluetooth: L2CAP: fix tx ident leak for commands without a response
Sungwoo Kim (1):
Bluetooth: sco: Fix a race condition in sco_sock_timeout()
Weiming Shi (1):
Bluetooth: bpa10x: avoid OOB read of revision string in bpa10x_setup()
Yousef Alhouseen (2):
Bluetooth: bnep: pin L2CAP connection during netdev registration
Bluetooth: fix UAF in bt_accept_dequeue()
drivers/bluetooth/bpa10x.c | 8 ++-
drivers/bluetooth/btintel_pcie.c | 102 ++++++++++++++--------------
drivers/bluetooth/btnxpuart.c | 6 ++
drivers/bluetooth/hci_ldisc.c | 14 ++--
include/net/bluetooth/hci.h | 5 +-
include/net/bluetooth/hci_core.h | 1 +
include/net/bluetooth/l2cap.h | 10 +--
net/bluetooth/6lowpan.c | 84 +++++++----------------
net/bluetooth/af_bluetooth.c | 17 +----
net/bluetooth/bnep/core.c | 27 ++++++--
net/bluetooth/hci_conn.c | 21 +-----
net/bluetooth/hci_sync.c | 133 +++++++++++++++++++++++++++++++++----
net/bluetooth/iso.c | 44 +++++++-----
net/bluetooth/l2cap_core.c | 140 ++++++++++++++++++++++++++++++---------
net/bluetooth/l2cap_sock.c | 107 ++++++++++++++----------------
net/bluetooth/mgmt.c | 5 ++
net/bluetooth/msft.c | 2 +-
net/bluetooth/sco.c | 15 ++++-
net/bluetooth/smp.c | 27 ++------
19 files changed, 467 insertions(+), 301 deletions(-)
^ permalink raw reply
* Re: [PATCH v2 3/5] net: phy: motorcomm: Enable optional clock for YT8531
From: Andrew Lunn @ 2026-07-06 14:56 UTC (permalink / raw)
To: Yanan He
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Wu, Maxime Coquelin, Alexandre Torgue, Frank,
Heiner Kallweit, Russell King, devicetree, linux-kernel,
linux-arm-kernel, linux-rockchip, netdev, linux-stm32
In-Reply-To: <20260706-rv1126-alientek-dlrv1126-v2-3-ff3176ca362b@gmail.com>
On Mon, Jul 06, 2026 at 05:14:43PM +0800, Yanan He wrote:
> Some boards feed the YT8531 PHY from an SoC-provided external
> reference clock described by the common ethernet-phy "clocks" property.
>
> Enable the optional PHY clock during probe so boards can model this
> clock as a PHY input instead of keeping the clock alive from the MAC
> driver.
>
> This is needed on the Alientek DLRV1126, where the PHY reference clock
> is provided by CLK_GMAC_ETHERNET_OUT.
>
> Signed-off-by: Yanan He <grumpycat921013@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply
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