From f0ea4824deebf21dacfbe397c4b0a5fd3e745a8d Mon Sep 17 00:00:00 2001 From: Marco Chen Date: Sat, 8 Aug 2026 15:54:50 -0400 Subject: [PATCH 001/535] iio: health: max30102: fix NULL dereference in interrupt handler The interrupt is requested in max30102_probe() and stays enabled for the lifetime of the device, but indio_dev->active_scan_mask is only valid while a buffer is enabled. When an interrupt arrives while no buffer is enabled, the handler dereferences the NULL active_scan_mask: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 Call trace: __bitmap_weight+0x64/0x98 (P) max30102_interrupt_handler+0x48/0x160 [max30102] Call max30102_fifo_count() at the top of the handler and return early unless it reports a FIFO sample is ready. Because FIFO_RDY is the only interrupt source enabled in max30102_chip_init(), an invocation of max30102_interrupt_handler() without the FIFO_RDY interrupt status bit set carries no data to read and can return before touching active_scan_mask. A negative return from max30102_fifo_count() indicates a failed interrupt status read and is treated the same way. Fixes: 90579b69e94b ("iio: health: max30102: Add MAX30105 support") Suggested-by: Jonathan Cameron Signed-off-by: Marco Chen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30102.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index c37316c86f14..aee96167f01e 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -290,9 +290,15 @@ static irqreturn_t max30102_interrupt_handler(int irq, void *private) { struct iio_dev *indio_dev = private; struct max30102_data *data = iio_priv(indio_dev); - unsigned int measurements = bitmap_weight(indio_dev->active_scan_mask, - iio_get_masklength(indio_dev)); - int ret, cnt = 0; + unsigned int measurements; + int ret, cnt; + + cnt = max30102_fifo_count(data); + if (cnt <= 0) + return IRQ_HANDLED; + + measurements = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); mutex_lock(&data->lock); From 56c423b233b214182c58c99861899327b45b0d6c Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 11 Aug 2026 10:52:53 +0800 Subject: [PATCH 002/535] iio: pressure: bmp280: fix out-of-bounds access in sampling frequency lookup The sampling frequency tables store each frequency as an integer part and a fractional part in micro units. num_sampling_freq_avail is initialized to the number of flattened integer elements because read_avail() returns the table as a flat array. bmp280_write_sampling_frequency(), however, indexes the same table as a two-dimensional array and uses num_sampling_freq_avail as the number of rows. Convert the flattened element count back to the number of rows before iterating over the table. Fixes: 10b40ffba2f9 ("iio: pressure: bmp280: Add more tunable config parameters for BMP380") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/bmp280-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index 1066792b9087..3940b8e61a06 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -839,7 +839,7 @@ static int bmp280_write_sampling_frequency(struct bmp280_data *data, int val, int val2) { const int (*avail)[2] = data->chip_info->sampling_freq_avail; - const int n = data->chip_info->num_sampling_freq_avail; + const int n = data->chip_info->num_sampling_freq_avail / 2; int ret, prev; int i; From 63213927b141085b6c8719b8037dab3074b7e0fe Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 6 Aug 2026 13:29:23 +0000 Subject: [PATCH 003/535] iio: light: gp2ap020a00f: drain irq_work after free_irq The threaded IRQ handler queues data->work through irq_work_queue() so the trigger is polled from a per-CPU context. free_irq() does not flush an irq_work the handler already queued, so after gp2ap020a00f_remove() returns that work may still run and call iio_trigger_poll() on data->trig, which the devm cleanup has already freed, causing a use-after-free. Add irq_work_sync(&data->work) after free_irq() in remove() and in the probe error path, mirroring commit 78601726d4a5 ("iio: trigger: sysfs: fix use-after-free on remove"). Found by an in-house static analysis tool, confirmed by manual review. Fixes: bf29fbeaa13d ("iio: gp2ap020a00f: Add a driver for the device") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Jonathan Cameron --- drivers/iio/light/gp2ap020a00f.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c index 63591b7ecb89..b435dc5e8706 100644 --- a/drivers/iio/light/gp2ap020a00f.c +++ b/drivers/iio/light/gp2ap020a00f.c @@ -1497,6 +1497,7 @@ static int gp2ap020a00f_probe(struct i2c_client *client) iio_trigger_unregister(data->trig); error_free_irq: free_irq(client->irq, indio_dev); + irq_work_sync(&data->work); error_uninit_buffer: iio_triggered_buffer_cleanup(indio_dev); error_regulator_disable: @@ -1519,6 +1520,7 @@ static void gp2ap020a00f_remove(struct i2c_client *client) iio_device_unregister(indio_dev); iio_trigger_unregister(data->trig); free_irq(client->irq, indio_dev); + irq_work_sync(&data->work); iio_triggered_buffer_cleanup(indio_dev); regulator_disable(data->vled_reg); } From 549b71ce050637dd32678371ad76c445a2f8c9aa Mon Sep 17 00:00:00 2001 From: Janani Sunil Date: Thu, 13 Aug 2026 15:56:56 +0200 Subject: [PATCH 004/535] iio: adc: adi-axi-adc: Initialize state mutex The AXI ADC register access paths serialize transactions with st->lock, but probe does not initialize it. Initialize the mutex before registering the backend. Fixes: 7ecb8ee5c93b ("iio: adc: adi-axi-adc: support digital interface calibration") Signed-off-by: Janani Sunil Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/adi-axi-adc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 589f03618ecf..fc7cc92b35f0 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -714,6 +714,10 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (!st) return -ENOMEM; + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); From 88fda1ed51a579eb51980436abc6557006a520a3 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 7 Aug 2026 11:56:41 +0300 Subject: [PATCH 005/535] iio: adc: ade9000: request interrupts after powering the device The IRQ handlers do SPI register accesses, but the interrupts were requested before the vdd regulator was enabled. An interrupt arriving while the chip is unpowered runs a handler against a dead chip, causing SPI errors or garbage reads. Request the interrupts after enabling the regulator. This keeps irq1 registered before ade9000_reset(), which waits on it, and lets devm free the interrupts before the regulator is disabled. Fixes: 81de7b4619fc ("iio: adc: add ade9000 support") Signed-off-by: Antoniu Miclaus Reviewed-by: Joshua Crofts Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index c6c3ea953fea..51cb43755a1f 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -1705,18 +1705,6 @@ static int ade9000_probe(struct spi_device *spi) if (ret) return ret; - ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); - if (ret) - return ret; - - ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); - if (ret) - return ret; - - ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); - if (ret) - return ret; - /* External CMOS clock input (optional - crystal can be used instead) */ st->clkin = devm_clk_get_optional_enabled(dev, NULL); if (IS_ERR(st->clkin)) @@ -1736,6 +1724,19 @@ static int ade9000_probe(struct spi_device *spi) return dev_err_probe(&spi->dev, ret, "Failed to get and enable vdd regulator\n"); + /* Request IRQs only after powering the chip; the handlers do SPI access. */ + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + indio_dev->channels = ade9000_channels; indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); From d01f63f673e18f3a99ce8d6fbf1a5f58b70990f7 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 7 Aug 2026 11:56:42 +0300 Subject: [PATCH 006/535] iio: adc: ade9000: fix overlapping scan_index for current and voltage channels Current channels used scan_index "num" and voltage "num + 1", so with phases 0, 1 and 2 the indices overlapped and no longer matched the IA=0, VA=1, IB=2, VB=3, IC=4, VC=5 layout expected by ade9000_waveform_buffer_config(). Phase B and C buffers were configured incorrectly. Use "num * 2" for current and "num * 2 + 1" for voltage to get the non-overlapping 0/1, 2/3, 4/5 layout. Fixes: 81de7b4619fc ("iio: adc: add ade9000 support") Signed-off-by: Antoniu Miclaus Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 51cb43755a1f..167b1f4d6279 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -444,7 +444,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { BIT(IIO_CHAN_INFO_CALIBSCALE), \ .event_spec = ade9000_current_events, \ .num_event_specs = ARRAY_SIZE(ade9000_current_events), \ - .scan_index = num, \ + .scan_index = num * 2, \ .indexed = 1, \ .scan_type = { \ .sign = 's', \ @@ -464,7 +464,7 @@ static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { BIT(IIO_CHAN_INFO_FREQUENCY), \ .event_spec = ade9000_voltage_events, \ .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \ - .scan_index = num + 1, /* interleave with current channels */ \ + .scan_index = num * 2 + 1, /* interleave with current channels */ \ .indexed = 1, \ .scan_type = { \ .sign = 's', \ From fafefa925ed7ec10383d9bf0378abf878b7f13cb Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Fri, 7 Aug 2026 11:56:43 +0300 Subject: [PATCH 007/535] iio: adc: ade9000: handle Phase C dip events in IRQ1 handler ADE9000_ST1_CROSSING_DEPTH is the exclusive upper bound for scanning the STATUS1 event bits, so a value of 25 left out the highest event bit, ADE9000_ST1_DIPC_BIT (bit 25). Phase C dip events were never reported. Bump the value to 26 so bit 25 is scanned. Fixes: 81de7b4619fc ("iio: adc: add ade9000 support") Signed-off-by: Antoniu Miclaus Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 167b1f4d6279..da6caabfe2a4 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -219,7 +219,7 @@ #define ADE9000_ST1_ERROR2_BIT BIT(30) #define ADE9000_ST1_ERROR3_BIT BIT(31) #define ADE9000_ST1_CROSSING_FIRST 6 -#define ADE9000_ST1_CROSSING_DEPTH 25 +#define ADE9000_ST1_CROSSING_DEPTH 26 #define ADE9000_WFB_TRG_DIP_BIT BIT(0) #define ADE9000_WFB_TRG_SWELL_BIT BIT(1) From 91908c0a78baddc8572aa3ab69e62ee2eb496ba4 Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Mon, 10 Aug 2026 23:44:46 +0700 Subject: [PATCH 008/535] iio: adc: max1363: sign-extend bipolar differential channel reads MAX1363 differential channels are bipolar, but max1363_read_single_chan() masks the raw value to the ADC resolution without sign-extending it. Negative differential readings are therefore reported to userspace as large positive values (e.g. -1 as 4095 on a 12-bit part). Sign-extend the masked value from the resolution bit for differential channels. Single-ended channels are unipolar and are left unchanged. Fixes: d1325cf45077 ("Staging: IIO: max1363 ADC driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/max1363.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 65a2d92bb112..629c53af6472 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -404,6 +404,14 @@ static int max1363_read_single_chan(struct iio_dev *indio_dev, data = rxbuf[0]; } + + /* + * Differential channels are bipolar and the device returns the sample + * in two's complement, so sign-extend it from the resolution bit. + */ + if (chan->differential) + data = sign_extend32(data, st->chip_info->bits - 1); + *val = data; return 0; From 0d3f6b5e639f0bca5c1466ec523a94ec4f9f4c24 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:49:56 +0300 Subject: [PATCH 009/535] iio: adc: rohm-bd79124: Fix rising alarm The rising alarm is using the cached falling alarm value causing wrong value to be used. Fix this by using the correct cached value for rising alarm. Fixes: 3f57a3b9ab74 ("iio: adc: Support ROHM BD79124 ADC") Signed-off-by: Matti Vaittinen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79124.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index ed5427288961..eab5286763a7 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -524,7 +524,7 @@ static int bd79124_enable_event(struct bd79124_data *data, return ret; if (dir == IIO_EV_DIR_RISING) { - limit = &data->alarm_f_limit[channel]; + limit = &data->alarm_r_limit[channel]; reg = BD79124_GET_HIGH_LIMIT_REG(channel); } else { limit = &data->alarm_f_limit[channel]; From edd56c01d174153bf90160279742ff6f3ad27a93 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:50:21 +0300 Subject: [PATCH 010/535] iio: adc: rohm-bd79124: Fix channel initialization The alarm limit register on BD79124 span to two consecutive registers. The first register for high-limit containing also hysteresis configuration. The initialization in driver writes only one 8-bit register, overwriting the hysteresis and leaving the other limit register uninitialized. Fix the limit initialization by using the bd79124_write_int_to_reg(), which correctly initializes the limit on both of the registers. Fixes: 3f57a3b9ab74 ("iio: adc: Support ROHM BD79124 ADC") Signed-off-by: Matti Vaittinen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79124.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index eab5286763a7..2c031a5e9eb0 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -919,13 +919,13 @@ static int bd79124_chan_init(struct bd79124_data *data, int channel) { int ret; - ret = regmap_write(data->map, BD79124_GET_HIGH_LIMIT_REG(channel), - BD79124_HIGH_LIMIT_MAX); + ret = bd79124_write_int_to_reg(data, BD79124_GET_HIGH_LIMIT_REG(channel), + BD79124_HIGH_LIMIT_MAX); if (ret) return ret; - return regmap_write(data->map, BD79124_GET_LOW_LIMIT_REG(channel), - BD79124_LOW_LIMIT_MIN); + return bd79124_write_int_to_reg(data, BD79124_GET_LOW_LIMIT_REG(channel), + BD79124_LOW_LIMIT_MIN); } static int bd79124_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) From d081c116a16489f7808d35fcf1ee9d4b9869d204 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:50:49 +0300 Subject: [PATCH 011/535] iio: adc: rohm-bd79124: Fix GPIO mask check The ROHM BD79124 has pins which can be configured as GPOs or as ADC inputs. The bd79124gpo_set_multiple() is intended to ensure that a pin which is requested to be toggled, is indeed configured as a GPO. The check uses XOR, causing it to fail if mask is not exactly same as GPO configuration. Eg, if not all GPOs are asked to be toggled at once. Fix this by checking that mask does not contain pins that are not configured as GPO, allowing some of the pins which are configured as GPO to be untouched. Fixes: 3f57a3b9ab74 ("iio: adc: Support ROHM BD79124 ADC") Signed-off-by: Matti Vaittinen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79124.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index 2c031a5e9eb0..f5b3096f553f 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -200,7 +200,7 @@ static int bd79124gpo_set_multiple(struct gpio_chip *gc, unsigned long *mask, if (ret) return ret; - if (all_gpos ^ *mask) { + if (*mask & ~all_gpos) { dev_dbg(data->dev, "Invalid mux config. Can't set value.\n"); return -EINVAL; From 196cc715dce3576a54065f5cb07185c75778f9a8 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:51:20 +0300 Subject: [PATCH 012/535] iio: adc: rohm-bd79124: Catch regmap errors at measurement start/stop The bd79124_start_measurement() and bd79124_stop_measurement() ignore errors from the regmap reads, causing potential use of uninitialized stack variable when deciding whether the measurement is already started/stopped. The bd79124_stop_measurement() may also ignore failure to clear the sequencer state bits, which may make the hardware to ignore the setting and leave hardware and driver states out of sync. Check the return value and bail-out if error is detected. Fixes: 3f57a3b9ab74 ("iio: adc: Support ROHM BD79124 ADC") Signed-off-by: Matti Vaittinen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79124.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index f5b3096f553f..8091a138cca8 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -381,6 +381,9 @@ static int bd79124_start_measurement(struct bd79124_data *data, int chan) /* See if already started */ ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &val); + if (ret) + return ret; + if (val & BIT(chan)) return 0; @@ -420,11 +423,16 @@ static int bd79124_stop_measurement(struct bd79124_data *data, int chan) /* See if already stopped */ ret = regmap_read(data->map, BD79124_REG_AUTO_CHANNELS, &enabled_chans); + if (ret) + return ret; + if (!(enabled_chans & BIT(chan))) return 0; ret = regmap_clear_bits(data->map, BD79124_REG_SEQ_CFG, BD79124_MSK_SEQ_START); + if (ret) + return ret; /* Clear the channel from the measured channels */ enabled_chans &= ~BIT(chan); From 55671fbc3d12ae1f381842433567fcb7ff96a298 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:51:38 +0300 Subject: [PATCH 013/535] iio: dac: rohm-bd79703: Do not allow writing SCALE The BD79703 has adds IIO_CHAN_INFO_SCALE in the info_mask_shared_by_type so users can read the scale, which depends on the used reference voltage. This, however, enables users to try writing the scale as well. This isn't really supported but the bd79703_write_raw() does not check the mask, and if written scale values pass the validation, the driver will proceed writing the DAC value when users writes the scale. Prevent the unsupported scale setting and return an error. Fixes: af6aca656a85 ("iio: dac: Support ROHM BD79703 DAC") Signed-off-by: Matti Vaittinen Signed-off-by: Jonathan Cameron --- drivers/iio/dac/rohm-bd79703.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/dac/rohm-bd79703.c b/drivers/iio/dac/rohm-bd79703.c index 6e2e7787f8a4..127ccc2c4a22 100644 --- a/drivers/iio/dac/rohm-bd79703.c +++ b/drivers/iio/dac/rohm-bd79703.c @@ -73,6 +73,9 @@ static int bd79703_write_raw(struct iio_dev *idev, { struct bd79703_data *data = iio_priv(idev); + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + if (val < 0 || val >= 1 << BD79703_DAC_BITS) return -EINVAL; From b0e951ffc5a28141a106562997ca136ec6a0e1a3 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:52:47 +0300 Subject: [PATCH 014/535] iio: pressure: rohm-bm1390: Return error when read fails The data reading function ignores the cached error value, and unconditionally returns 0. Return cached 'ret' -value after stopping the measurement so user knows if read failed and data is garbage. Fixes: 534674463a59 ("iio: bm1390: simplify using guard(mutex)") Signed-off-by: Matti Vaittinen Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/rohm-bm1390.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/pressure/rohm-bm1390.c b/drivers/iio/pressure/rohm-bm1390.c index 57941fb4a535..f9deb48e6a41 100644 --- a/drivers/iio/pressure/rohm-bm1390.c +++ b/drivers/iio/pressure/rohm-bm1390.c @@ -289,7 +289,7 @@ static int bm1390_read_data(struct bm1390_data *data, if (warn) dev_warn(data->dev, "Failed to stop measurement (%d)\n", warn); - return 0; + return ret; } static int bm1390_read_raw(struct iio_dev *idev, From 4e5b0b7c5d95814b6d76911a91572a0691925ca7 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:53:35 +0300 Subject: [PATCH 015/535] iio: light: rohm-bu27034: Fix error return When BU27034 fails to read gain value at integration-time setting, it returns 0. This leaves caller unaware of the fact that setting the integration time failed. Fix this by returning the relevant error instead of zero. Fixes: 439c2cef8157 ("iio: bu27034: simplify using guard(mutex)") Signed-off-by: Matti Vaittinen Signed-off-by: Jonathan Cameron --- drivers/iio/light/rohm-bu27034.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c index 28d111ac8c0a..f9a421618406 100644 --- a/drivers/iio/light/rohm-bu27034.c +++ b/drivers/iio/light/rohm-bu27034.c @@ -415,7 +415,7 @@ static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) for (i = 0; i < numg; i++) { ret = bu27034_get_gain(data, gains[i].chan, &gains[i].old_gain); if (ret) - return 0; + return ret; ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts, gains[i].old_gain, From 0e65412aee4f651d2c9785f542a13e4b44002a75 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 10 Aug 2026 10:54:32 +0300 Subject: [PATCH 016/535] iio: accel: kionix-kx022a: Fix array boundary check The driver performs a sanity check for an array size, when converting a register value to an array index. The check incorrectly accepts the sizeof(array) as a last index, when last valid index should be sizeof(array) - 1. Fix the check by bailing out when index >= sizeof(array). Fixes: 7c1d1677b322 ("iio: accel: Support Kionix/ROHM KX022A accelerometer") Signed-off-by: Matti Vaittinen Reviewed-by: Mehdi Djait Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index 02dd1db7a646..f671fab3dc31 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -649,7 +649,7 @@ static int kx022a_read_raw(struct iio_dev *idev, if (ret) return ret; - if ((regval & KX022A_MASK_ODR) > + if ((regval & KX022A_MASK_ODR) >= ARRAY_SIZE(kx022a_accel_samp_freq_table)) { dev_err(data->dev, "Invalid ODR\n"); return -EINVAL; From fbf33cb18334e7cab1ed4ad2e9a98a1299ad874c Mon Sep 17 00:00:00 2001 From: Zhang Jie Date: Tue, 18 Aug 2026 15:28:03 +0800 Subject: [PATCH 017/535] iio: proximity: sx9324: Correct proximity channel resolution The proximity channels were previously defined with 12 realbits. However, PROXDIFF is read from RegDiffMsb (0x65) and RegDiffLsb (0x66). The SX9324 datasheet assigns bits 7:0 of each register to PROXDIFF and documents it as a signed two's-complement value (Revision 3, Section 8, Table 8, page 43). In contrast, RegOffsetMsb explicitly marks bits 7:6 as reserved. Thus, PROXDIFF is a 16-bit signed value. With realbits = 12, sx_common_read_proximity() uses bit 11 as the sign bit in sign_extend32(), causing samples outside the 12-bit signed range to wrap into the [-2048, 2047] range. Correct the realbits value to 16 to accurately reflect the hardware. Tested on an SX9324-based device: a phase 0 DIFF readback of 0x7fff was reported as -1 before this change and as 32767 afterward. Fixes: 4c18a890dff8 ("iio:proximity:sx9324: Add SX9324 support") Cc: stable@vger.kernel.org Signed-off-by: Zhang Jie Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/sx9324.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/proximity/sx9324.c b/drivers/iio/proximity/sx9324.c index 13b4ef2896e3..255a36976ac4 100644 --- a/drivers/iio/proximity/sx9324.c +++ b/drivers/iio/proximity/sx9324.c @@ -222,7 +222,7 @@ static const struct iio_chan_spec_ext_info sx9324_channel_ext_info[] = { .scan_index = idx, \ .scan_type = { \ .sign = 's', \ - .realbits = 12, \ + .realbits = 16, \ .storagebits = 16, \ .endianness = IIO_BE, \ }, \ From e787fd23d5063c61582c3c27522240b6e6703fde Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 20 Aug 2026 11:19:52 +0200 Subject: [PATCH 018/535] iio: frequency: adf4377: Fully initialize clk_init_data and clk_parent_data The clk_init_data structure contains several mutually-exclusive members for different methods to specify the possible parents of a clock, prompting drivers to initialize only the members they need. However, not initializing all members may cause subtle issues, which are only exposed when CONFIG_INIT_STACK_ALL_PATTERN or CONFIG_INIT_STACK_NONE is enabled. adf4377_clk_register() fills in init.parent_data, and assumes that init.parent_names is NULL. However, the latter is uninitialized, and thus may cause a crash. Similarly, adf4377_clk_register() fills in only parent_data.fw_name, leaving other members of the clk_parent_data structure uninitialized. Make sure all members are fully initialized, to fix such bugs, and to avoid future breakage when converting drivers to a different method for specifying the parents. Fixes: 60e5448ddbec2dc2 ("iio: frequency: adf4377: add clk provider support") Signed-off-by: Geert Uytterhoeven Reviewed-by: Brian Masney Reviewed-by: Joshua Crofts Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/adf4377.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 4dd19a9aa994..7385be62fa6a 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -988,10 +988,10 @@ static const struct clk_ops adf4377_clk_ops = { static int adf4377_clk_register(struct adf4377_state *st) { + struct clk_parent_data parent_data = { }; struct spi_device *spi = st->spi; struct device *dev = &spi->dev; - struct clk_init_data init; - struct clk_parent_data parent_data; + struct clk_init_data init = { }; int ret; if (!device_property_present(dev, "#clock-cells")) From 1a7c522a35477d70320bd64aba8e9de0c1058c6f Mon Sep 17 00:00:00 2001 From: Laxman Acharya Padhya Date: Sun, 2 Aug 2026 01:22:00 +0545 Subject: [PATCH 019/535] iio: proximity: aw96103: validate firmware data length The firmware parser reads a length from the binary header and then uses it to walk six-byte register/value entries. A truncated firmware image can make the parser read beyond the copied firmware buffer. A value smaller than the four-byte count field also underflows, producing a very large length. Read the little-endian field with get_unaligned_le32(), as previously proposed by David Lechner. Validate the header size, reject an underflowed length, ensure the resulting payload is contained in the firmware image, and require it to contain whole register/value entries. Fixes: 07b241262dca ("iio: proximity: aw96103: Add support for aw96103/aw96105 proximity sensor") Link: https://lore.kernel.org/r/20260314-iio-proximity-aw96103-fix-firmware-read-v1-1-c74fe9ccd82b@baylibre.com Cc: stable@vger.kernel.org Signed-off-by: Laxman Acharya Padhya Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/aw96103.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/drivers/iio/proximity/aw96103.c b/drivers/iio/proximity/aw96103.c index 8fbb755dcae0..8352d51e5128 100644 --- a/drivers/iio/proximity/aw96103.c +++ b/drivers/iio/proximity/aw96103.c @@ -24,6 +24,7 @@ #define AW96103_BIN_VALID_DATA_OFFSET 64 #define AW96103_BIN_DATA_LEN_OFFSET 16 #define AW96103_BIN_DATA_REG_NUM_SIZE 4 +#define AW96103_BIN_REG_SIZE 6 #define AW96103_BIN_CHIP_TYPE_SIZE 8 #define AW96103_BIN_CHIP_TYPE_OFFSET 24 @@ -229,14 +230,27 @@ static const struct aw_chip_info aw_chip_info_tbl[] = { }, }; -static void aw96103_parsing_bin_file(struct aw_bin *bin) +static int aw96103_parsing_bin_file(struct aw_bin *bin) { + u32 data_len; + + if (bin->len < AW96103_BIN_VALID_DATA_OFFSET) + return -EINVAL; + + data_len = get_unaligned_le32(bin->data + AW96103_BIN_DATA_LEN_OFFSET); + if (data_len < AW96103_BIN_DATA_REG_NUM_SIZE) + return -EINVAL; + + bin->valid_data_len = data_len - AW96103_BIN_DATA_REG_NUM_SIZE; + if (bin->valid_data_len > bin->len - AW96103_BIN_VALID_DATA_OFFSET || + bin->valid_data_len % AW96103_BIN_REG_SIZE) + return -EINVAL; + bin->valid_data_addr = AW96103_BIN_VALID_DATA_OFFSET; - bin->valid_data_len = - *(unsigned int *)(bin->data + AW96103_BIN_DATA_LEN_OFFSET) - - AW96103_BIN_DATA_REG_NUM_SIZE; memcpy(bin->chip_type, bin->data + AW96103_BIN_CHIP_TYPE_OFFSET, AW96103_BIN_CHIP_TYPE_SIZE); + + return 0; } static const struct regmap_config aw96103_regmap_confg = { @@ -500,7 +514,7 @@ static int aw96103_bin_valid_loaded(struct aw96103 *aw96103, int ret; for (i = 0; i < aw_bin_data_s->valid_data_len; - i += 6, start_addr += 6) { + i += AW96103_BIN_REG_SIZE, start_addr += AW96103_BIN_REG_SIZE) { reg_addr = get_unaligned_le16(aw_bin_data_s->data + start_addr); reg_data = get_unaligned_le32(aw_bin_data_s->data + start_addr + 2); @@ -550,6 +564,8 @@ static int aw96103_para_loaded(struct aw96103 *aw96103) static int aw96103_cfg_all_loaded(const struct firmware *cont, struct aw96103 *aw96103) { + int ret; + if (!cont) return -EINVAL; @@ -561,7 +577,9 @@ static int aw96103_cfg_all_loaded(const struct firmware *cont, aw_bin->len = cont->size; memcpy(aw_bin->data, cont->data, cont->size); release_firmware(cont); - aw96103_parsing_bin_file(aw_bin); + ret = aw96103_parsing_bin_file(aw_bin); + if (ret) + return ret; return aw96103_bin_valid_loaded(aw96103, aw_bin); } From 5330036bc68d23672bfcc16c60c54701ae123083 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Mon, 3 Aug 2026 02:22:24 +0100 Subject: [PATCH 020/535] iio: buffer: Ensure bounce buffer used for unaligned case is zeroed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iio_push_to_buffers_with_ts_unaligned() leaks uninitialized heap memory to userspace if the data passed in is not a multiple of 8 bytes and the timestamp is enabled. Specify __GFP_ZERO for the devm_krealloc() to ensure any extra space is cleared. Fixes: 95ec3fdf2b79 ("iio: core: Introduce iio_push_to_buffers_with_ts_unaligned()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260529121005.1470-1-kimjinseob88%40gmail.com Reviewed-by: Andy Shevchenko Reviewed-by: Joshua Crofts Reviewed-by: Nuno Sá Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 2c9ec93dff47..902401be0b67 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -2462,7 +2462,8 @@ int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev, bb = devm_krealloc(&indio_dev->dev, iio_dev_opaque->bounce_buffer, - indio_dev->scan_bytes, GFP_KERNEL); + indio_dev->scan_bytes, + GFP_KERNEL | __GFP_ZERO); if (!bb) return -ENOMEM; iio_dev_opaque->bounce_buffer = bb; From 0f3f427076240e75ff265e61645627a71ca4918c Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 3 Aug 2026 08:03:43 +0000 Subject: [PATCH 021/535] iio: adc: xilinx-xadc: free IRQ before cancelling the unmask worker on unbind The ZYNQ XADC alarm IRQ handler queues zynq_unmask_work via schedule_delayed_work(); that work is cleared by a devm callback. Register the devm callback before devm_request_irq() so the devres LIFO teardown frees the IRQ first, ensuring the handler can no longer queue work by the time the workqueue is cleared. Otherwise the handler could re-arm the work and run it after the xadc structure has been freed. This issue was found by an in-house static analysis tool. Fixes: 2a9685d1a3b7 ("iio: adc: xilinx: use more devres helpers and remove remove()") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Reviewed-by: David Lechner Signed-off-by: Fan Wu Reviewed-by: Sai Krishna Potthuri Signed-off-by: Jonathan Cameron --- drivers/iio/adc/xilinx-xadc-core.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index cab66bb8cc1c..66202b3a3abb 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -1401,13 +1401,14 @@ static int xadc_probe(struct platform_device *pdev) } if (irq > 0) { - ret = devm_request_irq(dev, irq, xadc->ops->interrupt_handler, - 0, dev_name(dev), indio_dev); + /* Clear out any work queued by the interrupt */ + ret = devm_add_action_or_reset(dev, xadc_cancel_delayed_work, + &xadc->zynq_unmask_work); if (ret) return ret; - ret = devm_add_action_or_reset(dev, xadc_cancel_delayed_work, - &xadc->zynq_unmask_work); + ret = devm_request_irq(dev, irq, xadc->ops->interrupt_handler, + 0, dev_name(dev), indio_dev); if (ret) return ret; } From e1dea54496f90a62318cf4eeaa1e9ef8316e92b6 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Sun, 16 Aug 2026 03:27:24 +0900 Subject: [PATCH 022/535] iio: imu: adis16480: fix unprotected debugfs reads The firmware_revision and firmware_date file operations are open coded and never call debugfs_file_get(), which debugfs_create_file_unsafe() requires. debugfs_remove_recursive() therefore does not wait for a read in progress, and unbind frees the iio_dev underneath it. Use debugfs_create_file() instead. Fixes: 9bf94f836e32 ("imu:adis16480: fix debugfs_simple_attr.cocci warnings") Signed-off-by: Arka Mondal Reviewed-by: Andy Shevchenko Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/imu/adis16480.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c index 5187566c1876..d51786874e74 100644 --- a/drivers/iio/imu/adis16480.c +++ b/drivers/iio/imu/adis16480.c @@ -309,9 +309,9 @@ static void adis16480_debugfs_init(struct iio_dev *indio_dev) if (!IS_ENABLED(CONFIG_DEBUG_FS)) return; - debugfs_create_file_unsafe("firmware_revision", 0400, + debugfs_create_file("firmware_revision", 0400, d, adis16480, &adis16480_firmware_revision_fops); - debugfs_create_file_unsafe("firmware_date", 0400, + debugfs_create_file("firmware_date", 0400, d, adis16480, &adis16480_firmware_date_fops); debugfs_create_file_unsafe("serial_number", 0400, d, adis16480, &adis16480_serial_number_fops); From d94787287a113a1326211647d5e0deed1e9de105 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Sun, 16 Aug 2026 03:27:25 +0900 Subject: [PATCH 023/535] iio: imu: adis16400: fix unprotected debugfs reads adis16400_serial_number_fops is open coded and never calls debugfs_file_get(), so debugfs_remove_recursive() does not wait for a read in progress and unbind frees the iio_dev underneath it. Commit ae1d37a9bb4b ("iio: imu: adis16400: use DEFINE_DEBUGFS_ATTRIBUTE instead of DEFINE_SIMPLE_ATTRIBUTE") converted product_id and flash_count to DEFINE_DEBUGFS_ATTRIBUTE, which protects itself, but moved all three files to debugfs_create_file_unsafe(). serial_number prints three registers as "%.4x-%.4x-%.4x" and DEFINE_DEBUGFS_ATTRIBUTE() formats a single u64, so it cannot be converted without changing what the file returns. adis16475 and adis16550 already use debugfs_create_file() for their open coded files. Use it here too. Fixes: ae1d37a9bb4b ("iio: imu: adis16400: use DEFINE_DEBUGFS_ATTRIBUTE instead of DEFINE_SIMPLE_ATTRIBUTE") Signed-off-by: Arka Mondal Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/imu/adis16400.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/adis16400.c b/drivers/iio/imu/adis16400.c index 4842346f9f0c..8f664d055e9d 100644 --- a/drivers/iio/imu/adis16400.c +++ b/drivers/iio/imu/adis16400.c @@ -280,7 +280,7 @@ static void adis16400_debugfs_init(struct iio_dev *indio_dev) return; if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) - debugfs_create_file_unsafe("serial_number", 0400, + debugfs_create_file("serial_number", 0400, d, st, &adis16400_serial_number_fops); if (st->variant->flags & ADIS16400_HAS_PROD_ID) debugfs_create_file_unsafe("product_id", 0400, From 2b498df75d6c29aa3e760127ae0f0003df598b53 Mon Sep 17 00:00:00 2001 From: Arka Mondal Date: Sun, 16 Aug 2026 03:27:26 +0900 Subject: [PATCH 024/535] iio: gyro: adis16136: fix unprotected debugfs reads The serial_number file operations are open coded and never call debugfs_file_get(), which debugfs_create_file_unsafe() requires. debugfs_remove_recursive() therefore does not wait for a read in progress, and unbind frees the iio_dev underneath it. Use debugfs_create_file() instead. Fixes: 9aaea09b4cbd ("gyro:adis16136: fix debugfs_simple_attr.cocci warnings") Signed-off-by: Arka Mondal Reviewed-by: Andy Shevchenko Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/gyro/adis16136.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c index ff335f81e9ae..b687588ada03 100644 --- a/drivers/iio/gyro/adis16136.c +++ b/drivers/iio/gyro/adis16136.c @@ -145,7 +145,7 @@ static int adis16136_debugfs_init(struct iio_dev *indio_dev) struct adis16136 *adis16136 = iio_priv(indio_dev); struct dentry *d = iio_get_debugfs_dentry(indio_dev); - debugfs_create_file_unsafe("serial_number", 0400, + debugfs_create_file("serial_number", 0400, d, adis16136, &adis16136_serial_fops); debugfs_create_file_unsafe("product_id", 0400, d, adis16136, &adis16136_product_id_fops); From baf7e43b7fb5afb777353bf265ddf9d909f72b9f Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 19 Aug 2026 22:17:51 +0800 Subject: [PATCH 025/535] iio: admv1013: initialize callback mutex before registering notifier admv1013_probe() registers a clock notifier whose callback takes st->lock on POST_RATE_CHANGE. Initialize the mutex before devm_clk_notifier_register() so the callback cannot observe an uninitialized lock during probe. Use devm_mutex_init() so the lock lifetime is tied to the device and cleanup stays paired with the rest of the managed probe resources. Fixes: da35a7b526d9 ("iio: frequency: admv1013: add support for ADMV1013") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/admv1013.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/admv1013.c b/drivers/iio/frequency/admv1013.c index c9baaab574e3..cdf33ccfd1f9 100644 --- a/drivers/iio/frequency/admv1013.c +++ b/drivers/iio/frequency/admv1013.c @@ -601,11 +601,14 @@ static int admv1013_probe(struct spi_device *spi) "failed to get the LO input clock\n"); st->nb.notifier_call = admv1013_freq_change; - ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + + ret = devm_mutex_init(dev, &st->lock); if (ret) return ret; - mutex_init(&st->lock); + ret = devm_clk_notifier_register(dev, st->clkin, &st->nb); + if (ret) + return ret; ret = admv1013_init(st, vcm_uv); if (ret) From d380bf5f94a892e546d9a94e8557ad866b043a2a Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Wed, 19 Aug 2026 12:41:01 +0100 Subject: [PATCH 026/535] iio: accel: sca3000: fix frequency divider condition check When setting the sampling frequency, the check for `base_freq / 2` is followed by an independent `if` statement for `base_freq / 4`. If `val` equals `base_freq / 2`, the second check fails and falls through to the `else if (val != base_freq)` branch, returning `-EINVAL` erroneously. Fix this by chaining the checks with `else if`. Fixes: e0f3fc9b47e6 ("iio: accel: sca3000_core: implemented IIO_CHAN_INFO_SAMP_FREQ") Signed-off-by: Salah Triki Reviewed-by: Joshua Crofts Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/accel/sca3000.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index a92a563f6a4b..8f1ac1d3305e 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -638,7 +638,7 @@ static int sca3000_write_raw_samp_freq(struct sca3000_state *st, int val) if (val == base_freq / 2) ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_2; - if (val == base_freq / 4) + else if (val == base_freq / 4) ctrlval |= SCA3000_REG_OUT_CTRL_BUF_DIV_4; else if (val != base_freq) return -EINVAL; From 2093236e886ad9446ec3285ba520e024dd1464e1 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Wed, 19 Aug 2026 18:51:19 +0100 Subject: [PATCH 027/535] iio: frequency: admv1013: fix wrong channel field used in admv1013_read_raw() admv1013_read_raw() switches on chan->channel instead of chan->channel2 when handling IIO_CHAN_INFO_CALIBBIAS. The channel field only ever holds 0 or 1 (see ADMV1013_CHAN_CALIB()), while the IIO_MOD_I / IIO_MOD_Q modifiers are stored in channel2. As a result, the switch always falls through to the default case and calibbias reads always fail with -EINVAL, even though the corresponding admv1013_write_raw() path correctly uses channel2 and works as expected. Fix the read path to switch on chan->channel2, matching the write path and the actual channel_spec definition. Fixes: da35a7b526d9 ("iio: frequency: admv1013: add support for ADMV1013") Signed-off-by: Salah Triki Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/frequency/admv1013.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/frequency/admv1013.c b/drivers/iio/frequency/admv1013.c index cdf33ccfd1f9..078ea3e5aa03 100644 --- a/drivers/iio/frequency/admv1013.c +++ b/drivers/iio/frequency/admv1013.c @@ -195,7 +195,7 @@ static int admv1013_read_raw(struct iio_dev *indio_dev, switch (info) { case IIO_CHAN_INFO_CALIBBIAS: - switch (chan->channel) { + switch (chan->channel2) { case IIO_MOD_I: addr = ADMV1013_REG_OFFSET_ADJUST_I; break; From 2a57b9246cecd5dd797a42eb310d6c8816f26d9e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Thu, 6 Aug 2026 14:25:02 +0000 Subject: [PATCH 028/535] iio: trigger: cancel reenable_work before freeing trigger iio_trigger_notify_done_atomic() defers ->reenable() into trig->reenable_work on the system workqueue, and the worker dereferences the owning trigger through container_of(). Nothing cancels this work before iio_trig_release() frees the trigger, so a worker armed by the last in-flight IRQ can outlive the free and touch freed memory. Cancel it at the top of iio_trig_release(), which every free path reaches through the device core's final put_device(). Found by an in-house static analysis tool. Fixes: 9020ef659885 ("iio: trigger: Fix a scheduling whilst atomic issue seen on tsc2046") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-trigger.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c index 17781c12bc85..76926fee0f84 100644 --- a/drivers/iio/industrialio-trigger.c +++ b/drivers/iio/industrialio-trigger.c @@ -509,6 +509,8 @@ static void iio_trig_release(struct device *device) struct iio_trigger *trig = to_iio_trigger(device); int i; + cancel_work_sync(&trig->reenable_work); + if (trig->subirq_base) { for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { irq_modify_status(trig->subirq_base + i, From 4daa9ca557b4b98ae1ad38a6d1deaa3e31eb8edf Mon Sep 17 00:00:00 2001 From: Cong Nguyen Date: Fri, 28 Aug 2026 17:59:09 +0700 Subject: [PATCH 029/535] iio: proximity: pulsedlight: fix iio_device left registered on PM setup failure pm_runtime_set_active() failing in probe() jumps to error_unreg_buffer, which only calls iio_triggered_buffer_cleanup() -- it does not undo the iio_device_register() that already succeeded a few lines above. probe() then returns the error, the devm-managed indio_dev is freed, but the iio core still has it registered: the sysfs/chardev nodes stay live and point at freed memory. Add an error_unreg_dev label that unregisters the iio device before falling through to the existing buffer cleanup, mirroring the teardown order already used in lidar_remove(). Fixes: 4ac4e086fd8c ("iio: pulsedlight-lidar-lite: add runtime PM") Assisted-by: Claude:claude-opus-4 Signed-off-by: Cong Nguyen Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/pulsedlight-lidar-lite-v2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 400477b4c740..8a9ee21f2bf6 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -294,12 +294,14 @@ static int lidar_probe(struct i2c_client *client) ret = pm_runtime_set_active(&client->dev); if (ret) - goto error_unreg_buffer; + goto error_unreg_dev; pm_runtime_enable(&client->dev); pm_runtime_idle(&client->dev); return 0; +error_unreg_dev: + iio_device_unregister(indio_dev); error_unreg_buffer: iio_triggered_buffer_cleanup(indio_dev); From 0d0ffbcc92e30a8d656ad76a6e278fa3400fed85 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Fri, 28 Aug 2026 11:46:55 +0100 Subject: [PATCH 030/535] iio: buffer-dmaengine: fix sg entry iteration when building dma_vecs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iio_dmaengine_buffer_submit_block() counts scatterlist entries with sg_nents_for_len(), which walks the CPU-side lengths (sg->length), but then consumes the DMA-side fields (sg_dma_address()/sg_dma_len()). After dma_map_sgtable() the two views may differ: an IOMMU can coalesce the mapping so that only the first sgt->nents entries carry valid DMA addresses, with nents < orig_nents. On x86 with an IOMMU enabled, a DMABUF block backed by two 1 MiB system-heap chunks maps to a single 2 MiB IOVA range. The CPU-side count is 2, so the loop reads one entry past the mapped set and emits a garbage vec ({addr = ~0, len = 0}). The DMA engine driver rejects the vec array (prep returns NULL), the fence is signalled with -ENOMEM, which a userspace poller cannot observe, and the block is left in ACTIVE state so every further enqueue of it fails with -EBUSY. The visible symptom is a stream of zero-filled blocks followed by a wedged buffer. Platforms without an IOMMU never hit this because nents == orig_nents. Size the vec array with sgt->nents, i.e. the DMA-mapped view, and stop the fill loop once bytes_used is covered - which is allowed to be smaller than the block size - passing the number of vecs actually filled to dmaengine_prep_peripheral_dma_vec(). One vec per mapped entry is enough since coalescing can only ever reduce the number of entries. A single mapped entry longer than the device's maximum segment size would need more than one, but the DMA API already assumes no single segment exceeds it [1], and splitting a vec down to the hardware descriptor size is the DMA engine driver's job - which both current .device_prep_peripheral_dma_vec() implementations do. [1]: commit ab2cbeb0ed30 ("iommu/dma: Handle SG length overflow better") Assisted-by: Claude:claude-fable-5 Fixes: 7a86d469983a ("iio: buffer-dmaengine: Support new DMABUF based userspace API") Signed-off-by: Michael Hennerich Signed-off-by: Nuno Sá Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- .../iio/buffer/industrialio-buffer-dmaengine.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index ecc02a427b92..376486be3f55 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -104,10 +104,13 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (block->sg_table) { unsigned long flags; - sgl = block->sg_table->sgl; - nents = sg_nents_for_len(sgl, block->bytes_used); - if (nents < 0) - return nents; + /* + * Only the first sgt->nents entries carry a valid + * sg_dma_address()/sg_dma_len() pair as mapping the table may + * have coalesced entries, in which case nents is smaller than + * orig_nents. + */ + nents = block->sg_table->nents; vecs = kmalloc_array(nents, sizeof(*vecs), GFP_ATOMIC); if (!vecs) @@ -115,7 +118,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, len_total = block->bytes_used; - for (i = 0; i < nents; i++) { + sgl = block->sg_table->sgl; + for (i = 0; i < nents && len_total; i++) { vecs[i].addr = sg_dma_address(sgl); vecs[i].len = min(sg_dma_len(sgl), len_total); len_total -= vecs[i].len; @@ -123,6 +127,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, sgl = sg_next(sgl); } + nents = i; + if (block->cyclic) flags = DMA_PREP_REPEAT; else From 95e4eb426b4bd666fa2f37886c0ddf74a0cc6167 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 21:42:27 +0800 Subject: [PATCH 031/535] iio: adc: aspeed: propagate reset deassert errors aspeed_adc_probe() continues to register ADC resources after deasserting the shared reset, even if the reset controller reports a failure. A failed deassertion leaves the hardware unavailable, so stop probing before installing the cleanup action and enabling the ADC. Fixes: edf7550a1f93 ("iio: adc: aspeed: Deassert reset in probe") Signed-off-by: Pengpeng Hou Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/aspeed_adc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index 6169ce28261e..452c30cf04f2 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -585,7 +585,9 @@ static int aspeed_adc_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(data->rst), "invalid or missing reset controller device tree entry"); - reset_control_deassert(data->rst); + ret = reset_control_deassert(data->rst); + if (ret) + return ret; ret = devm_add_action_or_reset(dev, aspeed_adc_reset_assert, data->rst); if (ret) From b1c3c51b0916f7d1b2dceffc0e48abe185958825 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Maneyrol Date: Mon, 24 Aug 2026 17:16:58 +0200 Subject: [PATCH 032/535] iio: inv_sensors: fix estimated value larger than interrupt timestamp When interrupt timestamp interval is not valid, we use an estimated value that can in rare case be bigger than the interrupt timestamp. This is obviously wrong, so better use interrupt timestamp in this case. Fixes: 8f4b627656fa ("iio: inv_sensors: better timestamp alignment when using watermark") Cc: stable@vger.kernel.org Signed-off-by: Jean-Baptiste Maneyrol Signed-off-by: Jonathan Cameron --- .../iio/common/inv_sensors/inv_sensors_timestamp.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c index 88a82d1370c5..8c9e81a77e68 100644 --- a/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c +++ b/drivers/iio/common/inv_sensors/inv_sensors_timestamp.c @@ -167,11 +167,16 @@ void inv_sensors_timestamp_interrupt(struct inv_sensors_timestamp *ts, valid = inv_validate_period(ts, period); } - /* if interrupt interval is valid, sync with interrupt timestamp */ + /* + * If interrupt interval is valid, sync with interrupt timestamp. + * Otherwise, use estimated value while ensuring interrupt timestamp + * remains the maximum possible value. + */ + period = inv_align_timestamp_it(ts, sample_nb); if (valid) - ts->period = inv_align_timestamp_it(ts, sample_nb); + ts->period = period; else - ts->period = ts->mult * ts->chip_period.val; + ts->period = min(ts->mult * ts->chip_period.val, period); } EXPORT_SYMBOL_NS_GPL(inv_sensors_timestamp_interrupt, "IIO_INV_SENSORS_TIMESTAMP"); From 1b48c13075829ee4961feeabd816dac1c9111959 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Mon, 22 Dec 2025 15:46:46 +0100 Subject: [PATCH 033/535] virt: vmgenid: remap memory as decrypted It was found that AWS SEV-SNP enabled instances are not able to boot with commit 81256a50aa0f ("x86/mm: Make memremap(MEMREMAP_WB) map memory as encrypted by default") applied and the reason seems to be the vmgenid device which requires unencrypted writeable memory. A similar problem was previously fixed in DRM with commit 7dfede7d7edd ("drm/vmwgfx: Fix guests running with TDX/SEV"). Note, trusting vmgenid device in a Confidential VM is questionable: the malicious host may intentionally avoid notifying the guest when a copy is created. Fixes: 81256a50aa0f ("x86/mm: Make memremap(MEMREMAP_WB) map memory as encrypted by default") Signed-off-by: Vitaly Kuznetsov Cc: stable@vger.kernel.org # 6.15+ Signed-off-by: Jason A. Donenfeld --- drivers/virt/vmgenid.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c index 66135eac3abf..2cf0096aa217 100644 --- a/drivers/virt/vmgenid.c +++ b/drivers/virt/vmgenid.c @@ -75,7 +75,8 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) phys_addr = (obj->package.elements[0].integer.value << 0) | (obj->package.elements[1].integer.value << 32); - virt_addr = devm_memremap(&device->dev, phys_addr, VMGENID_SIZE, MEMREMAP_WB); + virt_addr = devm_memremap(&device->dev, phys_addr, VMGENID_SIZE, + MEMREMAP_WB | MEMREMAP_DEC); if (IS_ERR(virt_addr)) { ret = PTR_ERR(virt_addr); goto out; From 9f8139c6145cf17d690d6c414d43b4c0bef632fb Mon Sep 17 00:00:00 2001 From: Zhichen Wang Date: Tue, 25 Aug 2026 21:50:54 +0800 Subject: [PATCH 034/535] virt: vmgenid: set driver_data before registering notification handlers Both probe paths register their notification handler before assigning driver_data, which the handler dereferences. In the devicetree path, the notification IRQ can fire as soon as devm_request_irq() registers the handler: the interrupt may already be pending at probe time, for example when a VMM injects the generation-changed notification while restoring a guest from a snapshot that was taken before the driver had probed (Firecracker does exactly this on snapshot restore). The IRQ is also requested with IRQF_SHARED, so another device sharing the line can trigger the handler just as early. The handler then calls vmgenid_notify(), which dereferences the still-NULL driver_data and panics: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010 CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.18.38+ #1 PREEMPT(none) Hardware name: linux,dummy-virt (DT) pc : vmgenid_notify.isra.0+0x24/0x8c lr : vmgenid_of_irq_handler+0x14/0x34 Call trace: vmgenid_notify.isra.0+0x24/0x8c (P) vmgenid_of_irq_handler+0x14/0x34 __handle_irq_event_percpu+0x44/0x1bc handle_irq_event+0x4c/0xb4 handle_fasteoi_irq+0xf8/0x1f8 The ACPI path has the same ordering problem: the handler is installed with acpi_install_notify_handler() before driver_data is assigned. ACPI notifications are dispatched asynchronously from a workqueue, so the window is narrow, but a notification arriving between the two calls hits the same NULL dereference. Assign driver_data before registering the handlers. The state is fully initialized at that point, so the handlers are safe to run. Should registration fail, the probe error path leaves no dangling pointer behind: the driver core clears driver_data in device_unbind_cleanup(). Fixes: 7b1bcd6b50a6 ("virt: vmgenid: add support for devicetree bindings") Fixes: e07606713a90 ("virt: vmgenid: change implementation to use a platform driver") Cc: stable@vger.kernel.org Signed-off-by: Zhichen Wang Signed-off-by: Jason A. Donenfeld --- drivers/virt/vmgenid.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c index 2cf0096aa217..0d269edf283d 100644 --- a/drivers/virt/vmgenid.c +++ b/drivers/virt/vmgenid.c @@ -83,6 +83,8 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) } setup_vmgenid_state(state, virt_addr); + dev->driver_data = state; + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, vmgenid_acpi_handler, dev); if (ACPI_FAILURE(status)) { @@ -90,7 +92,6 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) goto out; } - dev->driver_data = state; out: ACPI_FREE(parsed.pointer); return ret; @@ -124,12 +125,13 @@ static int vmgenid_add_of(struct platform_device *pdev, if (ret < 0) return ret; + pdev->dev.driver_data = state; + ret = devm_request_irq(&pdev->dev, ret, vmgenid_of_irq_handler, IRQF_SHARED, "vmgenid", &pdev->dev); if (ret < 0) return ret; - pdev->dev.driver_data = state; return 0; } From df82be40ae0868be98c8febaf7928cf3e44f7ab9 Mon Sep 17 00:00:00 2001 From: Vladislav Leonov Date: Sun, 23 Aug 2026 15:20:33 +0300 Subject: [PATCH 035/535] dt-bindings: iio: adc: rockchip-saradc: Group single-entries into an enum list Group single-entries into an enum list Suggested-by: Krzysztof Kozlowski Signed-off-by: Vladislav Leonov Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/rockchip-saradc.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml index f0551d665916..78ec9a33da24 100644 --- a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml @@ -12,15 +12,16 @@ maintainers: properties: compatible: oneOf: - - const: rockchip,saradc - - const: rockchip,rk3066-tsadc - - const: rockchip,rk3399-saradc - - const: rockchip,rk3528-saradc + - enum: + - rockchip,saradc + - rockchip,rk3066-tsadc + - rockchip,rk3399-saradc + - rockchip,rk3528-saradc + - rockchip,rk3562-saradc + - rockchip,rk3588-saradc - items: - const: rockchip,rk3506-saradc - const: rockchip,rk3528-saradc - - const: rockchip,rk3562-saradc - - const: rockchip,rk3588-saradc - items: - enum: - rockchip,rk3576-saradc From da7c937e0abc86710e237e88bbaaff4a298e48d1 Mon Sep 17 00:00:00 2001 From: Vladislav Leonov Date: Sun, 23 Aug 2026 15:20:34 +0300 Subject: [PATCH 036/535] dt-bindings: iio: adc: rockchip-saradc: Fix RV1106 compatible rockchip,rv1106-saradc was folded into the fallback enum together with rockchip,rk3576-saradc, falling back to rockchip,rk3588-saradc. The two IP blocks are not equivalent, though: per the RV1106 TRM v0.3, chapter 20 (SAR-ADC), RV1106 only has 2 channels at 10-bit resolution, versus 8 channels at 12-bit on RK3588. Documenting it as falling back to rk3588-saradc allows device trees that would bind against the wrong channel table and report an incorrect voltage scale. Remove rockchip,rv1106-saradc from the RK3588 fallback enum and give it its own standalone compatible entry, matching the dedicated match table entry added by the driver patch. Fixes: bdba14f6122c ("dt-bindings: iio: adc: rockchip-saradc: Add RV1106 compatible") Suggested-by: Jonathan Cameron Signed-off-by: Vladislav Leonov Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/rockchip-saradc.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml index 78ec9a33da24..0cce357cb01d 100644 --- a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml @@ -19,13 +19,12 @@ properties: - rockchip,rk3528-saradc - rockchip,rk3562-saradc - rockchip,rk3588-saradc + - rockchip,rv1106-saradc - items: - const: rockchip,rk3506-saradc - const: rockchip,rk3528-saradc - items: - - enum: - - rockchip,rk3576-saradc - - rockchip,rv1106-saradc + - const: rockchip,rk3576-saradc - const: rockchip,rk3588-saradc - items: - enum: From 032c59c7681c661b63123231824170c025afb7e7 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:33 +0200 Subject: [PATCH 037/535] thunderbolt: Hold a router reference for each allocated HopID tb_stop() drops the reference to all DP tunnels but does not deactivate them, thus nothing cancels a dprx_work still in flight (which holds its own tunnel reference) and the tunnel can outlive tb_switch_remove(). The HopID releases in tb_path_free() then operate on freed IDAs and trigger warnings like ida_free called for id=8 which is not allocated. This can be triggered by unbinding the driver while a DP tunnel is still waiting for the DPRX capabilities read to finish. On the Apple NHI unplugging the cable runs into just that reliably because the read can never finish right now and because the unplug powers down the entire USB4 complex and removes the NHI device. Take a router reference whenever an input or output HopID is allocated and drop it again after the HopID is released. This keeps the ports and their HopID IDAs alive for as long as they are used. The KUnit tests allocate routers without ever registering their devices so initialize the embedded struct device there as well and drop its initial reference when the test is finished. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 9 ++++++++- drivers/thunderbolt/test.c | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 404c0693df50..cf571a7c98d0 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, { int port_max_hopid; struct ida *ida; + int ret; if (in) { port_max_hopid = port->config.max_in_hop_id; @@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, if (max_hopid < 0 || max_hopid > port_max_hopid) max_hopid = port_max_hopid; - return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + if (ret >= 0) + tb_switch_get(port->sw); + + return ret; } /** @@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) void tb_port_release_in_hopid(struct tb_port *port, int hopid) { ida_free(&port->in_hopids, hopid); + tb_switch_put(port->sw); } /** @@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) void tb_port_release_out_hopid(struct tb_port *port, int hopid) { ida_free(&port->out_hopids, hopid); + tb_switch_put(port->sw); } static inline bool tb_switch_is_reachable(const struct tb_switch *parent, diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 05652ee82fbf..799ada97cbc9 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -33,6 +33,18 @@ static void kunit_ida_init(struct kunit *test, struct ida *ida) kunit_alloc_resource(test, __ida_init, __ida_destroy, GFP_KERNEL, ida); } +static void tb_test_switch_release(struct device *dev) +{ + /* The memory is owned by KUnit, nothing to do here */ +} + +static void tb_test_switch_put(void *data) +{ + struct tb_switch *sw = data; + + put_device(&sw->dev); +} + static struct tb_switch *alloc_switch(struct kunit *test, u64 route, u8 upstream_port, u8 max_port_number) { @@ -44,6 +56,15 @@ static struct tb_switch *alloc_switch(struct kunit *test, u64 route, if (!sw) return NULL; + /* + * HopID allocations take a reference to their routers and those devices + * have to be initialized for that to work. + */ + sw->dev.release = tb_test_switch_release; + device_initialize(&sw->dev); + if (kunit_add_action_or_reset(test, tb_test_switch_put, sw)) + return NULL; + sw->config.upstream_port_number = upstream_port; sw->config.depth = tb_route_length(route); sw->config.route_hi = upper_32_bits(route); From 12f5d8b85a66a0ac08d082517d92ce136f8c0d42 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:34 +0200 Subject: [PATCH 038/535] thunderbolt: Make the DP tunnel activation callback mandatory tb_tunnel_alloc_dp() takes an optional callback which is run from dprx_work once the DPRX capabilities read has completed. Without that callback tb_dp_dprx_start() reads the capabilities synchronously and never queues the work. It however always takes a tunnel reference which is only dropped by dprx_work itself or by tb_dp_dprx_stop() when cancel_delayed_work() actually canceled that work. That reference is thus leaked for every tunnel without a callback. The only tunnels without one are those from tb_tunnel_discover_dp(), which are activated again when restoring from hibernation. Pass the callback to tb_tunnel_discover_dp() as well and drop the synchronous path such that the DPRX capabilities are always read from dprx_work. Hibernation restore then also no longer blocks for up to 12 seconds while waiting for that read to complete. Also fix up the KUnit tests. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 4 ++- drivers/thunderbolt/test.c | 37 +++++++++++++++++++--------- drivers/thunderbolt/tunnel.c | 47 ++++++++++++++++++++---------------- drivers/thunderbolt/tunnel.h | 8 +++--- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 47753a5c0f2e..20f0f8f4bdf7 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, const char *reason); static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, int retry, unsigned long delay); +static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) { @@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, switch (port->config.type) { case TB_TYPE_DP_HDMI_IN: - tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); + tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, + tb_dp_tunnel_active, tb); tb_increase_tmu_accuracy(tunnel); break; diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 799ada97cbc9..c8c648f31107 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -1407,6 +1407,10 @@ static void tb_test_tunnel_pcie(struct kunit *test) tb_tunnel_put(tunnel1); } +static void tb_test_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) +{ +} + static void tb_test_tunnel_dp(struct kunit *test) { struct tb_switch *host, *dev; @@ -1427,7 +1431,8 @@ static void tb_test_tunnel_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1473,7 +1478,8 @@ static void tb_test_tunnel_dp_chain(struct kunit *test) in = &host->ports[5]; out = &dev4->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1523,7 +1529,8 @@ static void tb_test_tunnel_dp_tree(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1588,7 +1595,8 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test) in = &dev6->ports[13]; out = &dev12->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1658,7 +1666,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) out2 = &dev5->ports[13]; out3 = &dev4->ports[14]; - tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, NULL, NULL); + tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel1 != NULL); KUNIT_EXPECT_EQ(test, tunnel1->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel1->src_port, in1); @@ -1666,7 +1675,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel1->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel1->paths[0]->path_length, 3); - tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, NULL, NULL); + tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel2 != NULL); KUNIT_EXPECT_EQ(test, tunnel2->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel2->src_port, in2); @@ -1674,7 +1684,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel2->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel2->paths[0]->path_length, 4); - tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, NULL, NULL); + tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel3 != NULL); KUNIT_EXPECT_EQ(test, tunnel3->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel3->src_port, in3); @@ -1772,7 +1783,8 @@ static void tb_test_tunnel_port_on_path(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel); KUNIT_EXPECT_TRUE(test, tb_tunnel_port_on_path(dp_tunnel, in)); @@ -2204,7 +2216,8 @@ static void tb_test_credit_alloc_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3); @@ -2440,7 +2453,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL1(struct kunit *test, in = &host->ports[5]; out = &dev->ports[13]; - dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel1); KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3); @@ -2477,7 +2491,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL2(struct kunit *test, in = &host->ports[6]; out = &dev->ports[14]; - dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel2); KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 7e8284575dff..6ac44b371266 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1109,8 +1109,7 @@ static void tb_dp_dprx_work(struct work_struct *work) mutex_unlock(&tb->lock); } - if (tunnel->callback) - tunnel->callback(tunnel, tunnel->callback_data); + tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); } @@ -1123,15 +1122,10 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) tb_tunnel_get(tunnel); tunnel->dprx_started = true; + tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); + queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - if (tunnel->callback) { - tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); - queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - return -EINPROGRESS; - } - - return tb_dp_is_usb4(tunnel->src_port->sw) ? - tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; + return -EINPROGRESS; } static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) @@ -1582,20 +1576,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) * @tb: Pointer to the domain structure * @in: DP in adapter * @alloc_hopid: Allocate HopIDs from visited ports + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * If @in adapter is active, follows the tunnel to the DP out adapter * and back. Returns the discovered tunnel or %NULL if there was no - * tunnel. + * tunnel. See tb_tunnel_alloc_dp() for @callback. * * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. */ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid) + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data) { struct tb_tunnel *tunnel; struct tb_port *port; struct tb_path *path; + if (WARN_ON(!callback)) + return NULL; + if (!tb_dp_port_is_enabled(in)) return NULL; @@ -1611,6 +1613,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; tunnel->src_port = in; + tunnel->callback = callback; + tunnel->callback_data = callback_data; + INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, &tunnel->dst_port, "Video", alloc_hopid); @@ -1677,16 +1682,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, * %0 if no available bandwidth. * @max_down: Maximum available downstream bandwidth for the DP tunnel. * %0 if no available bandwidth. - * @callback: Optional callback that is called when the DP tunnel is - * fully activated (or there is an error) - * @callback_data: Optional data for @callback + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * Allocates a tunnel between @in and @out that is capable of tunneling - * Display Port traffic. If @callback is not %NULL it will be called - * after tb_tunnel_activate() once the tunnel has been fully activated. - * It can call tb_tunnel_is_active() to check if activation was - * successful (or if it returns %false there was some sort of issue). - * The @callback is called without @tb->lock held. + * Display Port traffic. The @callback is called after tb_tunnel_activate() + * once the tunnel has been fully activated. It can call + * tb_tunnel_is_active() to check if activation was successful (or if it + * returns %false there was some sort of issue). The @callback is called + * without @tb->lock held. * * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ @@ -1701,7 +1706,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_path *path; bool pm_support; - if (WARN_ON(!in->cap_adap || !out->cap_adap)) + if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) return NULL; tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 4878763a82b3..7d1d255ab5a7 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -66,8 +66,8 @@ enum tb_tunnel_state { * @dprx_canceled: Was DPRX capabilities read poll canceled * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read - * @callback: Optional callback called when DP tunnel is fully activated - * @callback_data: Optional data for @callback + * @callback: Callback called when DP tunnel is fully activated + * @callback_data: Data for @callback * @paths: All paths required by the tunnel */ struct tb_tunnel { @@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down); struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid); + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data); struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, int max_up, int max_down, From 1fd1f67c94d30889377bba774f032ec15e8b9299 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:35 +0200 Subject: [PATCH 039/535] thunderbolt: Fix domain reference leak when DPRX read is canceled tb_tunnel_one_dp() takes a domain reference which is only dropped once tb_dp_tunnel_active() has run on the work queue. If that work is cancelled that reference is leaked. Since commit f5cc545f5969 ("thunderbolt: Wait for tb_domain_release() to complete when driver is removed") instead of just leaking memory this now also blocks in the completion wait forever when unbinding the driver. This can be triggered whenever a DP tunnel is torn down before the DPRX read has completed, e.g. by unplugging within the timeout, and then unbinding the driver. That reference only exists to keep the domain around while the DPRX work is scheduled so let the work itself own it: take it in tb_dp_dprx_start() and drop it in both places that end the work. Get/put are then paired inside the same file and it doesn't matter anymore if the callback ever runs. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 6 +----- drivers/thunderbolt/tunnel.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 20f0f8f4bdf7..1c5e0c9159c4 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1966,8 +1966,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); } mutex_unlock(&tb->lock); - - tb_domain_put(tb); } static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, @@ -2028,8 +2026,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, available_up, available_down); tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, - available_down, tb_dp_tunnel_active, - tb_domain_get(tb)); + available_down, tb_dp_tunnel_active, tb); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim_usb; @@ -2050,7 +2047,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, tb_tunnel_put(tunnel); err_reclaim_usb: tb_reclaim_usb3_bandwidth(tb, in, out); - tb_domain_put(tb); err_detach_group: tb_detach_bandwidth_group(in); err_dealloc_dp: diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 6ac44b371266..4f3a3717e37d 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1111,15 +1111,17 @@ static void tb_dp_dprx_work(struct work_struct *work) tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); + tb_domain_put(tb); } static int tb_dp_dprx_start(struct tb_tunnel *tunnel) { /* - * Bump up the reference to keep the tunnel around. It will be - * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. + * Bump up the references to keep the tunnel and the domain around + * until the work has run or has been canceled. */ tb_tunnel_get(tunnel); + tb_domain_get(tunnel->tb); tunnel->dprx_started = true; tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); @@ -1130,11 +1132,15 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) { + struct tb *tb = tunnel->tb; + if (tunnel->dprx_started) { tunnel->dprx_started = false; tunnel->dprx_canceled = true; - if (cancel_delayed_work(&tunnel->dprx_work)) + if (cancel_delayed_work(&tunnel->dprx_work)) { tb_tunnel_put(tunnel); + tb_domain_put(tb); + } } } From 419fa32fa5bc2d7fb9d0d15d5630b1c1090808b3 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:36 +0200 Subject: [PATCH 040/535] thunderbolt: Don't access a DP tunnel after its DPRX read was canceled tb_dp_dprx_work() checks ->dprx_canceled before it takes tb->lock so it misses a tb_dp_dprx_stop() that could not cancel the already running work. It then polls the DPRX capabilities and runs the callback for a tunnel that has already been torn down while the domain is suspending or going away. This can be hit by cancelling the DPRX read from outside the ordered tb->wq: During suspend tb_disconnect_and_release_dp() does just this and with a later patch tb_stop() will do it as well. The latter in combination with the Apple NHI where the DPRX read never completed is how I hit this. Check the flag with tb->lock held instead and check it again in tb_dp_tunnel_active() because the callback runs after the lock has been dropped again. Also clear the flag in tb_dp_dprx_start() so that it only ever describes the work that is currently in flight. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 12 ++++++++++++ drivers/thunderbolt/tunnel.c | 11 +++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 1c5e0c9159c4..70a1a585bd54 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1912,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) struct tb *tb = data; mutex_lock(&tb->lock); + + /* + * If the DPRX read was canceled the tunnel is already being torn + * down by whoever canceled it. Do not touch the adapters here + * because the routers may be gone by now. + */ + if (tunnel->dprx_canceled) { + tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); + mutex_unlock(&tb->lock); + return; + } + if (tb_tunnel_is_active(tunnel)) { int consumed_up, consumed_down, ret; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 4f3a3717e37d..d015e54abde4 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1093,8 +1093,14 @@ static void tb_dp_dprx_work(struct work_struct *work) struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); struct tb *tb = tunnel->tb; + /* + * The DPRX read can be canceled while this work is waiting for + * tb->lock. Check the flag only once it is held: while the lock is + * held the tunnel cannot be torn down under us and the adapters are + * safe to access. + */ + mutex_lock(&tb->lock); if (!tunnel->dprx_canceled) { - mutex_lock(&tb->lock); if (tb_dp_is_usb4(tunnel->src_port->sw) && tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { @@ -1106,8 +1112,8 @@ static void tb_dp_dprx_work(struct work_struct *work) } else { tb_tunnel_set_active(tunnel, true); } - mutex_unlock(&tb->lock); } + mutex_unlock(&tb->lock); tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); @@ -1124,6 +1130,7 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) tb_domain_get(tunnel->tb); tunnel->dprx_started = true; + tunnel->dprx_canceled = false; tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); From c222f80be55f6c17851af1c68a70ae4046c848e2 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:37 +0200 Subject: [PATCH 041/535] thunderbolt: Mark discovered tunnels as active Discovered tunnels that have been activated by whatever was running before us stay in TB_TUNNEL_INACTIVE until hibernation restore such that anything depending on tb_tunnel_is_active() skips them. Mark them active during discovery instead. These now also emit TUNNEL_EVENT=activated uevents during discovery. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index d015e54abde4..ffd2d04b3bfa 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, goto err_deactivate; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -1674,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tb_dp_dump(tunnel); + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -2306,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, tb_usb3_reclaim_available_bandwidth; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; From 0b457c6733f73421a3fb03786f13d67c618b5119 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:38 +0200 Subject: [PATCH 042/535] thunderbolt: Tear down inactive DP tunnels when the domain is stopped tb_stop() only tears down DMA tunnels so a DP tunnel that is still waiting for dprx_work to complete keeps that work queued while the routers are removed and the control channel is stopped. The work only stops once the DPRX timeout has passed and because it requeues itself until then the flush_workqueue() in tb_domain_remove() won't wait for its final run. The callback then runs against a domain that is already torn down. A reference to that domain is kept so the completion waiting for that domain to disappear in unbind will block until the timeout is eventually reached. Tear down DP tunnels that are not active yet as well which also cancels that work. Tunnels for displays that are already alive are untouched and keep working. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 70a1a585bd54..ba63610d2ce2 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -2960,11 +2960,12 @@ static void tb_stop(struct tb *tb) /* tunnels are only present after everything has been initialized */ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { /* - * DMA tunnels require the driver to be functional so we - * tear them down. Other protocol tunnels can be left - * intact. + * DMA tunnels and DP tunnels which are not yet active require + * the driver to be functional so we tear them down. + * Other protocol tunnels can be left intact. */ - if (tb_tunnel_is_dma(tunnel)) + if (tb_tunnel_is_dma(tunnel) || + (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) tb_tunnel_deactivate(tunnel); tb_tunnel_put(tunnel); } From 4310c6b8e75d6a47f7548e5948fbe6318aa4440a Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Mon, 31 Aug 2026 10:58:09 +0300 Subject: [PATCH 043/535] thunderbolt: Fix NULL dereference in tb_remove_work() There is a slight race between tb_remove_work() and tb_domain_remove() which leads to dereferencing a NULL tb->root_switch pointer inside tb_free_unplugged_xdomains(): Thread A Thread B tb_remove_work() tb_domain_remove() mutex_lock(&tb->lock) tb_stop() /* doesn't cancel a running callback */ cancel_delayed_work(&tcm->remove_work) ... tb_switch_remove(tb->root_switch) tb->root_switch = NULL mutex_unlock(&tb->lock) mutex_lock(&tb->lock) ... /* without checking ->root_switch */ tb_free_unplugged_xdomains(tb->root_switch) mutex_unlock(&tb->lock) Commit a8937f35cf39 ("thunderbolt: Remove XDomain from the bus without holding tb->lock") doesn't seem right to move tb_free_unplugged_xdomains() out of the &tb->lock section and the check for tb->root_switch, in particular. It states: For this reason separate removing the XDomain from the topology data structures (where we need the lock) from unregistering the device from the bus (where remove callbacks of the drivers are being called). tb_free_unplugged_xdomains() belongs to the former group of functions requiring the lock. And it also calls tb_xdomain_remove() which should only be called with &tb->lock held. Found by Linux Verification Center (linuxtesting.org) with Svace static analysis tool. Fixes: a8937f35cf39 ("thunderbolt: Remove XDomain from the bus without holding tb->lock") Cc: stable@vger.kernel.org Signed-off-by: Fedor Pchelkin Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index ba63610d2ce2..4e5d0578fd4b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -3285,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) struct tb *tb = tcm_to_tb(tcm); mutex_lock(&tb->lock); - if (tb->root_switch) + if (tb->root_switch) { tb_free_unplugged_children(tb->root_switch); + tb_free_unplugged_xdomains(tb->root_switch); + } mutex_unlock(&tb->lock); - - tb_free_unplugged_xdomains(tb->root_switch); } static int tb_runtime_resume(struct tb *tb) From cd243401994880b8b6810c53177bce56eebaece5 Mon Sep 17 00:00:00 2001 From: Carlo Szelinsky Date: Wed, 12 Aug 2026 13:42:28 +0300 Subject: [PATCH 044/535] USB: serial: option: add Quectel EG060W Add Quectel EG060W with product ID 0x6004. The three vendor-specific interfaces are DIAG, NMEA and AT; the CDC-NCM data interfaces are handled by the cdc_ncm driver. T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=2c7c ProdID=6004 Rev= 3.18 S: Manufacturer=Quectel S: Product=EG060W-EAAA S: SerialNumber=123456789ABCD C:* #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr= 2mA A: FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=0d Prot=00 I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=0d Prot=00 Driver=cdc_ncm E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm I:* If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Carlo Szelinsky Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e4ad14375d6a..f6d1803bfdad 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -280,6 +280,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EC200U 0x0901 #define QUECTEL_PRODUCT_EG912Y 0x6001 #define QUECTEL_PRODUCT_EC200S_CN 0x6002 +#define QUECTEL_PRODUCT_EG060W 0x6004 #define QUECTEL_PRODUCT_EC200A 0x6005 #define QUECTEL_PRODUCT_EG916Q 0x6007 #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 @@ -1268,6 +1269,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, From 35c950c9ec2c4c4d45dc9b63cfe8fca774ce33a0 Mon Sep 17 00:00:00 2001 From: Zhao Dongdong Date: Mon, 17 Aug 2026 15:14:27 +0800 Subject: [PATCH 045/535] USB: serial: option: add support for SIMCom SIM8260C Add support for SIMCom SIM8260C (1e0e:902b). T: Bus=08 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=5000 MxCh= 0 D: Ver= 3.20 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs= 1 P: Vendor=1e0e ProdID=902b Rev= 5.04 S: Manufacturer=SIMCOM S: Product=SDXLEMUR-LITE-MTP _SN:120696AB S: SerialNumber=0123456789ABCDEF C:* #Ifs= 8 Cfg#= 1 Atr=a0 MxPwr=896mA A: FirstIf#= 5 IfCount= 3 Cls=01(audio) Sub=00 Prot=20 I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=82(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=85(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=84(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=87(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=86(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan_simcom E: Ad=88(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=8e(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=0f(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 0 Cls=01(audio) Sub=01 Prot=20 Driver=snd-usb-audio I:* If#= 6 Alt= 0 #EPs= 0 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio I: If#= 6 Alt= 1 #EPs= 1 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio E: Ad=05(O) Atr=0d(Isoc) MxPS= 34 Ivl=1ms I:* If#= 7 Alt= 0 #EPs= 0 Cls=01(audio) Sub=02 Prot=20 Driver=(none) I: If#= 7 Alt= 1 #EPs= 1 Cls=01(audio) Sub=02 Prot=20 Driver=(none) E: Ad=89(I) Atr=0d(Isoc) MxPS= 34 Ivl=1ms Signed-off-by: Zhao Dongdong Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index f6d1803bfdad..0fe1694fcd74 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2170,6 +2170,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), + .driver_info = RSVD(4) }, { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ .driver_info = RSVD(3) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ From 7f11480cda081de78a05d8c97073df66326b50b3 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Mon, 31 Aug 2026 16:44:05 +0800 Subject: [PATCH 046/535] USB: serial: option: add Compal EXM-G1x support The Compal EXM-G1x is a Qualcomm SDX12-based LTE modem. Add support for its vendor-specific serial interfaces with protocols 0x30, 0x40, and 0x60. Relevant output from usb-devices: T: Bus=04 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 4 Spd=5000 MxCh= 0 D: Ver= 3.20 Cls=ef(misc ) Sub=02 Prot=01 MxPS= 9 #Cfgs= 1 P: Vendor=04b7 ProdID=8217 Rev=05.04 S: Manufacturer=COMPAL S: Product=EXM-G1x C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=896mA I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option I: If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#= 8 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan Tested on a Compal EXM-G1x modem. Assisted-by: Codex:GPT-5 Signed-off-by: Ian Lin Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 0fe1694fcd74..03c3785a1585 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2433,6 +2433,9 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ From a02188ddc24b7872f0c5e0c5873f827318717803 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 7 May 2026 08:06:48 +0300 Subject: [PATCH 047/535] thunderbolt: Fix KASAN reported use-after-free when request is canceled Alan reported that when doing stress testing sometimes KASAN notices use-after-free during control channel operation (stripped down keeping the relevant parts): BUG: KASAN: slab-use-after-free in tb_cfg_request_sync+0x240/0x250 [thunderbolt] Read of size 24 at addr ffff88811067f290 by task kworker/u40:2/1760 tb_cfg_request_sync+0x240/0x250 [thunderbolt] tb_cfg_read_raw+0x367/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Allocated by task 1760: __kmalloc_cache_noprof+0x1ee/0x550 tb_cfg_read_raw+0x1d3/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Freed by task 926: kfree+0x18f/0x4a0 tb_cfg_request_put+0xb7/0xe0 [thunderbolt] tb_cfg_request_work+0x82/0x120 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Second to last potentially related work creation: __queue_work+0x575/0xd00 queue_work_on+0x77/0x80 tb_cfg_request_cancel+0xc7/0x260 [thunderbolt] tb_cfg_request_sync+0x1f6/0x250 [thunderbolt] tb_cfg_read_raw+0x367/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 The last stack trace is helpful because it shows that we are cancelling a request and looking at tb_cfg_request_cancel() what might happen is that tb_cfg_request_work() completes right before tb_cfg_request_cancel() starts and because of this it will call schedule_work() queueing the same work to run again. However, it is already removed from the request_queue and reference count is dropped so when tb_cfg_request_work() triggers again it will access memory that is already released. Fix this so that we first make sure a cancelled request is not handed away from tb_cfg_request_find() or scheduled to run. Then instead of relying on the worker to clean up the request we will do it in tb_cfg_request_cancel() after the work is canceled from running. Make tb_cfg_request_dequeue() release the request only if it was actually removed from the queue. Reported-by: Alan Borzeszkowski Fixes: d7f781bfdbf4 ("thunderbolt: Rework control channel to be more reliable") Cc: stable@vger.kernel.org Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index cd47b627f97b..965988b18608 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -73,7 +73,6 @@ struct tb_ctl { #define tb_ctl_dbg_once(ctl, format, arg...) \ dev_dbg_once((ctl)->nhi->dev, format, ## arg) -static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ static DEFINE_MUTEX(tb_cfg_request_lock); @@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) static int tb_cfg_request_enqueue(struct tb_ctl *ctl, struct tb_cfg_request *req) { + tb_cfg_request_get(req); + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); WARN_ON(req->ctl); - mutex_lock(&ctl->request_queue_lock); + guard(mutex)(&ctl->request_queue_lock); if (!ctl->running) { - mutex_unlock(&ctl->request_queue_lock); + tb_cfg_request_put(req); return -ENOTCONN; } req->ctl = ctl; list_add_tail(&req->list, &ctl->request_queue); set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - mutex_unlock(&ctl->request_queue_lock); return 0; } +static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +{ + return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +} + +static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) +{ + return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +} + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) { struct tb_ctl *ctl = req->ctl; - mutex_lock(&ctl->request_queue_lock); - if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { - mutex_unlock(&ctl->request_queue_lock); - return; + guard(mutex)(&ctl->request_queue_lock); + if (tb_cfg_request_is_active(req)) { + list_del(&req->list); + clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + tb_cfg_request_put(req); } - - list_del(&req->list); - clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) - wake_up(&tb_cfg_request_cancel_queue); - mutex_unlock(&ctl->request_queue_lock); -} - -static bool tb_cfg_request_is_active(struct tb_cfg_request *req) -{ - return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); } static struct tb_cfg_request * @@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) mutex_lock(&pkg->ctl->request_queue_lock); list_for_each_entry(iter, &pkg->ctl->request_queue, list) { tb_cfg_request_get(iter); - if (iter->match(iter, pkg)) { + if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { req = iter; break; } @@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); if (req) { - if (req->copy(req, pkg)) - schedule_work(&req->work); + scoped_guard(mutex, &pkg->ctl->request_queue_lock) { + if (!tb_cfg_request_is_canceled(req) && + req->copy(req, pkg)) + schedule_work(&req->work); + } tb_cfg_request_put(req); } @@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) { struct tb_cfg_request *req = container_of(work, typeof(*req), work); - if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) + if (!tb_cfg_request_is_canceled(req)) req->callback(req->callback_data); tb_cfg_request_dequeue(req); - tb_cfg_request_put(req); } /** @@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, INIT_WORK(&req->work, tb_cfg_request_work); INIT_LIST_HEAD(&req->list); - tb_cfg_request_get(req); ret = tb_cfg_request_enqueue(ctl, req); if (ret) - goto err_put; + return ret; ret = tb_ctl_tx(ctl, req->request, req->request_size, req->request_type); @@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, err_dequeue: tb_cfg_request_dequeue(req); -err_put: - tb_cfg_request_put(req); - return ret; } @@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, */ void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) { - set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); - schedule_work(&req->work); - wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); + scoped_guard(mutex, &req->ctl->request_queue_lock) + set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); + cancel_work_sync(&req->work); + tb_cfg_request_dequeue(req); req->result.err = err; } From 96ff396e6e0eb754b73f1870ef7e72f2f80cdbe0 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 15 May 2026 10:28:40 +0300 Subject: [PATCH 048/535] thunderbolt: Use separate lock class for each ring When connected to another host and then unplugging cable lockdep triggers following: ====================================================== WARNING: possible circular locking dependency detected 7.1.0-rc2+ #1775 Tainted: G U ------------------------------------------------------ kworker/u16:6/312 is trying to acquire lock: ffff8881179c70a8 ((work_completion)(&ring->work)){+.+.}-{0:0}, at: __flush_work+0x3cf/0xd10 but task is already holding lock: ffff8881a8b810b0 (&net->connection_lock){+.+.}-{4:4}, at: tbnet_tear_down+0x110/0x720 [thunderbolt_net] which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #1 (&net->connection_lock){+.+.}-{4:4}: __mutex_lock+0x19a/0x2490 mutex_lock_nested+0x1b/0x30 tbnet_handle_packet+0x74c/0xd70 [thunderbolt_net] tb_xdomain_handle_request+0x37c/0x4b0 [thunderbolt] tb_domain_event_cb+0xc9/0x140 [thunderbolt] tb_ctl_handle_event+0xd6/0x2c0 [thunderbolt] tb_ctl_rx_callback+0x22c/0xa10 [thunderbolt] ring_work+0x715/0xcb0 [thunderbolt] process_one_work+0x902/0x1790 worker_thread+0x5cd/0xfe0 kthread+0x339/0x420 ret_from_fork+0x79a/0x9d0 ret_from_fork_asm+0x1a/0x30 -> #0 ((work_completion)(&ring->work)){+.+.}-{0:0}: __lock_acquire+0x1592/0x2640 lock_acquire+0x1a3/0x300 __flush_work+0x3e9/0xd10 flush_work+0x21/0x30 tb_ring_stop+0x240/0x840 [thunderbolt] tbnet_tear_down+0x2ff/0x720 [thunderbolt_net] tbnet_stop+0x47/0x1a0 [thunderbolt_net] __dev_close_many+0x19e/0x4e0 netif_close_many+0x1e8/0x640 unregister_netdevice_many_notify+0x6d3/0x22d0 unregister_netdevice_queue+0x2b9/0x3a0 unregister_netdev+0x1c/0x70 tbnet_remove+0x52/0xb0 [thunderbolt_net] tb_service_remove+0x8a/0xe0 [thunderbolt] device_remove+0xc5/0x190 device_release_driver_internal+0x3db/0x590 device_release_driver+0x12/0x20 bus_remove_device+0x2c1/0x580 device_del+0x3d9/0x9f0 device_unregister+0x17/0xc0 unregister_service+0x46/0x60 [thunderbolt] device_for_each_child_reverse+0xfa/0x180 tb_xdomain_unregister+0x57/0xe0 [thunderbolt] unregister_unplugged_xdomain+0x101/0x1a0 [thunderbolt] bus_for_each_dev+0x111/0x1a0 tb_domain_unregister_unplugged_xdomains+0x98/0xe0 [thunderbolt] tb_handle_hotplug+0xc3/0x2bb0 [thunderbolt] process_one_work+0x902/0x1790 worker_thread+0x5cd/0xfe0 kthread+0x339/0x420 ret_from_fork+0x79a/0x9d0 ret_from_fork_asm+0x1a/0x30 other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&net->connection_lock); lock((work_completion)(&ring->work)); lock(&net->connection_lock); lock((work_completion)(&ring->work)); This in fact is false positive because they involve unrelated rings (and unrelated work structures). In the first one it is ring 0 which is used for control traffic and in the second it is dealing with another ring used for the high-speed traffic. Fix this by using separate lock class for each ring worker. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 5 +++++ include/linux/thunderbolt.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 5809809f64d4..d4d1efa2afa0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, INIT_LIST_HEAD(&ring->in_flight); INIT_WORK(&ring->work, ring_work); init_waitqueue_head(&ring->wait); + lockdep_register_key(&ring->lock_key); + lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); ring->nhi = nhi; ring->hop = hop; @@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: + lockdep_unregister_key(&ring->lock_key); kfree(ring); return NULL; @@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) * to finish before freeing the ring. */ flush_work(&ring->work); + lockdep_unregister_key(&ring->lock_key); kfree(ring); } EXPORT_SYMBOL_GPL(tb_ring_free); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index d48623fda79b..b62dfa52b149 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -22,6 +22,7 @@ struct device; #include #include #include +#include #include #include #include @@ -565,6 +566,7 @@ struct tb_nhi { * @interval_nsec: Interval counter if interrupt throttling is to be * used with this ring (in ns) * @wait: Used to signal that the ring may be empty now + * @lock_key: Lock validator class key per-ring */ struct tb_ring { spinlock_t lock; @@ -590,6 +592,7 @@ struct tb_ring { void *poll_data; unsigned int interval_nsec; wait_queue_head_t wait; + struct lock_class_key lock_key; }; /* Leave ring interrupt enabled on suspend */ From e6df180e976fd08673214bc1305a46a8536aea9a Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 06:35:52 +0200 Subject: [PATCH 049/535] Revert "thunderbolt: xdomain: Notify peers after enumeration" This reverts commit e027dba038f0008df9bc9575f5c3e803e90636c6. Alan noticed that this causes the peers send change properties to each other continuously. Reported-by: Borzeszkowski, Alan Cc: Milo Chen Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index c179bd751fe4..05442df0e99c 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; - tb_xdomain_queue_properties_changed(xd); } break; From 6177bcd94be83f80a032c2e69a06606d325335b9 Mon Sep 17 00:00:00 2001 From: Sebastian Sjoholm Date: Thu, 3 Sep 2026 20:00:29 +0200 Subject: [PATCH 050/535] USB: serial: option: add Quectel RG660QB Add support for the Quectel RG660QB 5G module (USB ID 2c7c:013d). The interface layout is the same as the RG650V: If 0: DIAG ff/ff/30, 2 endpoints If 1: NMEA ff/00/00, 2 endpoints If 2: AT ff/00/00, 3 endpoints If 3: Modem ff/00/00, 3 endpoints If 4: QMI ff/ff/ff, 3 endpoints Match the serial interfaces on class/subclass/protocol so that the QMI interface is left to qmi_wwan. # lsusb -v -d 2c7c:013d Bus 004 Device 002: ID 2c7c:013d Quectel Wireless Solutions Co., Ltd. RG660QB-EU Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 3.20 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 9 idVendor 0x2c7c Quectel Wireless Solutions Co., Ltd. idProduct 0x013d bcdDevice 6.06 iManufacturer 1 Quectel iProduct 2 RG660QB-EU iSerial 3 f50119d0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0105 bNumInterfaces 5 bConfigurationValue 1 iConfiguration 4 DIAG_SER_RMNET bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 896mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 48 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 6 CDEV Serial ** UNRECOGNIZED: 05 24 00 10 01 ** UNRECOGNIZED: 05 24 01 00 00 ** UNRECOGNIZED: 04 24 02 02 ** UNRECOGNIZED: 05 24 06 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x000a 1x 10 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 6 CDEV Serial ** UNRECOGNIZED: 05 24 00 10 01 ** UNRECOGNIZED: 05 24 01 00 00 ** UNRECOGNIZED: 04 24 02 02 ** UNRECOGNIZED: 05 24 06 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x000a 1x 10 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 7 RmNet Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x88 EP 8 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x87 EP 7 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 6 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x05 EP 5 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 2 Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x0023 bNumDeviceCaps 2 SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000f Device can operate at Low Speed (1Mbps) Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 0 micro seconds bU2DevExitLat 0 micro seconds SuperSpeedPlus USB Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 10 bmAttributes 0x00000001 Sublink Speed Attribute count 1 Sublink Speed ID count 0 wFunctionalitySupport 0x1100 bmSublinkSpeedAttr[0] 0x000a4030 Speed Attribute ID: 0 10Gb/s Symmetric RX SuperSpeedPlus bmSublinkSpeedAttr[1] 0x000a40b0 Speed Attribute ID: 0 10Gb/s Symmetric TX SuperSpeedPlus Device Status: 0x0000 (Bus Powered) Tested with an early sample of the module on a Quectel 5G EVB connected over USB 3 to a Raspberry Pi 5. Assisted-by: LLM Signed-off-by: Sebastian Sjoholm Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 03c3785a1585..d530dbceccb1 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EM060K_12a 0x012a #define QUECTEL_PRODUCT_EM060K_12b 0x012b #define QUECTEL_PRODUCT_EM060K_12c 0x012c +#define QUECTEL_PRODUCT_RG660QB 0x013d #define QUECTEL_PRODUCT_EG91 0x0191 #define QUECTEL_PRODUCT_EG95 0x0195 #define QUECTEL_PRODUCT_BG96 0x0296 @@ -1276,6 +1277,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, From b5965c4221165045e19736794ca5f48ae3d67142 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Thu, 13 Aug 2026 10:48:12 +0800 Subject: [PATCH 051/535] mtd: block2mtd: Fix divide error when erase_size is zero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The erase size is parsed from the "block2mtd" module parameter and can be set to zero. add_device() then evaluates if (size % erase_size) with a zero divisor, which triggers a divide error: divide error: 0000 [#1] PREEMPT SMP PTI RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline] RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459 Call Trace: block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476 param_attr_store+0x214/0x310 kernel/params.c:589 module_attr_store+0x65/0x90 kernel/params.c:904 kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345 ... Reject a zero erase size before performing the modulo operation so the existing "erasesize must be a divisor of device size" error path reports the invalid argument and frees the device. Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size") Reported-by: syzbot+b320a4d5f65a61dbbf89@syzkaller.appspotmail.com Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE@google.com/ Suggested-by: Jörn Engel Cc: stable@vger.kernel.org Signed-off-by: Pei Xiao Signed-off-by: Miquel Raynal --- drivers/mtd/devices/block2mtd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 03e80b2c4f5a..349fa07be314 100644 --- a/drivers/mtd/devices/block2mtd.c +++ b/drivers/mtd/devices/block2mtd.c @@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size, } size = bdev_nr_bytes(bdev); - if ((long)size % erase_size) { + if (!erase_size || (long)size % erase_size) { pr_err("erasesize must be a divisor of device size\n"); goto err_free_block2mtd; } From 63d6cace2c4a7f36c1cb44f1bb5e0f3ef19e5c7f Mon Sep 17 00:00:00 2001 From: Han Xu Date: Thu, 13 Aug 2026 08:07:24 -0500 Subject: [PATCH 052/535] mtd: spinand: Enable QE on all dies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The QUAD ENABLE (QE) bit is stored in a per-die configuration register on some SPI-NAND devices. When a device contains multiple dies, updating the QE bit only on the currently selected die can leave the remaining dies operating in non-quad mode.   Iterate over all targets and update the QE setting on each die during initialization to ensure consistent quad I/O operation across the entire device. Tested on ISSI IS38SMW04G8B. Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support SPI NANDs") Cc: stable@vger.kernel.org Signed-off-by: Han Xu Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 8bf9301f25e7..bea352dd82d7 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand) static int spinand_init_quad_enable(struct spinand_device *spinand, bool enable) { - return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, - enable ? CFG_QUAD_ENABLE : 0); + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int target; + int ret; + + /* + * QE is a per-die setting on some devices. Program each target + * individually when enabling or disabling quad I/O mode. + */ + for (target = 0; target < nand->memorg.ntargets; target++) { + ret = spinand_select_target(spinand, target); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, + enable ? CFG_QUAD_ENABLE : 0); + if (ret) + return ret; + } + + return 0; } static int spinand_ecc_enable(struct spinand_device *spinand, From 26300879cd8e4612dce66bb8f6ff7cd35fcf3e59 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 16 Aug 2026 18:30:27 +0200 Subject: [PATCH 053/535] mtd: core: avoid double-free of OTP NVMEM device If factory OTP setup fails after the user OTP NVMEM device has been registered, mtd_otp_nvmem_add() unregisters the user device but leaves mtd->otp_user_nvmem set. On an error, the caller unregisters it again. For -EOPNOTSUPP, registration continues and normal teardown unregisters it again. Clear mtd->otp_user_nvmem after unregistering it. Fixes: e0489f6e221f ("mtd: core: fix error path for nvmem provider") Fixes: fe0b8213c012 ("mtd: core: Don't fail mtd_otp_nvmem_add() if OTP is unsupported") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 16629382a787..6158452be24d 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1083,6 +1083,7 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd) err: nvmem_unregister(mtd->otp_user_nvmem); + mtd->otp_user_nvmem = NULL; /* Don't report error if OTP is not supported. */ if (err == -EOPNOTSUPP) return 0; From 12b31d1acd20b3185bb5f0748db0cf6874c8e9d1 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 16 Aug 2026 18:30:58 +0200 Subject: [PATCH 054/535] mtd: core: call _get_device() with the master MTD __get_mtd_device() calls the master's _get_device() callback with mtd, which may be a partition. __put_mtd_device() passes the master. This breaks gluebi partitions because gluebi_get_device() uses container_of() and therefore requires the master MTD. Pass the master to _get_device(), matching _put_device(). Fixes: 46b5889cc2c5 ("mtd: implement proper partition handling") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Signed-off-by: Miquel Raynal --- drivers/mtd/mtdcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 6158452be24d..491a27d0315f 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -1333,7 +1333,7 @@ int __get_mtd_device(struct mtd_info *mtd) int err; if (master->_get_device) { - err = master->_get_device(mtd); + err = master->_get_device(master); if (err) return err; } From 39b975ff208610301bc400460ec245caeffa4ee0 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 24 Aug 2026 03:47:03 +0200 Subject: [PATCH 055/535] mtd: cfi_cmdset_0001: shrink do_write_buffer() stack frame arm32 allmodconfig fails to build with gcc: drivers/mtd/chips/cfi_cmdset_0001.c:1883:1: error: the frame size of 1296 bytes is larger than 1280 bytes [-Werror=frame-larger-than=] With MTD_MAP_BANK_WIDTH_32 a map_word is 32 bytes, and with KASAN_STACK every temporary gets its own redzoned slot. do_write_buffer() builds a map_word for each of the twelve commands it issues, and those temporaries dominate its frame. Issue the commands through a small noinline helper that takes the command as a plain value and builds the map_word in its own frame. The sequence of reads and writes to the chip is unchanged. The helper must not be inlined, and reusing one map_word local is not enough: clang cannot assume the callee does not observe the local, so it still returns each CMD() into a per-call-site temporary, and the frame grows (1760 -> 1824 bytes in cfi_intelext_writev(), into which clang inlines do_write_buffer()). Frame sizes with MTD_MAP_BANK_WIDTH_32 and KASAN_STACK: gcc 15.2 do_write_buffer() 1296 -> 520 clang 21 cfi_intelext_writev() 1760 -> 896 The helper itself takes 168 (gcc) / 160 (clang) bytes. Reported-by: Andy Shevchenko Closes: https://lore.kernel.org/all/20260408211407.2295175-1-andriy.shevchenko@linux.intel.com/ Suggested-by: Miquel Raynal Link: https://lore.kernel.org/all/87ik9cfm6g.fsf@bootlin.com/ Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Miquel Raynal --- drivers/mtd/chips/cfi_cmdset_0001.c | 42 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 6049ba2d6bcb..60d82a1acb01 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -1716,12 +1716,24 @@ static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t le } +/* + * Keep noinline: inlined, the map_word temporaries put do_write_buffer() over + * the frame-size limit with MTD_MAP_BANK_WIDTH_32 and KASAN_STACK. + */ +static noinline void __xipram cfi_write_cmd(struct map_info *map, + unsigned long cmd, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + + map_write(map, CMD(cmd), adr); +} + static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, unsigned long adr, const struct kvec **pvec, unsigned long *pvec_seek, int len) { struct cfi_private *cfi = map->fldrv_priv; - map_word status, write_cmd, datum; + map_word status, datum; unsigned long cmd_adr; int ret, wbufsize, word_gap, words; const struct kvec *vec; @@ -1740,9 +1752,6 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, if (is_LH28F640BF(cfi)) cmd_adr = adr; - /* Let's determine this according to the interleave only once */ - write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0xe8) : CMD(0xe9); - mutex_lock(&chip->mutex); ret = get_chip(map, chip, cmd_adr, FL_WRITING); if (ret) { @@ -1759,7 +1768,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, So we must check here and reset those bits if they're set. Otherwise we're just pissing in the wind */ if (chip->state != FL_STATUS) { - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; } status = map_read(map, cmd_adr); @@ -1767,21 +1776,22 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, xip_enable(map, chip, cmd_adr); printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %lx). Clearing.\n", status.x[0]); xip_disable(map, chip, cmd_adr); - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); } chip->state = FL_WRITING_TO_BUFFER; - map_write(map, write_cmd, cmd_adr); + cfi_write_cmd(map, (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? 0xe8 : 0xe9, + cmd_adr); ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0, 0); if (ret) { /* Argh. Not ready for write to buffer */ map_word Xstatus = map_read(map, cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; status = map_read(map, cmd_adr); - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); xip_enable(map, chip, cmd_adr); printk(KERN_ERR "%s: Chip not ready for buffer write. Xstatus = %lx, status = %lx\n", map->name, Xstatus.x[0], status.x[0]); @@ -1800,7 +1810,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, } /* Write length of data to come */ - map_write(map, CMD(words), cmd_adr ); + cfi_write_cmd(map, words, cmd_adr); /* Write data */ vec = *pvec; @@ -1837,7 +1847,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, *pvec_seek = vec_seek; /* GO GO GO */ - map_write(map, CMD(0xd0), cmd_adr); + cfi_write_cmd(map, 0xd0, cmd_adr); chip->state = FL_WRITING; ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, @@ -1845,7 +1855,7 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, chip->buffer_write_time, chip->buffer_write_time_max); if (ret) { - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); chip->state = FL_STATUS; xip_enable(map, chip, cmd_adr); printk(KERN_ERR "%s: buffer write error (status timeout)\n", map->name); @@ -1858,8 +1868,8 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, unsigned long chipstatus = MERGESTATUS(status); /* reset status */ - map_write(map, CMD(0x50), cmd_adr); - map_write(map, CMD(0x70), cmd_adr); + cfi_write_cmd(map, 0x50, cmd_adr); + cfi_write_cmd(map, 0x70, cmd_adr); xip_enable(map, chip, cmd_adr); if (chipstatus & 0x02) { From 200c32e3cd53132fbf99f1e2a5751f556755cf6d Mon Sep 17 00:00:00 2001 From: Menachem Adin Date: Thu, 27 Aug 2026 08:11:56 +0300 Subject: [PATCH 056/535] mtd: mtd_intel_dg: reset poll counter for each erase The non-posted erase polling counter is initialized only once per MTD erase request. Large requests therefore share the polling budget across all 4K erase commands and can fail with -ETIME even though no individual command timed out. Reset the counter for each 4K erase command so every operation gets the intended completion timeout. Cc: stable@vger.kernel.org Fixes: a1c940cbf505 ("drm/xe/nvm: add support for non-posted erase") Signed-off-by: Menachem Adin Signed-off-by: Alexander Usyskin Signed-off-by: Miquel Raynal --- drivers/mtd/devices/mtd_intel_dg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c index a566e86eb5e3..55a945c1a67a 100644 --- a/drivers/mtd/devices/mtd_intel_dg.c +++ b/drivers/mtd/devices/mtd_intel_dg.c @@ -387,7 +387,7 @@ idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_a void __iomem *base2 = nvm->base2; void __iomem *base = nvm->base; const u32 block = 0x10; - u32 iter = 0; + u32 iter; u32 reg; u64 i; @@ -396,6 +396,7 @@ idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_a iowrite32(region << 24 | block, base + NVM_ERASE_REG); if (nvm->non_posted_erase) { /* Wait for Erase Done */ + iter = 0; reg = ioread32(base2 + NVM_DEBUG_REG); while (!(reg & NVM_NON_POSTED_ERASE_DONE) && ++iter < NVM_NON_POSTED_ERASE_DONE_ITER) { From b890e6163761ddb580cc74f43e15f8bbde15647e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 31 Aug 2026 16:07:17 +0100 Subject: [PATCH 057/535] mtd: spinand: fix NULL pointer dereference with no ECC engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When "nand-no-ecc-engine" is set in DT, nanddev_get_ecc_engine() takes the NAND_ECC_ENGINE_TYPE_NONE path and returns success while leaving nand->ecc.engine NULL. The SPI-NAND code nevertheless dereferences it unconditionally to test for a pipelined engine, so probing such a device oopses immediately. Rather than open-coding the test three times, add a nand_ecc_is_pipelined() helper to the NAND core that folds the NULL check into the integration comparison, and use it everywhere. Future callers then cannot reintroduce the problem. Fixes: f9d7c7265bcf ("mtd: spinand: Create direct mapping descriptors for ECC operations") Cc: stable@vger.kernel.org Signed-off-by: Nuno Sá Signed-off-by: Miquel Raynal --- drivers/mtd/nand/ecc.c | 7 +++++++ drivers/mtd/nand/spi/core.c | 8 +++----- include/linux/mtd/nand.h | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c index 6ccdff3fc913..1d809cbd47e6 100644 --- a/drivers/mtd/nand/ecc.c +++ b/drivers/mtd/nand/ecc.c @@ -159,6 +159,13 @@ int nand_ecc_finish_io_req(struct nand_device *nand, } EXPORT_SYMBOL(nand_ecc_finish_io_req); +bool nand_ecc_is_pipelined(const struct nand_device *nand) +{ + return nand->ecc.engine && + nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED; +} +EXPORT_SYMBOL(nand_ecc_is_pipelined); + /* Define default OOB placement schemes for large and small page devices */ static int nand_ooblayout_ecc_sp(struct mtd_info *mtd, int section, struct mtd_oob_region *oobregion) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index bea352dd82d7..4fb769581595 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -526,8 +526,7 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, else rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && - req->mode != MTD_OPS_RAW) + if (nand_ecc_is_pipelined(nand) && req->mode != MTD_OPS_RAW) rdesc->info.op_tmpl->data.ecc = true; else rdesc->info.op_tmpl->data.ecc = false; @@ -621,8 +620,7 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand, wdesc = spinand->dirmaps[req->pos.plane].wdesc; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && - req->mode != MTD_OPS_RAW) + if (nand_ecc_is_pipelined(nand) && req->mode != MTD_OPS_RAW) wdesc->info.op_tmpl->data.ecc = true; else wdesc->info.op_tmpl->data.ecc = false; @@ -1279,7 +1277,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, struct spi_mem_dirmap_desc *desc; bool enable_ecc = false, secondary_op = false; - if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED) + if (nand_ecc_is_pipelined(nand)) enable_ecc = true; if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache) diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 09c8c93e4dba..6936180b6ea5 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -305,6 +305,8 @@ int nand_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req); int nand_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req); +bool nand_ecc_is_pipelined(const struct nand_device *nand); + bool nand_ecc_is_strong_enough(struct nand_device *nand); #if IS_REACHABLE(CONFIG_MTD_NAND_CORE) From 636fe10d8f3559210ff63108a208d39ea2e9c3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 31 Aug 2026 16:07:18 +0100 Subject: [PATCH 058/535] mtd: spinand: fix zero oobavail when no ECC engine is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 00c15b78b4b4 ("mtd: spinand: Allow the case where there is no ECC engine") made the OOB free bytes count conditional on having an ECC engine so that probing would not fail when none is requested. However mtd->oobavail is still assigned from ret just after that block, and ret is 0 there, so the device ends up advertising no available OOB bytes at all. mtd_oobavail() returns mtd->oobavail for MTD_OPS_AUTO_OOB, so a zero value makes every automatic OOB access fail with -EINVAL. JFFS2 fares worse: it keeps its cleanmarker in the OOB area on NAND and refuses to mount outright, reporting "inconsistent device description". No ECC engine also means no ooblayout was ever installed, which would make the count return -ENOTSUPP, so install the same fallback layout the on-die path already uses before counting unconditionally. Fixes: 00c15b78b4b4 ("mtd: spinand: Allow the case where there is no ECC engine") Cc: stable@vger.kernel.org Signed-off-by: Nuno Sá Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 4fb769581595..43bbe2629b85 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1996,12 +1996,17 @@ static int spinand_init(struct spinand_device *spinand) goto err_cleanup_ecc_engine; } - if (nand->ecc.engine) { - ret = mtd_ooblayout_count_freebytes(mtd); - if (ret < 0) - goto err_cleanup_ecc_engine; + if (!nand->ecc.engine) { + if (spinand->eccinfo.ooblayout) + mtd_set_ooblayout(mtd, spinand->eccinfo.ooblayout); + else + mtd_set_ooblayout(mtd, &spinand_noecc_ooblayout); } + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + goto err_cleanup_ecc_engine; + mtd->oobavail = ret; /* Propagate ECC information to mtd_info */ From 37adc9c5789c76db3b1ee7aeec3999b8503020d0 Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 1 Sep 2026 09:39:06 +0200 Subject: [PATCH 059/535] mtd: rawnand: vf610_nfc: fix reads on chips with more than 64 bytes of OOB The controller transfers 64 spare bytes per page and the driver only implements the matching 64-byte ECC layout, so attach_chip() shrinks mtd->oobsize when the chip provides more. That clamp does not survive: nand_scan_tail() runs nanddev_init() after ->attach_chip(), and it restores mtd->oobsize from the memory organization, which still holds the value detected from the chip. The driver then transfers writesize plus the chip's full OOB size, the hardware ECC parity ends up at a different offset than the layout the controller was set up for, and every ECC-protected read fails with -EBADMSG. Measured on a Colibri VF61 (MX30LF4G28AC, 2048-byte pages, 112 bytes of OOB): with the clamp lost, UBI cannot read the erase counter headers of the pages U-Boot has just written, and the on-flash bad block table written by an older kernel reads back with ECC errors, so the board does not boot. Kernels before commit a7ab085d7c16 ("mtd: rawnand: Initialize the nand_device object") are not affected because nothing overwrote the clamp there, which is why the same chip works with a v4.4 kernel and with U-Boot, whose copy of this driver has no memory organization to restore the value from. Edward Karpicz reported that the clamp no longer takes effect on this chip; see the link below. Instead of modifying the memory organization, keep the detected OOB size and give the driver its own mtd_ooblayout_ops: the same layout the NAND core uses for large pages, but computed on the first 64 OOB bytes instead of the whole OOB, so the ECC bytes stay where U-Boot and the old kernels put them. The data paths transfer writesize plus those 64 bytes, as the controller always has. Since mtd->oobsize now reports the chip's real spare size, fill the tail of oob_poi with 0xff after the 64 transferred bytes on ECC page reads: the core may copy the full mtd->oobsize from it, which would otherwise expose whatever the buffer held before. 0xff also matches what a raw read returns from flash, since the write path only ever programs the first 64 spare bytes. Reported-by: Edward Karpicz Link: https://community.toradex.com/t/colibri-vf50-vf61-on-the-current-bsp-mainline-u-boot-v2026-07-and-linux-6-18-lts/30735 Suggested-by: Miquel Raynal Fixes: a7ab085d7c16 ("mtd: rawnand: Initialize the nand_device object") Cc: stable@vger.kernel.org Signed-off-by: Mehmet Fide Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/vf610_nfc.c | 72 ++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c index 9940681810cf..1c3e7b167e53 100644 --- a/drivers/mtd/nand/raw/vf610_nfc.c +++ b/drivers/mtd/nand/raw/vf610_nfc.c @@ -505,6 +505,11 @@ static int vf610_nfc_exec_op(struct nand_chip *chip, check_only); } +static unsigned int vf610_nfc_spare_size(struct mtd_info *mtd) +{ + return min_t(unsigned int, mtd->oobsize, 64); +} + static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, uint8_t *oob, int page) { @@ -522,7 +527,7 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, return ecc_count; nfc->data_access = true; - nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize); + nand_read_oob_op(&nfc->chip, page, 0, oob, vf610_nfc_spare_size(mtd)); nfc->data_access = false; /* @@ -530,7 +535,7 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, * at least less then half of the ECC strength. */ return nand_check_erased_ecc_chunk(dat, nfc->chip.ecc.size, oob, - mtd->oobsize, NULL, 0, + vf610_nfc_spare_size(mtd), NULL, 0, flips_threshold); } @@ -551,7 +556,7 @@ static int vf610_nfc_read_page(struct nand_chip *chip, uint8_t *buf, { struct vf610_nfc *nfc = chip_to_nfc(chip); struct mtd_info *mtd = nand_to_mtd(chip); - int trfr_sz = mtd->writesize + mtd->oobsize; + int trfr_sz = mtd->writesize + vf610_nfc_spare_size(mtd); u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; int stat; @@ -577,11 +582,16 @@ static int vf610_nfc_read_page(struct nand_chip *chip, uint8_t *buf, */ vf610_nfc_rd_from_sram(buf, nfc->regs + NFC_MAIN_AREA(0), mtd->writesize, false); - if (oob_required) + if (oob_required) { + unsigned int spare = vf610_nfc_spare_size(mtd); + vf610_nfc_rd_from_sram(chip->oob_poi, nfc->regs + NFC_MAIN_AREA(0) + mtd->writesize, - mtd->oobsize, false); + spare, false); + /* Not transferred, and never written: reads back erased */ + memset(chip->oob_poi + spare, 0xff, mtd->oobsize - spare); + } stat = vf610_nfc_correct_data(chip, buf, chip->oob_poi, page); @@ -599,7 +609,7 @@ static int vf610_nfc_write_page(struct nand_chip *chip, const uint8_t *buf, { struct vf610_nfc *nfc = chip_to_nfc(chip); struct mtd_info *mtd = nand_to_mtd(chip); - int trfr_sz = mtd->writesize + mtd->oobsize; + int trfr_sz = mtd->writesize + vf610_nfc_spare_size(mtd); u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; u8 status; int ret; @@ -740,6 +750,49 @@ static void vf610_nfc_init_controller(struct vf610_nfc *nfc) } } +/* + * With 64 byte OOB chips the core's large page layout matches what + * U-Boot uses, and on chips with more, U-Boot and older kernels clamped + * mtd->oobsize to 64. Modifying the OOB size is no longer possible, the + * actual chip geometry must be respected, so to avoid breaking those + * existing setups use our own layout: the core's large page one, + * computed over the first 64 spare bytes only. + */ +static int vf610_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section || !total_ecc_bytes) + return -ERANGE; + + oobregion->length = total_ecc_bytes; + oobregion->offset = vf610_nfc_spare_size(mtd) - oobregion->length; + + return 0; +} + +static int vf610_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section) + return -ERANGE; + + oobregion->length = vf610_nfc_spare_size(mtd) - total_ecc_bytes - 2; + oobregion->offset = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops vf610_nfc_ooblayout_ops = { + .ecc = vf610_nfc_ooblayout_ecc, + .free = vf610_nfc_ooblayout_free, +}; + static int vf610_nfc_attach_chip(struct nand_chip *chip) { struct mtd_info *mtd = nand_to_mtd(chip); @@ -770,12 +823,7 @@ static int vf610_nfc_attach_chip(struct nand_chip *chip) return -ENXIO; } - /* Only 64 byte ECC layouts known */ - if (mtd->oobsize > 64) - mtd->oobsize = 64; - - /* Use default large page ECC layout defined in NAND core */ - mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + mtd_set_ooblayout(mtd, &vf610_nfc_ooblayout_ops); if (chip->ecc.strength == 32) { nfc->ecc_mode = ECC_60_BYTE; chip->ecc.bytes = 60; From 68fe2faf5c69d0e21f94cd695d28f0ba677d484f Mon Sep 17 00:00:00 2001 From: Mehmet Fide Date: Tue, 1 Sep 2026 09:39:07 +0200 Subject: [PATCH 060/535] mtd: rawnand: vf610_nfc: fix false bitflips on reads of erased pages When the ECC engine fails to decode a page, the driver re-reads the OOB area with the engine bypassed, but runs the erased-page check for the data area on the buffer left in the controller SRAM by the failed transfer. That buffer does not hold what is on the flash: the failing engine writes a bogus single-bit "correction" into it. In the 60-byte ECC mode the all-0xff content of an erased page always decodes to the same error location, so every erased page shows one stale zero bit at data offset 0x5FD, which the erased-page check then reports as a corrected bitflip. Edward Karpicz discovered this behaviour and identified the offset on a Colibri VF61; the analysis and the fix build on his finding. Measured with an instrumented driver on a Colibri VF50 (MX30LF1G18AC, 32-bit ECC): reading a 126 MiB partition with nanddump increased the corrected counter by 18035, exactly one per erased page, while raw reads of the same pages return clean 0xff. A v4.4 kernel on the VF61 (MX30LF4G28AC) accumulates the same false counts, so the behaviour follows the controller rather than the chip or the driver generation. Neither the Vybrid reference manual nor the published mask set errata (VFXXX_2N02G) document it. The 45-byte ECC mode is not affected. Restoring the known byte is not enough: on pages that fail to decode with content other than all-0xff the engine writes its correction wherever the syndrome points (measured at a different offset on such a page), so the check has to run on what the flash holds. Re-read the data area with the ECC engine bypassed, exactly as already done for the OOB area. The corrected counter then stays at zero on both boards. Reported-by: Edward Karpicz Link: https://community.toradex.com/t/colibri-vf50-vf61-on-the-current-bsp-mainline-u-boot-v2026-07-and-linux-6-18-lts/30735 Signed-off-by: Mehmet Fide Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/vf610_nfc.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c index 1c3e7b167e53..1b9b370adfab 100644 --- a/drivers/mtd/nand/raw/vf610_nfc.c +++ b/drivers/mtd/nand/raw/vf610_nfc.c @@ -519,6 +519,7 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, u8 ecc_status; u8 ecc_count; int flips_threshold = nfc->chip.ecc.strength / 2; + int ret; ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff; ecc_count = ecc_status & ECC_STATUS_ERR_COUNT; @@ -526,15 +527,21 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, if (!(ecc_status & ECC_STATUS_MASK)) return ecc_count; + /* The failed decode leaves a bogus correction in SRAM; re-read without ECC */ nfc->data_access = true; - nand_read_oob_op(&nfc->chip, page, 0, oob, vf610_nfc_spare_size(mtd)); + ret = nand_read_page_op(&nfc->chip, page, 0, dat, mtd->writesize); + if (!ret) + ret = nand_read_oob_op(&nfc->chip, page, 0, oob, + vf610_nfc_spare_size(mtd)); nfc->data_access = false; + if (ret) + return ret; /* - * On an erased page, bit count (including OOB) should be zero or - * at least less then half of the ECC strength. + * Run the erased-page check with the driver's historic threshold + * of half the ECC strength. */ - return nand_check_erased_ecc_chunk(dat, nfc->chip.ecc.size, oob, + return nand_check_erased_ecc_chunk(dat, mtd->writesize, oob, vf610_nfc_spare_size(mtd), NULL, 0, flips_threshold); } From 21f027016b1290d13c30b198ae7a00e6b3d1d5a5 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 15:05:42 +0800 Subject: [PATCH 061/535] mtd: rawnand: cadence: Initialize IRQ state before requesting IRQ The Cadence NAND interrupt handler uses both the IRQ lock and completion object. Registering the IRQ before initializing them leaves a window in which a pending interrupt can access uninitialized synchronization state. Initialize them before registering the handler. Fixes: ec4ba01e894d ("mtd: rawnand: Add new Cadence NAND driver to MTD subsystem") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Miquel Raynal --- drivers/mtd/nand/raw/cadence-nand-controller.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c index d53b35a8b3cb..2085632631c0 100644 --- a/drivers/mtd/nand/raw/cadence-nand-controller.c +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -3143,6 +3143,9 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl) goto free_buf_desc; } + spin_lock_init(&cdns_ctrl->irq_lock); + init_completion(&cdns_ctrl->complete); + if (devm_request_irq(cdns_ctrl->dev, cdns_ctrl->irq, cadence_nand_isr, IRQF_SHARED, "cadence-nand-controller", cdns_ctrl)) { @@ -3151,9 +3154,6 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl) goto free_buf; } - spin_lock_init(&cdns_ctrl->irq_lock); - init_completion(&cdns_ctrl->complete); - ret = cadence_nand_hw_init(cdns_ctrl); if (ret) goto disable_irq; From 0f300db7b7315b2c7ea0e2d4437b88de162d191f Mon Sep 17 00:00:00 2001 From: Vladyslav Ivashchenko Date: Fri, 4 Sep 2026 15:31:55 +0200 Subject: [PATCH 062/535] iio: adc: axp288: Add TS bias override for Haier HV103H The Haier HV103H firmware configures the AXP288 TS pin bias current to 60 uA. This causes the battery temperature reading to cross the charging temperature threshold under load, incorrectly stopping battery charging. Setting the TS bias current to 80 uA fixes the temperature measurement and prevents charging from being incorrectly disabled. Add a DMI quirk to use the 80 uA TS bias current on the Haier HV103H. Fixes: 9bcf15f75cac ("iio: adc: axp288: Fix TS-pin handling") Fixes: 048058399f19 ("iio: adc: axp288: Override TS pin bias current for some models") # Add necessary infrastructure Signed-off-by: Vladyslav Ivashchenko Reviewed-by: Hans de Goede Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/axp288_adc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c index c8283279c477..c98863d612ad 100644 --- a/drivers/iio/adc/axp288_adc.c +++ b/drivers/iio/adc/axp288_adc.c @@ -207,6 +207,14 @@ static const struct dmi_system_id axp288_adc_ts_bias_override[] = { }, .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, }, + { + /* Haier HV103H */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Haier"), + DMI_MATCH(DMI_PRODUCT_NAME, "HV103H"), + }, + .driver_data = (void *)(uintptr_t)AXP288_ADC_TS_BIAS_80UA, + }, { } }; From 8b958dd214ad0c003f56549e9e039cb8067dc1a2 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sun, 6 Sep 2026 23:58:34 +0800 Subject: [PATCH 063/535] iio: adc: sun4i-gpadc-iio: drop underflowing pm_runtime_put() calls Neither the error path in sun4i_gpadc_probe() nor sun4i_gpadc_remove() ever holds a runtime PM usage count. So the pm_runtime_put() in both places always triggers the "Runtime PM usage count underflow!" warning on every failed probe and every unbind. Drop both calls. Fixes: d1caa9905538 ("iio: adc: add support for Allwinner SoCs ADC") Signed-off-by: Felix Gu Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/sun4i-gpadc-iio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c index 203459ca9907..85382e435f2a 100644 --- a/drivers/iio/adc/sun4i-gpadc-iio.c +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -657,7 +657,6 @@ static int sun4i_gpadc_probe(struct platform_device *pdev) if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) iio_map_array_unregister(indio_dev); - pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); return ret; @@ -668,7 +667,6 @@ static void sun4i_gpadc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct sun4i_gpadc_iio *info = iio_priv(indio_dev); - pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); if (!IS_ENABLED(CONFIG_THERMAL_OF)) From 2cdc7ef31827db4fbc6283750420639569e1f0e9 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sun, 6 Sep 2026 23:58:35 +0800 Subject: [PATCH 064/535] iio: adc: sun4i-gpadc-iio: clean up on thermal zone registration failure If devm_thermal_of_zone_register() fails, probe returns without unregistering the IIO map array or disabling runtime PM. Jump to err_map to release them. Fixes: b0a242894f11 ("iio: adc: sun4i-gpadc-iio: register in the thermal after registering in pm") Signed-off-by: Felix Gu Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/sun4i-gpadc-iio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c index 85382e435f2a..ac5ba76b979e 100644 --- a/drivers/iio/adc/sun4i-gpadc-iio.c +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -638,10 +638,10 @@ static int sun4i_gpadc_probe(struct platform_device *pdev) * thermal because no thermal DT node is found. */ if (IS_ERR(info->tzd) && PTR_ERR(info->tzd) != -ENODEV) { - dev_err(&pdev->dev, - "could not register thermal sensor: %ld\n", - PTR_ERR(info->tzd)); - return PTR_ERR(info->tzd); + ret = dev_err_probe(&pdev->dev, + PTR_ERR(info->tzd), + "could not register thermal sensor\n"); + goto err_map; } } From 8065a6c02f629187131f3136bedb26f9080e3cca Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 2 Sep 2026 11:48:46 +0300 Subject: [PATCH 065/535] iio: light: rohm-bu27034: Fix infinite delay on error When reading an integration-time fails, the code will use error code to compute the sleep time. Fix this by using the smallest integration time as a default if reading fails. Fixes: e52afbd61039 ("iio: light: ROHM BU27034 Ambient Light Sensor") Suggested-by: Jonathan Cameron Signed-off-by: Matti Vaittinen Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/light/rohm-bu27034.c | 60 +++++++++++++++++++------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c index f9a421618406..92fca33387bb 100644 --- a/drivers/iio/light/rohm-bu27034.c +++ b/drivers/iio/light/rohm-bu27034.c @@ -137,6 +137,7 @@ static const struct iio_gain_sel_pair bu27034_gains[] = { #define BU27034_MEAS_MODE_200MS 2 #define BU27034_MEAS_MODE_400MS 4 +#define BU27034_INT_TIME_US_MIN (55 * USEC_PER_MSEC) static const struct iio_itime_sel_mul bu27034_itimes[] = { GAIN_SCALE_ITIME_US(400000, BU27034_MEAS_MODE_400MS, 8), GAIN_SCALE_ITIME_US(200000, BU27034_MEAS_MODE_200MS, 4), @@ -296,7 +297,7 @@ static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain) return 0; } -static int bu27034_get_int_time(struct bu27034_data *data) +static int bu27034_get_int_time(struct bu27034_data *data, int *itime) { int ret, sel; @@ -304,24 +305,30 @@ static int bu27034_get_int_time(struct bu27034_data *data) if (ret) return ret; - return iio_gts_find_int_time_by_sel(&data->gts, - sel & BU27034_MASK_MEAS_MODE); + ret = iio_gts_find_int_time_by_sel(&data->gts, + sel & BU27034_MASK_MEAS_MODE); + if (ret < 0) + return ret; + + *itime = ret; + + return 0; } static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val, int *val2) { - int gain, ret; + int gain, itime, ret; ret = bu27034_get_gain(data, channel, &gain); if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &itime); + if (ret) return ret; - return iio_gts_get_scale(&data->gts, gain, ret, val, val2); + return iio_gts_get_scale(&data->gts, gain, itime, val, val2); } static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val, @@ -397,12 +404,10 @@ static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) int ret, int_time_old, i; guard(mutex)(&data->mutex); - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &int_time_old); + if (ret) return ret; - int_time_old = ret; - if (!iio_gts_valid_time(&data->gts, time_us)) { dev_err(data->dev, "Unsupported integration time %u\n", time_us); @@ -841,7 +846,7 @@ static int bu27034_meas_set(struct bu27034_data *data, bool en) static int bu27034_get_single_result(struct bu27034_data *data, int chan, int *val) { - int ret; + int ret, itime; if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA1) return -EINVAL; @@ -850,11 +855,11 @@ static int bu27034_get_single_result(struct bu27034_data *data, int chan, if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &itime); + if (ret) return ret; - msleep(ret / 1000); + msleep(itime / 1000); return bu27034_read_result(data, chan, val); } @@ -904,12 +909,10 @@ static int bu27034_calc_mlux(struct bu27034_data *data, __le16 *res, int *val) if (ret) return ret; - ret = bu27034_get_int_time(data); - if (ret < 0) + ret = bu27034_get_int_time(data, &meastime); + if (ret) return ret; - meastime = ret; - d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100; helper64 = (u64)ch1 * (u64)gain0 * 100LLU; @@ -970,9 +973,9 @@ static int bu27034_read_raw(struct iio_dev *idev, switch (mask) { case IIO_CHAN_INFO_INT_TIME: *val = 0; - *val2 = bu27034_get_int_time(data); - if (*val2 < 0) - return *val2; + ret = bu27034_get_int_time(data, val2); + if (ret) + return ret; return IIO_VAL_INT_PLUS_MICRO; @@ -1157,11 +1160,20 @@ static int bu27034_buffer_thread(void *arg) { struct iio_dev *idev = arg; struct bu27034_data *data; - int wait_ms; + int wait_ms, ret; data = iio_priv(idev); - wait_ms = bu27034_get_int_time(data); + /* + * If reading the integration time fails, default to the minimum so we + * don't lose samples. This may waste CPU cycles, but as a hardening + * against theoretical, once-in-a-blue-moon error, this should be Ok. + */ + wait_ms = BU27034_INT_TIME_US_MIN; + ret = bu27034_get_int_time(data, &wait_ms); + if (ret) + dev_warn(data->dev, "Failed to get integration time\n"); + wait_ms /= 1000; wait_ms -= BU27034_MEAS_WAIT_PREMATURE_MS; From e65e128b51a37dd1a9056ac3d1705d805a5c8bb0 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Wed, 2 Sep 2026 11:49:05 +0300 Subject: [PATCH 066/535] iio: accel: kionix-kx022a: Prevent memory leak and fix state The driver allocates memory for samples at buffer enable path. If regmap operation fails in the kx022a_fifo_enable() at the buffer enable path, the allocated memory is never freed. Furthermore, the state information and previous hardware configuration(s) aren't undone, potentially leaving WMI interrupts and buffers enabled, or driver state flags wrong. Free the memory and revert the hardware configuration and state flags on error path. Fixes: e7123a4dfcd7 ("iio: accel: kionix-kx022a: Refactor driver and add chip_info structure") Signed-off-by: Matti Vaittinen Reviewed-by: Mehdi Djait Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kionix-kx022a.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/kionix-kx022a.c b/drivers/iio/accel/kionix-kx022a.c index f671fab3dc31..9ff70a373141 100644 --- a/drivers/iio/accel/kionix-kx022a.c +++ b/drivers/iio/accel/kionix-kx022a.c @@ -980,26 +980,44 @@ static int kx022a_fifo_enable(struct kx022a_data *data) guard(mutex)(&data->mutex); ret = __kx022a_turn_on_off(data, false); if (ret) - return ret; + goto err_free_out; /* Update watermark to HW */ ret = kx022a_fifo_set_wmi(data); if (ret) - return ret; + goto err_turn_on_out; /* Enable buffer */ ret = regmap_set_bits(data->regmap, data->chip_info->buf_cntl2, KX022A_MASK_BUF_EN); if (ret) - return ret; + goto err_turn_on_out; data->state |= KX022A_STATE_FIFO; ret = regmap_set_bits(data->regmap, data->ien_reg, KX022A_MASK_WMI); if (ret) - return ret; + goto err_buf_disable_out; - return __kx022a_turn_on_off(data, true); + ret = __kx022a_turn_on_off(data, true); + if (ret) + goto err_wmi_clear_out; + + return ret; + +err_wmi_clear_out: + regmap_clear_bits(data->regmap, data->ien_reg, + KX022A_MASK_WMI); +err_buf_disable_out: + regmap_clear_bits(data->regmap, data->chip_info->buf_cntl2, + KX022A_MASK_BUF_EN); + data->state &= ~KX022A_STATE_FIFO; +err_turn_on_out: + __kx022a_turn_on_off(data, true); +err_free_out: + kfree(data->fifo_buffer); + + return ret; } static int kx022a_buffer_postenable(struct iio_dev *idev) From ef7707c1b7c7bcd09298114fa64d1494727e20b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuna=20K=C4=B1l=C4=B1=C3=A7?= Date: Fri, 4 Sep 2026 12:25:17 +0000 Subject: [PATCH 067/535] iio: dac: mcp47a1: Allow full-scale output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP47A1 has 64 resistors but exposes 65 wiper positions. The valid DAC codes are 0 through 64, and code 64 selects VREF. The driver currently advertises 0 through 63 and uses 64 as the length argument to in_range(), which also rejects 64. Therefore userspace cannot select full-scale output. Advertise code 64 and make validation cover all 65 codes. Keep 64 as the scale denominator because the output voltage is VREF multiplied by the code and divided by 64. Fixes: 350d1fb9204b ("iio: dac: mcp47a1: add support for new device") Signed-off-by: Tuna Kılıç Reviewed-by: Joshua Crofts Tested-by: Joshua Crofts Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/dac/mcp47a1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/dac/mcp47a1.c b/drivers/iio/dac/mcp47a1.c index 0bf994aa0e4f..3ed306f3060f 100644 --- a/drivers/iio/dac/mcp47a1.c +++ b/drivers/iio/dac/mcp47a1.c @@ -26,7 +26,7 @@ struct mcp47a1_data { int vref_mV; }; -static const int mcp47a1_raw_avail[] = { 0, 1, MCP47A1_MAX_STEPS - 1 }; +static const int mcp47a1_raw_avail[] = { 0, 1, MCP47A1_MAX_STEPS }; static const struct iio_chan_spec mcp47a1_channel = { .type = IIO_VOLTAGE, @@ -46,7 +46,7 @@ static int mcp47a1_write(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (!in_range(val, 0, MCP47A1_MAX_STEPS)) + if (!in_range(val, 0, MCP47A1_MAX_STEPS + 1)) return -EINVAL; return i2c_smbus_write_byte_data(data->client, MCP47A1_CMD_CODE, From e24328974a88aa8541e94e8bb8dfa2ade1b3c45d Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Wed, 2 Sep 2026 00:21:26 +0900 Subject: [PATCH 068/535] iio: proximity: vl53l0x-i2c: claim direct mode for raw reads vl53l0x_read_raw() starts a single-shot ranging measurement and reads back the result. Once the triggered buffer is enabled the sensor runs in continuous mode and its data-ready interrupt is routed to the trigger, so a concurrent in_distance_raw read disturbs the streaming setup and never gets its completion, returning -ETIMEDOUT. The original submission claimed direct mode here, but it was dropped during review because the driver had no buffer support at the time [1]. Continuous (buffered) mode was later added without restoring the claim [2], reintroducing the conflict. Reject direct reads while buffered capture is active by claiming direct mode around the measurement, as the vl53l1x sibling already does. Fixes: 762186c6e7b1 ("iio: proximity: vl53l0x-i2c: Added continuous mode support") Link: https://lore.kernel.org/linux-iio/20180911160300.GA9212@himanshu-Vostro-3559/ [1] Link: https://lore.kernel.org/linux-iio/20240909101508.263085-3-abhashkumarjha123@gmail.com/ [2] Signed-off-by: Donggeun Yoo Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/vl53l0x-i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c index 9fe14ceb8be7..173e69753df7 100644 --- a/drivers/iio/proximity/vl53l0x-i2c.c +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -219,7 +219,10 @@ static int vl53l0x_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; ret = vl53l0x_read_proximity(data, chan, val); + iio_device_release_direct(indio_dev); if (ret < 0) return ret; From 7d38af8f20239a66e90de7632a79c9425256c574 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Mon, 24 Aug 2026 05:29:07 +0100 Subject: [PATCH 069/535] iio: proximity: vcnl3020: fix ISR bitmask check in IRQ handler The threaded IRQ handler contained multiple issues in handling interrupt events and clearing status flags: 1. ISR bit check: The handler incorrectly checked the Interrupt Status Register (VCNL_ISR) against VCNL_ICR_THRES_EN (BIT(1)), which is a bitmask meant for the Control Register (VCNL_PS_ICR). In VCNL_ISR, BIT(1) corresponds only to low-threshold interrupts. A high-threshold interrupt (VCNL_INT_TH_HI, BIT(0)) on its own was completely ignored and returned IRQ_NONE. 2. Event direction & channel index: The handler unconditionally pushed a RISING event code on channel index 1. The driver only registers a single proximity channel (index 0), and low-threshold interrupts should be reported with IIO_EV_DIR_FALLING. 3. ISR clearing: The write-back to acknowledge the interrupt only preserved BIT(1) instead of masking against both valid status bits. Fix this by checking both VCNL_INT_TH_HI and VCNL_INT_TH_LOW bits in VCNL_ISR, pushing separate IIO events with the correct direction and channel index (0), and properly clearing handled status bits. Fixes: 3363fbbe19e5 ("iio: proximity: vcnl3020: add periodic mode") Signed-off-by: Salah Triki Reviewed-by: Ivan Mikhaylov Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/vcnl3020.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c index 6b0d7a9038d9..768870127ff8 100644 --- a/drivers/iio/proximity/vcnl3020.c +++ b/drivers/iio/proximity/vcnl3020.c @@ -584,16 +584,26 @@ static irqreturn_t vcnl3020_handle_irq_thread(int irq, void *p) return IRQ_HANDLED; } - if (!(isr & VCNL_ICR_THRES_EN)) + if (!(isr & (VCNL_INT_TH_HI | VCNL_INT_TH_LOW))) return IRQ_NONE; - iio_push_event(indio_dev, - IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_RISING), - iio_get_time_ns(indio_dev)); + if (isr & VCNL_INT_TH_HI) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + } - rc = regmap_write(data->regmap, VCNL_ISR, isr & VCNL_ICR_THRES_EN); + if (isr & VCNL_INT_TH_LOW) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns(indio_dev)); + } + + rc = regmap_write(data->regmap, VCNL_ISR, isr & (VCNL_INT_TH_HI | VCNL_INT_TH_LOW)); if (rc) dev_err(data->dev, "Error (%d) writing in reg (0x%x)\n", rc, VCNL_ISR); From b51faf3f2e307f114854bbe6d495f03cd5370686 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Fri, 4 Sep 2026 16:00:46 +0800 Subject: [PATCH 070/535] USB: serial: option: add Compal EXC-T1 support The Compal EXC-T1 is a Qualcomm MDM9207-based LTE modem which reports "EXC-x1" as its USB product string. It exposes diagnostic, modem, AT, and NMEA ports as vendor-specific interfaces with subclass 0x10 and protocols 0x01 through 0x04. Add exact interface matches for these four ports, leaving the ff/ff/ff interface unbound. Relevant output from usb-devices: T: Bus=03 Lev=02 Prnt=03 Port=03 Cnt=01 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=04b7 ProdID=4d22 Rev=03.18 S: Manufacturer=CEI S: Product=EXC-x1 C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=10 Prot=01 Driver=option I: If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=02 Driver=option I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=03 Driver=option I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=04 Driver=option I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#= 5 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=05 Driver=qmi_wwan Tested on a Compal EXC-T1 modem using an Ubuntu kernel build. The four serial interfaces bound to option and exposed ttyUSB3 through ttyUSB6. Assisted-by: Codex:GPT-5 Signed-off-by: Ian Lin Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index d530dbceccb1..828b1be3fee2 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2436,6 +2436,10 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, From 33c920f7c5952dfc42d1b8ea6efe16b1f0bb0c5b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:43 +0200 Subject: [PATCH 071/535] USB: serial: fix port tear down use-after-free Some drivers for multiport devices access port driver data from completion handlers of shared URBs submitted at attach() or first open() and stopped at disconnect() or last close(), respectively. A simple NULL check before accessing the driver data makes sure that a port state container has at least been allocated, but a completion handler can still race with port tear down. Reorder the disconnect handling so that ports are not deregistered (and their driver data freed) until after all ports have been hung up and the driver disconnect() callback has run so that all I/O has been stopped. Fixes: 2d93148ab698 ("USB: serial: fix lifetime and locking problems") Reported-by: syzbot+e5e28c3e953b2eebb16e@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a7e6fb9.ec5dc6cc.21cb3f.00c0.GAE@google.com/ Cc: stable@vger.kernel.org # 2.6.30 Cc: Alan Stern Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 17edc057a311..a4fbc849c0fa 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1191,11 +1191,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) usb_serial_port_poison_urbs(port); wake_up_interruptible(&port->port.delta_msr_wait); cancel_work_sync(&port->work); + } + + if (serial->type->disconnect) + serial->type->disconnect(serial); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; if (device_is_registered(&port->dev)) device_del(&port->dev); } - if (serial->type->disconnect) - serial->type->disconnect(serial); release_sibling(serial, interface); From 63594e8ffe097bc0b7639936f33799a7653dded2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:44 +0200 Subject: [PATCH 072/535] USB: serial: fix dynamic id driver deregistration race Only free the dynamic ids after having deregistered the driver and removed the "new_id" attribute to avoid leaking any id added by a racing write to the attribute. Fixes: 93bacefc4cc0 ("USB serial: add dynamic id support to usb-serial core") Cc: stable@vger.kernel.org # 2.6.21 Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 9e2a18c0b218..295c61afa44c 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -165,7 +165,7 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) void usb_serial_bus_deregister(struct usb_serial_driver *driver) { - free_dynids(driver); driver_unregister(&driver->driver); + free_dynids(driver); } From 646e5b28b4fdbc1b828a2398d6ced2c1dd308a46 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:45 +0200 Subject: [PATCH 073/535] USB: serial: fix driver deregistration order USB serial driver modules register one driver for the USB bus and one or more drivers for the ports on the USB serial bus. When unloading a driver module, the USB driver must be deregistered before the USB serial bus drivers so that I/O is stopped before unbinding the ports to avoid use-after-free in completion handlers accessing port data. Note that the "new_id" attributes must first be removed to prevent new ids from being added and triggering a probe of the USB driver after it has been deregistered. Fixes: 765e0ba62613 ("usb-serial: new API for driver registration") Cc: stable@vger.kernel.org # 3.4 Cc: Alan Stern Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 4 ++++ drivers/usb/serial/usb-serial.c | 21 ++++++++++++++++++--- include/linux/usb/serial.h | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 295c61afa44c..ea1fe5d1e449 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -169,3 +169,7 @@ void usb_serial_bus_deregister(struct usb_serial_driver *driver) free_dynids(driver); } +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) +{ + driver_remove_file(&driver->driver, &driver_attr_new_id); +} diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index a4fbc849c0fa..11a0ed6d6546 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1469,7 +1469,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers { int rc; struct usb_driver *udriver; - struct usb_serial_driver * const *sd; + struct usb_serial_driver * const *sd, * const *s; /* * udriver must be registered before any of the serial drivers, @@ -1522,9 +1522,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers return 0; err_deregister_drivers: + for (s = serial_drivers; s < sd; ++s) + usb_serial_bus_remove_new_id(*s); + usb_deregister(udriver); while (sd-- > serial_drivers) usb_serial_deregister(*sd); - usb_deregister(udriver); err_free_driver: kfree(udriver); return rc; @@ -1542,10 +1544,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) { struct usb_driver *udriver = (*serial_drivers)->usb_driver; + struct usb_serial_driver * const *sd; + + /* + * udriver must be deregistered before the serial drivers so that + * I/O is stopped before unbinding the ports. + * + * Remove the new_id attributes to prevent ids from being added and + * triggering a probe of udriver after it has been deregistered. + */ + for (sd = serial_drivers; *sd; ++sd) + usb_serial_bus_remove_new_id(*sd); + + usb_deregister(udriver); for (; *serial_drivers; ++serial_drivers) usb_serial_deregister(*serial_drivers); - usb_deregister(udriver); + kfree(udriver); } EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 534e6650e2aa..ab0a32a90629 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, int usb_serial_bus_register(struct usb_serial_driver *device); void usb_serial_bus_deregister(struct usb_serial_driver *device); +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); extern const struct bus_type usb_serial_bus_type; extern struct tty_driver *usb_serial_tty_driver; From 8fe535227455db566bcaf40fd83db50b16dce935 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:46 +0200 Subject: [PATCH 074/535] USB: serial: use iterator for driver deregistration Use the new serial driver iterator variable also when deregistering serial drivers to improve readability. Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 11a0ed6d6546..9c51b44e2284 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1558,8 +1558,8 @@ void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_driver usb_deregister(udriver); - for (; *serial_drivers; ++serial_drivers) - usb_serial_deregister(*serial_drivers); + for (sd = serial_drivers; *sd; ++sd) + usb_serial_deregister(*sd); kfree(udriver); } From ae0e61d95677efa778379853e495d748e6efbf95 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 3 Sep 2026 18:31:46 +0200 Subject: [PATCH 075/535] USB: serial: fix ioctl hangup race The tty ioctls can race with hangup and end up calling into a tty driver for a device that is already gone or powered down. Serialise the callbacks using the tty port mutex and add the missing checks to make sure the port has not been hung up before accessing port driver data or hardware. This specifically avoids dereferencing a NULL-pointer if a device is disconnected during lengthy break signalling. Note that TIOCGICOUNT and TIOCMIWAIT do not access port driver data or hardware and are therefore not affected by the race. Fixes: f34d7a5b7010 ("tty: The big operations rework") Reported-by: Nick Bowler Link: https://lore.kernel.org/all/CADyTPExB2kYOOwkO0JqGhKaYVDqO9uS9WCw0J=MCTdVhcGOogA@mail.gmail.com/ Reported-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6a9087a4.4d659fcc.734b4.0013.GAE@google.com Cc: stable@vger.kernel.org # 2.6.26 Signed-off-by: Johan Hovold --- drivers/usb/serial/generic.c | 10 +++- drivers/usb/serial/usb-serial.c | 94 ++++++++++++++++++++++++++------- drivers/usb/serial/xr_serial.c | 10 ++-- 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 6eaf74930aa3..17272701fab0 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; unsigned int bps; unsigned long period; unsigned long expire; @@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) __func__, jiffies_to_msecs(timeout), jiffies_to_msecs(period)); expire = jiffies + timeout; - while (!port->serial->type->tx_empty(port)) { + for (;;) { + mutex_lock(&tport->mutex); + if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { + mutex_unlock(&tport->mutex); + break; + } + mutex_unlock(&tport->mutex); + schedule_timeout_interruptible(period); if (signal_pending(current)) break; diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 9c51b44e2284..f3c594f1a806 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) static void serial_wait_until_sent(struct tty_struct *tty, int timeout) { struct usb_serial_port *port = tty->driver_data; - struct usb_serial *serial = port->serial; dev_dbg(&port->dev, "%s\n", __func__); if (!port->serial->type->wait_until_sent) return; - mutex_lock(&serial->disc_mutex); - if (!serial->disconnected) - port->serial->type->wait_until_sent(tty, timeout); - mutex_unlock(&serial->disc_mutex); + port->serial->type->wait_until_sent(tty, timeout); } static void serial_throttle(struct tty_struct *tty) @@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) struct usb_serial_port *port = tty->driver_data; struct tty_port *tport = &port->port; unsigned int close_delay, closing_wait; + int ret = 0; mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } close_delay = jiffies_to_msecs(tport->close_delay) / 10; closing_wait = tport->closing_wait; @@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) if (port->serial->type->get_serial) port->serial->type->get_serial(tty, ss); - +out_unlock: mutex_unlock(&tport->mutex); - return 0; + return ret; } static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) @@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) closing_wait = msecs_to_jiffies(closing_wait * 10); mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } if (!capable(CAP_SYS_ADMIN)) { if (close_delay != tport->close_delay || @@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; int retval = -ENOIOCTLCMD; dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); @@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, retval = port->serial->type->tiocmiwait(tty, arg); break; default: - if (port->serial->type->ioctl) + if (!port->serial->type->ioctl) + break; + + if (cmd == TIOCSRS485) + down_write(&tty->termios_rwsem); + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + retval = -EIO; + else retval = port->serial->type->ioctl(tty, cmd, arg); + mutex_unlock(&tport->mutex); + + if (cmd == TIOCSRS485) + up_write(&tty->termios_rwsem); } return retval; @@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, const struct ktermios *old) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->set_termios) - port->serial->type->set_termios(tty, port, old); - else + if (!port->serial->type->set_termios) { tty_termios_copy_hw(&tty->termios, old); + return; + } + + mutex_lock(&tport->mutex); + if (!tty_io_error(tty)) + port->serial->type->set_termios(tty, port, old); + mutex_unlock(&tport->mutex); } static int serial_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->break_ctl) - return port->serial->type->break_ctl(tty, break_state); + if (!port->serial->type->break_ctl) + return -ENOTTY; - return -ENOTTY; + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->break_ctl(tty, break_state); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_proc_show(struct seq_file *m, void *v) @@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) static int serial_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmget) - return port->serial->type->tiocmget(tty); - return -ENOTTY; + if (!port->serial->type->tiocmget) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmget(tty); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmset) - return port->serial->type->tiocmset(tty, set, clear); - return -ENOTTY; + if (!port->serial->type->tiocmset) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmset(tty, set, clear); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_get_icount(struct tty_struct *tty, diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 352c765d8803..c08f4aa14a3d 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; struct xr_data *data = usb_get_serial_port_data(port); - down_read(&tty->termios_rwsem); - if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { - up_read(&tty->termios_rwsem); + /* core holds port mutex */ + if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) return -EFAULT; - } - up_read(&tty->termios_rwsem); return 0; } @@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, return -EFAULT; xr_sanitize_serial_rs485(&rs485); - down_write(&tty->termios_rwsem); + /* core holds termios rwsem and port mutex */ data->rs485 = rs485; xr_set_flow_mode(tty, port, NULL); - up_write(&tty->termios_rwsem); if (copy_to_user(argp, &rs485, sizeof(rs485))) return -EFAULT; From 86ad6489e38c76c06c0b4a4229391a794023b5e0 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 15:22:07 +0100 Subject: [PATCH 076/535] ARC: cleanup dead ARC_CANT_LLSC option in Kconfig Currently, the ARC_CANT_LLSC config option can never be enabled. It is also not referenced anywhere else. So let's remove this dead code. Note that I have only compile-tested these changes, because I do not have the hardware to runtime test. This dead code was found by kconfirm, a static analysis tool for Kconfig. Signed-off-by: Julian Braha Signed-off-by: Vineet Gupta --- arch/arc/Kconfig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/arc/Kconfig b/arch/arc/Kconfig index 2ed7186c81c5..80e61175bf52 100644 --- a/arch/arc/Kconfig +++ b/arch/arc/Kconfig @@ -331,13 +331,9 @@ config ARC_FPU_SAVE_RESTORE for floating point exceptions and rounding modes. These are preserved across task context switch when enabled. -config ARC_CANT_LLSC - def_bool n - config ARC_HAS_LLSC bool "Insn: LLOCK/SCOND (efficient atomic ops)" default y - depends on !ARC_CANT_LLSC if ISA_ARCV2 From 04eb7b5e043fbccf83d27a9be5c3973c2dbb110f Mon Sep 17 00:00:00 2001 From: Anthony Iliopoulos Date: Thu, 20 Aug 2026 15:13:39 +0200 Subject: [PATCH 077/535] arc: remove unused profile.h includes None of the code in these files uses declarations provided by profile.h. Remove the unnecessary header includes across the various files to clean up dependencies and reduce include bloat. Signed-off-by: Anthony Iliopoulos Signed-off-by: Vineet Gupta --- arch/arc/kernel/smp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arc/kernel/smp.c b/arch/arc/kernel/smp.c index 2d99dffed0ce..ee210a94ce45 100644 --- a/arch/arc/kernel/smp.c +++ b/arch/arc/kernel/smp.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include From d12c6a6f0c8fd5b5b41120f7be319f743dae3b4f Mon Sep 17 00:00:00 2001 From: blaze <1466528493@qq.com> Date: Wed, 9 Sep 2026 12:42:59 +0800 Subject: [PATCH 078/535] arc: kernel: Fix clk reference leak in show_cpuinfo() In show_cpuinfo(), clk_get() acquires a reference to the CPU clock, but clk_put() is never called to release it. Since show_cpuinfo() is the seq_file show callback for /proc/cpuinfo, this causes a clock reference count leak every time /proc/cpuinfo is read. The frequency is extracted via clk_get_rate() into a local variable, so the clock reference can be safely released immediately after. Fix this by calling clk_put(cpu_clk) right after clk_get_rate(). Fixes: 98f2892607c5 ("ARC: [cpuinfo] Add clk info in /proc/cpuinfo") Signed-off-by: blaze <1466528493@qq.com> Signed-off-by: Vineet Gupta --- arch/arc/kernel/setup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arc/kernel/setup.c b/arch/arc/kernel/setup.c index 7b6a9beba9db..8bebd0e6fc2f 100644 --- a/arch/arc/kernel/setup.c +++ b/arch/arc/kernel/setup.c @@ -593,6 +593,7 @@ static int show_cpuinfo(struct seq_file *m, void *v) cpu_id); } else { freq = clk_get_rate(cpu_clk); + clk_put(cpu_clk); } if (freq) seq_printf(m, "CPU speed\t: %lu.%02lu Mhz\n", From 9bc184a2eda8b773c88ecbff934001ad649b9cc1 Mon Sep 17 00:00:00 2001 From: Yuxiao Wang Date: Wed, 9 Sep 2026 12:44:00 +0000 Subject: [PATCH 079/535] nitro_enclaves: fix use-after-free on SLOT_ALLOC failure When ne_create_vm_ioctl() fails the SLOT_ALLOC request after anon_inode_getfile() has succeeded, the error path calls fput(enclave_file) and then frees ne_enclave. In normal userspace context, fput() defers the final __fput() via task_work. ne_enclave_release() therefore runs after ne_enclave has already been freed and dereferences ne_enclave->slot_uid, causing a use-after-free: KASAN: slab-use-after-free in ne_enclave_release. The enclave has no slot allocated and is not yet linked into the enclaves list on this error path, so ne_enclave_release() is expected to return early when slot_uid is zero. However, reading slot_uid already accesses the freed object. Clear enclave_file->private_data before fput() on the error path. ne_enclave_release() then returns immediately when private_data is NULL, leaving the ioctl error path as the sole owner of ne_enclave. This is safe because the file has not been fd_install()'d yet. Tested on an AWS EC2 m5.2xlarge with CONFIG_KASAN=y. Without the patch, the reproducer triggers a KASAN slab-use-after-free on every SLOT_ALLOC failure. With the patch, no KASAN report is produced and the SLOT_ALLOC error is still returned. Normal enclave creation and teardown are unaffected. Fixes: 9c8eb50fe9e2 ("nitro_enclaves: Add logic for terminating an enclave") Cc: stable@vger.kernel.org Co-developed-by: Zhaofeng Chen Signed-off-by: Zhaofeng Chen Signed-off-by: Yuxiao Wang Reviewed-by: Alexander Graf Link: https://patch.msgid.link/20260909124400.27857-1-graf@amazon.com Signed-off-by: Greg Kroah-Hartman --- drivers/virt/nitro_enclaves/ne_misc_dev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c index c91300a73f50..0d7bdad924b7 100644 --- a/drivers/virt/nitro_enclaves/ne_misc_dev.c +++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c @@ -1706,6 +1706,7 @@ static int ne_create_vm_ioctl(struct ne_pci_dev *ne_pci_dev, u64 __user *slot_ui return enclave_fd; put_file: + enclave_file->private_data = NULL; fput(enclave_file); put_fd: put_unused_fd(enclave_fd); From 310c2d4608d8c1a2baa57db1dfd319c5c40cf4a1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 1 Sep 2026 16:52:46 +0200 Subject: [PATCH 080/535] usb: gadget: midi2: Fix the jack descriptor array sizes The jack in/out descriptor arrays in struct f_midi2_usb_config have the size of MAX_CABLES, which look reasonable -- but it turned out to be incorrect. Namely, the entries for those arrays are added from both inputs and outputs, hence for each loop cycle, it adds two, ended up with as twice as the expected size. This inconsistency may result in potential OOB when a large number of jacks are set up via configfs, although the max number of cables (the loop count) is limited to MAX_CABLES. For addressing it, correct the jack_ins & jack_outs array sizes to twice, MAX_CABLES * 2. Fixes: 856fa444b098 ("usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors") Cc: stable Reported-by: syzbot+c35f34092a4bc9855be6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c35f34092a4bc9855be6 Link: https://lore.kernel.org/20260826134606.127250-1-eadavis@sina.com Cc: Edward Adam Davis Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260901145512.785142-1-tiwai@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 5b8b18281989..d693de427641 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { /* MIDI 1.0 jacks */ unsigned char jack_in, jack_out, jack_id; - struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; - struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; + struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; + struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; }; static int append_config(struct f_midi2_usb_config *config, void *d) From 63b269ec537dc0fd26d0bd64872c26860d108c9d Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 8 Sep 2026 06:02:15 +0000 Subject: [PATCH 081/535] usb: typec: ucsi: glink: fix use-after-free of ucsi on remove The pmic_glink client is released by the devres cleanup, which runs after pmic_glink_ucsi_remove() has returned, so its notification callbacks can queue work until then. Work that runs after ucsi_unregister() has been called touches freed state: it dereferences the connector array that ucsi_unregister() freed, or registers and unregisters the instance a second time. Disable both work items with disable_work_sync() before unregistering, and unregister only if the instance is still registered. This issue was found by an in-house static analysis tool. Fixes: 62b5412b1f4a ("usb: typec: ucsi: add PMIC Glink UCSI driver") Cc: stable Assisted-by: Codex:gpt-5.6 Link: https://lore.kernel.org/r/20260227190430.889-1-nathan.c.rebello@gmail.com Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260908060216.616045-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_glink.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 12e07b9fe622..1db0c88d817e 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) { struct pmic_glink_ucsi *ucsi = data; + /* Drain the work items before ucsi_destroy() frees the ucsi instance */ + cancel_work_sync(&ucsi->notify_work); + cancel_work_sync(&ucsi->register_work); + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ mutex_lock(&ucsi->lock); ucsi_destroy(ucsi->ucsi); @@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) { struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); - /* Unregister first to stop having read & writes */ - ucsi_unregister(ucsi->ucsi); + /* Callbacks can queue work until devres releases the client */ + disable_work_sync(&ucsi->notify_work); + disable_work_sync(&ucsi->register_work); + + /* register_work may have unregistered the instance already */ + if (ucsi->ucsi_registered) { + ucsi->ucsi_registered = false; + ucsi_unregister(ucsi->ucsi); + } } static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { From c5df31f10880737d13b75eff42faee7accb9f956 Mon Sep 17 00:00:00 2001 From: Marian Rotariu Date: Thu, 6 Aug 2026 11:53:02 +0300 Subject: [PATCH 082/535] usb: typec: tipd: don't send GAID when probe fails in APP mode When probe fails after the chip was already in APP mode (booted from EEPROM), the err_reset_controller path unconditionally calls tps->data->reset(), which for tps25750 sends the GAID 4CC command. GAID transitions the chip from APP back to PTCH, causing a second probe attempt that sees PTCH mode and tries to load the firmware from /lib/firmware, instead of reading the EEPROM as it should. The err_reset_controller path can be triggered by -EPROBE_DEFER from the probe sequence. GAID should only be sent when the controller actually loaded firmware at probe time. The abnormal behavior was seen on a tps25751d that has an EEPROM connected to it. Fixes: d49f90822015 ("usb: typec: tipd: add init and reset functions to tipd_data") Cc: stable Signed-off-by: Marian Rotariu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260806085302.1307606-1-marian.c.rotariu@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f76f563dc42b..d060150d2e15 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1792,6 +1792,7 @@ static int tps6598x_probe(struct i2c_client *client) const struct tipd_data *data; struct tps6598x *tps; struct fwnode_handle *fwnode; + bool patch_loaded = false; u32 status; u32 vid = 0; int ret; @@ -1847,6 +1848,7 @@ static int tps6598x_probe(struct i2c_client *client) return ret; if (ret == TPS_MODE_PTCH) { + patch_loaded = true; ret = tps->data->init(tps); if (ret) return ret; @@ -1937,7 +1939,8 @@ static int tps6598x_probe(struct i2c_client *client) tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); err_reset_controller: /* Reset PD controller to remove any applied patch */ - tps->data->reset(tps); + if (patch_loaded) + tps->data->reset(tps); return ret; } From 29480b05e6e226594dc11ccf2a036fbd5cdd0e42 Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Thu, 3 Sep 2026 16:59:03 +0530 Subject: [PATCH 083/535] usb: hub: use shorter 120ms post resume hold for SS root hubs Holding a USB3 hub PM runtime reference for 200ms at hub resume triggers an AMD platform issue. Users running Android adb report crashes after adb has been polling and waking up the USB subsystem once a second for some time. Vendors are working on a solution. Disabling USB runtime PM is one way to prevent this issue, but it is also proven that reducing the hold time to 120ms in resume also mitigates it. See Link for more details. Reducing the hold time to 120ms for the USB3 roothub is in itself a valid change and optimization, as the current 200ms is excessive: a root hub has no upstream suspended hub whose wake propagation needs to be accounted for, but still need some time for USB3 link training to complete. Keep the 200ms hold for external hubs that commit 8f5b7e2bec1c ("usb: hub: fix detection of high tier USB3 devices behind suspended hubs") is intended for. Reported-by: Mathieu Fluhr Link: https://lore.kernel.org/all/CAPyJwA_D9qw0T72f8zwM1yKjP+To=maVANbcdsWM7yRmbBxYvw@mail.gmail.com/ Cc: stable Fixes: 8f5b7e2bec1c ("usb: hub: fix detection of high tier USB3 devices behind suspended hubs") Signed-off-by: Lovekesh Solanki Acked-by: Mathias Nyman Link: https://patch.msgid.link/20260903112903.542719-1-lovekeshsolanki00@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3345b3298daf..b446905b48be 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -73,8 +73,11 @@ /* * Give SS hubs 200ms time after wake to train downstream links before * assuming no port activity and allowing hub to runtime suspend back. + * Root hubs have no upstream hub whose wake propagation needs to be + * accounted for, so they need less time, use 120ms for them. */ #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ +#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ /* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, except that ->state can @@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) queue_delayed_work(system_power_efficient_wq, &hub->post_resume_work, - msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); + msecs_to_jiffies(hdev->parent ? + USB_SS_PORT_U0_WAKE_TIME : + USB_SS_RH_PORT_U0_WAKE_TIME)); return; } From 4c7a1b9f1f1979f2b3b69428ada12680695159c2 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:27 +0000 Subject: [PATCH 084/535] usb: octeon-hcd: fix the FIFO-flush timeout computation cvmx_wait_tx_rx() computes its 100us deadline from (u64)octeon_get_clock_rate - the address of the function, not its return value; the parentheses have been missing since the CVMX_WAIT_FOR_FIELD32 macro became a function. The cast makes it compile silently, and the resulting deadline is effectively infinite. On a healthy controller the flush bit clears on the first read and nothing is noticed. On a controller whose PHY did not come up (for example when the reference-clock configuration is wrong for the board), txfflsh/rxfflsh never clear and probe spins forever in __delay() - observed as a hard hang with a soft-lockup splat on a CN5020 board, where the board watchdog then resets the system with no console output. Call the function, restoring the 100us timeout the code always intended. Fixes: 3e195a80e096 ("Staging: octeon-usb: Replaces CVMX_WAIT_FOR_FIELD32 macro with a function") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-1-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 34ce771fa0f5..7d121909f514 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -578,7 +578,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) int result; u64 address = CVMX_USBCX_GRSTCTL(usb->index); u64 done = cvmx_get_cycle() + 100 * - (u64)octeon_get_clock_rate / 1000000; + (u64)octeon_get_clock_rate() / 1000000; union cvmx_usbcx_grstctl c; while (1) { From 22bce6d2b3fd9e845d71e0037b6929913301462d Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:28 +0000 Subject: [PATCH 085/535] usb: octeon-hcd: fail the probe when the USB core does not respond If the USBN clock/PHY setup did not actually bring the core up (wrong board reference-clock configuration, for instance), every USBC CSR reads as all-ones and initialization marches on programming garbage, ending in timeouts or a wedge deep in the FIFO flush. Read GHWCFG3 - a hardwired configuration register that can be neither 0 nor all-ones on a live core - right before core initialization, and give up cleanly if the core is not answering. Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-2-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 7d121909f514..5a846c3fc34a 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -853,6 +853,14 @@ static int cvmx_usb_initialize(struct device *dev, * USBC_GAHBCFG[PTXFEMPLVL] * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 */ + usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GHWCFG3(usb->index)); + if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { + dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", + usbcx_gahbcfg.u32); + return -ENODEV; + } + usbcx_gahbcfg.u32 = 0; usbcx_gahbcfg.s.dmaen = !(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA); From aebbc7d63407f843a1c74806041792db65d69a51 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:29 +0000 Subject: [PATCH 086/535] usb: octeon-hcd: sleep on an hrtimer for far-off periodic transfers cvmx_usb_schedule() enables the SOF interrupt whenever any pipe has next_tx_frame in the future, and leaves it enabled until the transfer is due. An interrupt endpoint keeps such a deadline pending permanently, so a single attached hub (status pipe polled every 256ms) costs an interrupt on every SOF - 8000/s in high-speed mode, forever. On a 300MHz CN5020 that is measurably ~20% of one core spent counting frames. The frame counter is resynchronized from HFNUM at the top of every poll, so the driver does not actually need to see every SOF to know when a deadline arrives. Sleep on an hrtimer when the nearest deadline is more than a few frames away (one high-speed frame is 125us) and keep SOF interrupts only for deadlines that are imminent - or far enough away to risk the 16383-frame HFNUM wrap, where the interrupt path still tracks the counter extension. Measured on a CN5020 board with one 4-port hub attached and idle: 8200 -> 4 USB interrupts/s, with no change in enumeration or transfer behavior. Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-3-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 44 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 5a846c3fc34a..256e459ff6d8 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -378,8 +378,22 @@ struct octeon_hcd { struct cvmx_usb_transaction *active_split; struct cvmx_usb_tx_fifo periodic; struct cvmx_usb_tx_fifo nonperiodic; + struct hrtimer sof_timer; }; +static int cvmx_usb_poll(struct octeon_hcd *usb); + +static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) +{ + struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_poll(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return HRTIMER_NORESTART; +} + /* * This macro logically sets a single field in a CSR. It does the sequence * read, modify, and write @@ -1908,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) int channel; struct cvmx_usb_pipe *pipe; int need_sof; + u64 min_due; enum cvmx_usb_transfer ttype; if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { @@ -1948,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) * future that might need to be scheduled */ need_sof = 0; + min_due = ~0ull; for (ttype = CVMX_USB_TRANSFER_CONTROL; ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { - if (pipe->next_tx_frame > usb->frame_number) { - need_sof = 1; - break; - } + if (pipe->next_tx_frame > usb->frame_number && + pipe->next_tx_frame < min_due) + min_due = pipe->next_tx_frame; } } + if (min_due != ~0ull) { + u64 delta = min_due - usb->frame_number; + + /* + * frame_number is resynced from HFNUM on every poll, so a + * deadline that is many frames away does not need an + * interrupt on every SOF to count them down - sleep on the + * timer instead and keep SOF interrupts for deadlines within + * a few frames. Stay well below the 16383-frame wrap of + * HFNUM. One (micro)frame is 125us in high-speed mode. + */ + if (delta <= 4 || delta > 8000) + need_sof = 1; + else + hrtimer_start(&usb->sof_timer, + ns_to_ktime((delta - 2) * 125000), + HRTIMER_MODE_REL); + } USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk, sofmsk, need_sof); } @@ -3646,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) usb = (struct octeon_hcd *)hcd->hcd_priv; spin_lock_init(&usb->lock); + hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); usb->init_flags = initialize_flags; @@ -3696,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) unsigned long flags; usb_remove_hcd(hcd); + hrtimer_cancel(&usb->sof_timer); spin_lock_irqsave(&usb->lock, flags); status = cvmx_usb_shutdown(usb); spin_unlock_irqrestore(&usb->lock, flags); From 3d9bbf0ffeb9b7ac1b6ad06b52e037870a7e0c63 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Wed, 2 Sep 2026 20:24:04 +0530 Subject: [PATCH 087/535] usb: dwc3: am62: Fix runtime PM usage count leak on probe error path dwc3_ti_probe() takes a runtime PM reference with pm_runtime_get_noresume() before creating the dwc3 core child device, and releases it with pm_runtime_put_autosuspend() once probe has succeeded. The err_pm_disable error path only disables runtime PM, so the reference taken a few lines earlier is never dropped. The usage counter lives in struct device and is not reset when the driver is unbound, so the leaked reference outlives the failed probe. If the device is probed again, through a manual rebind or a module reload, the counter starts at one instead of zero and the pm_runtime_put_autosuspend() on the success path can no longer bring it back down. The wrapper then stays runtime resumed for good and autosuspend never kicks in. Drop the reference before disabling runtime PM, matching the ordering already used in dwc3_ti_remove(). Fixes: e8784c0aec03 ("drivers: usb: dwc3: Add AM62 USB wrapper driver") Cc: stable Assisted-by: Claude:claude-opus-5 Signed-off-by: Radhey Shyam Pandey Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260902-dwc3-am62-rpm-fix-v1-1-7a2807e33307@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-am62.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index 632634d6e81e..d03b3950ce04 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) err_pm_disable: clk_disable_unprepare(am62->usb2_refclk); + pm_runtime_put_noidle(dev); pm_runtime_disable(dev); pm_runtime_set_suspended(dev); return ret; From fd772af9512cdecb83de34a461801533828e77fd Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Fri, 4 Sep 2026 14:53:23 +0800 Subject: [PATCH 088/535] USB: dwc2: shut down wakeup timer before freeing HCD state dwc2_wakeup_detected() accesses the DWC2 host state and can rearm the wakeup timer. dwc2_hcd_free() currently deletes the timer only after freeing host-owned state, and timer_delete() does not synchronize a callback or prevent it from being queued again. Stop and shut down the timer in dwc2_hcd_release(), before the HCD resources are freed. This covers both the HCD initialization error path and normal HCD removal. Fixes: 7359d482eb4d ("staging: HCD files for the DWC2 driver") Cc: stable Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Reviewed-by: Thinh Nguyen Link: https://patch.msgid.link/20260904065323.4047026-1-runyu.xiao@seu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2414291aa908..cd0dc876b421 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) } cancel_work_sync(&hsotg->phy_reset_work); - - timer_delete(&hsotg->wkp_timer); } static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) { /* Turn off all host-specific interrupts */ dwc2_disable_host_interrupts(hsotg); + timer_shutdown_sync(&hsotg->wkp_timer); dwc2_hcd_free(hsotg); } From 90156585765b29304f07b0e4a1c56a969876fcfc Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Thu, 3 Sep 2026 00:22:21 +0000 Subject: [PATCH 089/535] usb: typec: tcpm: recover after failure to start the frs ams Reset the port if the tcpm fails to start the FAST_ROLE_SWAP AMS by initiating error recovery on it. This helps in cases where some cables (incorrectly) signal an FRS to an FRS capable port during disconnection. The TCPC autonomously starts sourcing VBUS on detecting the FRS signal. However, the VBUS sourcing is left on when the FAST_ROLE_SWAP AMS fails to start (as tcpm_sink_tx_ok is 0 as CC is open due to the cable disconnect). This is because the code sets the state to INVALID_STATE without resetting the port state. Log snippet before changes: [ 101.401960] AMS FAST_ROLE_SWAP start [ 101.401971] Sink TX No Go [ 101.401982] sourcing vbus [ 101.401987] VBUS on [ 101.402159] VBUS on [ 109.257809] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_READY, polarity 1, disconnected] [ 111.267442] VBUS on After changes: [ 70.541211] AMS FAST_ROLE_SWAP start [ 70.541220] Sink TX No Go [ 70.541228] state change SNK_READY -> ERROR_RECOVERY [rev3 NONE_AMS] [ 70.541362] VBUS on [ 70.541365] sourcing vbus [ 70.541367] VBUS on [ 70.541374] state change ERROR_RECOVERY -> PORT_RESET [rev3 NONE_AMS] [ 70.541410] disable vbus discharge ret:0 [ 70.543028] Setting usb_comm capable false [ 70.544009] Setting voltage/current limit 0 mV 0 mA [ 70.544034] polarity 0 [ 70.544239] Requesting mux state 0, usb-role 0, orientation 0 [ 70.555550] cc:=0 [ 70.555595] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev3 NONE_AMS] [ 70.555697] VBUS off [ 70.555702] VBUS VSAFE0V [ 70.555762] CC1: 5 -> 0, CC2: 0 -> 0 [state PORT_RESET, polarity 0, disconnected] [ 70.587794] VBUS off [ 70.587799] VBUS VSAFE0V [ 70.655672] state change PORT_RESET -> PORT_RESET_WAIT_OFF [delayed 100 ms] [ 70.655682] state change PORT_RESET_WAIT_OFF -> SNK_UNATTACHED [rev3 NONE_AMS] [ 70.655686] Start toggling [ 70.656274] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] Fixes: 0908c5aca31e ("usb: typec: tcpm: AMS and Collision Avoidance") Cc: stable Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260903-frs-error-handling-v1-1-ad0fee541847@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 2d6b14aa2085..8f80bdd62f94 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -7113,7 +7113,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) port->upcoming_state = FR_SWAP_SEND; ret = tcpm_ams_start(port, FAST_ROLE_SWAP); if (ret == -EAGAIN) - port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, ERROR_RECOVERY, 0); } else { tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); } From 8a0200d14acd9db388b85cd016e68a4e60d900ec Mon Sep 17 00:00:00 2001 From: Syed Labeeq Sajid Bukhari Date: Thu, 10 Sep 2026 19:03:43 +0500 Subject: [PATCH 090/535] usb: storage: sierra_ms: reject short SWoC info transfers sierra_get_swoc_info() requests sizeof(struct swoc_info) (60) bytes from the device via usb_control_msg(), but its callers only treat a negative return value as failure. A device that answers the vendor-specific GetSwocInfo request with a short IN transfer is therefore accepted, leaving the tail of the freshly allocated (kmalloc(), non-zeroing) swoc_info buffer uninitialized. truinst_show() subsequently prints swocInfo->rev, swocInfo->LinuxSKU and swocInfo->LinuxVer from that buffer into the world-readable (0444) "truinst" sysfs attribute. An emulated/malicious USB device (VID 0x1199, PID 0x0fff) can exploit this to disclose up to 5 bytes of stale kernel heap memory (kmalloc-64) to unprivileged userspace, once per sysfs read, indefinitely. On kernels built without init_on_alloc this leaks recently freed heap contents. Only accept the transfer when the full structure was received. sierra_ms_init() already retries failed queries, so well-behaved devices are unaffected. Fixes: 32fe5e393455 ("USB Storage Sierra: TRU-Install feature update") Cc: stable Signed-off-by: Syed Labeeq Sajid Bukhari Assisted-by: Kimi:K2 [Kimi Code CLI] Link: https://patch.msgid.link/20260910140343.49371-1-syedlabeeq@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/sierra_ms.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c index c5e3eeeb2144..992685ca6f1a 100644 --- a/drivers/usb/storage/sierra_ms.c +++ b/drivers/usb/storage/sierra_ms.c @@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, sizeof(struct swoc_info), /* __u16 size */ USB_CTRL_SET_TIMEOUT); /* int timeout */ + /* + * A short IN transfer leaves the tail of swocInfo uninitialized; + * only a full transfer is valid. + */ + if (result != sizeof(struct swoc_info)) + return -EIO; + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); return result; From c4b27b3605788bbf9e4251f3cd4ab94645ab036f Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sun, 6 Sep 2026 18:49:41 +0530 Subject: [PATCH 091/535] usb: typec: tipd: fix uninitialized typec_partner_desc in tps6598x_connect() tps6598x_connect() passes a stack-allocated typec_partner_desc to typec_register_partner() after initializing only usb_pd, accessory and identity. typec_register_partner() copies attach and deattach from the descriptor into the partner. With those fields left unset, garbage function pointers may be stored and later invoked from typec_partner_link_device() when a USB device is linked to the port. Uninitialized pd_revision and usb_capability similarly leak stack data through partner sysfs. Zero-initialize the descriptor so optional callbacks remain NULL and the remaining fields are zero. Fixes: 0a4c005bd171 ("usb: typec: driver for TI TPS6598x USB Power Delivery controllers") Cc: stable # 5.15+ Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260906131942.83153-2-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d060150d2e15..e55c3dc1aa69 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, static int tps6598x_connect(struct tps6598x *tps, u32 status) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; enum typec_pwr_opmode mode; int ret; @@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) desc.usb_pd = mode == TYPEC_PWR_MODE_PD; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) { ret = tps6598x_read_partner_identity(tps); From 88c7e458af53ff781a3f1051708395892b325576 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sun, 6 Sep 2026 18:49:42 +0530 Subject: [PATCH 092/535] usb: typec: tipd: fix uninitialized typec_partner_desc in cd321x_update_work() cd321x_update_work() passes a stack-allocated typec_partner_desc to typec_register_partner() after initializing only usb_pd, accessory and identity. typec_register_partner() copies attach and deattach from the descriptor into the partner. With those fields left unset, garbage function pointers may be stored and later invoked from typec_partner_link_device() when a USB device is linked to the port. Uninitialized pd_revision and usb_capability similarly leak stack data through partner sysfs. Zero-initialize the descriptor so optional callbacks remain NULL and the remaining fields are zero. Fixes: 82432bbfb9e8 ("usb: typec: tipd: Handle mode transitions for CD321x") Cc: stable # 6.18+ Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260906131942.83153-3-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e55c3dc1aa69..483539185177 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -849,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) /* Set up partner if we were previously disconnected (or changed). */ if (!tps->partner) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; desc.usb_pd = is_pd; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) desc.identity = &st.partner_identity; From be4219dd98608736e13e0b790ef742b76a13254d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 7 Sep 2026 11:51:30 +0200 Subject: [PATCH 093/535] USB: cdc-acm: fix racy TIOCMIWAIT implementation Check the wakeup condition after adding the task to the waitqueue and updating the task state to avoid missing a racing modem status update or disconnect. Store the old icount on entry and do not update it in the completion handler to avoid missing events or reporting spurious ones (due to modem status changes before TIOCMIWAIT was called). Fixes: 5a6a62bdb925 ("cdc-acm: add TIOCMIWAIT") Cc: stable Cc: Oliver Neukum Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260907095130.130636-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 36 +++++++++++++++++++----------------- drivers/usb/class/cdc-acm.h | 1 - 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7bc5329fa3ed..21417b4c5154 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) spin_lock_irqsave(&acm->read_lock, flags); acm->ctrlin = newctrl; - acm->oldcount = acm->iocount; if (difference & USB_CDC_SERIAL_STATE_DSR) acm->iocount.dsr++; @@ -1027,11 +1026,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) DECLARE_WAITQUEUE(wait, current); struct async_icount old, new; - do { + spin_lock_irq(&acm->read_lock); + old = acm->iocount; + spin_unlock_irq(&acm->read_lock); + + add_wait_queue(&acm->wioctl, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&acm->read_lock); - old = acm->oldcount; new = acm->iocount; - acm->oldcount = new; spin_unlock_irq(&acm->read_lock); if ((arg & TIOCM_DSR) && @@ -1044,22 +1048,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) old.rng != new.rng) break; - add_wait_queue(&acm->wioctl, &wait); - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - remove_wait_queue(&acm->wioctl, &wait); if (acm->disconnected) { - if (arg & TIOCM_CD) - break; - else - rv = -ENODEV; - } else { - if (signal_pending(current)) - rv = -ERESTARTSYS; + rv = -ENODEV; + break; } - } while (!rv); - + schedule(); + + if (signal_pending(current)) { + rv = -ERESTARTSYS; + break; + } + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&acm->wioctl, &wait); return rv; } diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 01f448a783c0..d245e60eb9f5 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -90,7 +90,6 @@ struct acm { unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ unsigned int ctrlout; /* output control lines (DTR, RTS) */ struct async_icount iocount; /* counters for control line changes */ - struct async_icount oldcount; /* for comparison of counter */ wait_queue_head_t wioctl; /* for ioctl */ unsigned int writesize; /* max packet size for the output bulk endpoint */ unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ From 8c51ea651d043c786c37668c51624bd6b338a0ef Mon Sep 17 00:00:00 2001 From: Pannarat Wiriyaarritham Date: Thu, 10 Sep 2026 09:33:37 +0700 Subject: [PATCH 094/535] usb: typec: ucsi: acpi: Assume UCSI 1.2 on Acer Nitro ANV15-41 The Acer Nitro ANV15-41 exposes a functional UCSI ACPI PPM but reports a UCSI VERSION value of zero. ucsi_register() rejects a zero version with -ENODEV, leaving the system without registered USB Type-C connectors. The PPM works correctly when using the UCSI 1.2 layout. With UCSI 1.2 assumed, both Type-C connectors register correctly and USB Power Delivery negotiation works. Add a DMI-specific UCSI operation for the Acer Nitro ANV15-41 that substitutes UCSI 1.2 only when firmware reports a zero version. Preserve any valid non-zero firmware version. Tested on an Acer Nitro ANV15-41 with BIOS V1.51. Fixes: c1b0bc2dabfa ("usb: typec: Add support for UCSI interface") Cc: stable Assisted-by: LLM Signed-off-by: Pannarat Wiriyaarritham Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260910023338.31816-1-pannarat.wiriyaarritham@danielcorp.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_acpi.c | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 18286d3e9cc5..5697ccc24ec3 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { .async_control = ucsi_acpi_async_control }; +static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_acpi_read_version(ucsi, version); + if (ret) + return ret; + + if (!*version) { + dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); + *version = UCSI_VERSION_1_2; + } + + return 0; +} + +static const struct ucsi_operations ucsi_acer_ops = { + .read_version = ucsi_acer_read_version, + .read_cci = ucsi_acpi_read_cci, + .poll_cci = ucsi_acpi_poll_cci, + .read_message_in = ucsi_acpi_read_message_in, + .write_message_out = ucsi_acpi_write_message_out, + .sync_control = ucsi_sync_control_common, + .async_control = ucsi_acpi_async_control +}; + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, void *val, size_t len, void *msg_out, size_t msg_out_size) @@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { }; static const struct dmi_system_id ucsi_acpi_quirks[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + }, + .driver_data = (void *)&ucsi_acer_ops, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), From c72f9f7bd23c91e944910d9bcbf12503d21a4a1f Mon Sep 17 00:00:00 2001 From: Bean Huo Date: Fri, 4 Sep 2026 12:51:55 +0200 Subject: [PATCH 095/535] nvme-pci: add NVME_QUIRK_DMAPOOL_ALIGN_512 for Micron 4100AT The Micron 4100AT fetches PRP Lists in 512 byte strides, but the driver allocates small PRP List descriptors in 256 byte strides. A descriptor in the last 256 bytes of a dmapool page makes the controller read past the page: the access lands on an adjacent IOVA, the IOMMU faults the command. That last block only became reachable with commit da9619a30e73 ("dmapool: link blocks across pages"), so this affects v6.4 and later. Align the small descriptor pool to 512 bytes, as commit ebefac564796 ("nvme-pci: 512 byte aligned dma pool segment quirk") already does for another controller with the same erratum. The last block then starts at offset 0xE00 and the fetch stays inside the page. Cc: # 6.4.x: ebefac564796: nvme-pci: 512 byte aligned dma pool segment quirk Cc: # 6.4.x Signed-off-by: Gaurav Sinha Signed-off-by: Bean Huo Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index da93b505d239..39ef2a18d05c 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -4196,6 +4196,8 @@ static const struct pci_device_id nvme_id_table[] = { .driver_data = NVME_QUIRK_IGNORE_DEV_SUBNQN }, { PCI_DEVICE(0x1344, 0x6001), /* Micron Nitro NVMe */ .driver_data = NVME_QUIRK_BOGUS_NID, }, + { PCI_DEVICE(0x1344, 0x6004), /* Micron 4100AT */ + .driver_data = NVME_QUIRK_DMAPOOL_ALIGN_512, }, { PCI_DEVICE(0x1c5c, 0x1504), /* SK Hynix PC400 */ .driver_data = NVME_QUIRK_DISABLE_WRITE_ZEROES, }, { PCI_DEVICE(0x1c5c, 0x174a), /* SK Hynix P31 SSD */ From 62b70e6c87121ae5b79ca20c3a5b005590336ec5 Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Tue, 8 Sep 2026 16:34:34 +0200 Subject: [PATCH 096/535] nvme-fc: do not warn on controller removal race In nvme_fc_reconnect_or_delete(), the system attempts to remove the controller when reconnect limits are reached by calling nvme_delete_ctrl(). This call is currently wrapped in a WARN_ON(). If a user concurrently deletes the controller via sysfs, the controller's state may have already transitioned out of the CONNECTING state. When this happens, nvme_delete_ctrl() correctly rejects the operation and returns -EBUSY. Because of the WARN_ON() wrapper, this race condition triggers an unnecessary kernel warning stack trace. Remove the WARN_ON() macro around nvme_delete_ctrl(); this allows the system to safely ignore the -EBUSY return value if the controller is already being deleted. Signed-off-by: Maurizio Lombardi Reviewed-by: Daniel Wagner Signed-off-by: Keith Busch --- drivers/nvme/host/fc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c index 48454cb7a0fc..3a4030bcfab7 100644 --- a/drivers/nvme/host/fc.c +++ b/drivers/nvme/host/fc.c @@ -3338,7 +3338,7 @@ nvme_fc_reconnect_or_delete(struct nvme_fc_ctrl *ctrl, int status) ctrl->cnum, min_t(int, portptr->dev_loss_tmo, (ctrl->ctrl.opts->max_reconnects * ctrl->ctrl.opts->reconnect_delay))); - WARN_ON(nvme_delete_ctrl(&ctrl->ctrl)); + nvme_delete_ctrl(&ctrl->ctrl); } } From 2f271b812170d4cad578bae1f91db72f3c2e36e7 Mon Sep 17 00:00:00 2001 From: Julian Sun Date: Tue, 8 Sep 2026 11:35:18 +0800 Subject: [PATCH 097/535] nvmet: preserve device path on allocation failure nvmet_ns_device_path_store() frees the old path before allocating its replacement, losing the existing configuration if allocation fails. Allocate the new path before freeing the old one. Fixes: a07b4970f464 ("nvmet: add a generic NVMe target") Signed-off-by: Julian Sun Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/target/configfs.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c index 6286e38436dd..b03b5d1c2dc3 100644 --- a/drivers/nvme/target/configfs.c +++ b/drivers/nvme/target/configfs.c @@ -534,6 +534,7 @@ static ssize_t nvmet_ns_device_path_store(struct config_item *item, struct nvmet_subsys *subsys = ns->subsys; size_t len; int ret; + char *new_path = NULL; mutex_lock(&subsys->lock); ret = -EBUSY; @@ -545,12 +546,14 @@ static ssize_t nvmet_ns_device_path_store(struct config_item *item, if (!len) goto out_unlock; - kfree(ns->device_path); ret = -ENOMEM; - ns->device_path = kmemdup_nul(page, len, GFP_KERNEL); - if (!ns->device_path) + new_path = kmemdup_nul(page, len, GFP_KERNEL); + if (!new_path) goto out_unlock; + kfree(ns->device_path); + ns->device_path = new_path; + mutex_unlock(&subsys->lock); return count; From 327c428ba79b13e4cc5253333a9d5c0aa5579bdf Mon Sep 17 00:00:00 2001 From: Hao Zhang Date: Sat, 12 Sep 2026 00:04:57 +0800 Subject: [PATCH 098/535] blk-cgroup: save IRQ state in blkg_tryget_closest() bio_set_dev() associates the bio with a blkg through bio_associate_blkg(). If the blkg lookup misses, blkg_tryget_closest() takes q->queue_lock with spin_lock_irq() and releases it with spin_unlock_irq(), which unconditionally enables local interrupts. Callers may call bio_set_dev() with interrupts already disabled, e.g. dm-thin's pool_map() does so while holding pool->lock taken with spin_lock_irq(). The nested spin_unlock_irq() then enables interrupts while pool->lock is still held, so an I/O completion softirq can run on the same CPU, re-acquire pool->lock (thin_endio(), or overwrite_endio() -> complete_mapping_preparation()) and deadlock. lockdep reports this as inconsistent SOFTIRQ-ON-W to IN-SOFTIRQ-W usage. Commit 3a762de55b4e ("block: save irq state in blkg_lookup_create()") fixed the same problem while the lock lived in blkg_lookup_create(), but commit 9327a865e395 ("blk-cgroup: don't nest queue_lock under rcu in blkg_lookup_create()") moved the locking into blkg_tryget_closest() and reverted it to spin_lock_irq(). Save and restore the caller's IRQ state instead. Fixes: 9327a865e395 ("blk-cgroup: don't nest queue_lock under rcu in blkg_lookup_create()") Cc: Ming Lei Signed-off-by: Hao Zhang Acked-by: Tejun Heo Link: https://patch.msgid.link/aqQmqb1k57PXj8Ef@192.168.1.215 Signed-off-by: Jens Axboe --- block/blk-cgroup.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 2b5c29434e42..b56db1cc6778 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -2091,6 +2091,7 @@ static inline struct blkcg_gq *blkg_tryget_closest(struct bio *bio, struct request_queue *q = bio->bi_bdev->bd_queue; struct blkcg *blkcg = css_to_blkcg(css); struct blkcg_gq *blkg; + unsigned long flags; rcu_read_lock(); blkg = blkg_lookup(blkcg, q); @@ -2105,11 +2106,11 @@ static inline struct blkcg_gq *blkg_tryget_closest(struct bio *bio, * Fast path failed, we're probably issuing IO in this cgroup the first * time, hold lock to create new blkg. */ - spin_lock_irq(&q->queue_lock); + spin_lock_irqsave(&q->queue_lock, flags); blkg = blkg_lookup_create(blkcg, bio->bi_bdev->bd_disk); if (blkg) blkg = blkg_lookup_tryget(blkg); - spin_unlock_irq(&q->queue_lock); + spin_unlock_irqrestore(&q->queue_lock, flags); return blkg; } From 59e0cc31cc5c43586c6eaada62c2c863bfbcbfb5 Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Wed, 9 Sep 2026 14:16:36 +0200 Subject: [PATCH 099/535] clk: ti: composite: resolve parent clocks by name again Trying to resolve them by DT index causes massive havoc on OMAP3, Clocks around the timers used as a system clocksource are not properly resolved causing ealy boot failures. The problem seems to be that parents must be resolved by looking into the clocks property of the component node marked with "ti,composite-mux-clock", with the switch to dt index, that node was not used anymore. It was used by the of_clk_parent_fill() call. To a lesser extent also OMAP4/5 boards are affected. Since this patch was introduced just because of a cleanup request and not to solve the actual problem (https://lore.kernel.org/linux-omap/alkZmw-XmnCOZfOD@redhat.com/) just revert it. Cleanup needs really more thought here. The similar change to the TI mux clock, which solves a problem on the AM3 platform, seems to be harmless. So just revert commit fe3dd92ac54a ("clk: ti: composite: resolve parent clocks by DT index, not by name") for now. Fixes: fe3dd92ac54a ("clk: ti: composite: resolve parent clocks by DT index, not by name") Reviewed-by: Mathieu Dubois-Briand Signed-off-by: Andreas Kemnade Signed-off-by: Brian Masney --- drivers/clk/ti/composite.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/drivers/clk/ti/composite.c b/drivers/clk/ti/composite.c index 83c3592cd179..c379bbdae25a 100644 --- a/drivers/clk/ti/composite.c +++ b/drivers/clk/ti/composite.c @@ -52,7 +52,7 @@ static const struct clk_ops ti_composite_gate_ops = { struct component_clk { int num_parents; - struct clk_parent_data *parent_data; + const char **parent_names; struct device_node *node; int type; struct clk_hw *hw; @@ -116,7 +116,7 @@ static void __init _register_composite(void *user, struct clk_hw_omap_comp *cclk = to_clk_hw_comp(hw); struct component_clk *comp; int num_parents = 0; - struct clk_parent_data *parent_data = NULL; + const char **parent_names = NULL; const char *name; int i; int ret; @@ -155,7 +155,7 @@ static void __init _register_composite(void *user, continue; if (comp->num_parents) { num_parents = comp->num_parents; - parent_data = comp->parent_data; + parent_names = comp->parent_names; break; } } @@ -166,8 +166,8 @@ static void __init _register_composite(void *user, } name = ti_dt_clk_name(node); - clk = clk_register_composite_pdata(NULL, name, - parent_data, num_parents, + clk = clk_register_composite(NULL, name, + parent_names, num_parents, _get_hw(cclk, CLK_COMPONENT_TYPE_MUX), &ti_clk_mux_ops, _get_hw(cclk, CLK_COMPONENT_TYPE_DIVIDER), @@ -190,7 +190,7 @@ static void __init _register_composite(void *user, if (!cclk->comp_clks[i]) continue; list_del(&cclk->comp_clks[i]->link); - kfree(cclk->comp_clks[i]->parent_data); + kfree(cclk->comp_clks[i]->parent_names); kfree(cclk->comp_clks[i]); } @@ -237,9 +237,8 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, int type) { unsigned int num_parents; - struct clk_parent_data *parent_data; + const char **parent_names; struct component_clk *clk; - unsigned int i; num_parents = of_clk_get_parent_count(node); @@ -248,21 +247,20 @@ int __init ti_clk_add_component(struct device_node *node, struct clk_hw *hw, return -EINVAL; } - parent_data = kzalloc_objs(*parent_data, num_parents); - if (!parent_data) + parent_names = kcalloc(num_parents, sizeof(char *), GFP_KERNEL); + if (!parent_names) return -ENOMEM; - for (i = 0; i < num_parents; i++) - parent_data[i].index = i; + of_clk_parent_fill(node, parent_names, num_parents); clk = kzalloc_obj(*clk); if (!clk) { - kfree(parent_data); + kfree(parent_names); return -ENOMEM; } clk->num_parents = num_parents; - clk->parent_data = parent_data; + clk->parent_names = parent_names; clk->hw = hw; clk->node = node; clk->type = type; From 80756e263846ab694984e749e8c626897331438f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 9 Sep 2026 10:07:37 +0900 Subject: [PATCH 100/535] thunderbolt: Reject oversized XDomain properties responses tb_xdp_properties_request() allocates room for 45 data dwords in its 252-byte response buffer. The XDomain length field is six bits wide, however, and a malicious peer can set it to 63. After the fixed response fields are subtracted, the driver treats this as 48 data dwords. Commit 322e93448d90 ("thunderbolt: Clamp XDomain response data copy to allocation size") only bounds the copy against data_len. If data_len is at least 48, memcpy() reads 192 bytes from the 180-byte res->data array, causing a 12-byte heap out-of-bounds read. Commit 4db2bd2ed478 ("thunderbolt: Limit XDomain response copy to actual frame size") limits the earlier copy but does not constrain this header-derived length. Reject response data lengths that exceed the allocated source buffer before copying them into the assembled property block. Fixes: d1ff70241a27 ("thunderbolt: Add support for XDomain discovery protocol") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 05442df0e99c..b187a60663f5 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, len += sizeof(res->hdr.xd_hdr) / 4; len -= sizeof(*res) / 4; + if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { + ret = -EINVAL; + goto err; + } if (res->offset != req.offset) { ret = -EINVAL; From 44b8a0bf5f96e8393312fc34b956766882341f16 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Thu, 27 Aug 2026 16:26:56 +0800 Subject: [PATCH 101/535] mtd: spi-nor: core: Fix mutex leak in spi_nor_rww_start_exclusive() Use the same guard(mutex) pattern as the other RWW helpers so nor->lock is released on both the busy and successful return paths. Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Reviewed-by: Miquel Raynal Reviewed-by: Tudor Ambarus [mw: rephrased commit message] Signed-off-by: Michael Walle --- drivers/mtd/spi-nor/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index ccf4396cdcd0..8bc117b46e02 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor) { struct spi_nor_rww *rww = &nor->rww; - mutex_lock(&nor->lock); + guard(mutex)(&nor->lock); if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe) return false; From d558af8ee0bb1b2e88e696be84adb93cf2b7be6f Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 7 Sep 2026 09:54:36 +0800 Subject: [PATCH 102/535] clk: spacemit: k3: add CPU PLL rate tables The K3 CPU PLL rate tables currently describe only one rate per PLL, although the hardware supports a wider range. PLL3 and PLL4 support rates from 1.05 to 2.4 GHz, while PLL5 and PLL8 support rates from 1.05 to 2 GHz. Populate the tables with every supported rate in 50 MHz steps. Fixes: e371a77255b8 ("clk: spacemit: k3: add the clock tree") Cc: stable@vger.kernel.org # 7.0+ Reviewed-by: Aurelien Jarno Tested-by: Aurelien Jarno Tested-by: Anirudh Srinivasan Signed-off-by: Troy Mitchell Reviewed-by: Yixun Lan Link: https://patch.msgid.link/20260907-k3-pll5-pll8-1800mhz-v5-1-5cc96d716b0a@linux.spacemit.com Signed-off-by: Yixun Lan --- drivers/clk/spacemit/ccu-k3.c | 92 +++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/drivers/clk/spacemit/ccu-k3.c b/drivers/clk/spacemit/ccu-k3.c index 92b930d5ff30..39df15c8a3e6 100644 --- a/drivers/clk/spacemit/ccu-k3.c +++ b/drivers/clk/spacemit/ccu-k3.c @@ -30,14 +30,87 @@ static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { }; static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), + CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), + CCU_PLLA_RATE(2050000000UL, 0x0b2a2d55, 0x00005500, 0xa0558686), + CCU_PLLA_RATE(2100000000UL, 0x0b2b3000, 0x00000000, 0xa0558787), + CCU_PLLA_RATE(2150000000UL, 0x0b2c32aa, 0x0000ab00, 0xa0558787), CCU_PLLA_RATE(2200000000UL, 0x0b2d3555, 0x00005500, 0xa0558787), + CCU_PLLA_RATE(2250000000UL, 0x0b2e3800, 0x00000000, 0xa0558888), + CCU_PLLA_RATE(2300000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0558888), + CCU_PLLA_RATE(2350000000UL, 0x0b303d55, 0x00005500, 0xa0558888), + CCU_PLLA_RATE(2400000000UL, 0x0b320000, 0x00000000, 0xa0558989), }; static const struct ccu_pll_rate_tbl pll4_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), + CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), + CCU_PLLA_RATE(2050000000UL, 0x0b2a2d55, 0x00005500, 0xa0558686), + CCU_PLLA_RATE(2100000000UL, 0x0b2b3000, 0x00000000, 0xa0558787), + CCU_PLLA_RATE(2150000000UL, 0x0b2c32aa, 0x0000ab00, 0xa0558787), CCU_PLLA_RATE(2200000000UL, 0x0b2d3555, 0x00005500, 0xa0558787), + CCU_PLLA_RATE(2250000000UL, 0x0b2e3800, 0x00000000, 0xa0558888), + CCU_PLLA_RATE(2300000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0558888), + CCU_PLLA_RATE(2350000000UL, 0x0b303d55, 0x00005500, 0xa0558888), + CCU_PLLA_RATE(2400000000UL, 0x0b320000, 0x00000000, 0xa0558989), }; static const struct ccu_pll_rate_tbl pll5_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), }; @@ -50,6 +123,25 @@ static const struct ccu_pll_rate_tbl pll7_rate_tbl[] = { }; static const struct ccu_pll_rate_tbl pll8_rate_tbl[] = { + CCU_PLLA_RATE(1050000000UL, 0x0b2b3000, 0x00000000, 0xa0458080), + CCU_PLLA_RATE(1100000000UL, 0x0b2d3555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1150000000UL, 0x0b2f3aaa, 0x0000ab00, 0xa0458181), + CCU_PLLA_RATE(1200000000UL, 0x0b320000, 0x00000000, 0xa0458181), + CCU_PLLA_RATE(1250000000UL, 0x0b340555, 0x00005500, 0xa0458181), + CCU_PLLA_RATE(1300000000UL, 0x0b360aaa, 0x0000ab00, 0xa0458282), + CCU_PLLA_RATE(1350000000UL, 0x0b381000, 0x00000000, 0xa0458282), + CCU_PLLA_RATE(1400000000UL, 0x0b3a1555, 0x00005500, 0xa0458282), + CCU_PLLA_RATE(1450000000UL, 0x0b3c1aaa, 0x0000ab00, 0xa0458383), + CCU_PLLA_RATE(1500000000UL, 0x0b3e2000, 0x00000000, 0xa0458383), + CCU_PLLA_RATE(1550000000UL, 0x0b402555, 0x00005500, 0xa0458383), + CCU_PLLA_RATE(1600000000UL, 0x0b422aaa, 0x0000ab00, 0xa0458484), + CCU_PLLA_RATE(1650000000UL, 0x0b443000, 0x00000000, 0xa0458484), + CCU_PLLA_RATE(1700000000UL, 0x0b463555, 0x00005500, 0xa0458484), + CCU_PLLA_RATE(1750000000UL, 0x0b483aaa, 0x0000ab00, 0xa0458585), + CCU_PLLA_RATE(1800000000UL, 0x0b4b0000, 0x00000000, 0xa0458585), + CCU_PLLA_RATE(1850000000UL, 0x0b4d0555, 0x00005500, 0xa0458585), + CCU_PLLA_RATE(1900000000UL, 0x0b4f0aaa, 0x0000ab00, 0xa0458686), + CCU_PLLA_RATE(1950000000UL, 0x0b511000, 0x00000000, 0xa0458686), CCU_PLLA_RATE(2000000000UL, 0x0b292aaa, 0x0000ab00, 0xa0558686), }; From c230bc2b926d137ed06cd4538dfccf57b80fb9ea Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 15:46:23 -0500 Subject: [PATCH 103/535] Revert "thunderbolt: Add quirk to reset host interface on DMA path teardown for AMD USB4 routers" This commit has caused a deadlock at shutdown. A proper fix with another approach will be coming later. Revert commit f1de1fc5f632cdeae1f5c2984572ab710d4dfcaa for now. Reported-by: juan.martinez@amd.com Closes: https://lore.kernel.org/linux-usb/20260825214237.4179813-1-juan.martinez@amd.com/ Cc: Sanath S Cc: Basavaraj Natikar Signed-off-by: Mario Limonciello Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 26 +------------------------- drivers/thunderbolt/nhi.c | 26 -------------------------- drivers/thunderbolt/nhi.h | 21 ++------------------- drivers/thunderbolt/nhi_regs.h | 4 ---- drivers/thunderbolt/pci.c | 23 ----------------------- 5 files changed, 3 insertions(+), 97 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 12c88509a54f..24611f05b3cd 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, transmit_ring, receive_path, receive_ring); } -static void tb_domain_reset_interface(struct tb *tb) -{ - struct tb_nhi *nhi = tb->nhi; - - if (!nhi->ops->reset_interface) - return; - - guard(mutex)(&tb->lock); - - /* The reset clears the ring state so stop the control channel */ - tb_ctl_stop(tb->ctl); - nhi->ops->reset_interface(nhi); - tb_ctl_start(tb->ctl); -} - /** * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain * @tb: Domain disabling the DMA paths @@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { - int ret; - if (!tb->cm_ops->disconnect_xdomain_paths) return -ENOTSUPP; - ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, transmit_ring, receive_path, receive_ring); - if (ret) - return ret; - - if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) - tb_domain_reset_interface(tb); - - return 0; } static int disconnect_xdomain(struct device *dev, void *data) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d4d1efa2afa0..be18f7b6595a 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } -/** - * nhi_reset_interface() - Reset the host interface - * @nhi: Host interface to reset - * - * Brings the registers in the memory BAR back to their default state and - * clears the End-to-End Flow Control state. The caller is responsible for - * stopping the control channel over the reset because it clears the ring - * state as well. - */ -void nhi_reset_interface(struct tb_nhi *nhi) -{ - u32 val; - - val = ioread32(nhi->iobase + REG_CAPS); - /* Only v1 host interfaces implement the reset */ - if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) - return; - - dev_dbg(nhi->dev, "issuing host interface reset\n"); - - iowrite32(REG_HOST_INTERFACE_RESET_RST, - nhi->iobase + REG_HOST_INTERFACE_RESET); - /* Wait for tHIReset (10 ms) to complete */ - usleep_range(10000, 20000); -} - static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index f72d6b274501..d488eadadfce 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); irqreturn_t ring_msix(int irq, void *data); int nhi_probe(struct tb_nhi *nhi); void nhi_shutdown(struct tb_nhi *nhi); -void nhi_reset_interface(struct tb_nhi *nhi); - extern const struct dev_pm_ops nhi_pm_ops; /** @@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; * @release_ring_irq: NHI specific interrupt release hook * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook - * @reset_interface: Resets the host interface */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -69,7 +66,6 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); - void (*reset_interface)(struct tb_nhi *nhi); }; /* @@ -120,24 +116,11 @@ struct tb_nhi_ops { #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e - #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 /* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) -#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 99df60b6db36..d6a197fabc74 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -115,10 +115,6 @@ struct ring_desc { #define REG_CAPS_VERSION_MASK GENMASK(23, 16) #define REG_CAPS_VERSION_2 0x40 -/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ -#define REG_HOST_INTERFACE_RESET 0x39858 -#define REG_HOST_INTERFACE_RESET_RST BIT(0) - #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 99333729f3c2..8462ccb59b7e 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) nhi->quirks |= QUIRK_E2E; break; } - } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { - switch (pdev->device) { - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: - /* - * These AMD hosts may hang the Tx ring when the - * DMA paths are torn down so they need the host - * interface reset after each teardown. - */ - nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; - break; - } } } @@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; /* Ice Lake specific NHI operations */ @@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { .release_ring_irq = nhi_pci_ring_release_msix, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) From 403f45735f383eea48bae0e05744c3b610d495bb Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 30 Aug 2026 21:46:44 -0600 Subject: [PATCH 104/535] virt: vmgenid: move to using dev_set/get_drvdata The prior commit moved the order of initializing driver_data around. In looking through the tree at what is normally done, it appears that actually few drivers set or get driver_data directly, but instead go through the dev_set/get_drvdata helpers, which are simple inline helpers that amount to the same exact code. So, for the sake of consistency, use the helpers. Signed-off-by: Jason A. Donenfeld --- drivers/virt/vmgenid.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c index 0d269edf283d..ce957f5b1c31 100644 --- a/drivers/virt/vmgenid.c +++ b/drivers/virt/vmgenid.c @@ -25,7 +25,7 @@ struct vmgenid_state { static void vmgenid_notify(struct device *device) { - struct vmgenid_state *state = device->driver_data; + struct vmgenid_state *state = dev_get_drvdata(device); u8 old_id[VMGENID_SIZE]; memcpy(old_id, state->this_id, sizeof(old_id)); @@ -83,7 +83,7 @@ static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state) } setup_vmgenid_state(state, virt_addr); - dev->driver_data = state; + dev_set_drvdata(dev, state); status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, vmgenid_acpi_handler, dev); @@ -125,7 +125,7 @@ static int vmgenid_add_of(struct platform_device *pdev, if (ret < 0) return ret; - pdev->dev.driver_data = state; + dev_set_drvdata(&pdev->dev, state); ret = devm_request_irq(&pdev->dev, ret, vmgenid_of_irq_handler, IRQF_SHARED, "vmgenid", &pdev->dev); From 9a7d3340cd0f615a05c81c8f972cbffc8eb96230 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 30 Aug 2026 22:52:36 -0700 Subject: [PATCH 105/535] siphash: clean up kernel-doc comments Use the correct function parameter names and add function return value descriptions to avoid kernel-doc warnings: Warning: include/linux/siphash.h:82 function parameter 'len' not described in 'siphash' Warning: include/linux/siphash.h:82 No description found for return value of 'siphash' Warning: include/linux/siphash.h:132 function parameter 'len' not described in 'hsiphash' Warning: include/linux/siphash.h:132 No description found for return value of 'hsiphash' Signed-off-by: Randy Dunlap Signed-off-by: Jason A. Donenfeld --- include/linux/siphash.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/linux/siphash.h b/include/linux/siphash.h index 9153e77382e1..00c58bbe549a 100644 --- a/include/linux/siphash.h +++ b/include/linux/siphash.h @@ -75,8 +75,10 @@ static inline u64 ___siphash_aligned(const __le64 *data, size_t len, /** * siphash - compute 64-bit siphash PRF value * @data: buffer to hash - * @size: size of @data + * @len: size of @data * @key: the siphash key + * + * Returns: siphash PRF value */ static inline u64 siphash(const void *data, size_t len, const siphash_key_t *key) @@ -125,8 +127,10 @@ static inline u32 ___hsiphash_aligned(const __le32 *data, size_t len, /** * hsiphash - compute 32-bit hsiphash PRF value * @data: buffer to hash - * @size: size of @data + * @len: size of @data * @key: the hsiphash key + * + * Returns: hsiphash PRF value */ static inline u32 hsiphash(const void *data, size_t len, const hsiphash_key_t *key) From 3d0a9e74ce9401c4e287df72cc75c9d4b25b4060 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:27:51 +0530 Subject: [PATCH 106/535] random: vDSO: fix repeated word 'to' in comment Drop the second 'to', reported by checkpatch.pl as a possible repeated word. Only touches a comment, no code changes. Signed-off-by: Hemanth Selam Signed-off-by: Jason A. Donenfeld --- include/vdso/getrandom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/vdso/getrandom.h b/include/vdso/getrandom.h index 6ca4d6de9e46..1d9dbd4d76c1 100644 --- a/include/vdso/getrandom.h +++ b/include/vdso/getrandom.h @@ -64,7 +64,7 @@ extern void __arch_chacha20_blocks_nostack(u8 *dst_bytes, const u32 *key, u32 *c * @opaque_state: Passed to __cvdso_getrandom(). * @opaque_len: Passed to __cvdso_getrandom(); * - * This function is implemented by making a single call to to __cvdso_getrandom(), whose + * This function is implemented by making a single call to __cvdso_getrandom(), whose * documentation may be consulted for more information. * * Returns: The return value of __cvdso_getrandom(). From 703749b069d53b55f92499e088b30b031e47b8f9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:37:44 +0200 Subject: [PATCH 107/535] random: fix vgetrandom_opaque_params kernel-doc struct vgetrandom_opaque_params contains size_of_opaque_state, but its kernel-doc describes size_per_opaque_state. This leaves the real member undescribed and adds a nonexistent member to the generated documentation. Use the member's actual name. Fixes: 4ad10a5f5f78 ("random: introduce generic vDSO getrandom() implementation") Signed-off-by: Karl Mehltretter Signed-off-by: Jason A. Donenfeld --- include/uapi/linux/random.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/random.h b/include/uapi/linux/random.h index 1dd047ec98a1..ef6614d9c704 100644 --- a/include/uapi/linux/random.h +++ b/include/uapi/linux/random.h @@ -58,7 +58,7 @@ struct rand_pool_info { /** * struct vgetrandom_opaque_params - arguments for allocating memory for vgetrandom * - * @size_per_opaque_state: Size of each state that is to be passed to vgetrandom(). + * @size_of_opaque_state: Size of each state passed to vgetrandom(). * @mmap_prot: Value of the prot argument in mmap(2). * @mmap_flags: Value of the flags argument in mmap(2). * @reserved: Reserved for future use. From 2cc67425a97eead2e3248b6d5415c13b768ec295 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 15 Sep 2026 13:37:03 +0100 Subject: [PATCH 108/535] Revert "interconnect: qcom: x1e80100: enable QoS configuration" This reverts commit 5a8b2cc36e796d595c9d97eab23c2be22805cc1c. Since the introduction of this QoS change, X1 machines based on the Purwa SoC (such as the Lenovo Mini X) take a hard reset at boot time. Other machines based on Hamoa (such as the X1E001DE Snapdragon Devkit) end-up with a similar hard reset while under load, most likely due to the PCIe ports being starved of traffic. Revert the whole thing until someone figures out what magic parameters allow QoS to work in a sensible manner, as a usable machine is somehow preferable to one that crashes efficiently. Link: https://lore.kernel.org/all/apgeBNSiP01YfNcs@google.com Link: https://lore.kernel.org/all/86ld9d4h4n.wl-maz@kernel.org Signed-off-by: Marc Zyngier Cc: Georgi Djakov Cc: Raviteja Laggyshetty Cc: Konrad Dybcio Cc: Abel Vesa Cc: Bjorn Andersson Cc: Mostafa Saleh Cc: Thorsten Leemhuis Reviewed-by: Abel Vesa Reviewed-by: Mostafa Saleh Tested-by: Mostafa Saleh Closes: https://krzk.eu/#/builders/102/builds/70/steps/23/logs/warnings__3_ Link: https://patch.msgid.link/20260915123703.3228329-1-maz@kernel.org Signed-off-by: Georgi Djakov --- drivers/interconnect/qcom/x1e80100.c | 485 --------------------------- 1 file changed, 485 deletions(-) diff --git a/drivers/interconnect/qcom/x1e80100.c b/drivers/interconnect/qcom/x1e80100.c index 8075e0ff2059..2ba2823c7860 100644 --- a/drivers/interconnect/qcom/x1e80100.c +++ b/drivers/interconnect/qcom/x1e80100.c @@ -173,13 +173,6 @@ static struct qcom_icc_node qhm_qspi = { .name = "qhm_qspi", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -188,13 +181,6 @@ static struct qcom_icc_node qhm_qup1 = { .name = "qhm_qup1", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -203,13 +189,6 @@ static struct qcom_icc_node xm_sdc4 = { .name = "xm_sdc4", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -218,13 +197,6 @@ static struct qcom_icc_node xm_ufs_mem = { .name = "xm_ufs_mem", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xe000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a1noc_snoc }, }; @@ -233,13 +205,6 @@ static struct qcom_icc_node qhm_qup0 = { .name = "qhm_qup0", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x16000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -248,13 +213,6 @@ static struct qcom_icc_node qhm_qup2 = { .name = "qhm_qup2", .channels = 1, .buswidth = 4, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x11000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -263,13 +221,6 @@ static struct qcom_icc_node qxm_crypto = { .name = "qxm_crypto", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x12000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -286,13 +237,6 @@ static struct qcom_icc_node xm_qdss_etr_0 = { .name = "xm_qdss_etr_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x13000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -301,13 +245,6 @@ static struct qcom_icc_node xm_qdss_etr_1 = { .name = "xm_qdss_etr_1", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x14000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -316,13 +253,6 @@ static struct qcom_icc_node xm_sdc2 = { .name = "xm_sdc2", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x15000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_a2noc_snoc }, }; @@ -407,13 +337,6 @@ static struct qcom_icc_node alm_gpu_tcu = { .name = "alm_gpu_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x271000 }, - .prio = 1, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -422,13 +345,6 @@ static struct qcom_icc_node alm_pcie_tcu = { .name = "alm_pcie_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27d000 }, - .prio = 3, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -437,13 +353,6 @@ static struct qcom_icc_node alm_sys_tcu = { .name = "alm_sys_tcu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x273000 }, - .prio = 6, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -461,13 +370,6 @@ static struct qcom_icc_node qnm_gpu = { .name = "qnm_gpu", .channels = 4, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 4, - .port_offsets = { 0x51000, 0x58000, 0xd1000, 0xd8000 }, - .prio = 0, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -476,13 +378,6 @@ static struct qcom_icc_node qnm_lpass = { .name = "qnm_lpass", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x275000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -492,13 +387,6 @@ static struct qcom_icc_node qnm_mnoc_hf = { .name = "qnm_mnoc_hf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x53000, 0xd3000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -507,13 +395,6 @@ static struct qcom_icc_node qnm_mnoc_sf = { .name = "qnm_mnoc_sf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x55000, 0xd5000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -522,13 +403,6 @@ static struct qcom_icc_node qnm_nsp_noc = { .name = "qnm_nsp_noc", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x57000, 0xd7000 }, - .prio = 0, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -538,13 +412,6 @@ static struct qcom_icc_node qnm_pcie = { .name = "qnm_pcie", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x277000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 2, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc }, }; @@ -553,13 +420,6 @@ static struct qcom_icc_node qnm_snoc_sf = { .name = "qnm_snoc_sf", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27b000 }, - .prio = 2, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 3, .link_nodes = { &qns_gem_noc_cnoc, &qns_llcc, &qns_pcie }, @@ -569,13 +429,6 @@ static struct qcom_icc_node xm_gic = { .name = "xm_gic", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x27f000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_llcc }, }; @@ -616,13 +469,6 @@ static struct qcom_icc_node qnm_av1_enc = { .name = "qnm_av1_enc", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x2f000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -631,13 +477,6 @@ static struct qcom_icc_node qnm_camnoc_hf = { .name = "qnm_camnoc_hf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x28000, 0x29000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_hf }, }; @@ -646,13 +485,6 @@ static struct qcom_icc_node qnm_camnoc_icp = { .name = "qnm_camnoc_icp", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x2a000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -661,13 +493,6 @@ static struct qcom_icc_node qnm_camnoc_sf = { .name = "qnm_camnoc_sf", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x2b000, 0x2c000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -676,13 +501,6 @@ static struct qcom_icc_node qnm_eva = { .name = "qnm_eva", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x33000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -691,13 +509,6 @@ static struct qcom_icc_node qnm_mdp = { .name = "qnm_mdp", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x2d000, 0x2e000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_hf }, }; @@ -706,13 +517,6 @@ static struct qcom_icc_node qnm_video = { .name = "qnm_video", .channels = 2, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 2, - .port_offsets = { 0x30000, 0x31000 }, - .prio = 0, - .urg_fwd = 1, - .prio_fwd_disable = 0, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -721,13 +525,6 @@ static struct qcom_icc_node qnm_video_cv_cpu = { .name = "qnm_video_cv_cpu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x32000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -736,13 +533,6 @@ static struct qcom_icc_node qnm_video_v_cpu = { .name = "qnm_video_v_cpu", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x34000 }, - .prio = 4, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_mem_noc_sf }, }; @@ -783,13 +573,6 @@ static struct qcom_icc_node xm_pcie_3 = { .name = "xm_pcie_3", .channels = 1, .buswidth = 64, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x7000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -798,13 +581,6 @@ static struct qcom_icc_node xm_pcie_4 = { .name = "xm_pcie_4", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x8000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -813,13 +589,6 @@ static struct qcom_icc_node xm_pcie_5 = { .name = "xm_pcie_5", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x9000 }, - .prio = 3, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_north_gem_noc }, }; @@ -828,13 +597,6 @@ static struct qcom_icc_node xm_pcie_0 = { .name = "xm_pcie_0", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x9000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -843,13 +605,6 @@ static struct qcom_icc_node xm_pcie_1 = { .name = "xm_pcie_1", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xa000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -858,13 +613,6 @@ static struct qcom_icc_node xm_pcie_2 = { .name = "xm_pcie_2", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -873,13 +621,6 @@ static struct qcom_icc_node xm_pcie_6a = { .name = "xm_pcie_6a", .channels = 1, .buswidth = 32, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -888,13 +629,6 @@ static struct qcom_icc_node xm_pcie_6b = { .name = "xm_pcie_6b", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_pcie_south_gem_noc }, }; @@ -919,13 +653,6 @@ static struct qcom_icc_node qnm_gic = { .name = "qnm_gic", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x1c000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_gemnoc_sf }, }; @@ -958,13 +685,6 @@ static struct qcom_icc_node xm_usb2_0 = { .name = "xm_usb2_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x6000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_north_snoc }, }; @@ -973,13 +693,6 @@ static struct qcom_icc_node xm_usb3_mp = { .name = "xm_usb3_mp", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0x7000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_north_snoc }, }; @@ -988,13 +701,6 @@ static struct qcom_icc_node xm_usb3_0 = { .name = "xm_usb3_0", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xa000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1003,13 +709,6 @@ static struct qcom_icc_node xm_usb3_1 = { .name = "xm_usb3_1", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xb000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1018,13 +717,6 @@ static struct qcom_icc_node xm_usb3_2 = { .name = "xm_usb3_2", .channels = 1, .buswidth = 8, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xc000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1033,13 +725,6 @@ static struct qcom_icc_node xm_usb4_0 = { .name = "xm_usb4_0", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xd000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1048,13 +733,6 @@ static struct qcom_icc_node xm_usb4_1 = { .name = "xm_usb4_1", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xe000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1063,13 +741,6 @@ static struct qcom_icc_node xm_usb4_2 = { .name = "xm_usb4_2", .channels = 1, .buswidth = 16, - .qosbox = &(const struct qcom_icc_qosbox) { - .num_ports = 1, - .port_offsets = { 0xf000 }, - .prio = 2, - .urg_fwd = 0, - .prio_fwd_disable = 1, - }, .num_links = 1, .link_nodes = { &qns_aggre_usb_south_snoc }, }; @@ -1795,21 +1466,11 @@ static struct qcom_icc_node * const aggre1_noc_nodes[] = { [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, }; -static const struct regmap_config x1e80100_aggre1_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x14400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_aggre1_noc = { - .config = &x1e80100_aggre1_noc_regmap_config, .nodes = aggre1_noc_nodes, .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), .bcms = aggre1_noc_bcms, .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), - .qos_requires_clocks = true, }; static struct qcom_icc_bcm * const aggre2_noc_bcms[] = { @@ -1827,16 +1488,7 @@ static struct qcom_icc_node * const aggre2_noc_nodes[] = { [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, }; -static const struct regmap_config x1e80100_aggre2_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x1c400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_aggre2_noc = { - .config = &x1e80100_aggre2_noc_regmap_config, .nodes = aggre2_noc_nodes, .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), .bcms = aggre2_noc_bcms, @@ -1921,16 +1573,7 @@ static struct qcom_icc_node * const cnoc_cfg_nodes[] = { [SLAVE_TCU] = &xs_sys_tcu_cfg, }; -static const struct regmap_config x1e80100_cnoc_cfg_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x6600, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_cnoc_cfg = { - .config = &x1e80100_cnoc_cfg_regmap_config, .nodes = cnoc_cfg_nodes, .num_nodes = ARRAY_SIZE(cnoc_cfg_nodes), .bcms = cnoc_cfg_bcms, @@ -1960,16 +1603,7 @@ static struct qcom_icc_node * const cnoc_main_nodes[] = { [SLAVE_PCIE_6B] = &xs_pcie_6b, }; -static const struct regmap_config x1e80100_cnoc_main_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x14400, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_cnoc_main = { - .config = &x1e80100_cnoc_main_regmap_config, .nodes = cnoc_main_nodes, .num_nodes = ARRAY_SIZE(cnoc_main_nodes), .bcms = cnoc_main_bcms, @@ -1999,16 +1633,7 @@ static struct qcom_icc_node * const gem_noc_nodes[] = { [SLAVE_MEM_NOC_PCIE_SNOC] = &qns_pcie, }; -static const struct regmap_config x1e80100_gem_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x311200, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_gem_noc = { - .config = &x1e80100_gem_noc_regmap_config, .nodes = gem_noc_nodes, .num_nodes = ARRAY_SIZE(gem_noc_nodes), .bcms = gem_noc_bcms, @@ -2023,16 +1648,7 @@ static struct qcom_icc_node * const lpass_ag_noc_nodes[] = { [SLAVE_LPASS_GEM_NOC] = &qns_lpass_ag_noc_gemnoc, }; -static const struct regmap_config x1e80100_lpass_ag_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xe080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_ag_noc = { - .config = &x1e80100_lpass_ag_noc_regmap_config, .nodes = lpass_ag_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_ag_noc_nodes), .bcms = lpass_ag_noc_bcms, @@ -2048,16 +1664,7 @@ static struct qcom_icc_node * const lpass_lpiaon_noc_nodes[] = { [SLAVE_LPIAON_NOC_LPASS_AG_NOC] = &qns_lpass_aggnoc, }; -static const struct regmap_config x1e80100_lpass_lpiaon_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x19080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_lpiaon_noc = { - .config = &x1e80100_lpass_lpiaon_noc_regmap_config, .nodes = lpass_lpiaon_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_lpiaon_noc_nodes), .bcms = lpass_lpiaon_noc_bcms, @@ -2072,16 +1679,7 @@ static struct qcom_icc_node * const lpass_lpicx_noc_nodes[] = { [SLAVE_LPICX_NOC_LPIAON_NOC] = &qns_lpi_aon_noc, }; -static const struct regmap_config x1e80100_lpass_lpicx_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x3a200, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_lpass_lpicx_noc = { - .config = &x1e80100_lpass_lpicx_noc_regmap_config, .nodes = lpass_lpicx_noc_nodes, .num_nodes = ARRAY_SIZE(lpass_lpicx_noc_nodes), .bcms = lpass_lpicx_noc_bcms, @@ -2126,16 +1724,7 @@ static struct qcom_icc_node * const mmss_noc_nodes[] = { [SLAVE_SERVICE_MNOC] = &srvc_mnoc, }; -static const struct regmap_config x1e80100_mmss_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x5b800, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_mmss_noc = { - .config = &x1e80100_mmss_noc_regmap_config, .nodes = mmss_noc_nodes, .num_nodes = ARRAY_SIZE(mmss_noc_nodes), .bcms = mmss_noc_bcms, @@ -2151,16 +1740,7 @@ static struct qcom_icc_node * const nsp_noc_nodes[] = { [SLAVE_CDSP_MEM_NOC] = &qns_nsp_gemnoc, }; -static const struct regmap_config x1e80100_nsp_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xe080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_nsp_noc = { - .config = &x1e80100_nsp_noc_regmap_config, .nodes = nsp_noc_nodes, .num_nodes = ARRAY_SIZE(nsp_noc_nodes), .bcms = nsp_noc_bcms, @@ -2177,16 +1757,7 @@ static struct qcom_icc_node * const pcie_center_anoc_nodes[] = { [SLAVE_ANOC_PCIE_GEM_NOC] = &qns_pcie_mem_noc, }; -static const struct regmap_config x1e80100_pcie_center_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x7000, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_center_anoc = { - .config = &x1e80100_pcie_center_anoc_regmap_config, .nodes = pcie_center_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_center_anoc_nodes), .bcms = pcie_center_anoc_bcms, @@ -2203,16 +1774,7 @@ static struct qcom_icc_node * const pcie_north_anoc_nodes[] = { [SLAVE_PCIE_NORTH] = &qns_pcie_north_gem_noc, }; -static const struct regmap_config x1e80100_pcie_north_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x9080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_north_anoc = { - .config = &x1e80100_pcie_north_anoc_regmap_config, .nodes = pcie_north_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_north_anoc_nodes), .bcms = pcie_north_anoc_bcms, @@ -2231,16 +1793,7 @@ static struct qcom_icc_node * const pcie_south_anoc_nodes[] = { [SLAVE_PCIE_SOUTH] = &qns_pcie_south_gem_noc, }; -static const struct regmap_config x1e80100_pcie_south_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xd080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_pcie_south_anoc = { - .config = &x1e80100_pcie_south_anoc_regmap_config, .nodes = pcie_south_anoc_nodes, .num_nodes = ARRAY_SIZE(pcie_south_anoc_nodes), .bcms = pcie_south_anoc_bcms, @@ -2262,16 +1815,7 @@ static struct qcom_icc_node * const system_noc_nodes[] = { [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf, }; -static const struct regmap_config x1e80100_system_noc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x1c080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_system_noc = { - .config = &x1e80100_system_noc_regmap_config, .nodes = system_noc_nodes, .num_nodes = ARRAY_SIZE(system_noc_nodes), .bcms = system_noc_bcms, @@ -2287,16 +1831,7 @@ static struct qcom_icc_node * const usb_center_anoc_nodes[] = { [SLAVE_USB_NOC_SNOC] = &qns_aggre_usb_snoc, }; -static const struct regmap_config x1e80100_usb_center_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x8800, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_center_anoc = { - .config = &x1e80100_usb_center_anoc_regmap_config, .nodes = usb_center_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_center_anoc_nodes), .bcms = usb_center_anoc_bcms, @@ -2312,21 +1847,11 @@ static struct qcom_icc_node * const usb_north_anoc_nodes[] = { [SLAVE_AGGRE_USB_NORTH] = &qns_aggre_usb_north_snoc, }; -static const struct regmap_config x1e80100_usb_north_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0x7080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_north_anoc = { - .config = &x1e80100_usb_north_anoc_regmap_config, .nodes = usb_north_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_north_anoc_nodes), .bcms = usb_north_anoc_bcms, .num_bcms = ARRAY_SIZE(usb_north_anoc_bcms), - .qos_requires_clocks = true, }; static struct qcom_icc_bcm * const usb_south_anoc_bcms[] = { @@ -2342,21 +1867,11 @@ static struct qcom_icc_node * const usb_south_anoc_nodes[] = { [SLAVE_AGGRE_USB_SOUTH] = &qns_aggre_usb_south_snoc, }; -static const struct regmap_config x1e80100_usb_south_anoc_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = 0xf080, - .fast_io = true, -}; - static const struct qcom_icc_desc x1e80100_usb_south_anoc = { - .config = &x1e80100_usb_south_anoc_regmap_config, .nodes = usb_south_anoc_nodes, .num_nodes = ARRAY_SIZE(usb_south_anoc_nodes), .bcms = usb_south_anoc_bcms, .num_bcms = ARRAY_SIZE(usb_south_anoc_bcms), - .qos_requires_clocks = true, }; static const struct of_device_id qnoc_of_match[] = { From d84e27d87738634b92001500435ae4dadba10a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Carrat?= Date: Thu, 10 Sep 2026 18:26:09 +0200 Subject: [PATCH 109/535] USB: serial: cp210x: add Corsair AX1500i Power Supply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Corsair AX1500i power supply exposes its Corsair Link monitoring interface on a mini-USB port through an on-board CP2103, using a Corsair-specific product ID. Without the ID in the table no driver binds and no tty is created. Tested by forcing the driver association with echo 1b1c 1c02 > /sys/bus/usb-serial/drivers/cp210x/new_id and then setting the serial port to 115200 8N1. Adding the ID is sufficient to talk to the device: the tty reaches a USB-to-SMBus bridge sitting behind the UART, which identifies itself as "USB to SMB Bridge (Firmware by Ross Fosler)" version 0.9 and relays PMBus reads to the power supply controller. Input voltage and current, input and output power, internal temperature and fan speed were all read back on an AX1500i. [ 7.806181] usb 3-14: New USB device found, idVendor=1b1c, idProduct=1c02, bcdDevice= 1.00 [ 698.526754] usbcore: registered new interface driver cp210x [ 698.526767] usbserial: USB Serial support registered for cp210x [ 698.527863] cp210x 3-14:1.0: cp210x converter detected [ 698.529263] usb 3-14: cp210x converter now attached to ttyUSB0 The related AX1600i (1b1c:1c11, iProduct "USB API") is expected to be the same kind of device but has not been tested here, so it is not added. Cc: stable@vger.kernel.org Signed-off-by: Jérémy Carrat Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 333b8b463704..c3f9add98333 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ From afc235a217360c6859068d2e37a4df305c4d1be2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 15 Sep 2026 14:40:11 +0200 Subject: [PATCH 110/535] USB: serial: ssu100: fix baud rate overflow The requested baud rate is incorrectly truncated to 16 bits so that line speeds above 65535 bps cannot be set. Use 32 bits for the rate and remainder while rejecting rates outside of [50,460800] to avoid having the divisor or remainder overflow. This issue was flagged by an LLM. Fixes: 52af95459939 ("USB: add USB serial ssu100 driver") Cc: stable@vger.kernel.org # 2.6.36 Cc: Bill Pemberton Assisted-by: LLM Signed-off-by: Johan Hovold --- drivers/usb/serial/ssu100.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c index b0d51558b73c..8c5b9ef708b3 100644 --- a/drivers/usb/serial/ssu100.c +++ b/drivers/usb/serial/ssu100.c @@ -32,6 +32,7 @@ #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 460800 #define ATC_DISABLED 0x00 @@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, { struct usb_device *dev = port->serial->dev; struct ktermios *termios = &tty->termios; - u16 baud, divisor, remainder; + speed_t baud, remainder; unsigned int cflag = termios->c_cflag; u16 urb_value = 0; /* will hold the new flags */ + u16 divisor; int result; if (cflag & PARENB) { @@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); @@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) dev_dbg(&port->dev, "%s - set uart failed\n", __func__); if (tty) - ssu100_set_termios(tty, port, &tty->termios); + ssu100_set_termios(tty, port, NULL); return usb_serial_generic_open(tty, port); } From ec06546b43b612cefc851558981b4955c0ef9e46 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 15 Sep 2026 14:40:12 +0200 Subject: [PATCH 111/535] USB: serial: quatech2: fix baud rate overflow The requested baud rate is incorrectly truncated to 16 bits so that line speeds above 65535 bps cannot be set. Use 32 bits for the rate while rejecting rates outside of [50,921600] to avoid having the 16-bit divisor overflow. Fixes: f7a33e608d9a ("USB: serial: add quatech2 usb to serial driver") Cc: stable@vger.kernel.org # 3.5 Cc: Bill Pemberton Signed-off-by: Johan Hovold --- drivers/usb/serial/quatech2.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index dc2b39810a26..3b4df3ac7c73 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -52,6 +52,7 @@ #define QT2_XMIT_FLUSH 0x05 /* no following info */ #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 921600 #define DEFAULT_BAUD_RATE 9600 @@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) static inline int qt2_set_port_config(struct usb_device *dev, unsigned char port_number, - u16 baudrate, u16 lcr) + speed_t baudrate, u16 lcr) { int divisor = calc_baud_divisor(baudrate); u16 index = ((u16) (lcr << 8) | (u16) (port_number)); @@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, struct usb_device *dev = port->serial->dev; struct qt2_port_private *port_priv; struct ktermios *termios = &tty->termios; - u16 baud; unsigned int cflag = termios->c_cflag; u16 new_lcr = 0; + speed_t baud; int status; port_priv = usb_get_serial_port_data(port); @@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + status = qt2_set_port_config(dev, port_priv->device_port, baud, new_lcr); if (status < 0) @@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) port_priv->device_port = (u8) device_port; if (tty) - qt2_set_termios(tty, port, &tty->termios); + qt2_set_termios(tty, port, NULL); return 0; From 98d00deb6ae4a83f567444215302ab7b101ed439 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Mon, 7 Sep 2026 20:30:41 +0200 Subject: [PATCH 112/535] usb: typec: tcpm: fix use-after-free of the kthread worker on port unregister tcpm_unregister_port() destroys the port's kthread worker first and calls tcpm_reset_port() afterwards. Since the Discover Identity retry mechanism was added, tcpm_reset_port() calls mod_vdm_discovery_cancel_delayed_work(), which does kthread_cancel_work_sync(&port->vdm_discovery_work). That dereferences work->worker, which still points at the worker that kthread_destroy_worker() has already freed: tcpm_unregister_port() kthread_destroy_worker(port->wq) -> kfree(worker) ... tcpm_reset_port() mod_vdm_discovery_cancel_delayed_work() kthread_cancel_work_sync(&port->vdm_discovery_work) __kthread_cancel_work_sync() raw_spin_lock_irqsave(&worker->lock, ...) <- freed memory KASAN report on 7.3-rc1 when unbinding a fusb302 port (RK3588): BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x10c/0x210 Write of size 4 at addr ffff00010122ef04 by task bash/8349 Call trace: _raw_spin_lock_irqsave+0x10c/0x210 __kthread_cancel_work_sync+0x60/0x408 kthread_cancel_work_sync+0x20/0x48 tcpm_reset_port+0x18c/0xb80 [tcpm] tcpm_unregister_port+0x104/0x2f8 [tcpm] fusb302_remove+0xc8/0x200 [fusb302] i2c_device_remove+0x7c/0x288 ... Allocated by task 112: kthread_create_worker_on_node+0x14c/0x2c8 tcpm_register_port+0x288/0x3918 [tcpm] fusb302_probe+0x604/0xc88 [fusb302] Freed by task 8349: kfree+0x260/0x558 kthread_destroy_worker+0xa0/0x130 tcpm_unregister_port+0x74/0x2f8 [tcpm] fusb302_remove+0xc8/0x200 [fusb302] With CONFIG_PROVE_LOCKING the same unbind shows up as "DEBUG_LOCKS_WARN_ON(lock->magic != lock)" in __lock_acquire, followed by an oops in the unbinding task, which then exits with interrupts disabled and the following shutdown hangs. The work itself cannot be pending at that point: kthread_destroy_worker() has flushed the worker and the discovery timer is cancelled right before the cancel call. So just remember that the worker is gone and skip the cancel in that case. Tested on an Orange Pi 5 Plus (RK3588, fusb302) with KASAN: unbinding the port reports the use-after-free above without this patch and nothing with it; the port binds again fine afterwards in both cases. Fixes: 205dc9cb39f5 ("usb: typec: tcpm: implement retry mechanism for Discover Identity VDMs") Signed-off-by: Igor Paunovic Assisted-by: LLM Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260907183041.8253-1-royalnet026@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 8f80bdd62f94..06692feb6d46 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) { hrtimer_cancel(&port->vdm_discovery_timer); - kthread_cancel_work_sync(&port->vdm_discovery_work); + if (port->wq) + kthread_cancel_work_sync(&port->vdm_discovery_work); } static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) @@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) port->registered = false; kthread_destroy_worker(port->wq); + port->wq = NULL; hrtimer_cancel(&port->vdm_discovery_timer); hrtimer_cancel(&port->enable_frs_timer); From 469600d5d292b1482c90dd6853555821d11b8c5f Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Thu, 10 Sep 2026 09:11:50 +0800 Subject: [PATCH 113/535] usb: typec: anx7411 - stop the IRQ before destroying the workqueue The IRQ is devm-managed, so it can still queue plat->work while remove() is tearing the queue down. Disable it first. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Cc: stable Signed-off-by: Linkai Gong Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260910011150.2966843-1-gonglinkai@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 41df115912b9..c3d36da692e4 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) anx7411_partner_unregister_altmode(plat); anx7411_unregister_partner(plat); - if (plat->workqueue) + if (plat->workqueue) { + disable_irq(plat->intp_irq); + cancel_work_sync(&plat->work); destroy_workqueue(plat->workqueue); + } i2c_unregister_device(plat->spi_client); From f1d6f7d25fe88ee49aaad96c8649e090fc3a4b68 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:10:33 -0400 Subject: [PATCH 114/535] usb: chipidea: tegra: disable runtime PM on resume failure tegra_usb_probe() enables runtime PM before resuming the device. If pm_runtime_resume_and_get() fails, probe returns without disabling runtime PM. The later error paths reach pm_runtime_force_suspend(), but this early return bypasses that cleanup. Disable runtime PM before returning the resume error. Do not use the fail_power_off path: the failed resume did not retain a usage reference, so its pm_runtime_put_sync_suspend() would be unbalanced. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8b85e11c1a7a ("usb: chipidea: tegra: Add runtime PM and OPP support") Cc: stable Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Peter Chen Link: https://patch.msgid.link/20260913041033.25144-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_tegra.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 372788f0f970..26225b034d3b 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); err = pm_runtime_resume_and_get(&pdev->dev); - if (err) + if (err) { + pm_runtime_disable(&pdev->dev); return err; + } if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) usb->needs_double_reset = true; From ebe9b68c396792c15d0ac690d592e449e74ed804 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 9 Sep 2026 09:56:55 +0000 Subject: [PATCH 115/535] usb: cdns3: fix use-after-free in cdns3_gadget_exit() cdns3_gadget_start() arms two works on system_freezable_wq: pending_status_wq for the deferred ep0 status stage and aligned_buf_wq for realigned request buffers. Both handlers use the cdns3_device the works are embedded in, and cdns3_pending_setup_status_handler() also calls the ep0 request completion. cdns3_gadget_exit() does not wait for these works. It frees all endpoints and aligned buffers and drops the last reference to the gadget device, which frees priv_dev, so a work queued before the exit can run after the free. Fix this by waiting for both works after the gadget driver is unbound and the IRQ is freed, when no new work can be queued, and before the endpoints and buffers are released. This issue was found by an in-house static analysis tool. Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver") Cc: stable Reported-by: Sicong Huang Closes: https://lore.kernel.org/linux-usb/7f5719b.8700.18f67b324d3.Coremail.congei42@163.com/ Suggested-by: Sicong Huang Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Acked-by: Peter Chen Link: https://patch.msgid.link/20260909095655.694527-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 42311c1bfada..0d272e9c556c 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) usb_del_gadget(&priv_dev->gadget); devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + /* The works can still be queued until the IRQ is freed. */ + cancel_work_sync(&priv_dev->pending_status_wq); + cancel_work_sync(&priv_dev->aligned_buf_wq); + cdns3_free_all_eps(priv_dev); while (!list_empty(&priv_dev->aligned_buf_list)) { From 901197dd4c7dcffbf97426487d4c047555c98554 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:08 -0400 Subject: [PATCH 116/535] usb: ohci-da8xx: disable controller wakeup on cleanup The OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Probe enables wakeup, but neither removal nor a later notifier registration failure disables it, leaving the wakeup source attached. Disable controller wakeup after removing the HCD on both paths. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-2-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-da8xx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 0938c0e7a8b6..a7cd5fb4f119 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) err_remove_hcd: usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); err: usb_put_hcd(hcd); return error; @@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); usb_put_hcd(hcd); } From 8dd8451c527e2a723bca20da5a6d724b8592551f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:09 -0400 Subject: [PATCH 117/535] usb: ohci-s3c2410: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-3-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-s3c2410.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index e623e24d3f8e..7c0e35be58f1 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); s3c2410_stop_hc(dev); usb_put_hcd(hcd); } From b1b0f8bdbbc891a4ee7f3b1a878c633e02b7bbeb Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:10 -0400 Subject: [PATCH 118/535] usb: ohci-spear: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-4-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-spear.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 6843d7cb3f9f..5aeabb051265 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) struct spear_ohci *sohci_p = to_spear_ohci(hcd); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); clk_disable_unprepare(sohci_p->clk); usb_put_hcd(hcd); From 08b2ccd7eb7e41f77a04a09112ca3edcc588e3f0 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:11 -0400 Subject: [PATCH 119/535] usb: ohci-st: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: d115837259ad ("usb: host: ohci-st: Add OHCI driver support for ST STB devices") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Patrice Chotard Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-5-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-st.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c index d1656fce5400..53010a277b67 100644 --- a/drivers/usb/host/ohci-st.c +++ b/drivers/usb/host/ohci-st.c @@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) int clk; usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); if (pdata->power_off) pdata->power_off(dev); From c4c02084d6687d5cd5edccaf52cc45e5160f1184 Mon Sep 17 00:00:00 2001 From: Tomer Pomeranc Date: Wed, 12 Aug 2026 22:53:15 +0300 Subject: [PATCH 120/535] binder: fix leaked fd fixups on TF_UPDATE_TXN supersede When a TF_UPDATE_TXN transaction supersedes a pending async transaction in a frozen process, the outdated transaction is freed with kfree() directly. This skips binder_free_txn_fixups(), leaking all binder_txn_fd_fixup entries and their fget()'d struct file references. The leaked file refcounts never reach zero, so the struct file objects are permanently pinned in memory. They survive process exit and accumulate across invocations until file-max exhaustion. Every other transaction cleanup path (binder_free_transaction(), binder_transaction() error paths, binder_release_work()) correctly calls binder_free_txn_fixups(). Add the missing call before kfree() in the t_outdated cleanup block. Fixes: 9864bb480133 ("Binder: add TF_UPDATE_TXN to replace outdated txn") Cc: stable Signed-off-by: Tomer Pomeranc Acked-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260812195316.259136-2-tomerpo@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 8f2ef1bd539f..f70aeb63adb4 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -2932,6 +2932,7 @@ static int binder_proc_transaction(struct binder_transaction *t, trace_binder_transaction_update_buffer_release(buffer); binder_release_entire_buffer(proc, NULL, buffer, false); binder_alloc_free_buf(&proc->alloc, buffer); + binder_free_txn_fixups(t_outdated); kfree(t_outdated); binder_stats_deleted(BINDER_STAT_TRANSACTION); } From 22c135635fdd9816c0ef140d6de7b2e4fdf73e89 Mon Sep 17 00:00:00 2001 From: Tomer Pomeranc Date: Wed, 12 Aug 2026 22:53:16 +0300 Subject: [PATCH 121/535] binder: fix is_failure flag for superseded transaction cleanup When a TF_UPDATE_TXN transaction supersedes a pending async transaction, binder_release_entire_buffer() is called with is_failure=false. Since the superseded transaction was never delivered, binder_apply_fd_fixups() was never called and no fds were installed in the target process. With is_failure=false, the BINDER_TYPE_FDA cleanup handler interprets stale buffer contents as installed fd numbers and passes them to binder_deferred_fd_close(), closing unrelated file descriptors. Pass is_failure=true since the transaction was never delivered to the target, matching the semantics of all other undelivered-transaction cleanup paths. Fixes: 9864bb480133 ("Binder: add TF_UPDATE_TXN to replace outdated txn") Cc: stable Signed-off-by: Tomer Pomeranc Acked-by: Carlos Llamas Link: https://patch.msgid.link/20260812195316.259136-3-tomerpo@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index f70aeb63adb4..bc8bc9ee43a5 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -2930,7 +2930,7 @@ static int binder_proc_transaction(struct binder_transaction *t, t_outdated->buffer = NULL; buffer->transaction = NULL; trace_binder_transaction_update_buffer_release(buffer); - binder_release_entire_buffer(proc, NULL, buffer, false); + binder_release_entire_buffer(proc, NULL, buffer, true); binder_alloc_free_buf(&proc->alloc, buffer); binder_free_txn_fixups(t_outdated); kfree(t_outdated); From 30d15f44a9e513ec2f257fdf192d9d1fa5af0799 Mon Sep 17 00:00:00 2001 From: Peiyang He Date: Sun, 13 Sep 2026 16:56:45 +0800 Subject: [PATCH 122/535] binderfs: fix UAF write in binder_add_device binderfs_binder_device_create() publishes the new dentry with d_make_persistent() and then calls simple_done_creating(), which drops the parent directory lock and the creator's dentry reference. It then calls binder_add_device() to register the device in the global binder_devices list. After simple_done_creating() releases the parent directory lock, a concurrent unlinkat() can remove the new device entry. Dropping the creator's dentry reference can then trigger binderfs_evict_inode(), freeing the device. binder_add_device() later accesses the freed object, causing UAF write. Found by a modified Syzkaller: BUG: KASAN: slab-use-after-free in hlist_add_head include/linux/list.h:1073 [inline] BUG: KASAN: slab-use-after-free in binder_add_device+0xa9/0xc0 drivers/android/binder.c:7068 Write of size 8 at addr ffff88805b340c00 by task syz.1.532/11389 CPU: 0 UID: 0 PID: 11389 Comm: syz.1.532 Not tainted 7.2.0 #4 PREEMPT(full) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x10e/0x1f0 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xf7/0x600 mm/kasan/report.c:482 kasan_report+0xe4/0x120 mm/kasan/report.c:595 hlist_add_head include/linux/list.h:1073 [inline] binder_add_device+0xa9/0xc0 drivers/android/binder.c:7068 binderfs_binder_device_create.isra.0+0x724/0x990 drivers/android/binderfs.c:196 binder_ctl_ioctl+0x186/0x1b0 drivers/android/binderfs.c:241 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7ff9027a833d Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ff903674018 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007ff902a35fa0 RCX: 00007ff9027a833d RDX: 0000200000000500 RSI: 00000000c1086201 RDI: 0000000000000004 RBP: 00007ff902850733 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007ff902a36038 R14: 00007ff902a35fa0 R15: 00007ffd7166bfa0 Allocated by task 11389: kasan_save_stack+0x33/0x60 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:398 [inline] __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415 kasan_kmalloc include/linux/kasan.h:263 [inline] __kmalloc_cache_noprof+0x2e4/0x6f0 mm/slub.c:5489 _kmalloc_noprof include/linux/slab.h:988 [inline] _kzalloc_noprof include/linux/slab.h:1309 [inline] binderfs_binder_device_create.isra.0+0x17a/0x990 drivers/android/binderfs.c:148 binder_ctl_ioctl+0x186/0x1b0 drivers/android/binderfs.c:241 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 11389: kasan_save_stack+0x33/0x60 mm/kasan/common.c:57 kasan_save_track+0x14/0x30 mm/kasan/common.c:78 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:253 [inline] __kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2677 [inline] slab_free mm/slub.c:6377 [inline] kfree+0x2fc/0x6e0 mm/slub.c:6692 binderfs_evict_inode+0x1e8/0x260 drivers/android/binderfs.c:268 evict+0x3c2/0xad0 fs/inode.c:825 iput_final fs/inode.c:2019 [inline] iput fs/inode.c:2068 [inline] iput+0x79a/0xd30 fs/inode.c:2031 dentry_unlink_inode+0x27f/0x460 fs/dcache.c:479 dentry_kill+0x25d/0xc20 fs/dcache.c:826 finish_dput fs/dcache.c:1001 [inline] dput.part.0+0xce/0x230 fs/dcache.c:1042 dput+0x1f/0x30 fs/dcache.c:1037 end_dirop+0x7d/0xa0 fs/namei.c:2956 binderfs_binder_device_create.isra.0+0x71c/0x990 drivers/android/binderfs.c:194 binder_ctl_ioctl+0x186/0x1b0 drivers/android/binderfs.c:241 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl fs/ioctl.c:583 [inline] __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x116/0x800 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff88805b340c00 which belongs to the cache kmalloc-512 of size 512 The buggy address is located 0 bytes inside of freed 512-byte region [ffff88805b340c00, ffff88805b340e00) Fix by calling binder_add_device() before d_make_persistent(), while the parent directory lock is still held and the dentry cannot be discarded. Cc: stable Fixes: b89aa544821d ("convert binderfs") Signed-off-by: Peiyang He Assisted-by: Codex:gpt-5.5 Acked-by: Carlos Llamas Link: https://patch.msgid.link/F6EF5FB778E87C98+20260913085645.1639558-1-peiyang_he@smail.nju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/android/binderfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 361d69f756f5..1ca31b9a0583 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -189,12 +189,11 @@ static int binderfs_binder_device_create(struct inode *ref_inode, goto err; } inode->i_private = device; + binder_add_device(device); d_make_persistent(dentry, inode); fsnotify_create(root->d_inode, dentry); simple_done_creating(dentry); - binder_add_device(device); - return 0; err: From 62479b6e5df82cbdf3c4145130928f233fae78fb Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 3 Sep 2026 09:22:52 +0000 Subject: [PATCH 123/535] rust_binder: cancel deferred work items in thread exit If there are deferred work items on the thread todo list, then they are not cleaned up in the Thread::release() method. Thus, update the code to clean up the work items even if they are deferred. This can happen if the thread dies while it has an active outgoing transaction. Cc: stable Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260903-binder-exit-get-work-v1-1-2d6129a238df@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/thread.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 18a14aa8a835..24f7b5be1b0e 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -686,6 +686,12 @@ pub(crate) fn push_return_work(&self, reply: u32) { self.inner.lock().push_return_work(reply); } + pub(crate) fn pop_work_even_if_deferred(&self) -> Option> { + let mut thread_inner = self.inner.lock(); + thread_inner.process_work_list = true; + thread_inner.pop_work() + } + fn translate_object( &self, obj_index: usize, @@ -1678,7 +1684,7 @@ pub(crate) fn release(self: &Arc) { self.unwind_transaction_stack(); // Cancel all pending work items. - while let Ok(Some(work)) = self.get_work_local(false) { + while let Some(work) = self.pop_work_even_if_deferred() { work.into_arc().cancel(); } } From 2a74ccd1bcc170e66525f34e9de8652b57e2cf3d Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 3 Sep 2026 11:36:03 +0000 Subject: [PATCH 124/535] rust_binder: reschedule node refcount update on thread exit When a thread exits via BINDER_THREAD_EXIT, its pending work items are cancelled. If a thread exits while holding a pending node refcount increment (e.g. pushed as deferred work to that thread), the refcount increment was previously dropped because Node::cancel() and NodeWrapper::cancel() were no-ops. Dropping the refcount update leaves the node's delivery state and count state desynchronized, and userspace will not receive the notification, which can cause the node to never be freed from the process's nodes tree when all external references are dropped. Fix this by implementing DeliverToRead::cancel() for Node and NodeWrapper to move the pending refcount update to the process's work queue on thread exit so another thread can deliver it to userspace. Cc: stable Fixes: eafedbc7c050 ("rust_binder: add Rust Binder driver") Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20260903-binder-thread-exit-node-v1-1-be09ff14f6a4@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/node.rs | 20 ++++++++++++--- drivers/android/binder/node/wrapper.rs | 34 +++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 0a82af14cda3..8dc3e3f2b834 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -51,9 +51,9 @@ /// about to drop the weak reference, then the strong increment could be processed after the /// other thread has already exited, which would be too late. /// -/// Note that trying to create a `ListArc` to the node can succeed even if `has_normal_push` is +/// Note that trying to create a `ListArc` to the node can succeed even if `has_pushed_node` is /// set. This is because another thread might just have popped the node from a todo list, but not -/// yet called `do_work`. However, if `has_normal_push` is false, then creating a `ListArc` should +/// yet called `do_work`. However, if `has_pushed_node` is false, then creating a `ListArc` should /// always succeed. /// /// Like the other fields in `NodeInner`, the delivery state is protected by the process lock. @@ -738,7 +738,21 @@ fn do_work( self.do_work_locked(writer, owner_inner) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + let _drop_outside_lock; + let mut owner_inner = self.owner.inner.lock(); + + // We only do something on BINDER_THREAD_EXIT, not process exit. + if owner_inner.is_dead { + return; + } + + // If BINDER_THREAD_EXIT is invoked on a thread with a pending node refcount update, we + // should move ourselves to ensure the refcount update is still delivered. + if let Some(node) = ListArc::try_from_arc_borrow(self.as_arc_borrow()) { + _drop_outside_lock = owner_inner.push_work(&self.owner, node); + } + } fn should_sync_wakeup(&self) -> bool { false diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs index 6e4ca01c941a..886626ca0d42 100644 --- a/drivers/android/binder/node/wrapper.rs +++ b/drivers/android/binder/node/wrapper.rs @@ -57,7 +57,39 @@ fn do_work( node.do_work_locked(writer, owner_inner) } - fn cancel(self: DArc) {} + fn cancel(self: DArc) { + let _drop_outside_lock; + let node = &self.node; + let mut owner_inner = node.owner.inner.lock(); + + // We only do something on BINDER_THREAD_EXIT, not process exit. + if owner_inner.is_dead { + return; + } + + // We transfer the responsibility of the node refcount update to the scheduled Node because + // NodeWrapper has no way to re-create the ListArc. + let inner = node.inner.access_mut(&mut owner_inner); + + let ds = &mut inner.delivery_state; + assert!(ds.has_pushed_wrapper); + assert!(ds.has_strong_zero2one); + ds.has_pushed_wrapper = false; + + // We are changing the state to one where the Node is the strong zero2one update instead of + // the wrapper. + ds.has_weak_zero2one = false; + + if !ds.has_pushed_node { + if let Some(node2) = ListArc::try_from_arc_borrow(node.as_arc_borrow()) { + ds.has_pushed_node = true; + _drop_outside_lock = owner_inner.push_work(&node.owner, node2); + } else { + // This can't actually happen. + ds.has_strong_zero2one = false; + } + } + } fn should_sync_wakeup(&self) -> bool { false From 137d6ccf0ec8c48ecf0facc47cc46926df5fd0f3 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Wed, 16 Sep 2026 12:08:32 +0000 Subject: [PATCH 125/535] rust_binderfs: add transaction_report feature entry rust_binderfs is missing the equivalent of commit f37b55ded8ed ("binder: add transaction_report feature entry"), which adds "transaction_report" to the binderfs feature list. This helps userspace determine if the BINDER_CMD_REPORT from the generic netlink API is supported. Cc: stable Fixes: f14e0c8183bc ("rust_binder: report netlink transactions") Signed-off-by: Carlos Llamas Link: https://patch.msgid.link/20260916120833.593407-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder/rust_binderfs.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index 300cc65562d1..46a37f1858dd 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -72,6 +72,7 @@ struct binder_features { bool oneway_spam_detection; bool extended_error; bool freeze_notification; + bool transaction_report; }; static const struct constant_table binderfs_param_stats[] = { @@ -89,6 +90,7 @@ static struct binder_features binder_features = { .oneway_spam_detection = true, .extended_error = true, .freeze_notification = true, + .transaction_report = true, }; static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) @@ -566,6 +568,12 @@ static int init_binder_features(struct super_block *sb) if (IS_ERR(dentry)) return PTR_ERR(dentry); + dentry = rust_binderfs_create_file(dir, "transaction_report", + &binder_features_fops, + &binder_features.transaction_report); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + return 0; } From ea31c6498c61307dc0d8ce338a7f268cb8cbdd57 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Thu, 17 Sep 2026 03:20:08 +0000 Subject: [PATCH 126/535] usb: typec: ucsi: displayport: Current CAM OOB index fixup Update ucsi_displayport_enter() to return invalid error when the PPM reports an current alternate mode greater than or equal to UCSI_MAX_ALTMODES. While here, remove 0xff current cam assignment on failed GET_CURRENT_CAM response. Fixes: 04cec690b1fd ("usb: typec: ucsi: displayport: Fix OOB altmode array index") Cc: stable Reviewed-by: Heikki Krogerus Reviewed-by: Benson Leung Signed-off-by: Jameson Thies Link: https://patch.msgid.link/20260917032008.1701888-1-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/displayport.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 8d2032d0762c..572da7bbd29c 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) if (ret < 0) { if (ucsi->version > 0x0100) goto err_unlock; - cur = 0xff; } - if (cur < UCSI_MAX_ALTMODES) { - ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + if (cur != 0xff) { + if (cur < UCSI_MAX_ALTMODES) + ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + else + ret = -EINVAL; + goto err_unlock; } From 95b2e0361c88753afa030c10698be0a1a5b67650 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Thu, 17 Sep 2026 12:56:43 +0200 Subject: [PATCH 127/535] ALSA: seq: Serialize compat port-info ioctls The native sequencer ioctl path serializes handler calls with client->ioctl_mutex, but the translated port-info compat path invokes the same handlers through snd_seq_kernel_client_ctl() without taking that mutex. This lets concurrent compat CREATE_PORT requests pass the port-count check before any request reaches the serialized insertion. The computed integer port index can then exceed the address field range and wrap to an existing index. Subsequent subscriber teardown can resolve the duplicate address to the wrong port and access a freed subscriber. Take ioctl_mutex while dispatching converted port-info requests, matching the native ioctl path. All translated port-info commands share this helper, so their accesses to the client port state are serialized as well. Fixes: b3defb791b26 ("ALSA: seq: Make ioctls race-free") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol gpt-6-astra Signed-off-by: Kyle Zeng Signed-off-by: Bruno Produit Link: https://patch.msgid.link/20260917105643.90102-1-bruno.produit@trailofbits.com Signed-off-by: Takashi Iwai --- sound/core/seq/seq_compat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 22679dca9aae..80110501da6f 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -44,7 +44,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned return -EFAULT; data->kernel = NULL; - err = snd_seq_kernel_client_ctl(client->number, cmd, data); + scoped_guard(mutex, &client->ioctl_mutex) { + err = snd_seq_kernel_client_ctl(client->number, cmd, data); + } if (err < 0) return err; From 4cae4ca34992d210d115e8e891f1e88c9df5f24c Mon Sep 17 00:00:00 2001 From: Seiji Adachi Date: Thu, 17 Sep 2026 22:18:51 +0900 Subject: [PATCH 128/535] ASoC: amd: yc: Add Lenovo ThinkPad E16 Gen 2 (21JN) to quirks Signed-off-by: Seiji Adachi Link: https://patch.msgid.link/20260917131851.2122144-1-seiji_adachi@yahoo.co.jp Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 385fede6d77f..ea173bdab1d5 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -248,6 +248,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21J6"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "21JN"), + } + }, { .driver_data = &acp6x_card, .matches = { From 8d78e4f906cf0a1984cf3c852653727835e1a9e0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 17 Sep 2026 15:58:51 +0200 Subject: [PATCH 129/535] ASoC: rt766: fix HID dependency for SND_SOC_SDCA_HID Selecting SND_SOC_SDCA_HID only works if HID is enabled: WARNING: unmet direct dependencies detected for SND_SOC_SDCA_HID Depends on [n]: SOUND [=y] && SND [=y] && SND_SOC [=y] && SND_SOC_SDCA [=y] && (HID [=n]=y [=y] || HID [=n]=SND_SOC_SDCA [=y]) Selected by [y]: - SND_SOC_RT766_SDCA_SDW [=y] && SOUND [=y] && SND [=y] && SND_SOC [=y] && SOUNDWIRE [=y] && SND_SOC_SDCA [=y] Change the dependency to require SND_SOC_SDCA_CLASS instead, which solves the problem without extra selects. Fixes: b50ecf5873df ("ASoC: rt766: add RT766/RT767 SDCA driver") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260917135950.405423-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d88593c2bac8..8e251e1d84b4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1989,9 +1989,7 @@ config SND_SOC_RT715_SDCA_SDW config SND_SOC_RT766_SDCA_SDW tristate "Realtek RT766 SDCA Codec - SDW" depends on SOUNDWIRE - depends on SND_SOC_SDCA - select SND_SOC_SDCA_HID - select SND_SOC_SDCA_IRQ + depends on SND_SOC_SDCA_CLASS select REGMAP_SOUNDWIRE select REGMAP_SOUNDWIRE_MBQ From 814f2da6995772c07a7c9c2cc6028b6e3f3ecc9c Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 31 Aug 2026 18:29:22 -0700 Subject: [PATCH 130/535] of/overlay: put property on deadprops only after changeset add succeeds add_changeset_property() links a new property of a not-yet-live target node into the node's deadprops list before handing it to of_changeset_add_property(). If that fails, the error path frees the property but leaves the freed pointer linked in deadprops, and of_node_release() frees it a second time when the aborted overlay's node is released. Record the changeset entry first and link the property into deadprops only on success. of_changeset_add_property() never looks at the node's property lists, so the order of the two steps is otherwise immaterial, and the error path frees a property that nothing references. Fixes: f96278810150 ("of: overlay: set node fields from properties when add new overlay node") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Abdurrahman Hussain Link: https://patch.msgid.link/20260831-nh-of-alias-overlay-v7-4-02754604805a@nexthop.ai Signed-off-by: Rob Herring (Arm) --- drivers/of/overlay.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 08d5351746be..51df2ad26f83 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -358,12 +358,13 @@ static int add_changeset_property(struct overlay_changeset *ovcs, return -ENOMEM; if (!prop) { - if (!target->in_livetree) { + ret = of_changeset_add_property(&ovcs->cset, target->np, + new_prop); + /* the detached node owns the property until the apply */ + if (!ret && !target->in_livetree) { new_prop->next = target->np->deadprops; target->np->deadprops = new_prop; } - ret = of_changeset_add_property(&ovcs->cset, target->np, - new_prop); } else { ret = of_changeset_update_property(&ovcs->cset, target->np, new_prop); From 44081d708b5b885487bac792db78324b7b4c267d Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 31 Aug 2026 18:29:23 -0700 Subject: [PATCH 131/535] of/overlay: only treat a positive changeset id as registered of_overlay_fdt_apply() stores the idr_alloc() return value in ovcs->id before checking it. On failure the stored id is negative, free_overlay_changeset()'s "if (ovcs->id)" check passes, idr_remove() is called with a negative id and list_del() runs on ovcs->ovcs_list, which is not initialized until after the id allocation. An allocation failure at that point dereferences NULL. Make free_overlay_changeset() treat only a strict-positive id as registered. The rest of the function already copes with a partially-initialized ovcs, so the error path stays a plain goto err_free_ovcs. Fixes: 61b4de4e0b38 ("of: overlay: minor restructuring") Cc: stable@vger.kernel.org Suggested-by: Geert Uytterhoeven Assisted-by: Claude:claude-fable-5 [Claude Code] Reviewed-by: Geert Uytterhoeven Signed-off-by: Abdurrahman Hussain Link: https://patch.msgid.link/20260831-nh-of-alias-overlay-v7-5-02754604805a@nexthop.ai Signed-off-by: Rob Herring (Arm) --- drivers/of/overlay.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 51df2ad26f83..0cc08567c300 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -864,7 +864,8 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) if (ovcs->cset.entries.next) of_changeset_destroy(&ovcs->cset); - if (ovcs->id) { + /* a failed idr_alloc() leaves its negative error in ovcs->id */ + if (ovcs->id > 0) { idr_remove(&ovcs_idr, ovcs->id); list_del(&ovcs->ovcs_list); ovcs->id = 0; From 1d62f0f2bc7edb12ed09f71203df0e47fa4efca4 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 31 Aug 2026 18:29:24 -0700 Subject: [PATCH 132/535] of/overlay: don't leak fragment references when changeset init fails init_overlay_changeset() stores the number of initialized fragments in ovcs->count only on full success. When find_target() or the __symbols__ lookup fails partway through, the target and overlay references taken for the fragments initialized so far are never dropped: free_overlay_changeset() bounds its cleanup loop by ovcs->count, which is still 0. Store the running count on the error path so free_overlay_changeset() puts whatever was set up. The store is guarded by ovcs->fragments because on the allocation-failure path cnt still holds the counting pass total while there is no fragments array; everywhere else cnt only counts fully-initialized fragments. Reported-by: Sashiko AI Closes: https://lore.kernel.org/20260805204009.CF3891F000E9@smtp.kernel.org Fixes: 61b4de4e0b38 ("of: overlay: minor restructuring") Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Abdurrahman Hussain Link: https://patch.msgid.link/20260831-nh-of-alias-overlay-v7-6-02754604805a@nexthop.ai Signed-off-by: Rob Herring (Arm) --- drivers/of/overlay.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 0cc08567c300..276870291656 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -854,6 +854,10 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, err_out: pr_err("%s() failed, ret = %d\n", __func__, ret); + /* let free_overlay_changeset() put the fragments set up so far */ + if (ovcs->fragments) + ovcs->count = cnt; + return ret; } From 21c89ff1fc86ffa517f3a9ca1ba5c48e9e37c1ba Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 31 Aug 2026 18:29:25 -0700 Subject: [PATCH 133/535] of/overlay: don't create "//" paths for fragments targeting the root dup_and_fixup_symbol_prop() rewrites a symbol value by replacing its "/fragment/__overlay__" prefix with the fragment's target path. When the fragment targets the root node the target path is "/" and the result starts with "//", which __of_find_node_by_full_path() cannot resolve: symbols pointing into such fragments silently stop resolving. Drop the target path when it is the root and the tail is non-empty. A value naming the fragment root itself (empty tail) keeps the "/". Fixes: d1651b03c2df ("of: overlay: add overlay symbols to live device tree") Assisted-by: Claude:claude-fable-5 [Claude Code] Signed-off-by: Abdurrahman Hussain Link: https://patch.msgid.link/20260831-nh-of-alias-overlay-v7-7-02754604805a@nexthop.ai Signed-off-by: Rob Herring (Arm) --- drivers/of/overlay.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 276870291656..83c56dd9b5d8 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -256,6 +256,9 @@ static struct property *dup_and_fixup_symbol_prop( if (!target_path) return NULL; target_path_len = strlen(target_path); + /* a root target renders as "/"; drop it to avoid "//" results */ + if (target_path_len == 1 && target_path[0] == '/' && path_tail_len) + target_path_len = 0; new_prop = kzalloc_obj(*new_prop); if (!new_prop) From 28114992dd2f03724eb3d024839b3f9f299656d3 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 17 Sep 2026 22:21:49 +0530 Subject: [PATCH 134/535] ASoC: amd: acp: enable TAS2783 and add RT712-VB SoundWire machine The ACP7.0 SoundWire machine table did not cover boards that combine TI TAS2783 smart amplifiers on SoundWire link 0 with a Realtek RT712-VB codec on link 1. In addition, the TAS2783 SoundWire codec driver was not selected by the AMD legacy SoundWire machine config, so it was not built for these platforms. Select SND_SOC_TAS2783_SDW from SND_SOC_AMD_LEGACY_SDW_MACH so the TAS2783 SoundWire codec driver is built together with the AMD legacy (no-DSP) SoundWire machine. Add a new link/address map, acp70_tas2783a_l0_rt712_vb_l1, describing the TAS2783 amplifiers on link 0 and the RT712-VB codec on link 1, and register a corresponding entry in snd_soc_acpi_amd_acp70_sdw_machines[]. The entry uses snd_soc_acpi_amd_sdca_is_device_rt712_vb() as its machine_check callback so it only matches platforms that actually expose the RT712-VB device. Signed-off-by: Syed Saba Kareem Link: https://patch.msgid.link/20260917165314.1880855-1-syed.sabakareem@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Kconfig | 1 + sound/soc/amd/acp/amd-acp70-acpi-match.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index a04c301df4d6..e880e44b9763 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -181,6 +181,7 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW + select SND_SOC_TAS2783_SDW help This option enables Legacy(No DSP) sound card support for SoundWire enabled AMD platforms along with ACP PDM controller. diff --git a/sound/soc/amd/acp/amd-acp70-acpi-match.c b/sound/soc/amd/acp/amd-acp70-acpi-match.c index 815e088b437d..2a9da6de7bbc 100644 --- a/sound/soc/amd/acp/amd-acp70-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp70-acpi-match.c @@ -748,7 +748,27 @@ static const struct snd_soc_acpi_link_adr acp70_rt721_only[] = { {} }; +static const struct snd_soc_acpi_link_adr acp70_tas2783a_l0_rt712_vb_l1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(tas2783_2_adr), + .adr_d = tas2783_2_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt712_vb_1_group1_adr), + .adr_d = rt712_vb_1_group1_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(1), + .links = acp70_tas2783a_l0_rt712_vb_l1, + .machine_check = snd_soc_acpi_amd_sdca_is_device_rt712_vb, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0), .links = acp70_tas2783_2, From bcd61aa247c28b721ac21dda6e4135f75a2de29f Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:44:56 +0800 Subject: [PATCH 135/535] ASoC: codecs: max98363: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. Fixes: 18c0af945fa3 ("ASoC: max98363: add soundwire amplifier driver") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260914104456.376666-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/max98363.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c index 0e32b7a94cb0..4a6d7b163acf 100644 --- a/sound/soc/codecs/max98363.c +++ b/sound/soc/codecs/max98363.c @@ -200,7 +200,7 @@ static int max98363_sdw_dai_hw_params(struct snd_pcm_substream *substream, struct max98363_priv *max98363 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; enum sdw_data_direction direction; struct sdw_stream_runtime *stream; From 174d160884199cad97413f33fe1b56027dc0d3e1 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Mon, 14 Sep 2026 18:45:51 +0800 Subject: [PATCH 136/535] ASoC: codecs: rt1017-sdca-sdw: fix uninitialized stream_config->type stream_config is not initialized before being passed to sdw_stream_add_slave(). The type field may contain garbage and is later copied to stream->type by sdw_config_stream(). Zero-initialize stream_config so type defaults to SDW_STREAM_PCM. While at it, use snd_sdw_params_to_config() helper instead of open-coding the same logic. Fixes: 2b7aecd58528 ("ASoC: rt1017: Add RT1017 SDCA amplifier driver") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi Link: https://patch.msgid.link/20260914104551.377880-1-yijiangshan@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt1017-sdca-sdw.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/rt1017-sdca-sdw.c b/sound/soc/codecs/rt1017-sdca-sdw.c index caf75e5657ef..01cc2511a7d0 100644 --- a/sound/soc/codecs/rt1017-sdca-sdw.c +++ b/sound/soc/codecs/rt1017-sdca-sdw.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -573,11 +574,10 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct rt1017_sdca_priv *rt1017 = snd_soc_component_get_drvdata(component); - struct sdw_stream_config stream_config; + struct sdw_stream_config stream_config = {0}; struct sdw_port_config port_config; - enum sdw_data_direction direction; struct sdw_stream_runtime *sdw_stream; - int retval, port, num_channels, ch_mask; + int retval, port; unsigned int sampling_rate; dev_dbg(dai->dev, "%s %s", __func__, dai->name); @@ -591,28 +591,17 @@ static int rt1017_sdca_pcm_hw_params(struct snd_pcm_substream *substream, /* SoundWire specific configuration */ /* port 1 for playback */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - direction = SDW_DATA_DIR_RX; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port = 1; - } else { - direction = SDW_DATA_DIR_TX; + else port = 2; - } - num_channels = params_channels(params); - ch_mask = (1 << num_channels) - 1; - - stream_config.frame_rate = params_rate(params); - stream_config.ch_count = num_channels; - stream_config.bps = snd_pcm_format_width(params_format(params)); - stream_config.direction = direction; - - port_config.ch_mask = ch_mask; + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); port_config.num = port; dev_dbg(dai->dev, "frame_rate %d, ch_count %d, bps %d, direction %d, ch_mask %d, port: %d\n", - params_rate(params), num_channels, snd_pcm_format_width(params_format(params)), - direction, ch_mask, port); + stream_config.frame_rate, stream_config.ch_count, stream_config.bps, + stream_config.direction, port_config.ch_mask, port); retval = sdw_stream_add_slave(rt1017->sdw_slave, &stream_config, &port_config, 1, sdw_stream); From 64c82e7bd9c82b0c9276297df34a3579e8658f55 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:07 +0800 Subject: [PATCH 137/535] iio: adc: pac1934: check ACPI label duplication A label duplication failure currently reaches the terminating-byte write. Release the ACPI result and abort before accessing the missing label; earlier labels are managed by devres. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 0fb528c8255b ("iio: adc: adding support for PAC193x") Assisted-by: LLM Signed-off-by: Slavin Liu Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1934.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 23055405a6e0..de59dc27c440 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -1108,6 +1108,10 @@ static int pac1934_acpi_parse_channel_config(struct i2c_client *client, devm_kmemdup(dev, rez->package.elements[i].string.pointer, (size_t)rez->package.elements[i].string.length + 1, GFP_KERNEL); + if (!info->labels[idx]) { + ACPI_FREE(rez); + return -ENOMEM; + } info->labels[idx][rez->package.elements[i].string.length] = '\0'; info->shunts[idx] = rez->package.elements[i + 1].integer.value * 1000; info->active_channels[idx] = (info->shunts[idx] != 0); From a961417c5ae77bbb728a23d9577ecd8f4d8a4b5a Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Mon, 21 Sep 2026 01:37:30 +0530 Subject: [PATCH 138/535] Input: synaptics - add LEN205b entry to smbus_pnp_ids for ThinkPad T490 Lenovo ThinkPad T490 has a Synaptics Touchpad that supports SMBus/RMI4 mode, but is not listed in smbus_pnp_ids table. Add LEN205b to smbus_pnp_ids[] passlist. Reported-by: John Rosencutter Closes: https://lore.kernel.org/all/CAOGuaRh025eKcBf9P8UrvtXNp68vQ0HVF2EbNMp-kpdaC1HNpA@mail.gmail.com/ Signed-off-by: Lovekesh Solanki Link: https://patch.msgid.link/20260920200730.1836756-1-lovekeshsolanki00@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index a8ce89d41faa..444285e58f41 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -192,6 +192,7 @@ static const char * const smbus_pnp_ids[] = { "LEN2054", /* E480 */ "LEN2055", /* E580 */ "LEN2058", /* E490 */ + "LEN205b", /* T490 */ "LEN2068", /* T14 Gen 1 */ "SYN1221", /* TUXEDO InfinityBook Pro 14 v5 */ "SYN3003", /* HP EliteBook 850 G1 */ From 495cd7f858b45d2ffa9b685bba3a8c23c1ca4dd3 Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Sun, 20 Sep 2026 17:05:41 +0530 Subject: [PATCH 139/535] Input: i8042 - add nomux quirk for Fujitsu LIFEBOOK U7410 On the Fujitsu LIFEBOOK U7410 the internal keyboard and touchpad die shortly after boot if i8042 multiplexing controller is probed and switched to muxed mode. These multiplexing setup commands disturb the EC emulated keyboard and atkbd reports "Spurious ACK" and "Unknown key pressed" warnings, after which both keyboard and touchpad stop delivering events. Both touchpad and keyboard work in BIOS and GRUB. Disabling multiplexer using i8042.nomux=1 makes both devices work on warm/cold boot. Add a DMI quirk to force nomux mode for this model. Reported-by: Heiko Brey Link: https://lore.kernel.org/all/89f36f3f5e4c2d07b9ff72bc0b355875c336e498.camel@gmx.de/ Signed-off-by: Lovekesh Solanki Link: https://patch.msgid.link/20260920113541.1484808-1-lovekeshsolanki00@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042-acpipnpio.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h index 54eb3687000a..1b1c5d4ac01c 100644 --- a/drivers/input/serio/i8042-acpipnpio.h +++ b/drivers/input/serio/i8042-acpipnpio.h @@ -671,6 +671,15 @@ static const struct dmi_system_id i8042_dmi_quirk_table[] __initconst = { }, .driver_data = (void *)(SERIO_QUIRK_NOAUX) }, + { + /* Fujitsu LIFEBOOK U7410 */ + /* Mux mode also disturbs the EC keyboard, causing spurious ACKs */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU CLIENT COMPUTING LIMITED"), + DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK U7410"), + }, + .driver_data = (void *)(SERIO_QUIRK_NOMUX) + }, { /* Gigabyte M912 */ .matches = { From d395e1b62d9694f2e4add9122f8d473eacd374aa Mon Sep 17 00:00:00 2001 From: Daniel Ortiz Date: Sat, 19 Sep 2026 20:19:20 -0500 Subject: [PATCH 140/535] ASoC: amd: yc: Add DMI quirk for Lenovo V15 G6 ARP The Lenovo V15 G6 ARP (product name 83UU) uses the ACP6x PDM interface for its internal digital microphone array. Its firmware exposes neither the AcpDmicConnected _DSD property nor the _WOV method, so acp6x_probe() falls through to the DMI check and returns -ENODEV, leaving acp_yc_mach.0 unbound and no capture device registered. Add a DMI quirk entry for this model. With it, the machine driver binds and the DMIC capture device is created: acp_yc_mach acp_yc_mach.0: Enabling ACP DMIC support via DMI card 2: acp6x [acp6x], device 0: DMIC capture dmic-hifi-0 Capture was verified working with arecord and PipeWire. Signed-off-by: Daniel Ortiz Link: https://patch.msgid.link/20260920011920.2482786-1-letargopausado@gmail.com Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index ea173bdab1d5..e798bf6a9cd0 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -388,6 +388,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83UU"), + } + }, { .driver_data = &acp6x_card, .matches = { From 687cb38c4330d52d58dbf426a3eb523850052d10 Mon Sep 17 00:00:00 2001 From: Dirk Gouders Date: Mon, 14 Sep 2026 13:01:09 +0200 Subject: [PATCH 141/535] selftests: ublk: fix unused_result error Commit 166b476b8dee (selftests/ublk: add shared memory zero-copy support in kublk, 2026-03-31) introduced a write(2), ignoring its return value and thus causing an error, when building. Fix this by using the common workaround "(void)!" to trick the compiler the result is being used. Signed-off-by: Dirk Gouders Reviewed-by: Ming Lei Link: https://patch.msgid.link/20260914110145.41627-2-dirk@gouders.net Signed-off-by: Jens Axboe --- tools/testing/selftests/ublk/kublk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 2400b4615766..5b19bfcfa8d7 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1589,7 +1589,7 @@ static int ublk_start_daemon(const struct dev_ctx *ctx, struct ublk_dev *dev) /* Signal listener thread to stop and wait for it */ if (linfo.stop_efd >= 0) { - write(linfo.stop_efd, &stop_val, sizeof(stop_val)); + (void)!write(linfo.stop_efd, &stop_val, sizeof(stop_val)); pthread_join(listener, NULL); close(linfo.stop_efd); ublk_shmem_sock_destroy(dinfo->dev_id, linfo.sock_fd); From e31ae4aeb049983fe3be0cf1e12c8074811a7685 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 20 Sep 2026 22:53:27 +0900 Subject: [PATCH 142/535] ASoC: wm8903: Move the DRC QR threshold to the register that holds it The DRC_THRESH_QR field is bits 7 and 6 of DRC_1, but the control sits on DRC_0. Its shift and max are right for the field, only the register is wrong. Those bits of DRC_0 belong to DRC_STARTUP_GAIN, which "DRC Startup Volume" claims as bits 10 to 6. So the two controls share bits 7 and 6. Writing either one moves the other, and the quick release threshold is never reached at all. The six fields of DRC_1 tile it exactly, so there is nowhere else the threshold could live. Point the control at DRC_1. Found by a sweep for two controls claiming the same bits of one register. No board with this codec was to hand, the register map is the driver's own wm8903.h. Fixes: f1c0a02f32f8 ("ALSA: ASoC: Add WM8903 CODEC driver") Signed-off-by: HyeongJun An Assisted-by: Claude:claude-opus-5 Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260920135327.3628704-1-sammiee5311@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 156e1e24a388..fbf388cf966d 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -676,7 +676,7 @@ SOC_ENUM("DRC Decay Rate", drc_decay), SOC_ENUM("DRC FF Delay", drc_ff_delay), SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), -SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_SINGLE_TLV("DRC QR Threshold Volume", WM8903_DRC_1, 6, 3, 0, drc_tlv_max), SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), From 4ebdb8a6231bde47d21597d5d0a193dcf85164ac Mon Sep 17 00:00:00 2001 From: "Vlastimil Babka (SUSE)" Date: Thu, 17 Sep 2026 16:38:37 +0200 Subject: [PATCH 143/535] MAINTAINERS: update slab.git URL The slab.git tree has been moved to a new URL as part of the mm-next integration. Reflect this in MAINTAINERS as well. The old URL will stay around with historical branches and tags. Signed-off-by: Vlastimil Babka (SUSE) Reviewed-by: David Hildenbrand (Arm) Acked-by: SJ Park Link: https://patch.msgid.link/20260917-maint-slab-git-v1-1-d21a5210aec9@kernel.org Signed-off-by: Harry Yoo (Meta) --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b..eddbd418493a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25182,7 +25182,7 @@ R: David Rientjes R: Roman Gushchin L: linux-mm@kvack.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/vbabka/slab.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mm/slab.git F: Documentation/admin-guide/mm/slab.rst F: Documentation/mm/slab.rst F: include/linux/mempool.h From 437f3727b4fd715266c296435c116288f82666fd Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 11:28:22 +0800 Subject: [PATCH 144/535] wifi: mac80211: count only matching reservations in reserved switch ieee80211_vif_use_reserved_switch() validates a replacement by counting assigned links on ctx->replace_ctx and treating reservations too broadly. A link can still be assigned to the old channel context while holding a reservation that belongs to a different replacement operation. That can make the current replacement appear complete too early. If another link finalizes first, the function can free the WILL_BE_REPLACED chanctx while the unmatched link and driver still use it, leading to a use-after-free in later beacon generation. Require an in-place reservation to point at the current replacement context, and require the reserved and assigned contexts to be the matching replacement pair, before counting or moving the link. Fixes: 5bcae31d9cb1 ("mac80211: implement multi-vif in-place reservations") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/4b6c4b8648ab1920c38a40be32c0b1a2797b4ac6.1785726007.git.zhilinz@nebusec.ai Signed-off-by: Johannes Berg --- net/mac80211/chan.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 75bb204ad743..17b935448f13 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -1772,7 +1772,8 @@ ieee80211_link_use_reserved_assign(struct ieee80211_link_data *link) } static bool -ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) +ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link, + struct ieee80211_chanctx *ctx) { struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_chanctx *old_ctx, *new_ctx; @@ -1782,6 +1783,9 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) new_ctx = link->reserved_chanctx; old_ctx = ieee80211_link_get_chanctx(link); + if (new_ctx != ctx) + return false; + if (!old_ctx) return false; @@ -1794,6 +1798,12 @@ ieee80211_link_has_in_place_reservation(struct ieee80211_link_data *link) if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER) return false; + if (new_ctx->replace_ctx != old_ctx) + return false; + + if (old_ctx->replace_ctx != new_ctx) + return false; + return true; } @@ -1823,7 +1833,8 @@ static int ieee80211_chsw_switch_vifs(struct ieee80211_local *local, } for_each_chanctx_user_reserved(local, ctx, &iter) { - if (!ieee80211_link_has_in_place_reservation(iter.link)) + if (!ieee80211_link_has_in_place_reservation(iter.link, + ctx)) continue; old_ctx = ieee80211_link_get_chanctx(iter.link); @@ -1925,7 +1936,9 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) { n_assigned++; - if (iter.link && iter.link->reserved_chanctx) { + if (iter.link && + ieee80211_link_has_in_place_reservation(iter.link, + ctx)) { n_reserved++; if (iter.link->reserved_ready) n_ready++; @@ -1950,7 +1963,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) use_reserved: ctx->conf.radar_enabled = false; for_each_chanctx_user_reserved(local, ctx, &iter) { - if (ieee80211_link_has_in_place_reservation(iter.link) && + if (ieee80211_link_has_in_place_reservation(iter.link, + ctx) && !iter.link->reserved_ready) return -EAGAIN; @@ -1991,7 +2005,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) } for_each_chanctx_user_reserved(local, ctx, &iter) { - if (!ieee80211_link_has_in_place_reservation(iter.link)) + if (!ieee80211_link_has_in_place_reservation(iter.link, + ctx)) continue; ieee80211_chan_bw_change(local, @@ -2040,7 +2055,7 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) struct ieee80211_bss_conf *link_conf = link->conf; u64 changed = 0; - if (!ieee80211_link_has_in_place_reservation(link)) + if (!ieee80211_link_has_in_place_reservation(link, ctx)) continue; rcu_assign_pointer(link_conf->chanctx_conf, @@ -2091,7 +2106,8 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) for_each_chanctx_user_reserved(local, ctx, &iter) { struct ieee80211_link_data *link = iter.link; - if (WARN_ON(ieee80211_link_has_in_place_reservation(link))) + if (WARN_ON(ieee80211_link_has_in_place_reservation(link, + ctx))) continue; if (!link->reserved_ready) From 3412e9a800786d1dec4a58367db1cba275e7ef11 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 12:35:10 +0800 Subject: [PATCH 145/535] wifi: mac80211: keep paired old chanctx alive An in-place replacement keeps reciprocal replace_ctx pointers between the old WILL_BE_REPLACED chanctx and the new REPLACES_OTHER chanctx. However, the early free paths only consider assigned and reserved link users when deciding whether to free the old chanctx. That lets the old chanctx be freed too early while the replacement partner still points back to it, either when the last assigned link is released or when that link successfully reassigns to another existing context. Later unreserve and switch-finalization paths can then follow a stale replace_ctx pointer. Keep a paired old chanctx alive until the replacement pair has been torn down. Apply the same check to both __ieee80211_link_release_channel() and ieee80211_link_use_reserved_reassign(). If the replacement is later abandoned, ieee80211_link_unreserve_chanctx() already tears down the pairing before freeing the old chanctx once no users remain. Fixes: 5bcae31d9cb1 ("mac80211: implement multi-vif in-place reservations") Cc: stable@vger.kernel.org Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/b9b582b0db3814f641648b8886bb50f9d68f93ac.1785730815.git.zhilinz@nebusec.ai Signed-off-by: Johannes Berg --- net/mac80211/chan.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 17b935448f13..9cba79507767 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -231,6 +231,13 @@ int ieee80211_chanctx_refcount(struct ieee80211_local *local, return num; } +static bool +ieee80211_chanctx_has_replace_partner(struct ieee80211_chanctx *ctx) +{ + return ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED && + ctx->replace_ctx; +} + static int ieee80211_num_chanctx(struct ieee80211_local *local, int radio_idx) { struct ieee80211_chanctx *ctx; @@ -1405,6 +1412,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) { struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_chanctx *ctx = link->reserved_chanctx; + struct ieee80211_chanctx *old_ctx = NULL; lockdep_assert_wiphy(sdata->local->hw.wiphy); @@ -1418,6 +1426,7 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) if (WARN_ON(!ctx->replace_ctx)) return; + old_ctx = ctx->replace_ctx; WARN_ON(ctx->replace_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED); WARN_ON(ctx->replace_ctx->replace_ctx != ctx); @@ -1428,6 +1437,11 @@ void ieee80211_link_unreserve_chanctx(struct ieee80211_link_data *link) list_del_rcu(&ctx->list); kfree_rcu(ctx, rcu_head); + + if (ieee80211_chanctx_refcount(sdata->local, + old_ctx) == 0) + ieee80211_free_chanctx(sdata->local, old_ctx, + false); } else { ieee80211_free_chanctx(sdata->local, ctx, false); } @@ -1707,7 +1721,8 @@ ieee80211_link_use_reserved_reassign(struct ieee80211_link_data *link) ieee80211_check_fast_xmit_iface(sdata); - if (ieee80211_chanctx_refcount(local, old_ctx) == 0) + if (ieee80211_chanctx_refcount(local, old_ctx) == 0 && + !ieee80211_chanctx_has_replace_partner(old_ctx)) ieee80211_free_chanctx(local, old_ctx, false); ieee80211_recalc_chanctx_min_def(local, new_ctx); @@ -2192,7 +2207,8 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link, } ieee80211_assign_link_chanctx(link, NULL, false); - if (ieee80211_chanctx_refcount(local, ctx) == 0) + if (ieee80211_chanctx_refcount(local, ctx) == 0 && + !ieee80211_chanctx_has_replace_partner(ctx)) ieee80211_free_chanctx(local, ctx, skip_idle_recalc); link->radar_required = false; From 7c1611780823b76219cb1478db53dfd890c4a9a2 Mon Sep 17 00:00:00 2001 From: Tianchu Chen Date: Fri, 4 Sep 2026 15:23:34 +0000 Subject: [PATCH 146/535] wifi: cw1200: fix TX padding OOB read leaking heap data to device The TX path rounds the transfer size up to the bus alignment (align_size(), up to 511 bytes for SDIO, 1 byte for SPI) but sends the padded length straight from the frame buffer, which has no room reserved for the padding. cw1200_data_write() therefore reads past the end of the frame and the bus transfer carries those bytes to the device. A bogus device can collect up to 511 bytes of kernel heap data per TX frame. The RX path does not have this problem: it allocates the skb with the aligned length (alloc_len) before reading. Compute the exact padding with the bus align_size() callback, make sure queued TX frames have tailroom for it, and zero the padding bytes before the transfer so nothing past the frame is exposed. This is not expected to change driver behavior on most cases: frame contents and transfer sizes are unchanged, only the content of the padding bytes (previously undefined stale memory, now zero), which the device never reads since the WSM header carries the real frame length. The skb expansion can only fail under memory exhaustion, in which case the frame is dropped like on the existing error paths of cw1200_tx(). Discovered by Atuin - Automated Vulnerability Discovery Engine. Fixes: a910e4a94f69 ("cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tianchu Chen Link: https://patch.msgid.link/7982b4a7e64f74e2068790ec389ca680a081f730@linux.dev Signed-off-by: Johannes Berg --- drivers/net/wireless/st/cw1200/bh.c | 8 +++++++- drivers/net/wireless/st/cw1200/txrx.c | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/st/cw1200/bh.c b/drivers/net/wireless/st/cw1200/bh.c index b034bab4b489..9367a981f1de 100644 --- a/drivers/net/wireless/st/cw1200/bh.c +++ b/drivers/net/wireless/st/cw1200/bh.c @@ -344,6 +344,7 @@ static int cw1200_bh_tx_helper(struct cw1200_common *priv, int *tx_burst) { size_t tx_len; + size_t aligned_len; u8 *data; int ret; struct wsm_hdr *wsm; @@ -376,9 +377,14 @@ static int cw1200_bh_tx_helper(struct cw1200_common *priv, atomic_inc(&priv->bh_tx); - tx_len = priv->hwbus_ops->align_size( + aligned_len = priv->hwbus_ops->align_size( priv->hwbus_priv, tx_len); + /* Zero the bus alignment padding so no stale data is sent. */ + if (aligned_len > tx_len) + memset(data + tx_len, 0, aligned_len - tx_len); + tx_len = aligned_len; + /* Check if not exceeding CW1200 capabilities */ if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE)) pr_debug("Write aligned len: %zu\n", tx_len); diff --git a/drivers/net/wireless/st/cw1200/txrx.c b/drivers/net/wireless/st/cw1200/txrx.c index 084d52b11f5b..e13695667a00 100644 --- a/drivers/net/wireless/st/cw1200/txrx.c +++ b/drivers/net/wireless/st/cw1200/txrx.c @@ -14,6 +14,7 @@ #include "wsm.h" #include "bh.h" #include "sta.h" +#include "hwbus.h" #include "debug.h" #define CW1200_INVALID_RATE_ID (0xFF) @@ -716,6 +717,7 @@ void cw1200_tx(struct ieee80211_hw *dev, struct ieee80211_sta *sta; struct wsm_tx *wsm; bool tid_update = false; + size_t pad_len; u8 flags = 0; int ret; @@ -764,6 +766,18 @@ void cw1200_tx(struct ieee80211_hw *dev, sta = t.sta; + /* + * The bus transfer is padded up to the bus alignment and the + * padding is transferred from the frame buffer. Make sure the + * buffer has room for the padding so the device never receives + * data past the end of the frame. + */ + pad_len = priv->hwbus_ops->align_size(priv->hwbus_priv, skb->len) - + skb->len; + if (skb_tailroom(skb) < pad_len && + pskb_expand_head(skb, 0, pad_len, GFP_ATOMIC)) + goto drop; + spin_lock_bh(&priv->ps_state_lock); { tid_update = cw1200_tx_h_pm_state(priv, &t); From ce67ebe9d8629776ce38e6995ae72fb903c828db Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Sat, 12 Sep 2026 14:47:55 +0100 Subject: [PATCH 147/535] wifi: wfx: validate MIB read length before copying to caller wfx_hif_read_mib() copies reply->length bytes from the firmware response into the caller-provided buffer without checking that reply->length does not exceed val_len. A firmware response with a length field larger than expected overruns the caller buffer and reads past the end of the reply allocation. Replace the dead -ENOMEM check with an active validation that reply->length fits within val_len before the memcpy. Fixes: f95a29d40782 ("staging: wfx: add HIF commands helpers") Assisted-by: LLM Signed-off-by: Aamir Ahmed Link: https://patch.msgid.link/AS8P251MB000155034FC391A2A54B9803C8BD2@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Johannes Berg --- drivers/net/wireless/silabs/wfx/hif_tx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/silabs/wfx/hif_tx.c b/drivers/net/wireless/silabs/wfx/hif_tx.c index 9f403d275cb1..9efb108bd877 100644 --- a/drivers/net/wireless/silabs/wfx/hif_tx.c +++ b/drivers/net/wireless/silabs/wfx/hif_tx.c @@ -207,9 +207,11 @@ int wfx_hif_read_mib(struct wfx_dev *wdev, int vif_id, u16 mib_id, void *val, si dev_warn(wdev->dev, "%s: confirmation mismatch request\n", __func__); ret = -EIO; } - if (ret == -ENOMEM) + if (!ret && le16_to_cpu(reply->length) > val_len) { dev_err(wdev->dev, "buffer is too small to receive %s (%zu < %d)\n", wfx_get_mib_name(mib_id), val_len, le16_to_cpu(reply->length)); + ret = -EIO; + } if (!ret) memcpy(val, &reply->mib_data, le16_to_cpu(reply->length)); else From f8ebe286394c0bbb4a6dbacb182a34ab6376922d Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 15:37:36 +0800 Subject: [PATCH 148/535] wifi: iwlegacy: 3945: free EEPROM data on remove il3945_pci_probe() calls il_eeprom_init(), which allocates memory for the EEPROM data. The probe failure paths release this allocation with il_eeprom_free(), but the remove path does not free it after a successful probe. As a result, the EEPROM data is leaked when the PCI device is removed. Free the EEPROM data in il3945_pci_remove() before releasing the mac80211 hardware structure. This issue was found by manual code inspection. Fixes: 073d3f5f1b3b ("iwlwifi: changing EEPROM layout handling") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260916073736.2952028-1-lgs201920130244@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlegacy/3945-mac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c index cbaf250626c5..f7ca182d1494 100644 --- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c +++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c @@ -3807,6 +3807,7 @@ il3945_pci_remove(struct pci_dev *pdev) il_free_channel_map(il); il_free_geos(il); + il_eeprom_free(il); kfree(il->scan_cmd); dev_kfree_skb(il->beacon_skb); ieee80211_free_hw(il->hw); From 75fc8a3ee5aee72f2fea3b6436170fae175f7f19 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 21 Sep 2026 08:57:02 +0000 Subject: [PATCH 149/535] ASoC: tegra: Fix ASRC Stream6 input threshold control The Stream6 Input Threshold control points at stream index 4, which is already used by Stream5. The threshold get and put callbacks derive the lane ID from the register offset, so the duplicate index makes Stream6 operate on lane 4 and leaves lane 5 inaccessible from userspace. Use stream index 5 for Stream6. Fixes: a2df8c2d5b36 ("ASoC: tegra: Add Tegra186 based ASRC driver") Signed-off-by: Sheetal Reviewed-by: Thierry Reding Link: https://patch.msgid.link/20260921085704.1248920-2-sheetal@nvidia.com Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index 7f360dfaf8b1..d8ae5d997615 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -828,7 +828,7 @@ static const struct snd_kcontrol_new tegra186_asrc_controls[] = { tegra186_asrc_put_input_threshold), SOC_SINGLE_EXT("Stream6 Input Threshold", - ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 4), 0, 3, 0, + ASRC_STREAM_REG(TEGRA186_ASRC_RX_THRESHOLD, 5), 0, 3, 0, tegra186_asrc_get_input_threshold, tegra186_asrc_put_input_threshold), From 7325a44474f20c1393a1c114c6811abce6ddfaf4 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Wed, 16 Sep 2026 19:36:48 +0800 Subject: [PATCH 150/535] wifi: mac80211: release mesh path quota on failed additions Repeated failed or duplicate mesh path additions can exhaust MESH_MAX_MPATHS while the path table holds fewer paths, which stops new paths to reachable destinations from being created. mesh_path_add() reserves a slot before allocating and inserting a path. If allocation fails, the function returns without releasing the slot. If the rhashtable insertion reports an error or an existing destination, the function discards its candidate but retains the reservation. No new path is installed in any of these cases. Release the reservation whenever the candidate is not installed. Fixes: ae76eef027f7 ("mac80211: return new mpath from mesh_path_add()") Fixes: 60854fd94573 ("mac80211: mesh: convert path table to rhashtable") Cc: stable@vger.kernel.org Assisted-by: LLM sparse kasan Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260916113649.28315-2-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mesh_pathtbl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 03171cf00855..770fc16b439b 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -694,8 +694,10 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, return ERR_PTR(-ENOSPC); new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC); - if (!new_mpath) + if (!new_mpath) { + atomic_dec(&sdata->u.mesh.mpaths); return ERR_PTR(-ENOMEM); + } tbl = &sdata->u.mesh.mesh_paths; spin_lock_bh(&tbl->walk_lock); @@ -708,6 +710,7 @@ struct mesh_path *mesh_path_add(struct ieee80211_sub_if_data *sdata, if (mpath) { kfree(new_mpath); + atomic_dec(&sdata->u.mesh.mpaths); if (IS_ERR(mpath)) return mpath; From a5f35d4b112af1415ff06bd501c8cc26cc08aa8b Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Wed, 16 Sep 2026 19:36:49 +0800 Subject: [PATCH 151/535] wifi: mac80211: account proxy paths against the mesh path limit Proxy-path churn can drive the interface path counter negative and let later mesh path additions exceed MESH_MAX_MPATHS. mesh_path_free_rcu() decrements the interface mpaths counter for entries from both the mesh and proxy path tables, but mpp_path_add() never reserves a slot in that counter. Removing or expiring a proxy path therefore returns a slot that was never charged. mesh_path_add() uses the same counter to enforce MESH_MAX_MPATHS, so once it falls below the number of installed paths the limit no longer holds. Reserve the shared quota before allocating a proxy path and release it on every unsuccessful addition. Mesh and proxy paths now share one 1024-entry budget, so mpp_path_add() can return -ENOSPC at that limit. Fixes: ece1a2e7e860 ("mac80211: Remove mesh paths when an interface is removed") Cc: stable@vger.kernel.org Assisted-by: LLM sparse kasan Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260916113649.28315-3-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mesh_pathtbl.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 770fc16b439b..1c55b14c2ac0 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -736,10 +736,15 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata, if (is_multicast_ether_addr(dst)) return -EOPNOTSUPP; + if (!atomic_add_unless(&sdata->u.mesh.mpaths, 1, MESH_MAX_MPATHS)) + return -ENOSPC; + new_mpath = mesh_path_new(sdata, dst, GFP_ATOMIC); - if (!new_mpath) + if (!new_mpath) { + atomic_dec(&sdata->u.mesh.mpaths); return -ENOMEM; + } memcpy(new_mpath->mpp, mpp, ETH_ALEN); tbl = &sdata->u.mesh.mpp_paths; @@ -752,10 +757,12 @@ int mpp_path_add(struct ieee80211_sub_if_data *sdata, hlist_add_head_rcu(&new_mpath->walk_list, &tbl->walk_head); spin_unlock_bh(&tbl->walk_lock); - if (ret) + if (ret) { kfree(new_mpath); - else + atomic_dec(&sdata->u.mesh.mpaths); + } else { mesh_fast_tx_flush_addr(sdata, dst); + } sdata->u.mesh.mpp_paths_generation++; return ret; From 67a9e80c3ca20c9044cd6ee72cd6278dd19b5023 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Wed, 16 Sep 2026 19:37:35 +0800 Subject: [PATCH 152/535] wifi: mac80211: drain PS delivery work during station teardown Removing an AP or mesh station can notify the driver and wake TXQs after the station has already been removed from the driver, and can update num_sta_ps and the power-save queues after station cleanup has purged them. sta_deliver_ps_frames() tests sta->dead only before it starts delivery. A worker that passes that test can be delayed while teardown sets sta->dead and removes the station from the driver; when it resumes it still runs the full delivery. The existing cancel_work_sync() sits near the end of __cleanup_single_sta(), after the final driver state transition and after PS accounting and queue purging, so it drains the worker only once the state it has to protect has already changed. Before commit d34ba2168a3c ("mac80211: don't delay station destruction") cleanup was deferred through the same ordered workqueue as the delivery work, which kept the two ordered. Cancel the work after setting sta->dead and before moving the driver state, and move the existing cancellation to the start of __cleanup_single_sta(). The first drain closes the driver-lifetime window; driver callbacks during the remaining state transitions can still queue drv_deliver_wk through ieee80211_sta_block_awake(), so retain the second drain at the start of __cleanup_single_sta(). That drain also covers insertion failures, which reach cleanup without setting sta->dead. Fixes: d34ba2168a3c ("mac80211: don't delay station destruction") Cc: stable@vger.kernel.org Assisted-by: LLM sparse smatch coccinelle kasan Signed-off-by: Zhao Li Link: https://patch.msgid.link/20260916113735.31029-1-enderaoelyther@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/sta_info.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index fdf00cbf49d8..e81cd155197b 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -137,6 +137,8 @@ static void __cleanup_single_sta(struct sta_info *sta) struct ieee80211_local *local = sdata->local; struct ps_data *ps; + cancel_work_sync(&sta->drv_deliver_wk); + if (test_sta_flag(sta, WLAN_STA_PS_STA) || test_sta_flag(sta, WLAN_STA_PS_DRIVER) || test_sta_flag(sta, WLAN_STA_PS_DELIVER)) { @@ -166,8 +168,6 @@ static void __cleanup_single_sta(struct sta_info *sta) if (ieee80211_vif_is_mesh(&sdata->vif)) mesh_sta_cleanup(sta); - cancel_work_sync(&sta->drv_deliver_wk); - /* * Destroy aggregation state here. It would be nice to wait for the * driver to finish aggregation stop and then clean up, but for now @@ -1582,6 +1582,8 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc) sta->dead = true; + cancel_work_sync(&sta->drv_deliver_wk); + local->num_sta--; local->sta_generation++; From a8b8881d0c1aff544fae190469d86db7b07f43a9 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 12:23:25 +0000 Subject: [PATCH 153/535] wifi: wlcore: Fix runtime PM leak in wlcore_remove() pm_runtime_get_sync() keeps the runtime PM usage counter elevated even when it returns an error, so the reference taken at the beginning of wlcore_remove() has to be dropped on every exit path. When the device was not initialized the function returned early without doing so, leaking the reference. Release it with pm_runtime_put_noidle() as done on the other early error paths of this driver. Fixes: fa2648a34e73f ("wlcore: Add support for runtime PM") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917122325.2150910-1-vulab@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/ti/wlcore/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index edf6ca23c6c3..26aabf4ea127 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -6809,8 +6809,10 @@ void wlcore_remove(struct platform_device *pdev) if (pdev_data->family && pdev_data->family->nvs_name) wait_for_completion(&wl->nvs_loading_complete); - if (!wl->initialized) + if (!wl->initialized) { + pm_runtime_put_noidle(wl->dev); return; + } if (wl->wakeirq >= 0) { dev_pm_clear_wake_irq(wl->dev); From 5e4f3509793b4db2c0835340224743db680e861f Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Fri, 18 Sep 2026 11:40:04 +0800 Subject: [PATCH 154/535] wifi: mac80211: drop oversized fragments to avoid extra_len overflow In ieee80211_rx_h_defragment(), fragment payloads are accumulated into entry->extra_len as subsequent fragments arrive: entry->extra_len += rx->skb->len; When the final fragment arrives, the head fragment is dequeued, and pskb_expand_head() is called with entry->extra_len to ensure sufficient tailroom for all queued fragments before copying: if (skb_tailroom(rx->skb) < entry->extra_len) { if (unlikely(pskb_expand_head(rx->skb, 0, entry->extra_len, GFP_ATOMIC))) { ... } } while ((skb = __skb_dequeue(&entry->skb_list))) { skb_put_data(rx->skb, skb->data, skb->len); dev_kfree_skb(skb); } In commit 69f132236827 ("mac80211: shrink struct ieee80211_fragment_entry"), entry->extra_len was narrowed from unsigned int to u16 in order to reduce structure memory footprint. However, an IEEE 802.11 frame sequence can contain up to 16 fragments (frag numbers 0..15). If a sequence of large fragments arrives (e.g. from a malicious peer or rogue AP), the sum of fragment lengths can exceed 65535 bytes (for example, 15 fragments of 4400 bytes total 66000 bytes). Because extra_len is a u16, this addition overflows and wraps around modulo 65536 (e.g. 66000 wraps to 464). Consequently, pskb_expand_head() allocates only the truncated amount of tailroom (or is skipped entirely if the head fragment already has >= 464 bytes of tailroom). When skb_put_data() subsequently iterates through the queued skb list, it appends the full payload into the undersized buffer, causing skb_put() to trigger skb_over_panic() and crash the kernel in softirq context. A legitimate MSDU in IEEE 802.11 is at most 2304 bytes (or up to 7935/11454 bytes for A-MSDU, which is not fragmented), well below U16_MAX. Fix this without increasing the size of struct ieee80211_fragment_entry by checking whether adding the incoming fragment length would exceed U16_MAX. If so, purge the queued fragments and drop the frame. Fixes: 69f132236827 ("mac80211: shrink struct ieee80211_fragment_entry") Signed-off-by: Yuchao Zhang Link: https://patch.msgid.link/20260918034004.85078-2-ndaugoing@gmail.com [assign a separate drop reason] Signed-off-by: Johannes Berg --- net/mac80211/drop.h | 1 + net/mac80211/rx.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/net/mac80211/drop.h b/net/mac80211/drop.h index f06a8aa905c5..f433ee17cce5 100644 --- a/net/mac80211/drop.h +++ b/net/mac80211/drop.h @@ -108,6 +108,7 @@ typedef unsigned int __bitwise ieee80211_rx_result; R(RX_DROP_U_UNHANDLED_PREQ) \ R(RX_DROP_U_UNHANDLED_MGMT_STYPE) \ R(RX_DROP_U_NO_LINK) \ + R(RX_DROP_U_DEFRAG_OVERFLOW) \ /* this line for the trailing \ - add before this */ /* having two enums allows for checking ieee80211_rx_result use with sparse */ diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 5e26be8e27d8..7ead509304eb 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -2499,6 +2499,12 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx) } skb_pull(rx->skb, ieee80211_hdrlen(fc)); + if (unlikely((u32)entry->extra_len + rx->skb->len > U16_MAX)) { + I802_DEBUG_INC(rx->local->rx_handlers_drop_defrag); + __skb_queue_purge(&entry->skb_list); + return RX_DROP_U_DEFRAG_OVERFLOW; + } + __skb_queue_tail(&entry->skb_list, rx->skb); entry->last_frag = frag; entry->extra_len += rx->skb->len; From ee3aadc43ea69d98f666431e787137ac0badbb85 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Fri, 18 Sep 2026 05:26:51 +0000 Subject: [PATCH 155/535] wifi: mac80211: fix mesh fast xmit path deletion UAF mesh_fast_tx_cache() stores raw mesh_path pointers. Path deletion flushes the cache and then frees the path with kfree_rcu(), but a lookup that already holds the path can insert a new cache entry after the flush. The cache then points at freed memory. Set MESH_PATH_DELETED before flushing, and skip inserting a cache entry if the path or MPP path is already deleted. Check this under the cache walk lock so it is ordered with the flush. Use WRITE_ONCE() for the deletion flag updates because the cache check reads flags without taking state_lock. Fixes: d5edb9ae8d56 ("wifi: mac80211: mesh fast xmit support") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Link: https://patch.msgid.link/1daa7a98199fe6965c2da7c42717073a6fd39a0b.1789615568.git.zihanx@nebusec.ai Signed-off-by: Johannes Berg --- net/mac80211/mesh_pathtbl.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 1c55b14c2ac0..07a21dfa8674 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -577,6 +577,12 @@ void mesh_fast_tx_cache(struct ieee80211_sub_if_data *sdata, goto unlock_sta; spin_lock(&cache->walk_lock); + if ((READ_ONCE(mpath->flags) & MESH_PATH_DELETED) || + (mppath && (READ_ONCE(mppath->flags) & MESH_PATH_DELETED))) { + kfree(entry); + goto unlock_cache; + } + prev = rhashtable_lookup_get_insert_fast(&cache->rht, &entry->rhash, fast_tx_rht_params); @@ -808,7 +814,8 @@ static void mesh_path_free_rcu(struct mesh_table *tbl, struct ieee80211_sub_if_data *sdata = mpath->sdata; spin_lock_bh(&mpath->state_lock); - mpath->flags |= MESH_PATH_RESOLVING | MESH_PATH_DELETED; + WRITE_ONCE(mpath->flags, + mpath->flags | MESH_PATH_RESOLVING | MESH_PATH_DELETED); mesh_gate_del(tbl, mpath); spin_unlock_bh(&mpath->state_lock); timer_shutdown_sync(&mpath->timer); @@ -822,6 +829,9 @@ static void __mesh_path_del(struct mesh_table *tbl, struct mesh_path *mpath) { hlist_del_rcu(&mpath->walk_list); rhashtable_remove_fast(&tbl->rhead, &mpath->rhash, mesh_rht_params); + spin_lock_bh(&mpath->state_lock); + WRITE_ONCE(mpath->flags, mpath->flags | MESH_PATH_DELETED); + spin_unlock_bh(&mpath->state_lock); if (tbl == &mpath->sdata->u.mesh.mpp_paths) mesh_fast_tx_flush_addr(mpath->sdata, mpath->dst); else From db7b86bd97b380ece8fa39cfdd7502a9fd35e56f Mon Sep 17 00:00:00 2001 From: Heyang Tan Date: Fri, 18 Sep 2026 16:33:49 +0800 Subject: [PATCH 156/535] wifi: p54: validate firmware record lengths The p54 firmware parser accesses component ID and component version records without checking that their lengths cover the fields being read. A malformed record can therefore make the parser read past the record. Validate the component ID and component version record lengths before accessing their data. The descriptor record is also checked against the shortest descriptor length found in the supported firmware images. Reject descriptor values that underflow the RX offset or produce an empty RX range before updating the driver state. Assisted-by: LLM Codex Signed-off-by: Heyang Tan Tested-by: Christian Lamparter Link: https://patch.msgid.link/20260918083349.891-1-thy15333007817@163.com Signed-off-by: Johannes Berg --- drivers/net/wireless/intersil/p54/fwio.c | 42 +++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/intersil/p54/fwio.c b/drivers/net/wireless/intersil/p54/fwio.c index a3d9053f043c..f028a7bfa734 100644 --- a/drivers/net/wireless/intersil/p54/fwio.c +++ b/drivers/net/wireless/intersil/p54/fwio.c @@ -52,6 +52,13 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) u32 code = le32_to_cpu(bootrec->code); switch (code) { case BR_CODE_COMPONENT_ID: + if (len < sizeof(struct bootrec_comp_id) / + sizeof(*bootrec->data)) { + wiphy_err(priv->hw->wiphy, + "firmware component ID is too short\n"); + return -EINVAL; + } + priv->fw_interface = be32_to_cpup((__be32 *) bootrec->data); switch (priv->fw_interface) { @@ -72,16 +79,41 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw) } break; case BR_CODE_COMPONENT_VERSION: + if (len < DIV_ROUND_UP(sizeof(struct bootrec_comp_ver), + sizeof(*bootrec->data))) { + wiphy_err(priv->hw->wiphy, + "firmware component version is too short\n"); + return -EINVAL; + } + /* 24 bytes should be enough for all firmwares */ - if (strnlen((unsigned char *) bootrec->data, 24) < 24) - fw_version = (unsigned char *) bootrec->data; + if (strnlen((unsigned char *)bootrec->data, + sizeof(struct bootrec_comp_ver)) < + sizeof(struct bootrec_comp_ver)) + fw_version = (unsigned char *)bootrec->data; break; case BR_CODE_DESCR: { struct bootrec_desc *desc = (struct bootrec_desc *)bootrec->data; - priv->rx_start = le32_to_cpu(desc->rx_start); - /* FIXME add sanity checking */ - priv->rx_end = le32_to_cpu(desc->rx_end) - 0x3500; + u32 rx_start, rx_end; + + /* 0xa is the shortest descriptor in supported firmware. */ + if (len < 0xa) { + wiphy_err(priv->hw->wiphy, + "firmware descriptor is too short\n"); + return -EINVAL; + } + + rx_start = le32_to_cpu(desc->rx_start); + rx_end = le32_to_cpu(desc->rx_end); + if (rx_end < 0x3500 || rx_end - 0x3500 <= rx_start) { + wiphy_err(priv->hw->wiphy, + "firmware descriptor has invalid RX range\n"); + return -EINVAL; + } + + priv->rx_start = rx_start; + priv->rx_end = rx_end - 0x3500; priv->headroom = desc->headroom; priv->tailroom = desc->tailroom; priv->privacy_caps = desc->privacy_caps; From 15fb9e3cea55fbc378261ce35a32b0f8828f0000 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:46:20 +0800 Subject: [PATCH 157/535] wifi: mac80211: minstrel_ht: validate fixed rate index The fixed_rate_idx debugfs file is a plain u32 attribute, so any value can be written to it. minstrel_ht_update_stats() then copies the value into mi->max_tp_rate[] and mi->max_prob_rate, and minstrel_ht_set_rate() uses MI_RATE_GROUP()/MI_RATE_IDX() to index minstrel_mcs_groups[] (42 entries) and mi->groups[].rates[] (10 entries) with it. Writing e.g. 65535 selects group 4095 and rate index 15, far outside both arrays. The out-of-bounds entries are dereferenced and updated as struct minstrel_rate_stats (retry counts, etc.), so the invalid index corrupts adjacent kernel memory. With CONFIG_UBSAN_BOUNDS the access is reported as an array-index-out-of-bounds in minstrel_ht_set_rate(). Only a valid group/rate pair, or U32_MAX to disable fixed rate processing, can be used safely, so reject any other value when the debugfs file is written. The file is only reachable through a root-only debugfs mount, so this is not a privilege boundary. The out-of-bounds access is still a bug that must not be reachable through a writable debugfs attribute. Fixes: 24f7580e852b ("minstrel_ht: fixed rate mode through debugfs") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Link: https://patch.msgid.link/288e6f6d30dc82ff40da502755e6666982f6b735.1789798000.git.xuyuqiabc@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/rc80211_minstrel_ht.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index b73ef3adfcc5..c26221287515 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1947,14 +1948,39 @@ minstrel_ht_alloc(struct ieee80211_hw *hw) } #ifdef CONFIG_MAC80211_DEBUGFS +static int minstrel_ht_fixed_rate_idx_get(void *data, u64 *val) +{ + *val = *(u32 *)data; + return 0; +} + +static int minstrel_ht_fixed_rate_idx_set(void *data, u64 val) +{ + u32 idx = val; + + /* U32_MAX is the default and keeps fixed rate processing disabled */ + if (val != U32_MAX && + (val > U16_MAX || + MI_RATE_GROUP(idx) >= ARRAY_SIZE(minstrel_mcs_groups) || + MI_RATE_IDX(idx) >= MCS_GROUP_RATES)) + return -EINVAL; + + *(u32 *)data = idx; + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(minstrel_ht_fixed_rate_idx_fops, + minstrel_ht_fixed_rate_idx_get, + minstrel_ht_fixed_rate_idx_set, "%llu\n"); + static void minstrel_ht_add_debugfs(struct ieee80211_hw *hw, void *priv, struct dentry *debugfsdir) { struct minstrel_priv *mp = priv; mp->fixed_rate_idx = (u32) -1; - debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, - &mp->fixed_rate_idx); + debugfs_create_file("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, + &mp->fixed_rate_idx, &minstrel_ht_fixed_rate_idx_fops); } #endif From 5bfd4b0b40b79781d900cb2f7da0685070f53c67 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Sat, 19 Sep 2026 16:48:12 +0800 Subject: [PATCH 158/535] wifi: mac80211: handle empty FILS association request payload fils_encrypt_assoc_req() accepts a (Re)Association Request frame whose last element is the FILS Session element. crypt_len is then zero and aes_siv_encrypt() is called with an empty plaintext. kmemdup() returns ZERO_SIZE_PTR for a zero-length allocation, so the unconditional sg_init_one() puts an invalid page into the scatterlist. With CONFIG_DEBUG_SG this triggers a kernel BUG in sg_set_buf(); without the debug check the bogus page is handed to the crypto backend. AES-SIV is defined for empty plaintext and the S2V result is then the complete output, so handle this case before the CTR step, which has nothing to encrypt. Fixes: 39404feee691 ("mac80211: FILS AEAD protection for station mode association frames") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Link: https://patch.msgid.link/325c048982d637a3af80d74ac5b36e9369b93f1e.1789751126.git.xuyuqiabc@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/fils_aead.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/mac80211/fils_aead.c b/net/mac80211/fils_aead.c index 293590976489..9c4f00d61981 100644 --- a/net/mac80211/fils_aead.c +++ b/net/mac80211/fils_aead.c @@ -89,6 +89,14 @@ static int aes_siv_encrypt(const u8 *key, size_t key_len, if (res) return res; + /* With an empty plaintext there is no ciphertext to produce and + * the S2V result is the complete output. + */ + if (!plain_len) { + memcpy(out, v, AES_BLOCK_SIZE); + return 0; + } + /* Use a temporary buffer of the plaintext to handle need for * overwriting this during AES-CTR. */ From 9eda41e32263806a66835e275efdcc0c5f46bbc4 Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Sat, 19 Sep 2026 11:24:54 +0200 Subject: [PATCH 159/535] wifi: cfg80211: fix RTS threshold setting for single-radio PHY Due to an incorrect old_radio_rts_threshold check, we do not set rdev->wiphy.rts_threshold for a single-radio PHY, and the threshold value is not propagated to lower layers. Cc: stable@vger.kernel.org Fixes: 264637941cf4 ("wifi: cfg80211: Add Support to Set RTS Threshold for each Radio") Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260919092454.50266-1-stf_xl@wp.pl Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9fafd7673d85..ddce61ec016a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4563,12 +4563,13 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) rdev->wiphy.retry_long = retry_long; if (changed & WIPHY_PARAM_FRAG_THRESHOLD) rdev->wiphy.frag_threshold = frag_threshold; - if ((changed & WIPHY_PARAM_RTS_THRESHOLD) && - old_radio_rts_threshold) { + if (changed & WIPHY_PARAM_RTS_THRESHOLD) { rdev->wiphy.rts_threshold = rts_threshold; - for (i = 0 ; i < rdev->wiphy.n_radio; i++) - rdev->wiphy.radio_cfg[i].rts_threshold = - rdev->wiphy.rts_threshold; + if (old_radio_rts_threshold) { + for (i = 0; i < rdev->wiphy.n_radio; i++) + rdev->wiphy.radio_cfg[i].rts_threshold = + rdev->wiphy.rts_threshold; + } } if (changed & WIPHY_PARAM_COVERAGE_CLASS) rdev->wiphy.coverage_class = coverage_class; From 50b617ba788e5c27dfc6efc90556cdcc9509028d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:23 +0000 Subject: [PATCH 160/535] io_uring/bpf_filter: mark source as COW when cloning filters io_bpf_filter_clone() shares the filter table between source and destination and marks the destination as needing a copy before it can be modified, but leaves the source as is. Registering another filter on the source then modifies the shared table in place, and the change shows up in everything that cloned it. Mark the source as COW as well. Fixes: ed82f35b926b ("io_uring: allow registration of per-task restrictions") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919112523.3872581-1-benquike@gmail.com [axboe: edited patch and commit message] Signed-off-by: Jens Axboe --- io_uring/bpf_filter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index c0037632b7af..4a21511c4811 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -253,6 +253,7 @@ void io_bpf_filter_clone(struct io_restriction *dst, struct io_restriction *src) * If the src filter is going away, just ignore it. */ if (refcount_inc_not_zero(&src->bpf_filters->refs)) { + src->bpf_filters_cow = true; dst->bpf_filters = src->bpf_filters; dst->bpf_filters_cow = true; } From 2107751744287996a9bbcf40b5055a5b7fe30b5c Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:16 +0000 Subject: [PATCH 161/535] io_uring: only post the dummy skip CQE on CQE_MIXED rings io_cqe_cache_refill() posts a dummy skip CQE when a 32b CQE would straddle the end of the ring, so the real one can start at offset 0. That only makes sense on CQE_MIXED rings, where slots are 16b and a 32b CQE needs two of them back to back. On a CQE32 ring every slot is already 32b, but io_fill_cqe_aux32() still passes cqe32 == true, and the dummy then gets written at cqes[off] rather than cqes[off << 1], inside another CQE. Limit the dummy to CQE_MIXED rings. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 76f049e29aa2..88fed477e2b0 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -733,7 +733,8 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) * Post dummy CQE if a 32b CQE is needed and there's only room for a * 16b CQE before the ring wraps. */ - if (cqe32 && off + 1 == ctx->cq_entries) { + if (cqe32 && (ctx->flags & IORING_SETUP_CQE_MIXED) && + off + 1 == ctx->cq_entries) { if (!io_fill_nop_cqe(ctx, off)) return false; off = 0; From ab5853c068b1d654d7c4a535417ca3eb3e67403b Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:18 +0000 Subject: [PATCH 162/535] io_uring: fix free entry check for 32b CQEs on CQE32 rings On CQE32 rings, len is in units of 32b CQEs when io_cqe_cache_refill() checks for free entries, so requiring cqe32 + 1 of them means insisting on 2 free CQEs where 1 is enough, and a ring with a single free entry needlessly goes to overflow. Check for a free CQE directly in that case, and keep the cqe32 + 1 check for CQE_MIXED where the units are 16b. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 88fed477e2b0..4847a8f8fac8 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -743,12 +743,13 @@ bool io_cqe_cache_refill(struct io_ring_ctx *ctx, bool overflow, bool cqe32) free = ctx->cq_entries - io_cqring_queued(ctx); /* we need a contiguous range, limit based on the current array offset */ len = min(free, ctx->cq_entries - off); - if (len < (cqe32 + 1)) - return false; - if (ctx->flags & IORING_SETUP_CQE32) { + if (!len) + return false; off <<= 1; len <<= 1; + } else if (len < (cqe32 + 1)) { + return false; } ctx->cqe_cached = &rings->cqes[off]; From f11a16c8200189a3752b5233092f88503969166d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 20:35:19 +0000 Subject: [PATCH 163/535] io_uring: zero big_cqe for aux CQEs on CQE32 rings io_fill_cqe_aux() only clears big_cqe if cqe32 is set, which is only the case for CQE_MIXED. On a CQE32 ring every CQE is 32b, and the extra 16b are left holding whatever was in that slot before. Clear it for CQE32 rings as well, like it was before CQE_MIXED support was added. Fixes: e26dca67fde1 ("io_uring: add support for IORING_SETUP_CQE_MIXED") Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919203516.2581409-1-benquike@gmail.com [axboe: split from Hui's original patch, edited commit message] Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 4847a8f8fac8..2cb9b58d04dd 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -783,7 +783,7 @@ static bool io_fill_cqe_aux(struct io_ring_ctx *ctx, u64 user_data, s32 res, WRITE_ONCE(cqe->res, res); WRITE_ONCE(cqe->flags, cflags); - if (cqe32) { + if (cqe32 || (ctx->flags & IORING_SETUP_CQE32)) { WRITE_ONCE(cqe->big_cqe[0], 0); WRITE_ONCE(cqe->big_cqe[1], 0); } From 7e0ad05ebc72fffba2bdf2400400902d94ed7272 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Wed, 12 Aug 2026 16:58:05 +0200 Subject: [PATCH 164/535] wifi: ath11k: reset ar->num_stations on hardware start When (multiple) hw restart occurs, we end up in a situation where we cannot accept new stations / mesh peers. This seems to be the 'num_stations' in 'struct ath11k' that did not get reset properly in this case. In ath11k_mac_op_start(), it was indeed the only accounting var that did not get reset so let's clear it too. This avoids problems like those: [1248828.088023] WARNING: CPU: 0 PID: 12281 at net/mac80211/util.c:1682 ieee80211_reconfig_stations+0x98/0xc0 [1248828.088115] Call trace: [1248828.088116] ieee80211_reconfig_stations+0x98/0xc0 (P) [1248828.088119] ieee80211_reconfig+0x774/0x1438 [1248828.088123] ieee80211_restart_work+0x10c/0x178 [1248828.088127] process_one_work+0x178/0x3c0 [1248828.088130] worker_thread+0x280/0x440 [1248828.088133] kthread+0xe0/0xf8 [1248828.088137] ret_from_fork+0x10/0x20 [1248828.088141] ---[ end trace 0000000000000000 ]--- [1248836.686604] ath11k_pci 0000:04:00.0: refusing to associate station: too many connected already (128) [1248836.686609] ath11k_pci 0000:04:00.0: Failed to add station: xx:xx:xx:xx:xx:xx for VDEV: 1 Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.9.0.1-01977-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Nicolas Escande Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260812-num-station-v3-1-871d20e043f6@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/mac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index ae91b57c8422..c1fa42edd363 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -6716,6 +6716,7 @@ static int ath11k_mac_op_start(struct ieee80211_hw *hw) ar->num_started_vdevs = 0; ar->num_created_vdevs = 0; ar->num_peers = 0; + ar->num_stations = 0; ar->allocated_vdev_map = 0; /* Configure monitor status ring with default rx_filter to get rx status From 1af8dc9ca726cf53767f9c0661171396f95c146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Tue, 8 Sep 2026 19:05:18 +0200 Subject: [PATCH 165/535] wifi: ath9k: Clean up device initialisation guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ath9k driver has a couple of guards against the RX path running until device initialisation has completed. However, they are problematic for several reasons: - They use smp_wmb() paired with a data_race() annotation, instead of the idiomatic smp_store_release()/smp_load_acquire() pair. - The guard in ath9k_wmi_event_tasklet() is weirdly late in the function. - There are two of them when there's really no reason the rx init gets to run before the whole initialisation is complete. Consolidate the guards on the single ath9k_htc_priv->initialized variable and use smp_store_release()/smp_load_acquire() to access it. Move the store into ath9k_init_device() to make sure it's set in all paths in the driver. Fixes: 24355fcb0d4c ("wifi: ath9k: delay all of ath9k_wmi_event_tasklet() until init is complete") Fixes: b0ec7e55fce6 ("ath9k_htc: fix NULL pointer dereference at ath9k_htc_rxep()") Signed-off-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260908170520.112946-1-toke@redhat.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/htc.h | 1 - drivers/net/wireless/ath/ath9k/htc_drv_init.c | 7 +++---- drivers/net/wireless/ath/ath9k/htc_drv_txrx.c | 8 ++------ drivers/net/wireless/ath/ath9k/wmi.c | 12 ++++++------ 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/htc.h b/drivers/net/wireless/ath/ath9k/htc.h index 6c33e898b300..4d2b75d0212e 100644 --- a/drivers/net/wireless/ath/ath9k/htc.h +++ b/drivers/net/wireless/ath/ath9k/htc.h @@ -281,7 +281,6 @@ struct ath9k_htc_rxbuf { struct ath9k_htc_rx { struct list_head rxbuf; spinlock_t rxbuflock; - bool initialized; }; #define ATH9K_HTC_TX_CLEANUP_INTERVAL 50 /* ms */ diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c index 6de78ae85726..911eaa4f776a 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c @@ -910,6 +910,9 @@ static int ath9k_init_device(struct ath9k_htc_priv *priv, ath9k_init_leds(priv); ath9k_start_rfkill_poll(priv); + /* signal completion to ath9k_htc_rxep() and ath9k_wmi_event_tasklet() */ + smp_store_release(&priv->initialized, true); + return 0; err_world: @@ -966,10 +969,6 @@ int ath9k_htc_probe_device(struct htc_target *htc_handle, struct device *dev, htc_handle->drv_priv = priv; - /* Allow ath9k_wmi_event_tasklet() to operate. */ - smp_wmb(); - priv->initialized = true; - return 0; err_init: diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c index bed7ea2425a0..95f48c7dca15 100644 --- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c +++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c @@ -1141,8 +1141,8 @@ void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb, struct ath9k_htc_rxbuf *rxbuf = NULL, *tmp_buf = NULL; unsigned long flags; - /* Check if ath9k_rx_init() completed. */ - if (!data_race(priv->rx.initialized)) + /* Check if ath9k_init_device() completed. */ + if (!smp_load_acquire(&priv->initialized)) goto err; spin_lock_irqsave(&priv->rx.rxbuflock, flags); @@ -1200,10 +1200,6 @@ int ath9k_rx_init(struct ath9k_htc_priv *priv) list_add_tail(&rxbuf->list, &priv->rx.rxbuf); } - /* Allow ath9k_htc_rxep() to operate. */ - smp_wmb(); - priv->rx.initialized = true; - return 0; err: diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c index 284e8c13b043..44e91815a159 100644 --- a/drivers/net/wireless/ath/ath9k/wmi.c +++ b/drivers/net/wireless/ath/ath9k/wmi.c @@ -146,6 +146,12 @@ void ath9k_wmi_event_tasklet(struct tasklet_struct *t) unsigned long flags; u16 cmd_id; + /* Check if ath9k_init_device() completed. */ + if (!smp_load_acquire(&priv->initialized)) { + tasklet_schedule(t); + return; + } + do { spin_lock_irqsave(&wmi->wmi_lock, flags); skb = __skb_dequeue(&wmi->wmi_event_queue); @@ -155,12 +161,6 @@ void ath9k_wmi_event_tasklet(struct tasklet_struct *t) } spin_unlock_irqrestore(&wmi->wmi_lock, flags); - /* Check if ath9k_htc_probe_device() completed. */ - if (!data_race(priv->initialized)) { - kfree_skb(skb); - continue; - } - hdr = (struct wmi_cmd_hdr *) skb->data; cmd_id = be16_to_cpu(hdr->command_id); wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr)); From 93ce8ea70451e800601bc9444aa33043de15d67b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Fri, 14 Aug 2026 15:58:37 +0800 Subject: [PATCH 166/535] wifi: ath9k: reject short WMI command responses ath9k_wmi_rsp_callback() removes the validated WMI header and copies the number of bytes expected by the waiting command into its response buffer. A device response shorter than that expectation can therefore be read beyond the skb payload. Record -EMSGSIZE for a short response, complete the waiter, and return the stored status from the command path. This also prevents callers from consuming stale response bytes as a successful reply. Fixes: fb9987d0f748 ("ath9k_htc: Support for AR9271 chipset.") Assisted-by: LLM Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260814075837.20005-1-pengpeng@iscas.ac.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/wmi.c | 16 ++++++++++++++-- drivers/net/wireless/ath/ath9k/wmi.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c index 44e91815a159..4a08babb6d9d 100644 --- a/drivers/net/wireless/ath/ath9k/wmi.c +++ b/drivers/net/wireless/ath/ath9k/wmi.c @@ -206,9 +206,16 @@ static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb) { skb_pull(skb, sizeof(struct wmi_cmd_hdr)); - if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0) + if (wmi->cmd_rsp_buf && wmi->cmd_rsp_len) { + if (skb->len < wmi->cmd_rsp_len) { + wmi->cmd_rsp_status = -EMSGSIZE; + goto complete; + } memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len); + } + wmi->cmd_rsp_status = 0; +complete: complete(&wmi->cmd_wait); } @@ -300,6 +307,7 @@ static int ath9k_wmi_cmd_issue(struct wmi *wmi, /* record the rsp buffer and length */ wmi->cmd_rsp_buf = rsp_buf; wmi->cmd_rsp_len = rsp_len; + wmi->cmd_rsp_status = 0; wmi->last_seq_id = wmi->tx_seq_id; spin_unlock_irqrestore(&wmi->wmi_lock, flags); @@ -356,9 +364,13 @@ int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id, return -ETIMEDOUT; } + spin_lock_irqsave(&wmi->wmi_lock, flags); + ret = wmi->cmd_rsp_status; + spin_unlock_irqrestore(&wmi->wmi_lock, flags); + mutex_unlock(&wmi->op_mutex); - return 0; + return ret; out: ath_dbg(common, WMI, "WMI failure for: %s\n", wmi_cmd_to_name(cmd_id)); diff --git a/drivers/net/wireless/ath/ath9k/wmi.h b/drivers/net/wireless/ath/ath9k/wmi.h index 5c3b710b8f31..0bb9d31f1a1c 100644 --- a/drivers/net/wireless/ath/ath9k/wmi.h +++ b/drivers/net/wireless/ath/ath9k/wmi.h @@ -157,6 +157,7 @@ struct wmi { u16 tx_seq_id; u8 *cmd_rsp_buf; u32 cmd_rsp_len; + int cmd_rsp_status; bool stopped; struct list_head pending_tx_events; From aeaca27c1db3106967ffbb2b517d98c835f6d500 Mon Sep 17 00:00:00 2001 From: Georgios Karantzas Date: Fri, 11 Sep 2026 03:11:43 +0300 Subject: [PATCH 167/535] wifi: ath9k_htc: bound TX aggregation to MAX_TX_BUF_SIZE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __hif_usb_tx() dequeues up to MAX_TX_AGGR_NUM (20) frames into a single tx_buf of MAX_TX_BUF_SIZE (32768) bytes, limiting the batch by record count but never by cumulative byte length. With large frames (MTU 2304), 20 aggregated frames of 2292 bytes each exceed the allocation (20 * 2296 = 45920 bytes), so the memcpy() in the loop writes up to 13152 bytes past tx_buf->buf before usb_submit_urb(). Peek the queue head and stop before copying any record that would cross MAX_TX_BUF_SIZE, then dispatch the current batch. Leftover skbs remain queued and are drained on the next URB completion. The byte bound changes the loop's exit semantics: it can now exit before i == tx_skb_cnt - 1. Stock only finalized tx_buf->len on that last index (len += offset), so an early break would submit a URB holding only the last record's length while every dequeued skb is freed on completion, silently dropping frames. Make tx_buf->len a running total and advance tx_buf->offset per record instead; the stride round_up(nskb->len + 4, 4) is identical to the stock stride when the loop runs to completion. Tested on hardware with an MTU 2304 flood: the loop stops at record 15 (len = 32144, offset = 32144, within 32768), no oversized URB is submitted, and MTU 1500 pings pass 20/20. Fixes: fb9987d0f748 ("ath9k_htc: Support for AR9271 chipset.") Signed-off-by: Georgios Karantzas Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260911001143.4507-1-gck.kara@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/hif_usb.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index d3491ff08e6e..7d2f292e20c5 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -14,6 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include "htc.h" @@ -328,11 +329,14 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) tx_skb_cnt = min_t(u16, hif_dev->tx.tx_skb_cnt, MAX_TX_AGGR_NUM); for (i = 0; i < tx_skb_cnt; i++) { + nskb = skb_peek(&hif_dev->tx.tx_skb_queue); + if (!nskb) + break; + + if (tx_buf->offset + nskb->len + 4 > MAX_TX_BUF_SIZE) + break; + nskb = __skb_dequeue(&hif_dev->tx.tx_skb_queue); - - /* Should never be NULL */ - BUG_ON(!nskb); - hif_dev->tx.tx_skb_cnt--; buf = tx_buf->buf; @@ -342,13 +346,8 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG); buf += 4; memcpy(buf, nskb->data, nskb->len); - tx_buf->len = nskb->len + 4; - - if (i < (tx_skb_cnt - 1)) - tx_buf->offset += (((tx_buf->len - 1) / 4) + 1) * 4; - - if (i == (tx_skb_cnt - 1)) - tx_buf->len += tx_buf->offset; + tx_buf->len = tx_buf->offset + nskb->len + 4; + tx_buf->offset += round_up(nskb->len + 4, 4); __skb_queue_tail(&tx_buf->skb_queue, nskb); TX_STAT_INC(hif_dev, skb_queued); From 0ee5c5d804d592a8328a425b9274487d393d3a05 Mon Sep 17 00:00:00 2001 From: Liu Dalin Date: Thu, 27 Aug 2026 09:54:44 +0800 Subject: [PATCH 168/535] rtc: dev: zero-initialize struct rtc_wkalrm to prevent information leak The struct rtc_wkalrm has padding holes after the 'pending' member. When returned to userspace via the RTC_WKALRM_RD ioctl, uninitialized kernel stack data residing in these holes may be leaked. Zero-initialize the struct at declaration to eliminate this information leak risk. Fixes smatch warnings: - drivers/rtc/dev.c:392 rtc_dev_ioctl() warn: check that 'alarm' doesn't leak information (struct has a hole after 'pending') Fixes: 36e14f5fdfdf ("rtc: rename core files") Assisted-by: smatch:2.0 [static analysis] Signed-off-by: Liu Dalin Link: https://patch.msgid.link/B28B7DE18F3A9447+20260827015444.3314227-1-liudalin@kylinsec.com.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/dev.c b/drivers/rtc/dev.c index 8ba7c25d2565..be173c9511fa 100644 --- a/drivers/rtc/dev.c +++ b/drivers/rtc/dev.c @@ -216,7 +216,7 @@ static long rtc_dev_ioctl(struct file *file, struct rtc_device *rtc = file->private_data; const struct rtc_class_ops *ops = rtc->ops; struct rtc_time tm; - struct rtc_wkalrm alarm; + struct rtc_wkalrm alarm = { 0 }; struct rtc_param param; void __user *uarg = (void __user *)arg; From b430d1f6d80c005b486f028910e1ac3c91812710 Mon Sep 17 00:00:00 2001 From: "Johnny.Fan" Date: Mon, 31 Aug 2026 22:00:14 +0800 Subject: [PATCH 169/535] rtc: efi: restore alarm support with runtime capability probe Restore the EFI RTC alarm functionality (read_alarm/set_alarm and procfs output) that was removed by commit 18a3510bc87d ("rtc: efi: Remove wakeup functionality"). The removal was motivated by the observation that many EFI platforms lack RTC wakeup support while also omitting the RT_PROP table, causing the wakeup capability to be incorrectly advertised. While that is true, removing the feature entirely also breaks platforms where the wakeup runtime service actually works. On CIX SoCs, the RTC is an I2C device owned by firmware, and in the ACPI boot configuration the I2C bus it sits on is not exposed to the OS, so the EFI runtime services are the only way Linux can access the RTC at all. Both Linux and Windows use this same interface by design, so the RTC state is consistent across the operating systems installed on the machine, and Windows relies on the EFI wakeup services for its own RTC alarm support. With the alarm support removed from rtc-efi, Linux loses the equivalent capability entirely on these platforms: rtcwake fails for both suspend-to-RAM and power-off wakeup, as there is no other path to program the RTC alarm. This is a real product requirement that CIX customers have explicitly requested, not a hypothetical use case. To avoid exposing a broken alarm on firmware that does not implement the wakeup runtime services, probe the capability at ->probe() time by actually calling GetWakeupTime() -- if it fails, clear RTC_FEATURE_ALARM and the device behaves as before the removal. Also set RTC_FEATURE_ALARM_WAKEUP_ONLY since EFI wakeup time is a wakeup-only alarm, not a periodic/update interrupt source. Acked-by: Ard Biesheuvel Reviewed-by: Feng Tang Reviewed-by: Fugang Duan Signed-off-by: Johnny.Fan Link: https://patch.msgid.link/20260831140014.839222-1-johnny.fan@cixtech.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-efi.c | 80 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-efi.c b/drivers/rtc/rtc-efi.c index b4f44999ef0f..a8c18ebc5599 100644 --- a/drivers/rtc/rtc-efi.c +++ b/drivers/rtc/rtc-efi.c @@ -52,6 +52,7 @@ compute_wday(efi_time_t *eft, int yday) static void convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft) { + memset(eft, 0, sizeof(*eft)); eft->year = wtime->tm_year + 1900; eft->month = wtime->tm_mon + 1; eft->day = wtime->tm_mday; @@ -112,6 +113,35 @@ convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime) return true; } +static int efi_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + efi_time_t eft; + efi_status_t status; + + memset(&eft, 0, sizeof(eft)); + status = efi.get_wakeup_time((efi_bool_t *)&wkalrm->enabled, + (efi_bool_t *)&wkalrm->pending, &eft); + if (status != EFI_SUCCESS) + return -EINVAL; + + if (!convert_from_efi_time(&eft, &wkalrm->time)) + return -EIO; + + return rtc_valid_tm(&wkalrm->time); +} + +static int efi_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + efi_time_t eft; + efi_status_t status; + + convert_to_efi_time(&wkalrm->time, &eft); + + status = efi.set_wakeup_time((efi_bool_t)!!wkalrm->enabled, &eft); + + return status == EFI_SUCCESS ? 0 : -EINVAL; +} + static int efi_read_time(struct device *dev, struct rtc_time *tm) { efi_status_t status; @@ -146,13 +176,21 @@ static int efi_set_time(struct device *dev, struct rtc_time *tm) static int efi_procfs(struct device *dev, struct seq_file *seq) { - efi_time_t eft; + efi_time_t eft, alm; efi_time_cap_t cap; + efi_bool_t enabled, pending; + struct rtc_device *rtc = dev_get_drvdata(dev); memset(&eft, 0, sizeof(eft)); + memset(&alm, 0, sizeof(alm)); memset(&cap, 0, sizeof(cap)); efi.get_time(&eft, &cap); + if (test_bit(RTC_FEATURE_ALARM, rtc->features) && + efi.get_wakeup_time(&enabled, &pending, &alm) != EFI_SUCCESS) { + enabled = 0; + pending = 0; + } seq_printf(seq, "Time\t\t: %u:%u:%u.%09u\n" @@ -168,6 +206,25 @@ static int efi_procfs(struct device *dev, struct seq_file *seq) /* XXX fixme: convert to string? */ seq_printf(seq, "Timezone\t: %u\n", eft.timezone); + if (test_bit(RTC_FEATURE_ALARM, rtc->features)) { + seq_printf(seq, + "Alarm Time\t: %u:%u:%u.%09u\n" + "Alarm Date\t: %u-%u-%u\n" + "Alarm Daylight\t: %u\n" + "Enabled\t\t: %s\n" + "Pending\t\t: %s\n", + alm.hour, alm.minute, alm.second, alm.nanosecond, + alm.year, alm.month, alm.day, + alm.daylight, + enabled == 1 ? "yes" : "no", + pending == 1 ? "yes" : "no"); + + if (alm.timezone == EFI_UNSPECIFIED_TIMEZONE) + seq_puts(seq, "Alarm Timezone\t: unspecified\n"); + else + seq_printf(seq, "Alarm Timezone\t: %d\n", alm.timezone); + } + /* * now prints the capabilities */ @@ -183,6 +240,8 @@ static int efi_procfs(struct device *dev, struct seq_file *seq) static const struct rtc_class_ops efi_rtc_ops = { .read_time = efi_read_time, .set_time = efi_set_time, + .read_alarm = efi_read_alarm, + .set_alarm = efi_set_alarm, .proc = efi_procfs, }; @@ -191,6 +250,7 @@ static int __init efi_rtc_probe(struct platform_device *dev) struct rtc_device *rtc; efi_time_t eft; efi_time_cap_t cap; + efi_bool_t enabled, pending; /* First check if the RTC is usable */ if (efi.get_time(&eft, &cap) != EFI_SUCCESS) @@ -203,7 +263,23 @@ static int __init efi_rtc_probe(struct platform_device *dev) platform_set_drvdata(dev, rtc); rtc->ops = &efi_rtc_ops; - clear_bit(RTC_FEATURE_ALARM, rtc->features); + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); + + /* + * The EFI_RT_SUPPORTED_WAKEUP_SERVICES bit defaults to enabled + * and only gets cleared when the RT_PROP table explicitly says + * wakeup is unsupported. Many platforms lack an RT_PROP table + * even though they don't implement the wakeup runtime service, + * so probe by actually calling GetWakeupTime() to avoid exposing + * a broken alarm to userspace. + */ + if (efi_rt_services_supported(EFI_RT_SUPPORTED_WAKEUP_SERVICES) && + efi.get_wakeup_time(&enabled, &pending, &eft) == EFI_SUCCESS) { + set_bit(RTC_FEATURE_ALARM, rtc->features); + set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->features); + } else { + clear_bit(RTC_FEATURE_ALARM, rtc->features); + } device_init_wakeup(&dev->dev, true); From 6a4117eee82dd85f11bf898bf66026764011eeb6 Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Sat, 5 Sep 2026 19:38:08 +0100 Subject: [PATCH 170/535] rtc: ac100: Assign .num before accessing .hws Commit f316cdff8d67 ("clk: Annotate struct clk_hw_onecell_data with __counted_by") annotated the hws member of 'struct clk_hw_onecell_data' with __counted_by, which informs the bounds sanitizer (UBSAN_BOUNDS) about the number of elements in .hws[], so that it can warn when .hws[] is accessed out of bounds. As noted in that change, the __counted_by member must be initialized with the number of elements before the first array access happens, otherwise there will be a warning from each access prior to the initialization because the number of elements is zero. This occurs in ac100_rtc_register_clks() due to .num being assigned only after every clkout clock has been stored in .hws[]. With CONFIG_UBSAN_BOUNDS and a compiler that implements __counted_by (GCC 15.1+ or Clang 20.1+), this triggers an array-index-out-of-bounds report during probe, and with CONFIG_UBSAN_TRAP the first store traps. Initialize .num with AC100_CLKOUT_NUM, the number of elements .hws[] was allocated with, right after the allocation. That is the value the loop counter ends up at on the success path anyway, so the provider's behaviour is unchanged. Cc: stable@vger.kernel.org Fixes: f316cdff8d67 ("clk: Annotate struct clk_hw_onecell_data with __counted_by") Assisted-by: LLM Signed-off-by: Aamir Ahmed Reviewed-by: Chen-Yu Tsai Reviewed-by: Gustavo A. R. Silva Link: https://patch.msgid.link/AS8P251MB00013E724A77A355668B6CCEC8B42@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ac100.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c index bba7115ff3ad..a2f465438fd0 100644 --- a/drivers/rtc/rtc-ac100.c +++ b/drivers/rtc/rtc-ac100.c @@ -317,6 +317,8 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) if (!chip->clk_data) return -ENOMEM; + chip->clk_data->num = AC100_CLKOUT_NUM; + chip->rtc_32k_clk = clk_hw_register_fixed_rate(chip->dev, AC100_RTC_32K_NAME, NULL, 0, @@ -360,7 +362,6 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) chip->clk_data->hws[i] = &clk->hw; } - chip->clk_data->num = i; ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, chip->clk_data); if (ret) goto err_unregister_rtc_32k; From fcf0b58e976e83adf246b014518e49c662b96f5e Mon Sep 17 00:00:00 2001 From: Aamir Ahmed Date: Sat, 5 Sep 2026 21:41:52 +0100 Subject: [PATCH 171/535] rtc: ac100: Fix clock provider use-after-free on probe failure ac100_rtc_register_clks() registers the RTC-32k clock with clk_hw_register_fixed_rate() and the clock provider with of_clk_add_hw_provider(). Neither is device managed, and both are released only from ac100_rtc_remove(). The driver core does not call remove() when probe() fails: really_probe() reaches probe_failed below the device_remove() call and goes straight to releasing the device's managed resources. ac100_rtc_probe() ends with return devm_rtc_register_device(chip->rtc); so if that fails after the clocks have been registered, the provider is left on the global of_clk_providers list holding chip->clk_data, which was allocated with devm_kzalloc() and is freed while probe() unwinds. A later lookup on this device tree node then reads freed memory in of_clk_hw_onecell_get(). Consumers that do exactly that exist in tree: the wifi power sequence nodes on sun8i-a83t-bananapi-m3 and sun8i-a83t-cubietruck-plus take <&ac100_rtc 1>, and the sun9i-a80 boards route osc32k through <&ac100_rtc 0>. The window is narrow, as devm_rtc_register_device() can only fail with -ENOMEM here, but the provider is left dangling whenever it does. The RTC-32k clock is leaked on the same path. Since clk_core_lookup() searches a global list that is not scoped per device, __clk_register() rejects the duplicate name with -EEXIST, so the leak also makes a later probe of the same device fail. The error path that returns -EINVAL when the ADDA 4M parent clock cannot be found leaks the RTC-32k clock in the same way. No provider has been registered at that point, so that one is a leak rather than a use-after-free. Register both with devm_clk_hw_register_fixed_rate() and devm_of_clk_add_hw_provider() so that they are released whenever the device goes away, on a failed probe as well as on unbind. Devres releases in reverse order of acquisition, so the provider is removed before chip->clk_data is freed, and the clkout children are now unregistered before their parent rather than after it. ac100_rtc_unregister_clks() and the remove callback then have nothing left to do and are removed. Fixes: d00a18a42c14 ("rtc: ac100: Add RTC driver for X-Powers AC100") Reported-by: Sashiko AI Closes: https://lore.kernel.org/linux-rtc/20260905184936.155E11F00A3A@smtp.kernel.org/ Assisted-by: LLM Signed-off-by: Aamir Ahmed Link: https://patch.msgid.link/AS8P251MB0001A489CC45A591ABBB9BDCC8B42@AS8P251MB0001.EURP251.PROD.OUTLOOK.COM Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ac100.c | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c index a2f465438fd0..c579c4bf5578 100644 --- a/drivers/rtc/rtc-ac100.c +++ b/drivers/rtc/rtc-ac100.c @@ -319,10 +319,10 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) chip->clk_data->num = AC100_CLKOUT_NUM; - chip->rtc_32k_clk = clk_hw_register_fixed_rate(chip->dev, - AC100_RTC_32K_NAME, - NULL, 0, - AC100_RTC_32K_RATE); + chip->rtc_32k_clk = devm_clk_hw_register_fixed_rate(chip->dev, + AC100_RTC_32K_NAME, + NULL, 0, + AC100_RTC_32K_RATE); if (IS_ERR(chip->rtc_32k_clk)) { ret = PTR_ERR(chip->rtc_32k_clk); dev_err(chip->dev, "Failed to register RTC-32k clock: %d\n", @@ -356,28 +356,14 @@ static int ac100_rtc_register_clks(struct ac100_rtc_dev *chip) if (ret) { dev_err(chip->dev, "Failed to register clk '%s': %d\n", init.name, ret); - goto err_unregister_rtc_32k; + return ret; } chip->clk_data->hws[i] = &clk->hw; } - ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, chip->clk_data); - if (ret) - goto err_unregister_rtc_32k; - - return 0; - -err_unregister_rtc_32k: - clk_unregister_fixed_rate(chip->rtc_32k_clk->clk); - - return ret; -} - -static void ac100_rtc_unregister_clks(struct ac100_rtc_dev *chip) -{ - of_clk_del_provider(chip->dev->of_node); - clk_unregister_fixed_rate(chip->rtc_32k_clk->clk); + return devm_of_clk_add_hw_provider(chip->dev, of_clk_hw_onecell_get, + chip->clk_data); } /* @@ -615,13 +601,6 @@ static int ac100_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(chip->rtc); } -static void ac100_rtc_remove(struct platform_device *pdev) -{ - struct ac100_rtc_dev *chip = platform_get_drvdata(pdev); - - ac100_rtc_unregister_clks(chip); -} - static const struct of_device_id ac100_rtc_match[] = { { .compatible = "x-powers,ac100-rtc" }, { }, @@ -630,7 +609,6 @@ MODULE_DEVICE_TABLE(of, ac100_rtc_match); static struct platform_driver ac100_rtc_driver = { .probe = ac100_rtc_probe, - .remove = ac100_rtc_remove, .driver = { .name = "ac100-rtc", .of_match_table = of_match_ptr(ac100_rtc_match), From ac41b05d15db3134e839148290d67415ee0bdb58 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Sun, 6 Sep 2026 16:44:09 +0530 Subject: [PATCH 172/535] rtc: mpfs: fix unchecked devm_clk_get() error pointer in probe() devm_clk_get(&pdev->dev, "rtcref")'s return value was passed straight into clk_get_rate() without checking it for an error first, unlike the "rtc" clock a few lines above which is correctly checked with IS_ERR(). clk_get_rate() only guards against a NULL clk, not an error pointer: if (!clk) return 0; ... rate = clk_core_get_rate_recalc(clk->core); so if devm_clk_get() ever returns an error pointer here (for example ERR_PTR(-EPROBE_DEFER), which is the normal, expected outcome if the clkcfg clock-provider this RTC depends on has not registered its clocks yet by the time this driver probes), clk_get_rate() dereferences that error pointer instead of returning 0, crashing instead of letting probe defer. Capture the clock in the existing 'clk' local and check it with IS_ERR() before calling clk_get_rate(), matching the handling already used for the "rtc" clock in this same function. Signed-off-by: Manush Prajwal Fixes: 0b31d703598d ("rtc: Add driver for Microchip PolarFire SoC") Reviewed-by: Conor Dooley Link: https://patch.msgid.link/6a9d4b02.7d74b517.206c4c.75f8@mx.google.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mpfs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-mpfs.c b/drivers/rtc/rtc-mpfs.c index ece6de4a6adb..60596b0ea9d8 100644 --- a/drivers/rtc/rtc-mpfs.c +++ b/drivers/rtc/rtc-mpfs.c @@ -256,8 +256,12 @@ static int mpfs_rtc_probe(struct platform_device *pdev) return ret; } + clk = devm_clk_get(&pdev->dev, "rtcref"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + /* prescaler hardware adds 1 to reg value */ - prescaler = clk_get_rate(devm_clk_get(&pdev->dev, "rtcref")) - 1; + prescaler = clk_get_rate(clk) - 1; if (prescaler > MAX_PRESCALER_COUNT) { dev_dbg(&pdev->dev, "invalid prescaler %lu\n", prescaler); return -EINVAL; From 055ef5ce9f67a6a3a1363fa663007f8196cc0fb8 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 16:03:18 +0800 Subject: [PATCH 173/535] rtc: spear: initialize IRQ state before requesting alarm IRQ devm_request_irq() enables the interrupt before it returns, so the handler may run while probe is still initializing the device. Initialize the MMIO address and spinlock before requesting the alarm IRQ. Fixes: 0942a71e435f ("rtc: add support for spear rtc") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Link: https://patch.msgid.link/20260902080318.3498434-1-runyu.xiao@seu.edu.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-spear.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-spear.c b/drivers/rtc/rtc-spear.c index 959acff8faff..d12bccb3cf5e 100644 --- a/drivers/rtc/rtc-spear.c +++ b/drivers/rtc/rtc-spear.c @@ -363,6 +363,12 @@ static int spear_rtc_probe(struct platform_device *pdev) if (irq < 0) return irq; + config->ioaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(config->ioaddr)) + return PTR_ERR(config->ioaddr); + + spin_lock_init(&config->lock); + status = devm_request_irq(&pdev->dev, irq, spear_rtc_irq, 0, pdev->name, config); if (status) { @@ -371,10 +377,6 @@ static int spear_rtc_probe(struct platform_device *pdev) return status; } - config->ioaddr = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(config->ioaddr)) - return PTR_ERR(config->ioaddr); - config->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(config->clk)) return PTR_ERR(config->clk); @@ -383,7 +385,6 @@ static int spear_rtc_probe(struct platform_device *pdev) if (status < 0) return status; - spin_lock_init(&config->lock); platform_set_drvdata(pdev, config); config->rtc->ops = &spear_rtc_ops; From 37aba16e7943634eb3e5e2e7e850ebb38500d935 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 21 Sep 2026 16:45:36 +0206 Subject: [PATCH 174/535] serial: 8250: Change console_msr_work to IRQ_WORK_LAZY Change console_msr_work (defers MSR handling) to be IRQ_WORK_LAZY, thus not raising an IRQ upon irq_work queueing. The irq_work is then handled on the next kernel tick. This additional delay is acceptable because any pending MSR handling is also performed in the interrupt handler, if mctrl is queried, and during console resume. Using IRQ_WORK_LAZY has the benefit of not needing to raise an IRQ for each line of atomic console printing when modem control is active. On a side note, the Tegra20 and Tegra30 platforms can hang if an irq_work IRQ is raised while entering cpuidle states. This problem was reproducible by performing atomic printing while entering cpuidle. So this change also provides a workaround for these platforms (as long as they are not running tickless). The discussion on this topic is linked below. Link: https://lore.kernel.org/lkml/f3757a75-0ba1-4558-bf57-f19ab7e59a4c@nvidia.com Fixes: d3539347022a ("serial: 8250: Switch to nbcon console, take 2") Signed-off-by: John Ogness Reviewed-by: Petr Mladek Tested-by: Jon Hunter Link: https://patch.msgid.link/20260921143939.170314-1-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 38fa45e74a37..1acb73e45f8a 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -3597,7 +3597,7 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe) up->console_line_ended = true; up->console_msr_work_allow = true; - init_irq_work(&up->console_msr_work, console_msr_handler); + up->console_msr_work = IRQ_WORK_INIT_LAZY(console_msr_handler); if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); From 6bdcdb31318c10e6369cff8e5b32c37397e98091 Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Mon, 21 Sep 2026 10:52:04 +0800 Subject: [PATCH 175/535] wifi: mac80211: validate TX status rate metadata minstrel_ht_tx_status() converts the HT/VHT rate reported in a TX status into a group and rate index and uses those to index the minstrel_ht rate tables. The existing validation only checked that an entry was present and had tries recorded; it did not check that the reported MCS, spatial stream count and bandwidth are representable by the tables, so malformed metadata passed to minstrel_ht_get_stats() or minstrel_ht_ri_get_stats() could be used as an out-of-bounds index. Validate the rate metadata in the common TX status path instead of only in minstrel_ht, since the values are used by more than rate control. The legacy ieee80211_tx_rate array and the rate_info based entries are sanitized in ieee80211_tx_status_ext() and ieee80211_tx_rate_update() before any consumer runs: an entry that does not describe a valid rate for its encoding is dropped. minstrel_ht keeps the checks that depend on its own tables, i.e. the supported number of spatial streams, the MCS group size and the supported bandwidths. This does not take away the driver's responsibility to report sane values, it only prevents a single bad report from corrupting mac80211 state. No WARN_ON() is added: with panic_on_warn a driver bug would otherwise turn into a denial of service. Fixes: a5f69d94d8e2 ("mac80211: Get rid of search loop for rate group index") Fixes: 9208247d74bc ("mac80211: minstrel_ht: add basic support for VHT rates <= 3SS@80MHz") Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Link: https://patch.msgid.link/607e985d71fbbe63ebfc182a37f882f04f980f7f.1789958876.git.xuyuqiabc@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/rc80211_minstrel_ht.c | 59 ++++++++++++++++++++++--- net/mac80211/status.c | 70 ++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c index c26221287515..45e7a7ea7690 100644 --- a/net/mac80211/rc80211_minstrel_ht.c +++ b/net/mac80211/rc80211_minstrel_ht.c @@ -1194,6 +1194,54 @@ minstrel_ht_update_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) mi->sample_time = jiffies; } +/* + * Check whether an HT/VHT rate from a TX status entry maps to an entry + * in the minstrel_ht rate tables. Values that cannot be represented + * there must not be used for indexing mi->groups[] and the MCS groups. + */ +static bool +minstrel_ht_txstat_rate_valid(struct ieee80211_tx_rate *rate) +{ + unsigned int bw; + + if (!(rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS))) + return true; + + if (rate->flags & IEEE80211_TX_RC_MCS) { + /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */ + return rate->idx < MINSTREL_MAX_STREAMS * 8; + } + + /* minstrel_ht has no VHT groups for 160 MHz and wider */ + bw = !!(rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + + 2 * !!(rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH); + if ((rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH) || bw > BW_80) + return false; + + return ieee80211_rate_get_vht_nss(rate) <= MINSTREL_MAX_STREAMS && + ieee80211_rate_get_vht_mcs(rate) < MCS_GROUP_RATES; +} + +static bool +minstrel_ht_ri_txstat_rate_valid(struct rate_info *rate) +{ + if (!(rate->flags & (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS))) + return true; + + if (rate->flags & RATE_INFO_FLAGS_MCS) { + /* minstrel_ht supports up to MINSTREL_MAX_STREAMS streams */ + return rate->mcs < MINSTREL_MAX_STREAMS * 8; + } + + /* minstrel_ht has VHT groups only for 20/40/80 MHz */ + if (rate->bw != RATE_INFO_BW_20 && rate->bw != RATE_INFO_BW_40 && + rate->bw != RATE_INFO_BW_80) + return false; + + return rate->nss <= MINSTREL_MAX_STREAMS && + rate->mcs < MCS_GROUP_RATES; +} + static bool minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, struct ieee80211_tx_rate *rate) @@ -1206,9 +1254,8 @@ minstrel_ht_txstat_valid(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, if (!rate->count) return false; - if (rate->flags & IEEE80211_TX_RC_MCS || - rate->flags & IEEE80211_TX_RC_VHT_MCS) - return true; + if (rate->flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS)) + return minstrel_ht_txstat_rate_valid(rate); for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++) if (rate->idx == mp->cck_rates[i]) @@ -1236,9 +1283,9 @@ minstrel_ht_ri_txstat_valid(struct minstrel_priv *mp, if (!rate_status->try_count) return false; - if (rate_status->rate_idx.flags & RATE_INFO_FLAGS_MCS || - rate_status->rate_idx.flags & RATE_INFO_FLAGS_VHT_MCS) - return true; + if (rate_status->rate_idx.flags & + (RATE_INFO_FLAGS_MCS | RATE_INFO_FLAGS_VHT_MCS)) + return minstrel_ht_ri_txstat_rate_valid(&rate_status->rate_idx); for (i = 0; i < ARRAY_SIZE(mp->cck_rates); i++) { if (rate_status->rate_idx.legacy == diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 3d811f652603..414979e2a1d9 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -1164,6 +1164,73 @@ void ieee80211_tx_status_skb(struct ieee80211_hw *hw, struct sk_buff *skb) } EXPORT_SYMBOL(ieee80211_tx_status_skb); +/* + * Check whether the HT/VHT rate information in a TX status entry is + * valid for its encoding. This is not specific to any rate control + * algorithm; all status consumers rely on the values being sane. + */ +static bool ieee80211_tx_status_rate_info_valid(const struct rate_info *rate) +{ + if (rate->flags & RATE_INFO_FLAGS_MCS) + return rate->mcs < 32; + + if (rate->flags & RATE_INFO_FLAGS_VHT_MCS) + return rate->nss >= 1 && rate->nss <= 8 && rate->mcs <= 11; + + return true; +} + +static bool +ieee80211_tx_status_tx_rate_valid(const struct ieee80211_tx_rate *rate) +{ + if (rate->flags & IEEE80211_TX_RC_MCS) + return rate->idx < 32; + + if (rate->flags & IEEE80211_TX_RC_VHT_MCS) + return ieee80211_rate_get_vht_mcs(rate) <= 11; + + return true; +} + +/* + * Drop TX status rate entries that don't describe a valid rate. The + * status information is used by rate control and by other mac80211 + * code, and a malformed entry must not be able to corrupt state beyond + * the driver that reported it. + */ +static void +ieee80211_tx_status_drop_invalid_rates(struct ieee80211_tx_status *status) +{ + int i; + + for (i = 0; i < status->n_rates; i++) { + struct ieee80211_rate_status *rs = &status->rates[i]; + + if (ieee80211_tx_status_rate_info_valid(&rs->rate_idx)) + continue; + + rs->try_count = 0; + memset(&rs->rate_idx, 0, sizeof(rs->rate_idx)); + } + + if (!status->info) + return; + + for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { + struct ieee80211_tx_rate *rate; + + rate = &status->info->status.rates[i]; + if (rate->idx < 0) + break; + + if (ieee80211_tx_status_tx_rate_valid(rate)) + continue; + + rate->idx = -1; + rate->count = 0; + } +} + void ieee80211_tx_status_ext(struct ieee80211_hw *hw, struct ieee80211_tx_status *status) { @@ -1176,6 +1243,8 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw, bool acked, noack_success, ack_signal_valid; u16 tx_time_est; + ieee80211_tx_status_drop_invalid_rates(status); + if (pubsta) { sta = container_of(pubsta, struct sta_info, sta); @@ -1300,6 +1369,7 @@ void ieee80211_tx_rate_update(struct ieee80211_hw *hw, .sta = pubsta, }; + ieee80211_tx_status_drop_invalid_rates(&status); rate_control_tx_status(local, &status); if (ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL)) From e588e83e5c7a0f6d7a77dbbb58927c8c8cfd598e Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 10 Sep 2026 14:54:02 +0800 Subject: [PATCH 176/535] wifi: mac80211: shut down RX BA session timer on teardown Tearing down an RX BA session can leave the session timer queued on a freed tid_rx. Later timer-wheel processing and the timer callback itself then run against freed memory. An RX BAR handler can dereference tid_rx under RCU before teardown unpublishes it and stay in its read-side critical section while teardown deletes the session timer. The stale handler can then rearm the timer after timer_delete_sync() returns and before the RCU callback frees tid_rx. Use timer_shutdown_sync() so rearm attempts become ineffective once teardown starts. Keep reorder_timer unchanged: teardown marks the session removed under reorder_lock and its release path checks that state before rearming. session_timer has no equivalent guard because ieee80211_rx_h_ctrl() rearms it before taking reorder_lock. Fixes: a87f736d942c ("mac80211: use RCU for RX aggregation") Cc: stable@kernel.org # 5.15+ Assisted-by: LLM sparse kasan Signed-off-by: Zhao Li Signed-off-by: Johannes Berg --- net/mac80211/agg-rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/agg-rx.c b/net/mac80211/agg-rx.c index 9629e00069a1..01d6703767c6 100644 --- a/net/mac80211/agg-rx.c +++ b/net/mac80211/agg-rx.c @@ -102,7 +102,7 @@ void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, if (!tid_rx) return; - timer_delete_sync(&tid_rx->session_timer); + timer_shutdown_sync(&tid_rx->session_timer); /* make sure ieee80211_sta_reorder_release() doesn't re-arm the timer */ spin_lock_bh(&tid_rx->reorder_lock); From a10a2f80476d166fd81c361ec0319493671b694e Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Thu, 10 Sep 2026 14:52:51 +0800 Subject: [PATCH 177/535] wifi: mac80211: keep fallback association elements alive Associating to a non-transmitted MBSSID profile reads freed memory while determining the operating channel and applying HT capabilities. When an association response omits WMM, HT, or VHT information, ieee80211_assoc_config_link() parses the cached BSS IEs and copies the missing element pointers out of that fallback parse. For a non-transmitted MBSSID profile those pointers refer to merged-profile storage owned by the parse object, and the fallback block frees that object at its end. The copied pointers are used afterwards to determine the operating channel, configure bandwidth, and apply HT capabilities. Keep the fallback parse result alive until association setup finishes. Fixes: 5023b14cf4df ("mac80211: support profile split between elements") Cc: stable@kernel.org Assisted-by: LLM sparse kasan [reorder variable declaration] Signed-off-by: Zhao Li Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f51167f0fc46..3c31c16860ab 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -5877,6 +5877,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ; const struct cfg80211_bss_ies *bss_ies = NULL; + struct ieee802_11_elems *bss_elems = NULL; struct ieee80211_supported_band *sband; struct ieee802_11_elems *elems; u16 capab_info; @@ -6007,7 +6008,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, (is_5ghz && link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_VHT && (!elems->vht_cap_elem || !elems->vht_operation)))) { const struct cfg80211_bss_ies *ies; - struct ieee802_11_elems *bss_elems; rcu_read_lock(); ies = rcu_dereference(cbss->ies); @@ -6069,7 +6069,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, "AP bug: VHT operation missing from AssocResp\n"); } } - kfree(bss_elems); } /* @@ -6342,6 +6341,7 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, ret = true; out: kfree(elems); + kfree(bss_elems); kfree(bss_ies); return ret; } From cc45f313d85533d647ce619326302aad6f797c14 Mon Sep 17 00:00:00 2001 From: Zhao Li Date: Mon, 14 Sep 2026 23:28:25 +0800 Subject: [PATCH 178/535] wifi: cfg80211: preserve hidden-group beacon IE ownership A hidden-group probe-response entry borrows its beacon IEs from the group's beacon entry. cfg80211_update_assoc_bss_entry() can pass such an entry to cfg80211_update_known_bss() when merging scan entries after a channel switch. Copying the borrowed beacon_ies into the associated entry gives the same allocation two owners. Releasing the group's beacon entry can then leave the associated entry pointing to freed IEs, which are freed again when that entry is released. Skip beacon IE replacement when the source entry belongs to a hidden group. Preserve probe-response processing, the subsequent metadata updates, and the existing rejection path for conflicting hidden beacon updates. Fixes: 0afd425b1b64 ("cfg80211: fix duplicated scan entries after channel switch") Reported-by: Yilin Zhang Closes: https://lore.kernel.org/linux-wireless/20260901080427.1983015-1-yilinzhang@moonshot.ai/ Assisted-by: Codex:gpt-6-astra Signed-off-by: Zhao Li Signed-off-by: Johannes Berg --- net/wireless/scan.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/net/wireless/scan.c b/net/wireless/scan.c index caa9c6495f20..d35c826c4b1a 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -1992,20 +1992,22 @@ cfg80211_update_known_bss(struct cfg80211_registered_device *rdev, return false; } - old = rcu_access_pointer(known->pub.beacon_ies); + if (!new->pub.hidden_beacon_bss) { + old = rcu_access_pointer(known->pub.beacon_ies); - rcu_assign_pointer(known->pub.beacon_ies, new->pub.beacon_ies); + rcu_assign_pointer(known->pub.beacon_ies, new->pub.beacon_ies); - /* Override IEs if they were from a beacon before */ - if (old == rcu_access_pointer(known->pub.ies)) - rcu_assign_pointer(known->pub.ies, new->pub.beacon_ies); + /* Override IEs if they were from a beacon before */ + if (old == rcu_access_pointer(known->pub.ies)) + rcu_assign_pointer(known->pub.ies, new->pub.beacon_ies); - cfg80211_update_hidden_bsses(known, - rcu_access_pointer(new->pub.beacon_ies), - old); + cfg80211_update_hidden_bsses(known, + rcu_access_pointer(new->pub.beacon_ies), + old); - if (old) - kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); + if (old) + kfree_rcu((struct cfg80211_bss_ies *)old, rcu_head); + } } known->pub.beacon_interval = new->pub.beacon_interval; From 7f93ca31f7fd9b42e7d692f59514436d448ac908 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 16 Sep 2026 11:48:32 +0200 Subject: [PATCH 179/535] wifi: mac80211: prevent AP VLAN tx from other interfaces Since the station lookup was factored out of ieee80211_build_hdr(), ieee80211_lookup_ra_sta() uses sta_info_get_bss() for all AP and AP VLAN frames, not only for control port frames. A unicast frame sent on one interface thus matches a station of a different VLAN of the same BSS and is transmitted to it. Only match stations of the interface the frame is sent on, except for control port frames, since those may be sent for VLAN stations directly on the BSS interface. Fixes: 97ffe75791b3 ("mac80211: factor out station lookup from ieee80211_build_hdr()") Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260916094833.164219-1-nbd@nbd.name Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 2 +- net/mac80211/tx.c | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d05f59467399..9374c2942489 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2475,7 +2475,7 @@ static inline bool ieee80211_require_encrypted_assoc(__le16 fc, /* sta_out needs to be checked for ERR_PTR() before using */ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct sta_info **sta_out); + struct sta_info **sta_out, bool bss); static inline void ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 814399989b5e..c8217f6d146e 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2532,7 +2532,7 @@ static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb) int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct sta_info **sta_out) + struct sta_info **sta_out, bool bss) { struct sta_info *sta; @@ -2553,7 +2553,10 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, *sta_out = ERR_PTR(-ENOENT); return 0; } - sta = sta_info_get_bss(sdata, skb->data); + if (bss) + sta = sta_info_get_bss(sdata, skb->data); + else + sta = sta_info_get(sdata, skb->data); break; #ifdef CONFIG_MAC80211_MESH case NL80211_IFTYPE_MESH_POINT: @@ -4419,7 +4422,8 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags)) goto out; - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, + skb->protocol == sdata->control_port_protocol)) goto out_free; if (IS_ERR(sta)) @@ -4848,7 +4852,10 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, rcu_read_lock(); - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (unlikely(sdata->control_port_protocol == ehdr->h_proto)) + goto skip_offload; + + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) { kfree_skb(skb); goto out; } @@ -4869,8 +4876,7 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, link = &sdata->deflink; key = rcu_dereference(link->default_multicast_key); } else if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded || - !test_sta_flag(sta, WLAN_STA_AUTHORIZED) || - sdata->control_port_protocol == ehdr->h_proto)) { + !test_sta_flag(sta, WLAN_STA_AUTHORIZED))) { goto skip_offload; } else { key = rcu_dereference(sta->ptk[sta->ptk_idx]); @@ -4931,7 +4937,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, rcu_read_lock(); - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, false)) { kfree_skb(skb); skb = ERR_PTR(-EINVAL); goto out; @@ -5002,7 +5008,7 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local, } result = ieee80211_tx(sdata, NULL, skb, true); } else if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) { - if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) { + if (ieee80211_lookup_ra_sta(sdata, skb, &sta, true)) { dev_kfree_skb(skb); return true; } @@ -6639,7 +6645,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, * AF_PACKET */ rcu_read_lock(); - err = ieee80211_lookup_ra_sta(sdata, skb, &sta); + err = ieee80211_lookup_ra_sta(sdata, skb, &sta, true); if (err) { dev_kfree_skb(skb); rcu_read_unlock(); From d30fba93ebfc11e4604a7fcc73fdf28684191dde Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:44 +0200 Subject: [PATCH 180/535] ASoC: codecs: rt286: Revert delay value for jack setup Fat fingered the change when reorganizing the jack-detect initialization. Equivalent changes for rt274 and rt298: Commit a43b4394bb35 ("ASoC: codecs: rt274: Always init jack_detect_work") Commit 1eb73102da28 ("ASoC: codecs: rt298: Reorganize jack detect handling") carry no update to the delay value. >From user perspective, no difference has been observed between 1250 and 50 values on Intel's SkyLake, KabyLake and AmberLake configurations. Fixes: 3082afe097cc ("ASoC: codecs: rt286: Reorganize jack detect handling") Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-1-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt286.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 4217467904d6..bcd9f5abcda8 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -953,7 +953,7 @@ static int rt286_probe(struct snd_soc_component *component) if (rt286->i2c->irq) schedule_delayed_work(&rt286->jack_detect_work, - msecs_to_jiffies(50)); + msecs_to_jiffies(1250)); return 0; } From 6eda8938a790f65bd24fcfaae7b40ad4613fb7a2 Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:45 +0200 Subject: [PATCH 181/535] ASoC: codecs: rt274: Fix format setting for 44100 rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per specification, Sample Base Rate (bit 14) shall be set to 1 for 44.1 kHz. Fixes: c7e79b2b2d2d ("ASoC: rt274: add rt274 codec driver") Signed-off-by: Amadeusz Sławiński Signed-off-by: Cezary Rojewski Link: https://patch.msgid.link/20260918111745.3589393-2-cezary.rojewski@intel.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt274.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index f8c370106504..3c47f1ae8db9 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -619,9 +619,11 @@ static int rt274_hw_params(struct snd_pcm_substream *substream, unsigned int val = 0; int d_len_code = 0, c_len_code = 0; + /* bit 14 Sample Rate Base, 0: 48kHz 1: 44.1kHz */ switch (params_rate(params)) { - /* bit 14 0:48K 1:44.1K */ case 44100: + val |= BIT(14); + break; case 48000: break; default: From 5d35f9be9b54a1b8f7df85ec5261557e43a999bb Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 22 Sep 2026 20:30:29 +0800 Subject: [PATCH 182/535] ASoC: amd: acp: Add DMI quirk for Lenovo Yoga Pro 7 14ASP9 The Lenovo Yoga Pro 7 14ASP9 (83HN) has no digital microphone on the ACP PDM interface; all internal mics are wired to the Realtek ALC287 codec. Add 83HN to acp70_acpi_flag_override_table to prevent the legacy ACP driver from binding and exposing a non-functional PDM capture device, consistent with the sibling 83W5/83V9 entries. Reported-by: Mikail Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221886 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260922123029.534152-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 2d50be8190c1..4d8f86aa3d16 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -51,6 +51,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83V9"), }, }, + { + /* Lenovo Yoga Pro 7 14ASP9 (Strix Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83HN"), + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC"), From 3fabd8ec206bd48a45ef77aed170d25b322ad9a2 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 23 Sep 2026 10:06:58 +0200 Subject: [PATCH 183/535] Revert "HID: logitech: add Bolt receiver support for Logitech HID++ devices" This reverts commit 022eb347ff3a48281e7e69c3addcb11bf24afa53. Introducing Bolt as a DJ receiver created a lot of troubles for users with their scroll wheel going crazy. This is because we have 2 issues: - Bolt doesn't say which device sends which event, so having 2 mice connected on the same receiver means one will be normal and the other deadly slow or deadly fast - userspace also tinker with those settings, meaning that users are not happy with the new default settings. The end result is that the feature is not ready for a final kernel, and the sensible thing to do is to revert the patch and revisit it in a later kernel if we can work around the 2 issues I've mentioned above. Link: https://lore.kernel.org/all/DL68726HJQCD.7PCDT9IGHUB1@rcpassos.me/ Link: https://lore.kernel.org/all/20260920094508.39682-1-roman.stingler@gmail.com/ Link: https://lore.kernel.org/all/20260904034843.1340846-1-rafael@rcpassos.me Link: https://lore.kernel.org/all/20260922-feature-bolt-fix-v1-0-63b0fa8da0d3@gmail.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 48 +++--------------------------- drivers/hid/hid-logitech-hidpp.c | 51 +++----------------------------- 2 files changed, 8 insertions(+), 91 deletions(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 1d619d2345e1..390d106d5dc1 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -121,7 +121,6 @@ enum recvr_type { recvr_type_27mhz, recvr_type_bluetooth, recvr_type_dinovo, - recvr_type_bolt, }; struct dj_report { @@ -1157,10 +1156,6 @@ static void logi_hidpp_recv_queue_notif(struct hid_device *hdev, logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); workitem.reports_supported |= STD_KEYBOARD; break; - case 0x10: - device_type = "Bolt"; - logi_hidpp_dev_conn_notif_equad(hdev, hidpp_report, &workitem); - break; } /* custom receiver device (eg. powerplay) */ @@ -1750,24 +1745,6 @@ static int logi_dj_hidpp_event(struct hid_device *hdev, dj_dev = djrcv_dev->paired_dj_devices[device_index]; - /* - * Bolt receivers send explicit unpair notifications as HID++ events; - * queue device removal when we receive one. - */ - if (djrcv_dev->type == recvr_type_bolt && - hidpp_report->report_id == REPORT_ID_HIDPP_SHORT && - hidpp_report->sub_id == REPORT_TYPE_NOTIF_DEVICE_UNPAIRED) { - struct dj_workitem workitem = { - .device_index = device_index, - .type = WORKITEM_TYPE_UNPAIRED, - }; - - kfifo_in(&djrcv_dev->notif_fifo, &workitem, sizeof(workitem)); - schedule_work(&djrcv_dev->work); - spin_unlock_irqrestore(&djrcv_dev->lock, flags); - return false; - } - /* * With 27 MHz receivers, we do not get an explicit unpair event, * remove the old device if the user has paired a *different* device. @@ -1907,9 +1884,6 @@ static int logi_dj_probe(struct hid_device *hdev, * treat these as logitech-dj interfaces then this causes input events * reported through this extra interface to not be reported correctly. * To avoid this, we treat these as generic-hid devices. - * - * Bolt receivers only use LOGITECH_DJ_INTERFACE_NUMBER for receiver - * reporting. Treat all other Bolt interfaces as generic-hid devices. */ switch (id->driver_data) { case recvr_type_dj: no_dj_interfaces = 3; break; @@ -1923,20 +1897,10 @@ static int logi_dj_probe(struct hid_device *hdev, } if (hid_is_usb(hdev)) { intf = to_usb_interface(hdev->dev.parent); - if (intf) { - bool generic_hid_interface; - - if (id->driver_data == recvr_type_bolt) - generic_hid_interface = - intf->altsetting->desc.bInterfaceNumber != - LOGITECH_DJ_INTERFACE_NUMBER; - else - generic_hid_interface = - intf->altsetting->desc.bInterfaceNumber >= no_dj_interfaces; - if (generic_hid_interface) { - hdev->quirks |= HID_QUIRK_INPUT_PER_APP; - return hid_hw_start(hdev, HID_CONNECT_DEFAULT); - } + if (intf && intf->altsetting->desc.bInterfaceNumber >= + no_dj_interfaces) { + hdev->quirks |= HID_QUIRK_INPUT_PER_APP; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); } } @@ -2139,10 +2103,6 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3), .driver_data = recvr_type_gaming_hidpp_ls_1_3}, - { /* Logitech Bolt receiver (0xc548) */ - HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, - USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), - .driver_data = recvr_type_bolt}, { /* Logitech lightspeed receiver (0xc54d) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4), diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 493763a12518..9646380febac 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -988,8 +988,7 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) } /* the device might not be connected */ - if (ret == HIDPP_ERROR_CONNECT_FAIL || - ret == HIDPP_ERROR_RESOURCE_ERROR || + if (ret == HIDPP_ERROR_RESOURCE_ERROR || ret == HIDPP_ERROR_UNKNOWN_DEVICE) return -EIO; @@ -4405,50 +4404,8 @@ static int hidpp_initialize_battery(struct hidpp_device *hidpp) return ret; } -static bool hidpp_is_bolt_child(struct hid_device *hdev) -{ - struct device *parent = hdev->dev.parent; - struct hid_device *receiver_hdev; - - if (!parent) - return false; - - receiver_hdev = to_hid_device(parent); - return receiver_hdev->vendor == USB_VENDOR_ID_LOGITECH && - receiver_hdev->product == USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER; -} - -static int hidpp_bolt_init(struct hidpp_device *hidpp) -{ - struct hid_device *hdev = hidpp->hid_dev; - char *name; - int ret; - - ret = hidpp_serial_init(hidpp); - if (ret) - return ret; - - name = hidpp_get_device_name(hidpp); - if (!name) - return -EIO; - - snprintf(hdev->name, sizeof(hdev->name), "%s", name); - dbg_hid("HID++ Bolt: Got name: %s\n", name); - - kfree(name); - return 0; -} - -static int hidpp_receiver_init(struct hidpp_device *hidpp) -{ - if (hidpp_is_bolt_child(hidpp->hid_dev)) - return hidpp_bolt_init(hidpp); - - return hidpp_unifying_init(hidpp); -} - /* Get name + serial for USB and Bluetooth HID++ devices */ -static void hidpp_non_receiver_init(struct hidpp_device *hidpp) +static void hidpp_non_unifying_init(struct hidpp_device *hidpp) { struct hid_device *hdev = hidpp->hid_dev; char *name; @@ -4800,9 +4757,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Get name + serial, store in hdev->name + hdev->uniq */ if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) - hidpp_receiver_init(hidpp); + hidpp_unifying_init(hidpp); else - hidpp_non_receiver_init(hidpp); + hidpp_non_unifying_init(hidpp); if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) connect_mask &= ~HID_CONNECT_HIDINPUT; From 11ea5a63d30ad6d305b1c22301cd99ff45a98c8d Mon Sep 17 00:00:00 2001 From: Daniel Starke Date: Wed, 26 Aug 2026 08:30:26 +0200 Subject: [PATCH 184/535] tty: n_gsm: fix missing modem controls after DLCI open in convergence layer type 2 The current implementation only updates the virtual V.24 control signals after DLCI open in basic option mode. In advanced option mode with convergence layer type 2 an empty data frame needs to be transmitted to inform the peer about the initial virtual V.24 control signals. Not doing so has two unwanted side effects: 1. hardware flow control status is unclear (initial CTS line is unknown) 2. DLCI user presence is not transmitted (initial DTR line is unknown) Applications waiting on these lines can not start after a DLCI has been opened until the first data frame has been transmitted. Fix this by using gsm_modem_update() thoroughly which handles basic and advanced option mode correctly. Add a flag in gsm_modem_update() and the delegated gsm_modem_upd_via_msc() to control whether waiting for the MSC command response is possible. Remove obsolete gsm_modem_send_initial_msc(). Fixes: 3cf0b3c243e5 ("tty: n_gsm: Don't block input queue by waiting MSC") Cc: stable Signed-off-by: Daniel Starke Link: https://patch.msgid.link/20260826063026.2472-1-daniel.starke@siemens.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_gsm.c | 47 ++++++++++++++------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index c13e050de83b..e55779ff0af2 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -454,14 +454,13 @@ static const u8 gsm_fcs8[256] = { static void gsm_dlci_close(struct gsm_dlci *dlci); static int gsmld_output(struct gsm_mux *gsm, u8 *data, int len); -static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk); +static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk, bool wait); static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, u8 ctrl); static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg); static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr); static void gsmld_write_trigger(struct gsm_mux *gsm); static void gsmld_write_task(struct work_struct *work); -static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci); /** * gsm_fcs_add - update FCS @@ -2174,7 +2173,7 @@ static void gsm_dlci_open(struct gsm_dlci *dlci) pr_debug("DLCI %d goes open.\n", dlci->addr); /* Send current modem state */ if (dlci->addr) { - gsm_modem_send_initial_msc(dlci); + gsm_modem_update(dlci, 0, false); } else { /* Start keep-alive control */ gsm->ka_num = 0; @@ -4136,9 +4135,10 @@ static void gsm_modem_upd_via_data(struct gsm_dlci *dlci, u8 brk) * gsm_modem_upd_via_msc - send modem bits via control frame * @dlci: channel * @brk: break signal + * @wait: wait for the MSC command */ -static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) +static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk, bool wait) { u8 modembits[3]; struct gsm_control *ctrl; @@ -4155,41 +4155,22 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk) modembits[2] = (brk << 4) | 2 | EA; /* Length, Break, EA */ len++; } + if (!wait) + return gsm_control_command(dlci->gsm, CMD_MSC, modembits, len); ctrl = gsm_control_send(dlci->gsm, CMD_MSC, modembits, len); if (ctrl == NULL) return -ENOMEM; return gsm_control_wait(dlci->gsm, ctrl); } -/** - * gsm_modem_send_initial_msc - Send initial modem status message - * - * @dlci: channel - * - * Send an initial MSC message after DLCI open to set the initial - * modem status lines. This is only done for basic mode. - * Does not wait for a response as we cannot block the input queue - * processing. - */ -static int gsm_modem_send_initial_msc(struct gsm_dlci *dlci) -{ - u8 modembits[2]; - - if (dlci->adaption != 1 || dlci->gsm->encoding != GSM_BASIC_OPT) - return 0; - - modembits[0] = (dlci->addr << 2) | 2 | EA; /* DLCI, Valid, EA */ - modembits[1] = (gsm_encode_modem(dlci) << 1) | EA; - return gsm_control_command(dlci->gsm, CMD_MSC, (const u8 *)&modembits, 2); -} - /** * gsm_modem_update - send modem status line state * @dlci: channel * @brk: break signal + * @wait: wait for the MSC command */ -static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk) +static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk, bool wait) { if (dlci->gsm->dead) return -EL2HLT; @@ -4199,7 +4180,7 @@ static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk) return 0; } else if (dlci->gsm->encoding == GSM_BASIC_OPT) { /* Send as MSC control message. */ - return gsm_modem_upd_via_msc(dlci, brk); + return gsm_modem_upd_via_msc(dlci, brk, wait); } /* Modem status lines are not supported. */ @@ -4265,7 +4246,7 @@ static void gsm_dtr_rts(struct tty_port *port, bool active) modem_tx &= ~(TIOCM_DTR | TIOCM_RTS); if (modem_tx != dlci->modem_tx) { dlci->modem_tx = modem_tx; - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } } @@ -4471,7 +4452,7 @@ static int gsmtty_tiocmset(struct tty_struct *tty, if (modem_tx != dlci->modem_tx) { dlci->modem_tx = modem_tx; - return gsm_modem_update(dlci, 0); + return gsm_modem_update(dlci, 0, true); } return 0; } @@ -4553,7 +4534,7 @@ static void gsmtty_throttle(struct tty_struct *tty) dlci->modem_tx &= ~TIOCM_RTS; dlci->throttled = true; /* Send an MSC with RTS cleared */ - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } static void gsmtty_unthrottle(struct tty_struct *tty) @@ -4565,7 +4546,7 @@ static void gsmtty_unthrottle(struct tty_struct *tty) dlci->modem_tx |= TIOCM_RTS; dlci->throttled = false; /* Send an MSC with RTS set */ - gsm_modem_update(dlci, 0); + gsm_modem_update(dlci, 0, true); } static int gsmtty_break_ctl(struct tty_struct *tty, int state) @@ -4583,7 +4564,7 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state) if (encode > 0x0F) encode = 0x0F; /* Best effort */ } - return gsm_modem_update(dlci, encode); + return gsm_modem_update(dlci, encode, true); } static void gsmtty_cleanup(struct tty_struct *tty) From 5b114bfc1dfd88f9d50822d532f7f1fce0f7388a Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Mon, 3 Aug 2026 22:45:56 +0800 Subject: [PATCH 185/535] tty: vt: fix memory leak in vc_allocate() If the screen buffer allocation fails in vc_allocate(), the error handling path jumps to `err_free`. However, this path fails to release the unicode screen map attached to `vc->uni_pagedict_loc`. During the early stages of vc_allocate(), the unicode screen map is either newly allocated via con_set_default_unimap() or shares the default unicode map from a previously initialized console (which increments its refcount). If the subsequent kzalloc() for the screen buffer fails, the err_free path frees the vc structure but leaves the attached uni_pagedict with an elevated refcount. This results in an unreferenced object memory leak, as the reference to the dictionary is lost and its refcount can never reach zero. This issue was discovered by DevGen (an automated virtual device modeling fuzzer based on Syzkaller). During fuzzing with kernel fault injection (failslab) enabled, the fuzzer forcefully failed the kzalloc() for the screen buffer, exposing this error-handling path leak. Fix this by checking *vc->uni_pagedict_loc and calling con_free_unimap(vc) in the err_free path before kfree(vc). This safely decrements the refcount and releases the dictionary memory if this was the last reference. The explicit check is added to maintain consistency with other callers. Fixes: 34902b7f2754 ("tty: vt, get rid of weird source code flow") Cc: stable Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Link: https://patch.msgid.link/20260803144556.163856-1-25181214217@stu.xidian.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 57edf37495a8..51f8fd103dcf 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -1134,6 +1134,8 @@ int vc_allocate(unsigned int currcons) /* return 0 on success */ return 0; err_free: visual_deinit(vc); + if (*vc->uni_pagedict_loc) + con_free_unimap(vc); kfree(vc); vc_cons[currcons].d = NULL; return err; From 845a2737e0f5ffe5ca8d38de556012b0c9472cdd Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 3 Sep 2026 18:37:36 +0200 Subject: [PATCH 186/535] tty: abort break signalling on hangup The break ioctls can race with hangup and end up calling into a tty driver for a device that is already gone or powered down. Drivers must handle this race, but TCSBRK and TCSBRKP should still be aborted to avoid calling back into the driver after a user-controlled timeout (possibly even after the tty has been reopened). Note that drivers should disable any break state on shutdown so just return on hangup (calling break_ctl() again is racy and will most likely fail for drivers handling the race). Fixes: f34d7a5b7010 ("tty: The big operations rework") Cc: stable Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260903163736.1499280-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 36 +++++++++++++++++++++++++++--------- include/linux/tty.h | 1 + 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 48569035da56..1c30faae9ec1 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -620,6 +620,8 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session) tty_ldisc_hangup(tty, cons_filp != NULL); + wake_up_interruptible(&tty->break_wait); + spin_lock_irq(&tty->ctrl.lock); clear_bit(TTY_THROTTLED, &tty->flags); clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); @@ -2431,6 +2433,7 @@ static int tiocgetd(struct tty_struct *tty, int __user *p) * send_break - performed time break * @tty: device to break on * @duration: timeout in mS + * @file: file object * * Perform a timed break on hardware that lacks its own driver level timed * break functionality. @@ -2438,8 +2441,9 @@ static int tiocgetd(struct tty_struct *tty, int __user *p) * Locking: * @tty->atomic_write_lock serializes */ -static int send_break(struct tty_struct *tty, unsigned int duration) +static int send_break(struct file *file, struct tty_struct *tty, unsigned int duration) { + long timeout; int retval; if (tty->ops->break_ctl == NULL) @@ -2453,13 +2457,26 @@ static int send_break(struct tty_struct *tty, unsigned int duration) return -EINTR; retval = tty->ops->break_ctl(tty, -1); - if (!retval) { - msleep_interruptible(duration); - retval = tty->ops->break_ctl(tty, 0); - } else if (retval == -EOPNOTSUPP) { - /* some drivers can tell only dynamically */ - retval = 0; + if (retval) { + if (retval == -EOPNOTSUPP) { + /* some drivers can tell only dynamically */ + retval = 0; + } + goto out_unlock; } + + timeout = msecs_to_jiffies(duration); + timeout = wait_event_interruptible_timeout(tty->break_wait, + tty_hung_up_p(file), + timeout); + /* return early on hangup only */ + if (timeout > 0) { + retval = -EIO; + goto out_unlock; + } + + retval = tty->ops->break_ctl(tty, 0); +out_unlock: tty_write_unlock(tty); if (signal_pending(current)) @@ -2727,10 +2744,10 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) * This is used by the tcdrain() termios function. */ if (!arg) - return send_break(tty, 250); + return send_break(file, tty, 250); return 0; case TCSBRKP: /* support for POSIX tcsendbreak() */ - return send_break(tty, arg ? arg*100 : 250); + return send_break(file, tty, arg ? arg * 100 : 250); case TIOCMGET: return tty_tiocmget(tty, p); @@ -3090,6 +3107,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); + init_waitqueue_head(&tty->break_wait); INIT_WORK(&tty->hangup_work, do_tty_hangup); mutex_init(&tty->atomic_write_lock); spin_lock_init(&tty->ctrl.lock); diff --git a/include/linux/tty.h b/include/linux/tty.h index 0a46e4054dec..ad4e4a835f90 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -230,6 +230,7 @@ struct tty_struct { struct fasync_struct *fasync; wait_queue_head_t write_wait; wait_queue_head_t read_wait; + wait_queue_head_t break_wait; struct work_struct hangup_work; void *disc_data; void *driver_data; From 56964f6a31238fb41d5830bb42520c5b10ff7d8f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 8 Sep 2026 10:40:00 +0200 Subject: [PATCH 187/535] tty: tty_port: restore TTY_IO_ERROR on activation failure Make sure to restore the TTY_IO_ERROR flag if activation fails to prevent I/O attempts on a port that is gone or powered down. This specifically avoids issues like kernel panic due to unclocked accesses or NULL-pointer dereferences when accessing powered down or deregistered ports when a TTY ioctl races with hangup and open. Note that the flag is set again in tty_port_close() but not restoring it on activation failure leaves a window where drivers fail to handle the ioctl-hangup race. Fixes: a9a37ec33a1b ("tty: tty_port: Move the IO_ERROR clear") Cc: stable # 2.6.33 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260908084000.218528-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_port.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index 54359310e293..ae33987207b6 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -767,8 +767,10 @@ int tty_port_open(struct tty_port *port, struct tty_struct *tty, clear_bit(TTY_IO_ERROR, &tty->flags); if (port->ops->activate) { int retval = port->ops->activate(port, tty); - if (retval) + if (retval) { + set_bit(TTY_IO_ERROR, &tty->flags); return retval; + } } tty_port_set_initialized(port, true); } From b48820de8c1a596c5b99775524dfce73c6576c5d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 10 Sep 2026 14:51:14 +0200 Subject: [PATCH 188/535] tty: port: fix tty_port_tty_vhangup() race Drivers rely on tty_port_tty_vhangup() to synchronously hang up their ports before tearing down resources as part of deregistration (e.g. on physical disconnect). If tty_port_tty_vhangup() races with another hangup it may however return before the port has been shut down. This is in turn can lead to use-after-free and NULL-pointer dereferences in racing ioctls (including a synchronous hangup) and in processing of racing asynchronous hangups (carrier loss). Add the missing serialisation to tty_port_tty_vhangup() and tty_port_hangup() so that the former does not return until the port has been shut down also when there is a racing hangup. Note that serial core does not use tty_port_hangup(), but it already holds the port mutex when clearing port->tty and shutting down the port in uart_hangup(). Fixes: 7ca0ff9ab321 ("tty: Add a full port_close function") Cc: stable@kernel.org # 2.6.32: 2b5eac0f8c6e: tty: introduce and use tty_port_tty_vhangup() helper Cc: stable@kernel.org # 2.6.32 Reported-by: syzbot+5fabc1ae99ff40690d84@syzkaller.appspotmail.com Link: https://lore.kernel.org/6a944641.4d659fcc.734b4.003c.GAE@google.com Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260910125114.640880-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_port.c | 87 ++++++++++++++++++++++++++-------------- include/linux/tty_port.h | 22 +--------- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index ae33987207b6..b22ff9eb7778 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -340,19 +340,9 @@ void tty_port_tty_set(struct tty_port *port, struct tty_struct *tty) } EXPORT_SYMBOL(tty_port_tty_set); -/** - * tty_port_shutdown - internal helper to shutdown the device - * @port: tty port to be shut down - * @tty: the associated tty - * - * It is used by tty_port_hangup() and tty_port_close(). Its task is to - * shutdown the device if it was initialized (note consoles remain - * functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes - * @port->ops->shutdown(). - */ -static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) +static void tty_port_shutdown_locked(struct tty_port *port, struct tty_struct *tty) { - guard(mutex)(&port->mutex); + lockdep_assert_held(&port->mutex); if (port->console) return; @@ -372,6 +362,23 @@ static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) port->ops->shutdown(port); } +/** + * tty_port_shutdown - internal helper to shutdown the device + * @port: tty port to be shut down + * @tty: the associated tty + * + * It is used by tty_port_hangup() and tty_port_close(). Its task is to + * shutdown the device if it was initialized (note consoles remain + * functioning). It lowers DTR/RTS (if @tty has HUPCL set) and invokes + * @port->ops->shutdown(). + */ +static void tty_port_shutdown(struct tty_port *port, struct tty_struct *tty) +{ + guard(mutex)(&port->mutex); + + tty_port_shutdown_locked(port, tty); +} + /** * tty_port_hangup - hangup helper * @port: tty port @@ -385,36 +392,58 @@ void tty_port_hangup(struct tty_port *port) { struct tty_struct *tty; - scoped_guard(spinlock_irqsave, &port->lock) { - port->count = 0; - tty = port->tty; - if (tty) - set_bit(TTY_IO_ERROR, &tty->flags); - port->tty = NULL; - } + scoped_guard(mutex, &port->mutex) { + scoped_guard(spinlock_irqsave, &port->lock) { + port->count = 0; + tty = port->tty; + if (tty) + set_bit(TTY_IO_ERROR, &tty->flags); + port->tty = NULL; + } - tty_port_set_active(port, false); - tty_port_shutdown(port, tty); + tty_port_set_active(port, false); + tty_port_shutdown_locked(port, tty); + } tty_kref_put(tty); wake_up_interruptible(&port->open_wait); wake_up_interruptible(&port->delta_msr_wait); } EXPORT_SYMBOL(tty_port_hangup); -void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async) +/** + * tty_port_tty_hangup - helper to hang up a tty asynchronously + * @port: tty port + * @check_clocal: hang only ttys with %CLOCAL unset? + */ +void tty_port_tty_hangup(struct tty_port *port, bool check_clocal) { scoped_guard(tty_port_tty, port) { struct tty_struct *tty = scoped_tty(); - if (!check_clocal || !C_CLOCAL(tty)) { - if (async) - tty_hangup(tty); - else - tty_vhangup(tty); - } + if (!check_clocal || !C_CLOCAL(tty)) + tty_hangup(tty); } } -EXPORT_SYMBOL_GPL(__tty_port_tty_hangup); +EXPORT_SYMBOL_GPL(tty_port_tty_hangup); + +/** + * tty_port_tty_vhangup - helper to hang up a tty synchronously + * @port: tty port + */ +void tty_port_tty_vhangup(struct tty_port *port) +{ + struct tty_struct *tty; + + mutex_lock(&port->mutex); + tty = tty_port_tty_get(port); + mutex_unlock(&port->mutex); + + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } +} +EXPORT_SYMBOL_GPL(tty_port_tty_vhangup); /** * tty_port_tty_wakeup - helper to wake up a tty diff --git a/include/linux/tty_port.h b/include/linux/tty_port.h index 23cad403bb8f..8d22c59c6f15 100644 --- a/include/linux/tty_port.h +++ b/include/linux/tty_port.h @@ -245,7 +245,8 @@ bool tty_port_carrier_raised(struct tty_port *port); void tty_port_raise_dtr_rts(struct tty_port *port); void tty_port_lower_dtr_rts(struct tty_port *port); void tty_port_hangup(struct tty_port *port); -void __tty_port_tty_hangup(struct tty_port *port, bool check_clocal, bool async); +void tty_port_tty_hangup(struct tty_port *port, bool check_clocal); +void tty_port_tty_vhangup(struct tty_port *port); void tty_port_tty_wakeup(struct tty_port *port); int tty_port_block_til_ready(struct tty_port *port, struct tty_struct *tty, struct file *filp); @@ -264,25 +265,6 @@ static inline int tty_port_users(struct tty_port *port) return port->count + port->blocked_open; } -/** - * tty_port_tty_hangup - helper to hang up a tty asynchronously - * @port: tty port - * @check_clocal: hang only ttys with %CLOCAL unset? - */ -static inline void tty_port_tty_hangup(struct tty_port *port, bool check_clocal) -{ - __tty_port_tty_hangup(port, check_clocal, true); -} - -/** - * tty_port_tty_vhangup - helper to hang up a tty synchronously - * @port: tty port - */ -static inline void tty_port_tty_vhangup(struct tty_port *port) -{ - __tty_port_tty_hangup(port, false, false); -} - #ifdef CONFIG_TTY void tty_kref_put(struct tty_struct *tty); __DEFINE_CLASS_IS_CONDITIONAL(tty_port_tty, true); From f658e6bcd00a4eafaa9c98c0b7483cc2c5dac539 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 10 Sep 2026 15:08:13 +0200 Subject: [PATCH 189/535] serial: revert guards in uart_wait_modem_status() Mixing scope-based and regular cleanup is discouraged and uart_port_deref() used by uart_wait_modem_status() falls in the latter category. Revert the guard conversion in preparation for fixing a hangup race. Fixes: 56609c050051 ("serial: serial_core: use guard()s") Cc: stable@kernel.org # 6.18 Cc: Jiri Slaby (SUSE) Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260910130816.642699-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 95774b0f1484..1553bc6cbe7b 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1213,15 +1213,16 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) uport = uart_port_ref(state); if (!uport) return -EIO; - scoped_guard(uart_port_lock_irq, uport) { - memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); - uart_enable_ms(uport); - } + uart_port_lock_irq(uport); + memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); + uart_enable_ms(uport); + uart_port_unlock_irq(uport); add_wait_queue(&port->delta_msr_wait, &wait); for (;;) { - scoped_guard(uart_port_lock_irq, uport) - memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); + uart_port_lock_irq(uport); + memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); + uart_port_unlock_irq(uport); set_current_state(TASK_INTERRUPTIBLE); From 8ca59f7baee7c72cf7729950b4ab2e7f910c9549 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 10 Sep 2026 15:08:14 +0200 Subject: [PATCH 190/535] serial: fix ioctl hangup race The tty ioctls can race with hangup and end up calling into a tty driver for a device that is already gone or powered down. Add the missing checks to make sure the port has not been hung up before accessing the hardware to avoid issues like kernel panic due to unclocked accesses. Note that TIOCGSERIAL, TIOCGICOUNT do not access hardware and are therefore not affected by the race. Also note that the initialised check in uart_set_ldisc() added by commit 2f70e49ed860 ("serial_core: Check for port state when tty is in error state") should have been done under the port lock to avoid racing with uart_do_autoconfig(). Fixes: 04f378b198da ("tty: BKL pushdown") Cc: stable@kernel.org # 2.6.26 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260910130816.642699-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 41 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 1553bc6cbe7b..139b93876229 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -896,7 +896,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, upf_t old_flags, new_flags; int retval; - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; new_port = new_info->port; @@ -1119,7 +1119,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state) guard(mutex)(&port->mutex); uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; if (uport->type != PORT_UNKNOWN && uport->ops->break_ctl) @@ -1144,7 +1144,7 @@ static int uart_do_autoconfig(struct tty_struct *tty, struct uart_state *state) */ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->mutex) { uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return -EIO; if (tty_port_users(port) != 1) @@ -1199,7 +1199,7 @@ static void uart_enable_ms(struct uart_port *uport) * FIXME: This wants extracting into a common all driver implementation * of TIOCMWAIT using tty_port. */ -static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) +static int uart_wait_modem_status(struct tty_struct *tty, struct uart_state *state, unsigned long arg) { struct uart_port *uport; struct tty_port *port = &state->port; @@ -1213,11 +1213,21 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) uport = uart_port_ref(state); if (!uport) return -EIO; + + mutex_lock(&port->mutex); + if (tty_io_error(tty)) { + mutex_unlock(&port->mutex); + ret = -EIO; + goto out_deref; + } + uart_port_lock_irq(uport); memcpy(&cprev, &uport->icount, sizeof(struct uart_icount)); uart_enable_ms(uport); uart_port_unlock_irq(uport); + mutex_unlock(&port->mutex); + add_wait_queue(&port->delta_msr_wait, &wait); for (;;) { uart_port_lock_irq(uport); @@ -1246,6 +1256,7 @@ static int uart_wait_modem_status(struct uart_state *state, unsigned long arg) } __set_current_state(TASK_RUNNING); remove_wait_queue(&port->delta_msr_wait, &wait); +out_deref: uart_port_deref(uport); return ret; @@ -1568,7 +1579,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) /* This should only be used when the hardware is present. */ if (cmd == TIOCMIWAIT) - return uart_wait_modem_status(state, arg); + return uart_wait_modem_status(tty, state, arg); /* rs485_config requires more locking than others */ if (cmd == TIOCSRS485) @@ -1624,14 +1635,13 @@ static void uart_set_ldisc(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; struct uart_port *uport; - struct tty_port *port = &state->port; - - if (!tty_port_initialized(port)) - return; guard(mutex)(&state->port.mutex); uport = uart_port_check(state); - if (uport && uport->ops->set_ldisc) + if (!uport || tty_io_error(tty)) + return; + + if (uport->ops->set_ldisc) uport->ops->set_ldisc(uport, &tty->termios); } @@ -1647,7 +1657,7 @@ static void uart_set_termios(struct tty_struct *tty, guard(mutex)(&state->port.mutex); uport = uart_port_check(state); - if (!uport) + if (!uport || tty_io_error(tty)) return; /* @@ -1799,7 +1809,14 @@ static void uart_wait_until_sent(struct tty_struct *tty, int timeout) * 'timeout' / 'expire' give us the maximum amount of time * we wait. */ - while (!port->ops->tx_empty(port)) { + for (;;) { + mutex_lock(&state->port.mutex); + if (tty_io_error(tty) || port->ops->tx_empty(port)) { + mutex_unlock(&state->port.mutex); + break; + } + mutex_unlock(&state->port.mutex); + msleep_interruptible(jiffies_to_msecs(char_time)); if (signal_pending(current)) break; From 40adffec8c0090533dfaa863ced7901b60bbf1dc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 10 Sep 2026 15:08:15 +0200 Subject: [PATCH 191/535] serial: fix TIOCMIWAIT race The task state must be updated before checking the wakeup condition to avoid missing a racing modem status update. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/r/20260904115542.E20D11F00A3D@smtp.kernel.org Cc: stable@kernel.org Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260910130816.642699-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 139b93876229..f7b30f77b231 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1230,12 +1230,12 @@ static int uart_wait_modem_status(struct tty_struct *tty, struct uart_state *sta add_wait_queue(&port->delta_msr_wait, &wait); for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + uart_port_lock_irq(uport); memcpy(&cnow, &uport->icount, sizeof(struct uart_icount)); uart_port_unlock_irq(uport); - set_current_state(TASK_INTERRUPTIBLE); - if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || From eb35b73e133686e5b999eb7130c4e3d22fe03089 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 10 Sep 2026 15:08:16 +0200 Subject: [PATCH 192/535] serial: abort TIOCMIWAIT on hangup Add the missing hangup check to the TIOCMIWAIT wait loop to avoid going back to sleep (indefinitely) after being woken on hangup. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/r/20260904115542.E20D11F00A3D@smtp.kernel.org Cc: stable@kernel.org Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260910130816.642699-5-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index f7b30f77b231..33c8f3b6ac81 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1244,6 +1244,11 @@ static int uart_wait_modem_status(struct tty_struct *tty, struct uart_state *sta break; } + if (tty_io_error(tty)) { + ret = -EIO; + break; + } + schedule(); /* see if a signal did it */ From 615f35fd5aee169cc4881a8594f2fa234a4d0bb8 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 11 Sep 2026 11:01:32 +0200 Subject: [PATCH 193/535] tty: amiserial: fix broken TIOCMIWAIT A change replacing interruptible_sleep_on() inadvertently broke the TIOCMIWAIT implementation which now immediately returns -EIO (unless there is a racing modem status update). Replace the old hangup (and signal) detection with a check for the TTY_IO_ERROR flag which is set on hangup. Fixes: 591cee0a3563 ("tty/amiserial: avoid interruptible_sleep_on") Reported-by: sashiko-bot@kernel.org Link: https://lore.kernel.org/20260910132029.D80801F00898@smtp.kernel.org Cc: stable@kernel.org # 3.14 Cc: Arnd Bergmann Signed-off-by: Johan Hovold Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260911090132.723531-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/amiserial.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/tty/amiserial.c b/drivers/tty/amiserial.c index 28af0fd98181..60f6060b893d 100644 --- a/drivers/tty/amiserial.c +++ b/drivers/tty/amiserial.c @@ -1138,11 +1138,6 @@ static int rs_ioctl(struct tty_struct *tty, local_irq_save(flags); cnow = info->icount; /* atomic copy */ local_irq_restore(flags); - if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && - cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) { - ret = -EIO; /* no change => error */ - break; - } if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || @@ -1150,6 +1145,10 @@ static int rs_ioctl(struct tty_struct *tty, ret = 0; break; } + if (tty_io_error(tty)) { + ret = -EIO; + break; + } schedule(); /* see if a signal did it */ if (signal_pending(current)) { From e62e601cfd3c842633afca6f864866d03fe1556f Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Thu, 6 Aug 2026 11:54:34 +0100 Subject: [PATCH 194/535] MAINTAINERS: Add self for the sb1250-duart serial driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wrote this driver and seem to have been the main remaining user now. Signed-off-by: Maciej W. Rozycki Reviewed-by: Philippe Mathieu-Daudé Link: https://patch.msgid.link/alpine.DEB.2.21.2608060423110.55540@angie.orcam.me.uk Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..495d7ec3f959 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24471,6 +24471,11 @@ R: Marc Murphy S: Supported F: arch/arm/boot/dts/ti/omap/am335x-sancloud* +SB1250-DUART BROADCOM SOC SERIAL DRIVER +M: "Maciej W. Rozycki" +S: Maintained +F: drivers/tty/serial/sb1250-duart.c + SBOM M: Luis Augenstein M: Maximilian Huber From 1f71f565e3585da1098ff19ca3c24687dab59185 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 3 Aug 2026 16:10:54 +0200 Subject: [PATCH 195/535] tty: vcc: zero-initialize control packet in vcc_send_ctl() The stack-allocated struct vio_vcc pkt was partially initialized, leaving the tag.stype_env field uninitialized before being sent via ldc_write(), potentially leaking kernel stack data to the LDC peer. Assisted-by: AISLE:Snapshot Cc: stable Signed-off-by: Joshua Rogers Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260803-aisle-tty-vcc-v2-1-a50bc9b15193@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vcc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/vcc.c b/drivers/tty/vcc.c index 27a55465bf5e..3947bd2b75ac 100644 --- a/drivers/tty/vcc.c +++ b/drivers/tty/vcc.c @@ -492,7 +492,7 @@ static ssize_t domain_show(struct device *dev, static int vcc_send_ctl(struct vcc_port *port, int ctl) { - struct vio_vcc pkt; + struct vio_vcc pkt = {}; int rv; pkt.tag.type = VIO_TYPE_CTRL; From 03b49c991abb8d021ca6fe51cd173447236b0552 Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 3 Aug 2026 16:10:55 +0200 Subject: [PATCH 196/535] tty: vcc: hold port lock when clearing tty pointer in vcc_cleanup vcc_cleanup() sets port->tty to NULL without holding port->lock, racing with vcc_event() LDC callbacks that read port->tty under port->lock and then use tty->port. This can cause a use-after-free when the callback dereferences tty->port after cleanup has already destroyed and freed it. Assisted-by: AISLE:Snapshot Cc: stable Signed-off-by: Joshua Rogers Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260803-aisle-tty-vcc-v2-2-a50bc9b15193@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vcc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/vcc.c b/drivers/tty/vcc.c index 3947bd2b75ac..9adb533f7034 100644 --- a/drivers/tty/vcc.c +++ b/drivers/tty/vcc.c @@ -986,7 +986,8 @@ static void vcc_cleanup(struct tty_struct *tty) port = vcc_get(tty->index, true); if (port) { - port->tty = NULL; + scoped_guard(spinlock_irqsave, &port->lock) + port->tty = NULL; if (port->removed) { vcc_table_remove(tty->index); From 73f3e6ca7a6a7c952977c7c94f1c6dddebcafa34 Mon Sep 17 00:00:00 2001 From: Kendall Willis Date: Tue, 25 Aug 2026 17:06:07 -0500 Subject: [PATCH 197/535] serial: 8250_omap: fix wake irq cleared during suspend The wake irq was cleared in shutdown(), which runs during the suspend sequence, making it impossible to wake the system via UART. Move wake irq setup to probe() and teardown to remove() so the irq remains armed during suspend when the UART is a wakeup source. Cc: stable@kernel.org Fixes: 61929cf0169d ("tty: serial: Add 8250-core based omap driver") Signed-off-by: Kendall Willis Reviewed-by: Tony Lindgren Link: https://patch.msgid.link/20260825-uart-wakeirq-fix-v2-1-0b9fba03d9b6@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 3c7775df27ef..ceecb39fb82d 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -711,12 +711,6 @@ static int omap_8250_startup(struct uart_port *port) struct uart_8250_dma *dma = &priv->omap8250_dma; int ret; - if (priv->wakeirq) { - ret = dev_pm_set_dedicated_wake_irq(port->dev, priv->wakeirq); - if (ret) - return ret; - } - #ifdef CONFIG_PM up->capabilities |= UART_CAP_RPM; #endif @@ -787,7 +781,6 @@ static void omap_8250_shutdown(struct uart_port *port) } disable_irq_nosync(port->irq); - dev_pm_clear_wake_irq(port->dev); serial8250_release_dma(up); up->dma = NULL; @@ -1588,6 +1581,12 @@ static int omap8250_probe(struct platform_device *pdev) priv->wakeirq = irq_of_parse_and_map(np, 1); + if (priv->wakeirq) { + ret = dev_pm_set_dedicated_wake_irq(&pdev->dev, priv->wakeirq); + if (ret) + goto err; + } + ret = serial8250_register_8250_port(&up); if (ret < 0) { dev_err(&pdev->dev, "unable to register 8250 port\n"); @@ -1603,6 +1602,7 @@ static int omap8250_probe(struct platform_device *pdev) return 0; err: + dev_pm_clear_wake_irq(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); flush_work(&priv->qos_work); @@ -1625,6 +1625,7 @@ static void omap8250_remove(struct platform_device *pdev) omap_8250_shutdown(&up->port); serial8250_unregister_port(priv->line); priv->line = -ENODEV; + dev_pm_clear_wake_irq(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); flush_work(&priv->qos_work); From 499485a67fbacf4d9afbdb43522388c64f853428 Mon Sep 17 00:00:00 2001 From: Ruslan Valiyev Date: Wed, 26 Aug 2026 10:46:54 +0200 Subject: [PATCH 198/535] serial: core: fix NULL pointer dereference in serial_core_unregister_port() port->port_dev is NULL when no port device is installed: it is cleared on teardown, and never set if registration failed before serial_core_port_device_add(). serial_core_unregister_port() passes it straight to serial_core_get_ctrl_dev(), which dereferences it: KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] RIP: serial_core_unregister_port Call Trace: serial8250_unregister_port serial8250_remove unbind_store Return early when there is no port device, and read port->port_dev under port_mutex. Also clear port->port_dev on the serial_core_register_port() error path, where the port device has already been removed. Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM") Reported-by: syzbot+9f57c1b2792029198fcf@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9f57c1b2792029198fcf Cc: stable@kernel.org Assisted-by: Claude:claude-opus-5 Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Ruslan Valiyev Link: https://syzkaller.appspot.com/bug?extid=9f57c1b2792029198fcf Link: https://lore.kernel.org/all/20260826073237.1377668-1-linuxoid@gmail.com/ Reviewed-by: Tony Lindgren Link: https://patch.msgid.link/20260826084654.1392851-1-linuxoid@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 33c8f3b6ac81..6332ed545c89 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3381,6 +3381,7 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) err_unregister_port_dev: serial_base_port_device_remove(port->port_dev); + port->port_dev = NULL; err_unregister_ctrl_dev: serial_base_ctrl_device_remove(new_ctrl_dev); @@ -3395,12 +3396,24 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port) { struct device *phys_dev = port->dev; - struct serial_port_device *port_dev = port->port_dev; - struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); + struct serial_port_device *port_dev; + struct serial_ctrl_device *ctrl_dev; int ctrl_id = port->ctrl_id; guard(mutex)(&port_mutex); + /* + * A NULL port device means there is no registered port device to + * remove: serial_core_remove_one_port() clears port_dev on + * teardown, and it is never set if registration failed before + * serial_core_port_device_add(). + */ + port_dev = port->port_dev; + if (!port_dev) + return; + + ctrl_dev = serial_core_get_ctrl_dev(port_dev); + port->flags |= UPF_DEAD; serial_core_remove_one_port(drv, port); From 98401cf912fe8a081ef8e500c421b6d957b55a99 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 15:56:50 +0000 Subject: [PATCH 199/535] serial: ma35d1: Fix clock reference leak in ma35d1serial_probe() of_clk_get() returns a clock with a reference that has to be released with clk_put(). ma35d1serial_probe() never does that, so the reference is leaked on the probe error paths and also when the port is removed. Release it on the error paths and in ma35d1serial_remove(). Fixes: 930cbf92db01 ("tty: serial: Add Nuvoton ma35d1 serial driver support") Cc: stable@kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917155650.2161274-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/ma35d1_serial.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c index 920fe7ff5083..0809655f4395 100644 --- a/drivers/tty/serial/ma35d1_serial.c +++ b/drivers/tty/serial/ma35d1_serial.c @@ -728,7 +728,7 @@ static int ma35d1serial_probe(struct platform_device *pdev) ret = clk_prepare_enable(up->clk); if (ret) - goto err_iounmap; + goto err_put_clk; if (up->port.line != 0) up->port.uartclk = clk_get_rate(up->clk); @@ -755,6 +755,9 @@ static int ma35d1serial_probe(struct platform_device *pdev) err_clk_disable: clk_disable_unprepare(up->clk); +err_put_clk: + clk_put(up->clk); + err_iounmap: iounmap(up->port.membase); return ret; @@ -770,6 +773,7 @@ static void ma35d1serial_remove(struct platform_device *dev) uart_remove_one_port(&ma35d1serial_reg, port); clk_disable_unprepare(up->clk); + clk_put(up->clk); } static int ma35d1serial_suspend(struct platform_device *dev, pm_message_t state) From ef3134cd0d4831ae2137aee2883987859b80b2c6 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 15:55:23 +0000 Subject: [PATCH 200/535] kgdboc: Fix tty driver reference leak in configure_kgdboc() tty_find_polling_driver() returns the tty driver with an extra reference taken by tty_driver_kref_get(). configure_kgdboc() stores it in kgdb_tty_driver but the reference is never dropped: it is leaked when kgdb_register_io_module() fails, when the configuration is cleared, and on every reconfigure. Release the reference both when unconfiguring and on the error path. Fixes: 7d7b93c1452f ("tty: kref the tty driver object") Cc: stable@kernel.org Signed-off-by: Wentao Liang Reviewed-by: Douglas Anderson Link: https://patch.msgid.link/20260917155523.2161202-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/kgdboc.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/tty/serial/kgdboc.c b/drivers/tty/serial/kgdboc.c index 09648d643897..64e3f8cf2f55 100644 --- a/drivers/tty/serial/kgdboc.c +++ b/drivers/tty/serial/kgdboc.c @@ -188,6 +188,11 @@ static void cleanup_kgdboc(void) kgdboc_unregister_kbd(); kgdb_unregister_io_module(&kgdboc_io_ops); + + if (kgdb_tty_driver) { + tty_driver_kref_put(kgdb_tty_driver); + kgdb_tty_driver = NULL; + } } static int configure_kgdboc(void) @@ -254,6 +259,10 @@ static int configure_kgdboc(void) noconfig: kgdboc_unregister_kbd(); + if (kgdb_tty_driver) { + tty_driver_kref_put(kgdb_tty_driver); + kgdb_tty_driver = NULL; + } configured = 0; return err; From 9d8955281d9229a899e2041659a59b389027b711 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 15:58:34 +0000 Subject: [PATCH 201/535] serial: vt8500: Fix clock reference leak in vt8500_serial_probe() of_clk_get() returns a clock with a reference that has to be released with clk_put(). If clk_prepare_enable() fails the function returns without doing so, leaking the reference. Add the missing clk_put() on that error path. Fixes: 12faa35ae5cb ("serial: vt8500: UART uses gated clock rather than 24Mhz reference") Cc: stable@kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917155834.2161361-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/vt8500_serial.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/vt8500_serial.c b/drivers/tty/serial/vt8500_serial.c index 78a1c1eea11b..fc6fdc751b16 100644 --- a/drivers/tty/serial/vt8500_serial.c +++ b/drivers/tty/serial/vt8500_serial.c @@ -656,6 +656,7 @@ static int vt8500_serial_probe(struct platform_device *pdev) ret = clk_prepare_enable(vt8500_port->clk); if (ret) { dev_err(&pdev->dev, "failed to enable clock\n"); + clk_put(vt8500_port->clk); return ret; } From 189b9dc8c96ec9486482f27c1372b04a7152175e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 9 Sep 2026 08:40:02 +0000 Subject: [PATCH 202/535] serial: 8250_bcm7271: fix use-after-free in brcmuart_remove() brcmuart_handle_irq() starts priv->hrt to work around a receive-timeout quirk of the 8250 core when hardware flow control is enabled; the timer callback brcmuart_hrtimer_func() dereferences priv. brcmuart_remove() cancels the hrtimer before it unregisters the port, so a receive-timeout interrupt that arrives in between re-arms the timer. After devm frees priv, the callback runs on freed memory. Unregister the port before cancelling the hrtimer. serial8250_unregister_port() shuts the port down and frees its IRQ, so brcmuart_handle_irq() can no longer restart the timer, and the final hrtimer_cancel() drains any timer that was armed before or during the unregister. This issue was found by an in-house static analysis tool. Fixes: 41a469482de2 ("serial: 8250: Add new 8250-core based Broadcom STB driver") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260909084002.686911-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_bcm7271.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_bcm7271.c b/drivers/tty/serial/8250/8250_bcm7271.c index cb7d594d263e..5f1140f05150 100644 --- a/drivers/tty/serial/8250/8250_bcm7271.c +++ b/drivers/tty/serial/8250/8250_bcm7271.c @@ -1121,8 +1121,8 @@ static void brcmuart_remove(struct platform_device *pdev) struct brcmuart_priv *priv = platform_get_drvdata(pdev); debugfs_remove_recursive(priv->debugfs_dir); - hrtimer_cancel(&priv->hrt); serial8250_unregister_port(priv->line); + hrtimer_cancel(&priv->hrt); brcmuart_free_bufs(&pdev->dev, priv); if (priv->dma_enabled) brcmuart_arbitration(priv, 0); From 1cd9b1be5a9c56fd69e3f555b200a1844f9b873e Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Tue, 15 Sep 2026 18:56:27 +0530 Subject: [PATCH 203/535] soc: qcom: geni-se: Correct QUP Core ICC vote constants The GENI_TO_CORE ("qup-core") ICC vote selects the QUP Core 2X clock rate. The CORE_2X_*_MHZ constants are expressed in Bps, but their values are several orders of magnitude too small. For example, the 50 MHz threshold is represented by 2500 rather than 25000000 Bps. As a result, clients using these constants can severely under-vote the QUP Core clock. Correct the constants to their intended Bps thresholds so that the ICC provider selects the corresponding QUP Core 2X clock rate. Drop the unused definitions. Fixes: 58ffbba6a399 ("soc: qcom: geni: Support for ICC voting") Cc: stable@kernel.org Signed-off-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260915-correct-icc-bandwidth-vote-constants-v2-1-f78eb611204e@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- include/linux/soc/qcom/geni-se.h | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 29a53bbc0dd4..5f18d281e6a4 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -347,17 +347,12 @@ struct geni_se { #define QUP_SE_VERSION_2_5 0x20050000 /* - * Define bandwidth thresholds that cause the underlying Core 2X interconnect - * clock to run at the named frequency. These baseline values are recommended - * by the hardware team, and are not dynamically scaled with GENI bandwidth - * beyond basic on/off. + * QUP Core 2X clock votes used by GENI clients through the "qup-core" ICC + * path. Values are in Bps and must be converted with Bps_to_icc() before + * setting avg_bw. */ -#define CORE_2X_19_2_MHZ 960 -#define CORE_2X_50_MHZ 2500 -#define CORE_2X_100_MHZ 5000 -#define CORE_2X_150_MHZ 7500 -#define CORE_2X_200_MHZ 10000 -#define CORE_2X_236_MHZ 16383 +#define CORE_2X_19_2_MHZ 9600000 +#define CORE_2X_50_MHZ 25000000 #define GENI_DEFAULT_BW Bps_to_icc(1000) From 63ef71d1474b5417b5b2dec700cd251c44a570d9 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Tue, 15 Sep 2026 18:56:28 +0530 Subject: [PATCH 204/535] tty: serial: qcom_geni_serial: Keep console RX functional after deep idle At baud rates up to 115200, the serial console uses only a 1 kBps keepalive vote for the GENI_TO_CORE ("qup-core") ICC path. This vote keeps the path active but does not request a QUP Core 2X clock rate. When the CPU enters a deeper idle state, the missing Core clock vote can leave the console RX path unresponsive. Use the 19.2 MHz Core 2X vote at low baud rates, while retaining the 50 MHz vote at higher baud rates, so that console RX remains functional after deep idle transitions. Fixes: 7cf563b2c846 ("tty: serial: qcom_geni_serial: Add interconnect support") Cc: stable@kernel.org Reviewed-by: Konrad Dybcio Signed-off-by: Viken Dadhaniya Link: https://patch.msgid.link/20260915-correct-icc-bandwidth-vote-constants-v2-2-f78eb611204e@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 3633723acef8..f7e81b652255 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1482,9 +1482,8 @@ static int geni_serial_set_rate(struct geni_se *se, unsigned long baud) * Bump up BW vote on CPU and CORE path as driver supports FIFO mode * only. */ - avg_bw_core = (baud > 115200) ? Bps_to_icc(CORE_2X_50_MHZ) - : GENI_DEFAULT_BW; - port->se.icc_paths[GENI_TO_CORE].avg_bw = avg_bw_core; + avg_bw_core = baud > 115200 ? CORE_2X_50_MHZ : CORE_2X_19_2_MHZ; + port->se.icc_paths[GENI_TO_CORE].avg_bw = Bps_to_icc(avg_bw_core); port->se.icc_paths[CPU_TO_GENI].avg_bw = Bps_to_icc(baud); geni_icc_set_bw(&port->se); From 2a42e6fe1293f2e2d579c9fe5bbab056dd8b880e Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 23 Sep 2026 17:37:32 +0800 Subject: [PATCH 205/535] block: reject polled dio with user integrity metadata IOCB_HAS_METADATA leaves iocb->private as a uio_meta, while iocb_bio_iopoll() expects a bio. Since 2729a60bbfb9, metadata I/O always uses __blkdev_direct_IO(), which never publishes the bio for polling, so IOPOLL calls bio_poll() on the uio_meta. Reproducer: io_uring IOPOLL plus a PI attribute on an integrity device (scsi_debug dif=1 dix=1). KASAN reports: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004 KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] RIP: 0010:bio_poll+0x9d/0x4b0 Call Trace: iocb_bio_iopoll+0x5b/0x80 io_uring_classic_poll+0xf3/0x140 io_do_iopoll+0x4e3/0xc00 io_iopoll_check+0x275/0x560 __do_sys_io_uring_enter+0x2e5/0x720 Return -EOPNOTSUPP when HIPRI is combined with user metadata. Fixes: 2729a60bbfb9 ("block: don't silently ignore metadata for sync read/write") Assisted-by: LLM Signed-off-by: Yang Xiuwei Link: https://patch.msgid.link/20260923093732.1504291-1-yangxiuwei@kylinos.cn Signed-off-by: Jens Axboe --- block/fops.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/block/fops.c b/block/fops.c index 2ce7c6c4714e..c915ec0126d7 100644 --- a/block/fops.c +++ b/block/fops.c @@ -402,6 +402,11 @@ static ssize_t blkdev_direct_IO(struct kiocb *iocb, struct iov_iter *iter) if (blkdev_dio_invalid(bdev, iocb, iter)) return -EINVAL; + /* HIPRI needs private as bio; HAS_METADATA keeps it as uio_meta */ + if ((iocb->ki_flags & IOCB_HIPRI) && + (iocb->ki_flags & IOCB_HAS_METADATA)) + return -EOPNOTSUPP; + if (iov_iter_rw(iter) == WRITE) { u16 max_write_streams = bdev_max_write_streams(bdev); From a3bdf68feecc57af5c11fb599f860ac9790ffad9 Mon Sep 17 00:00:00 2001 From: Yao Kai Date: Wed, 23 Sep 2026 09:43:10 +0800 Subject: [PATCH 206/535] io_uring: initialize task context before running the BPF loop Submitting SQEs through an io_uring BPF loop can trigger a NULL pointer dereference in io_submit_sqes(), as shown by the following arm64 report: [ 2049.380301] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 [ 2049.388788] pc : io_submit_sqes+0x48/0x6c4 [ 2049.389153] lr : bpf_io_uring_submit_sqes+0x10/0x1c [ 2049.396310] Call trace: [ 2049.396531] io_submit_sqes+0x48/0x6c4 (P) [ 2049.396893] bpf_io_uring_submit_sqes+0x10/0x1c [ 2049.397293] bpf_prog_1a2a95cb199202b5+0x3c/0x68 [ 2049.397699] bpf__io_uring_bpf_ops_loop_step+0x5c/0x94 [ 2049.398149] io_run_loop+0x70/0x2e4 [ 2049.398461] __arm64_sys_io_uring_enter+0xf0/0x710 [ 2049.398882] invoke_syscall+0x54/0x10c [ 2049.399414] el0_svc_common.constprop.0+0x40/0xe0 [ 2049.399829] do_el0_svc+0x1c/0x28 [ 2049.400125] el0_svc+0x38/0x1d0 [ 2049.400409] el0t_64_sync_handler+0xa0/0xe4 [ 2049.400779] el0t_64_sync+0x198/0x19c io_uring_enter() dispatches to io_run_loop() before reaching the normal submission path's io_uring_add_tctx_node() call. The loop only checks whether the caller is allowed to run task work; it does not initialize current->io_uring or associate the task with the ring. A BPF call to bpf_io_uring_submit_sqes() then reaches io_get_task_refs(), which assumes that current->io_uring is valid. A ring created with IORING_SETUP_R_DISABLED, IORING_SETUP_SINGLE_ISSUER and IORING_SETUP_DEFER_TASKRUN can be enabled by a different task that has never used io_uring. Enabling the ring makes that task the submitter without allocating its io_uring task context. Its first BPF-driven submission of a pending SQE can therefore cause a kernel Oops. Call io_uring_add_tctx_node() in io_run_loop() before invoking the loop and propagate any initialization error. Do this before acquiring uring_lock, since task context setup may acquire the same mutex. This also establishes the task-to-ring association used for cancellation. Fixes: 033af2b3eb19 ("io_uring: introduce callback driven main loop") Signed-off-by: Yao Kai Link: https://patch.msgid.link/20260923014310.591292-1-yaokai34@huawei.com Signed-off-by: Jens Axboe --- io_uring/loop.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/io_uring/loop.c b/io_uring/loop.c index bbbb6ef14e6a..3d472b7827fa 100644 --- a/io_uring/loop.c +++ b/io_uring/loop.c @@ -2,6 +2,7 @@ #include "io_uring.h" #include "wait.h" #include "loop.h" +#include "tctx.h" static inline int io_loop_nr_cqes(const struct io_ring_ctx *ctx, const struct iou_loop_params *lp) @@ -81,6 +82,10 @@ int io_run_loop(struct io_ring_ctx *ctx) { int ret; + ret = io_uring_add_tctx_node(ctx); + if (unlikely(ret)) + return ret; + if (!io_allowed_run_tw(ctx)) return -EEXIST; From ab6c756f28c741d704a7c3e04814bfc3adf6f818 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 22 Sep 2026 10:27:10 -0700 Subject: [PATCH 207/535] blk-mq: set RQF_USE_SCHED when the operation is known The cached requests are allocated for one operation but can be handed out for another. A passthrough command has RQF_USE_SCHED cleared, so using those flags for a subsequent read/write bio will insert it into the scheduler without ->prepare_request() and frees it without ->finish_request(). For kyber, this leaks the domain token acquired at dispatch and stalls the queue. Don't set RQF_USE_SCHED based on the first operation the batch happened to be allocated for. Instead, set it after the request is claimed by an operation. Introduce a helper function, blk_mq_rq_late_init() for this, and absorb blk_mq_rq_time_init() since the two run together. Fixes: 4b6a5d9cea91 ("block: enable batched allocation for blk_mq_alloc_request()") Link: https://lore.kernel.org/linux-block/20260916151655.2588-1-15815827059@163.com/ Reported-by: Henry Hu Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260922172711.1573186-1-kbusch@meta.com Signed-off-by: Jens Axboe --- block/blk-mq.c | 59 ++++++++++++++++++++++++++++----------------- block/mq-deadline.c | 2 +- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index a26a11c73ee3..5df6d2244db8 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -447,16 +447,6 @@ static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data, WRITE_ONCE(rq->deadline, 0); req_ref_set(rq, 1); - if (rq->rq_flags & RQF_USE_SCHED) { - struct elevator_queue *e = data->q->elevator; - - INIT_HLIST_NODE(&rq->hash); - RB_CLEAR_NODE(&rq->rb_node); - - if (e->type->ops.prepare_request) - e->type->ops.prepare_request(rq); - } - return rq; } @@ -498,6 +488,12 @@ __blk_mq_alloc_requests_batch(struct blk_mq_alloc_data *data) return rq_list_pop(data->cached_rqs); } +static bool blk_op_bypass_sched(blk_opf_t opf) +{ + return (opf & REQ_OP_MASK) == REQ_OP_FLUSH || + blk_op_is_passthrough(opf); +} + static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) { struct elevator_mq_ops *ops; @@ -518,12 +514,10 @@ static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) * Flush/passthrough requests are special and go directly to the * dispatch list, they are not subject to the async_depth limit. */ - if ((data->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH || - blk_op_is_passthrough(data->cmd_flags)) + if (blk_op_bypass_sched(data->cmd_flags)) return; WARN_ON_ONCE(data->flags & BLK_MQ_REQ_RESERVED); - data->rq_flags |= RQF_USE_SCHED; /* * By default, sync requests have no limit, and async requests are @@ -534,6 +528,29 @@ static void blk_mq_limit_depth(struct blk_mq_alloc_data *data) ops->limit_depth(data->cmd_flags, data); } +/* + * Finish initializing a request once it has been claimed for an operation. + * Cached requests are allocated before that operation is known. + */ +static void blk_mq_rq_late_init(struct request *rq, u64 alloc_time_ns) +{ + struct elevator_queue *e; + + blk_mq_rq_time_init(rq, alloc_time_ns); + + if (!(rq->rq_flags & RQF_SCHED_TAGS) || (rq->rq_flags & RQF_RESV) || + blk_op_bypass_sched(rq->cmd_flags)) + return; + + rq->rq_flags |= RQF_USE_SCHED; + INIT_HLIST_NODE(&rq->hash); + RB_CLEAR_NODE(&rq->rb_node); + + e = rq->q->elevator; + if (e->type->ops.prepare_request) + e->type->ops.prepare_request(rq); +} + static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) { struct request_queue *q = data->q; @@ -562,7 +579,7 @@ static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) if (data->nr_tags > 1) { rq = __blk_mq_alloc_requests_batch(data); if (rq) { - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); return rq; } data->nr_tags = 1; @@ -590,7 +607,7 @@ static struct request *__blk_mq_alloc_requests(struct blk_mq_alloc_data *data) if (!(data->rq_flags & RQF_SCHED_TAGS)) blk_mq_inc_active_requests(data->hctx); rq = blk_mq_rq_ctx_init(data, blk_mq_tags_from_data(data), tag); - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); return rq; } @@ -637,8 +654,6 @@ static struct request *blk_mq_alloc_cached_request(struct request_queue *q, if (plug->nr_ios == 1) return NULL; rq = blk_mq_rq_cache_fill(q, plug, opf, flags); - if (!rq) - return NULL; } else { rq = rq_list_peek(&plug->cached_rqs); if (!rq || rq->q != q) @@ -650,11 +665,11 @@ static struct request *blk_mq_alloc_cached_request(struct request_queue *q, return NULL; rq_list_pop(&plug->cached_rqs); - blk_mq_rq_time_init(rq, blk_time_get_ns()); + rq->cmd_flags = opf; + INIT_LIST_HEAD(&rq->queuelist); + blk_mq_rq_late_init(rq, blk_time_get_ns()); } - rq->cmd_flags = opf; - INIT_LIST_HEAD(&rq->queuelist); return rq; } @@ -766,7 +781,7 @@ struct request *blk_mq_alloc_request_hctx(struct request_queue *q, if (!(data.rq_flags & RQF_SCHED_TAGS)) blk_mq_inc_active_requests(data.hctx); rq = blk_mq_rq_ctx_init(&data, blk_mq_tags_from_data(&data), tag); - blk_mq_rq_time_init(rq, alloc_time_ns); + blk_mq_rq_late_init(rq, alloc_time_ns); rq->__data_len = 0; rq->phys_gap_bit = 0; rq->__sector = (sector_t) -1; @@ -3163,9 +3178,9 @@ void blk_mq_submit_bio(struct bio *bio) new_request: if (rq) { rq_qos_throttle(rq->q, bio); - blk_mq_rq_time_init(rq, blk_time_get_ns()); rq->cmd_flags = bio->bi_opf; INIT_LIST_HEAD(&rq->queuelist); + blk_mq_rq_late_init(rq, blk_time_get_ns()); } else { rq = blk_mq_get_new_requests(q, plug, bio); if (unlikely(!rq)) { diff --git a/block/mq-deadline.c b/block/mq-deadline.c index 5f643c0ce2a8..e5db1ee097c3 100644 --- a/block/mq-deadline.c +++ b/block/mq-deadline.c @@ -685,7 +685,7 @@ static void dd_insert_requests(struct blk_mq_hw_ctx *hctx, blk_mq_free_requests(&free); } -/* Callback from inside blk_mq_rq_ctx_init(). */ +/* Callback from inside blk_mq_rq_late_init(). */ static void dd_prepare_request(struct request *rq) { rq->elv.priv[0] = NULL; From 9d2c70986bb7838c1c441a93bda415eecb52f3dd Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 22 Sep 2026 10:27:11 -0700 Subject: [PATCH 208/535] blk-mq: allow cached requests to be used for flush operations The cached request lookups reject a request whose op_is_flush() state does not match the incoming operation. That check is from when rq->elv and rq->flush shared a union, so a request could not be both a scheduler request and carry flush state. Commit be4c427809b0 ("blk-mq: use the I/O scheduler for writes from the flush state machine") made them separate members of struct request. A cached request now gets its scheduler state when it is claimed by an operation, so one claimed for a flush is set up exactly as a freshly allocated request. Drop the check so a cached batch can mix flush and non-flush operations. Signed-off-by: Keith Busch Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260922172711.1573186-2-kbusch@meta.com Signed-off-by: Jens Axboe --- block/blk-mq.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index 5df6d2244db8..025a799f35b9 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -661,8 +661,6 @@ static struct request *blk_mq_alloc_cached_request(struct request_queue *q, if (blk_mq_get_hctx_type(opf) != rq->mq_hctx->type) return NULL; - if (op_is_flush(rq->cmd_flags) != op_is_flush(opf)) - return NULL; rq_list_pop(&plug->cached_rqs); rq->cmd_flags = opf; @@ -3075,8 +3073,6 @@ static struct request *blk_mq_get_cached_request(struct blk_plug *plug, if (type != rq->mq_hctx->type && (type != HCTX_TYPE_READ || rq->mq_hctx->type != HCTX_TYPE_DEFAULT)) return NULL; - if (op_is_flush(rq->cmd_flags) != op_is_flush(opf)) - return NULL; rq_list_pop(&plug->cached_rqs); return rq; } From a8e8cf9bf446b65449c7bc39ad84956c347d5f9c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:04:11 +0200 Subject: [PATCH 209/535] serial: qcom-geni: avoid unused-function warning The qcom_geni_serial_poll_rx_fifo_locked() function is only used for console support, but defined outside of the #ifdef block: drivers/tty/serial/qcom_geni_serial.c:918:13: error: 'qcom_geni_serial_poll_rx_fifo_locked' defined but not used [-Werror=unused-function] 918 | static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) Move the function definition to a more appropriate place that avoids the warning. Fixes: 8200871327d1 ("serial: qcom-geni: Keep FIFO RX active during console TX") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260915200435.3524505-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 190 +++++++++++++------------- 1 file changed, 96 insertions(+), 94 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index f7e81b652255..60c13cfac28c 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -171,7 +171,6 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport); static void qcom_geni_serial_resume_tx(struct uart_port *uport); -static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport); static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) { @@ -467,6 +466,102 @@ static int qcom_geni_serial_poll_init(struct uart_port *uport) #endif #ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE +static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) +{ + u32 i; + unsigned char buf[sizeof(u32)]; + struct tty_port *tport; + struct qcom_geni_serial_port *port = to_dev_port(uport); + + tport = &uport->state->port; + for (i = 0; i < bytes; ) { + int c; + int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD); + + ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1); + i += chunk; + if (drop) + continue; + + for (c = 0; c < chunk; c++) { + int sysrq; + + uport->icount.rx++; + if (port->brk && buf[c] == 0) { + port->brk = false; + if (uart_handle_break(uport)) + continue; + } + + sysrq = uart_prepare_sysrq_char(uport, buf[c]); + + if (!sysrq) + tty_insert_flip_char(tport, buf[c], TTY_NORMAL); + } + } + if (!drop) + tty_flip_buffer_push(tport); +} +#else +static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) +{ + +} +#endif + +static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop) +{ + u32 status; + u32 word_cnt; + u32 last_word_byte_cnt; + u32 last_word_partial; + u32 total_bytes; + + status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS); + word_cnt = status & RX_FIFO_WC_MSK; + last_word_partial = status & RX_LAST; + last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >> + RX_LAST_BYTE_VALID_SHFT; + + if (!word_cnt) + return; + total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1); + if (last_word_partial && last_word_byte_cnt) + total_bytes += last_word_byte_cnt; + else + total_bytes += BYTES_PER_FIFO_WORD; + handle_rx_console(uport, total_bytes, drop); +} + +#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE +/* Caller holds the UART port lock. */ +static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + struct tty_port *tport = &uport->state->port; + u32 s_irq_status; + bool drop_rx = false; + + s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); + writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); + + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { + uport->icount.overrun++; + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + } + + if (s_irq_status & (S_GP_IRQ_0_EN | S_GP_IRQ_1_EN)) { + if (s_irq_status & S_GP_IRQ_0_EN) + uport->icount.parity++; + drop_rx = true; + } else if (s_irq_status & (S_GP_IRQ_2_EN | S_GP_IRQ_3_EN)) { + uport->icount.brk++; + port->brk = true; + } + + qcom_geni_serial_handle_rx_fifo(uport, drop_rx); +} + static void qcom_geni_serial_drain_fifo(struct uart_port *uport) { struct qcom_geni_serial_port *port = to_dev_port(uport); @@ -676,47 +771,6 @@ static void qcom_geni_serial_console_device_unlock(struct console *co, #endif } -static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) -{ - u32 i; - unsigned char buf[sizeof(u32)]; - struct tty_port *tport; - struct qcom_geni_serial_port *port = to_dev_port(uport); - - tport = &uport->state->port; - for (i = 0; i < bytes; ) { - int c; - int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD); - - ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1); - i += chunk; - if (drop) - continue; - - for (c = 0; c < chunk; c++) { - int sysrq; - - uport->icount.rx++; - if (port->brk && buf[c] == 0) { - port->brk = false; - if (uart_handle_break(uport)) - continue; - } - - sysrq = uart_prepare_sysrq_char(uport, buf[c]); - - if (!sysrq) - tty_insert_flip_char(tport, buf[c], TTY_NORMAL); - } - } - if (!drop) - tty_flip_buffer_push(tport); -} -#else -static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) -{ - -} #endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */ static void handle_rx_uart(struct uart_port *uport, u32 bytes) @@ -890,58 +944,6 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport) port->tx_queued = 0; } -static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop) -{ - u32 status; - u32 word_cnt; - u32 last_word_byte_cnt; - u32 last_word_partial; - u32 total_bytes; - - status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS); - word_cnt = status & RX_FIFO_WC_MSK; - last_word_partial = status & RX_LAST; - last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >> - RX_LAST_BYTE_VALID_SHFT; - - if (!word_cnt) - return; - total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1); - if (last_word_partial && last_word_byte_cnt) - total_bytes += last_word_byte_cnt; - else - total_bytes += BYTES_PER_FIFO_WORD; - handle_rx_console(uport, total_bytes, drop); -} - -/* Caller holds the UART port lock. */ -static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - struct tty_port *tport = &uport->state->port; - u32 s_irq_status; - bool drop_rx = false; - - s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); - writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); - - if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { - uport->icount.overrun++; - tty_insert_flip_char(tport, 0, TTY_OVERRUN); - } - - if (s_irq_status & (S_GP_IRQ_0_EN | S_GP_IRQ_1_EN)) { - if (s_irq_status & S_GP_IRQ_0_EN) - uport->icount.parity++; - drop_rx = true; - } else if (s_irq_status & (S_GP_IRQ_2_EN | S_GP_IRQ_3_EN)) { - uport->icount.brk++; - port->brk = true; - } - - qcom_geni_serial_handle_rx_fifo(uport, drop_rx); -} - static void qcom_geni_serial_stop_rx_fifo(struct uart_port *uport) { u32 irq_en; From 3098c989bd38edff871d798acd7f1d1b269f7edd Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 28 Aug 2026 17:32:17 +0300 Subject: [PATCH 210/535] serial: qcom-geni: Fix unbalanced runtime PM resume for no_console_suspend When no_console_suspend is set for the serial console, the system suspend callback skips pm_runtime_force_suspend() to keep the console active. The resume callback still unconditionally calls pm_runtime_force_resume(), which triggers an "Unbalanced pm_runtime_enable!" warning. Only force resume when the suspend callback force suspended the device. Fixes: d0cd9c8d0fd5 ("serial: qcom-geni: add force suspend/resume to system sleep callbacks") Signed-off-by: Abel Vesa Reviewed-by: Praveen Talari Tested-by: David Heidelberg # OnePlus 6T (sdm845) Link: https://patch.msgid.link/20260828-serial-qcom-geni-fix-unbalanced-rpm-resume-v1-1-af3e5aef17a6@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 60c13cfac28c..82cca5867cbe 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -2100,9 +2100,11 @@ static int qcom_geni_serial_resume(struct device *dev) struct uart_port *uport = &port->uport; struct qcom_geni_private_data *private_data = uport->private_data; - ret = pm_runtime_force_resume(dev); - if (ret) - return ret; + if (console_suspend_enabled || !uart_console(uport)) { + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + } ret = uart_resume_port(private_data->drv, uport); if (uart_console(uport)) { From 2de1066dc859036f35fefd52483b2f3712462971 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 19 Aug 2026 18:46:47 +0200 Subject: [PATCH 211/535] serial: qcom-geni: keep registered console runtime active Commit aee1f94dab13 ("serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown") moved runtime PM references from the serial-core PM callback to the port startup and shutdown callbacks. The serial core used to leave the console port powered after registration, but after the callback was removed the temporary probe reference is always released. Console writes are not tied to an open TTY and can run from contexts which cannot resume the device, so a runtime-suspended console can stall. This was observed on a Qualcomm X1E80100 CRD device, as the boot console became flakey across boots. Acquire a runtime PM reference when the console is set up and release it through the console exit callback. This keeps the device active only while the console is registered and balances the reference when the console is disabled or re-enabled through sysfs. Fixes: aee1f94dab13 ("serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown") Assisted-by: LLM Signed-off-by: Konrad Dybcio Reviewed-by: Abel Vesa Tested-by: David Heidelberg # OnePlus 6T (sdm845) Link: https://patch.msgid.link/20260819-topic-geni_console_poweredoff-v1-1-7f7bb3aad8da@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 82cca5867cbe..a851b8f0ef04 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1626,16 +1626,37 @@ static int qcom_geni_console_setup(struct console *co, char *options) if (unlikely(!uport->membase)) return -ENXIO; + ret = pm_runtime_resume_and_get(uport->dev); + if (ret < 0) + return ret; + if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); - if (ret) + if (ret) { + pm_runtime_put_sync(uport->dev); return ret; + } } if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); - return uart_set_options(uport, co, baud, parity, bits, flow); + ret = uart_set_options(uport, co, baud, parity, bits, flow); + if (ret) + pm_runtime_put_sync(uport->dev); + + return ret; +} + +static int qcom_geni_console_exit(struct console *co) +{ + struct qcom_geni_serial_port *port; + + port = get_port_from_line(co->index, true, NULL); + if (IS_ERR(port)) + return PTR_ERR(port); + + return pm_runtime_put_sync(port->uport.dev); } static void qcom_geni_serial_earlycon_write(struct console *con, @@ -1752,6 +1773,7 @@ static struct console cons_ops = { .device_unlock = qcom_geni_serial_console_device_unlock, .device = uart_console_device, .setup = qcom_geni_console_setup, + .exit = qcom_geni_console_exit, .flags = CON_PRINTBUFFER | CON_NBCON, .index = -1, .data = &qcom_geni_console_driver, From 5acb2dace582f061aab5e14c399ac9480315f44b Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 23 Sep 2026 08:58:47 -0300 Subject: [PATCH 212/535] HID: multitouch: stop the release timer from being rearmed on remove mt_remove() quiesces the sticky-finger timer before stopping the hardware: timer_delete_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); hid_hw_stop(hdev); timer_delete_sync() waits for a running callback and dequeues the timer, but it does not stop the timer from being armed again. The transport is still delivering reports at that point, and the report path rearms it: if (app->quirks & MT_QUIRK_STICKY_FINGERS) { if (td->mt_io_flags & MT_IO_SLOTS_MASK) mod_timer(&td->release_timer, jiffies + msecs_to_jiffies(100)); A report that arrives after timer_delete_sync() has returned therefore leaves the timer queued. td is allocated with devm_kzalloc() against hdev->dev, so it is freed when the driver is unbound, after mt_remove() returns. When the timer fires afterwards, mt_expired_timeout() dereferences the freed td: struct mt_device *td = timer_container_of(td, t, release_timer); struct hid_device *hdev = td->hdev; if (test_and_set_bit_lock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags)) Simply moving the teardown after hid_hw_stop() does not fix this on its own, because mt_expired_timeout() calls mt_release_contacts(), which walks hdev->inputs; the timer still has to be quiesced before hid_hw_stop() tears the input devices down. Use timer_shutdown_sync() instead, which additionally makes any later mod_timer() a no-op, so neither ordering constraint has to be traded off against the other. This is the final-teardown pattern the function was introduced for, and hid-wiimote already uses it for the same reason. Fixes: 4f4001bc76fd ("HID: multitouch: fix rare Win 8 cases when the touch up event gets missing") Cc: stable@vger.kernel.org Reported-by: Sashiko AI review Closes: https://sashiko.dev/#/patchset/20260723224211.613112-1-you@example.com?part=1 Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Benjamin Tissoires Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index ab4ac261fe76..4e19a0c4d238 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2249,7 +2249,7 @@ static void mt_remove(struct hid_device *hdev) { struct mt_device *td = hid_get_drvdata(hdev); - timer_delete_sync(&td->release_timer); + timer_shutdown_sync(&td->release_timer); sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); hid_hw_stop(hdev); From 40eb4b18ad167b63644c27fa67a7400ba050379a Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 22 Sep 2026 12:10:20 +0530 Subject: [PATCH 213/535] Bluetooth: btintel: fix buffer over-read in btintel_hw_error() The "Exception info %s" print uses skb->data + 1, but the 12-byte payload is not guaranteed to be NUL-terminated and can be read past its end. Bound the print with "%.*s" and skb->len - 1. Fixes: 973bb97e5aee ("Bluetooth: btintel: Add generic function for handling hardware errors") Cc: stable@vger.kernel.org Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 909a265fd906..e35fff90b31d 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -289,7 +289,8 @@ void btintel_hw_error(struct hci_dev *hdev, u8 code) goto unlock; } - bt_dev_err(hdev, "Exception info %s", (char *)(skb->data + 1)); + bt_dev_err(hdev, "Exception info %.*s", (int)(skb->len - 1), + (char *)(skb->data + 1)); kfree_skb(skb); From 7dbeb6935ac331b0cba6c41cabd8e9d02d5f64ea Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 22 Sep 2026 12:10:21 +0530 Subject: [PATCH 214/535] Bluetooth: btintel_pcie: fix plen overflow in btintel_pcie_recv_frame() plen is u16 and computed as HDR_SIZE + dlen. For ACL/ISO frames dlen is a 16-bit field, so a value >= 0xFFFC wraps the sum, bypassing the "skb->len < plen" check and letting skb_trim() truncate the packet. Widen plen to u32 so the addition cannot wrap. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Cc: stable@vger.kernel.org Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 2819e001797b..d05af27790f9 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1197,7 +1197,7 @@ static int btintel_pcie_recv_frame(struct btintel_pcie_data *data, { int ret; u8 pkt_type; - u16 plen; + u32 plen; u32 pcie_pkt_type; void *pdata; struct hci_dev *hdev = data->hdev; From f771f96557a4d953a7e4d045870547271e36e819 Mon Sep 17 00:00:00 2001 From: Chandrashekar Devegowda Date: Tue, 22 Sep 2026 12:10:22 +0530 Subject: [PATCH 215/535] Bluetooth: btintel_pcie: reject oversized TX packets in send_frame() btintel_pcie_send_frame() eventually copies skb->data into a fixed BTINTEL_PCIE_BUFFER_SIZE (4096) DMA buffer via memcpy() in btintel_pcie_prepare_tx(), with a 4-byte PCIe type header prepended. A user-space process with HCI_CHANNEL_USER access can inject an oversized packet and overflow the buffer. Reject packets larger than BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN on entry. Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Cc: stable@vger.kernel.org Signed-off-by: Chandrashekar Devegowda Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index d05af27790f9..f333b01f7465 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -2219,6 +2219,16 @@ static int btintel_pcie_send_frame(struct hci_dev *hdev, if (test_bit(BTINTEL_PCIE_RECOVERY_IN_PROGRESS, &data->flags)) return -ENODEV; + /* Account for the 4-byte PCIe type header prepended before the + * DMA copy. Written as a subtraction to avoid wrap-around on + * attacker-controlled skb->len. + */ + if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { + bt_dev_err(hdev, "Packet too large: %u > %u", skb->len, + BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN); + return -EMSGSIZE; + } + /* Due to the fw limitation, the type header of the packet should be * 4 bytes unlike 1 byte for UART. In UART, the firmware can read * the first byte to get the packet type and redirect the rest of data From 74847177eca5638827ab74cdf77f0b10fc40ca6c Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 22 Sep 2026 10:01:41 +0800 Subject: [PATCH 216/535] Bluetooth: hci_core: Fix inquiry cache timestamps on 64-bit systems On 64-bit systems, outgoing BR/EDR connections always fall back to page scan repetition mode R2 with no clock offset once the system has been up for more than five minutes, even when inquiry found the peer only seconds earlier. This lengthens paging and can increase the risk of a Page Timeout. The inquiry cache stores jiffies in __u32 timestamps, but its age helpers subtract them from unsigned long jiffies. INITIAL_JIFFIES casts -300 * HZ through unsigned int, so jiffies crosses 2^32 five minutes after boot on 64-bit systems. Assigning it to __u32 then drops the upper 32 bits. In one trace, a 7.6-second-old entry (HZ=1000) was reported as 2^32 + 7620 ticks old and rejected by hci_acl_create_conn_sync(). hci_inquiry() is affected by the same truncation when checking the whole cache. 32-bit systems are unaffected because unsigned long is 32 bits wide there. Use unsigned long for both timestamps so they have the same width as jiffies on 32-bit and 64-bit systems, and update the debugfs format specifier accordingly. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 4 ++-- net/bluetooth/hci_debugfs.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 4105c446ca98..ca77ad0d9393 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -62,7 +62,7 @@ struct inquiry_entry { NAME_PENDING, NAME_KNOWN, } name_state; - __u32 timestamp; + unsigned long timestamp; struct inquiry_data data; }; @@ -78,7 +78,7 @@ struct discovery_state { struct list_head all; /* All devices found during inquiry */ struct list_head unknown; /* Name state not known */ struct list_head resolve; /* Name needs to be resolved */ - __u32 timestamp; + unsigned long timestamp; bdaddr_t last_adv_addr; u8 last_adv_addr_type; s8 last_adv_rssi; diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c index aadffaaff20e..2559fb6324d6 100644 --- a/net/bluetooth/hci_debugfs.c +++ b/net/bluetooth/hci_debugfs.c @@ -364,7 +364,7 @@ static int inquiry_cache_show(struct seq_file *f, void *p) list_for_each_entry(e, &cache->all, all) { struct inquiry_data *data = &e->data; - seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %u\n", + seq_printf(f, "%pMR %d %d %d 0x%.2x%.2x%.2x 0x%.4x %d %d %lu\n", &data->bdaddr, data->pscan_rep_mode, data->pscan_period_mode, data->pscan_mode, data->dev_class[2], From d1ac915fb4184faad1b62148be50f317de7c24b5 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Tue, 22 Sep 2026 03:49:28 +0900 Subject: [PATCH 217/535] Bluetooth: hci_core: free the HCI ID if naming fails hci_register_dev() allocates an ID before calling dev_set_name(). If naming fails, it returns without releasing that ID. The device has not been registered and hdev->id has not been assigned, so the normal unregister path cannot release it. Free the allocated ID before returning the naming error. Fixes: dcda165706b9 ("Bluetooth: hci_core: Fix build warnings") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reported-by: Changyul Lee Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index d183efaf9063..bbe391525530 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2557,8 +2557,10 @@ int hci_register_dev(struct hci_dev *hdev) return id; error = dev_set_name(&hdev->dev, "hci%u", id); - if (error) + if (error) { + ida_free(&hci_index_ida, id); return error; + } hdev->name = dev_name(&hdev->dev); hdev->id = id; From c9c15d4d8956df8c564f7c210e4dc5b9b820d97a Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Tue, 22 Sep 2026 18:33:11 +0530 Subject: [PATCH 218/535] Bluetooth: btintel: validate DDC record lengths Parse DDC record lengths in unsigned storage so 0xFF does not wrap. Reject records smaller than the mandatory three-byte header, records exceeding U8_MAX, and records exceeding remaining firmware bytes before issuing Intel_Write_DDC. This issue was reported by Claude Mythos. Fixes: 145f2368c5fd ("Bluetooth: btintel: Add Device Configuration support") Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index e35fff90b31d..c69a5d226e88 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -409,8 +409,16 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name) /* DDC file contains one or more DDC structure which has * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2). */ - while (fw->size > fw_ptr - fw->data) { - u8 cmd_plen = fw_ptr[0] + sizeof(u8); + while (fw->size > (size_t)(fw_ptr - fw->data)) { + size_t remaining = fw->size - (fw_ptr - fw->data); + unsigned int cmd_plen = fw_ptr[0] + 1U; + + if (cmd_plen < 3 || cmd_plen > U8_MAX || cmd_plen > remaining) { + bt_dev_err(hdev, "Malformed DDC record (plen=%u, remaining=%zu)", + cmd_plen, remaining); + release_firmware(fw); + return -EINVAL; + } skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr, HCI_INIT_TIMEOUT); From 53f7f7955a684e820bdd6bd6792c45c4b434f637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Paku=C5=82a?= Date: Tue, 22 Sep 2026 15:17:58 +0200 Subject: [PATCH 219/535] HID: universal-pidff: Add support for Turtle Beach VelocityOne Race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add usb VID and device PID to hid-ids.h Signed-off-by: Tomasz Pakuła Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 3 +++ drivers/hid/hid-universal-pidff.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 9a473f72c8cb..cb7f16d75fce 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1510,6 +1510,9 @@ #define USB_DEVICE_ID_ASUS_MD_5110 0x5110 #define USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART 0x7100 +#define USB_VENDOR_ID_TURTLE_BEACH 0x10f5 +#define USB_DEVICE_ID_TB_VELOCITYONE_RACE 0x7077 + #define USB_VENDOR_ID_TWINHAN 0x6253 #define USB_DEVICE_ID_TWINHAN_IR_REMOTE 0x0100 diff --git a/drivers/hid/hid-universal-pidff.c b/drivers/hid/hid-universal-pidff.c index 60180467a0bf..f1854cfe42d0 100644 --- a/drivers/hid/hid-universal-pidff.c +++ b/drivers/hid/hid-universal-pidff.c @@ -187,6 +187,7 @@ static const struct hid_device_id universal_pidff_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) }, { HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_TURTLE_BEACH, USB_DEVICE_ID_TB_VELOCITYONE_RACE)}, {} }; MODULE_DEVICE_TABLE(hid, universal_pidff_devices); From 5e5c3b9db1c5910d6a9175c97b76a95566e4dc32 Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 22 Sep 2026 10:46:37 +0800 Subject: [PATCH 220/535] HID: Intel-thc-hid: Intel-quicki2c: Fix buffer overflow The input and output buffers are used to store complete HID-over-I2C packets, including their protocol headers. However, their sizes are currently calculated from the payload length only. Allocate enough space for both the payload and the packet header to prevent a buffer overflow when the payload reaches its maximum size. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina --- drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c index 0d2ad7bc3648..2537288b5026 100644 --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c @@ -606,7 +606,8 @@ static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev) */ max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_input_len), SZ_4K); - qcdev->input_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + qcdev->input_buf = devm_kzalloc(qcdev->dev, + HIDI2C_PACKET_LEN(max_report_len), GFP_KERNEL); if (!qcdev->input_buf) return -ENOMEM; @@ -616,7 +617,8 @@ static int quicki2c_alloc_report_buf(struct quicki2c_device *qcdev) max_report_len = max(le16_to_cpu(qcdev->dev_desc.max_output_len), max_report_len); - qcdev->report_buf = devm_kzalloc(qcdev->dev, max_report_len, GFP_KERNEL); + qcdev->report_buf = devm_kzalloc(qcdev->dev, + HIDI2C_PACKET_LEN(max_report_len), GFP_KERNEL); if (!qcdev->report_buf) return -ENOMEM; From a1cdd72371270640bd357bd3e3b2ec0be17c9536 Mon Sep 17 00:00:00 2001 From: Even Xu Date: Tue, 22 Sep 2026 10:46:38 +0800 Subject: [PATCH 221/535] HID: Intel-thc-hid: Intel-quickspi: Fix buffer overflow The input buffers are used to store complete HID-over-SPI packets, including their protocol headers. However, their sizes are currently calculated from the payload length only. Allocate enough space for both the payload and the packet header to prevent a buffer overflow when the payload reaches its maximum size. Signed-off-by: Even Xu Signed-off-by: Jiri Kosina --- drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c index 89226f5ce45e..ad676fde211b 100644 --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c @@ -552,7 +552,8 @@ static int quickspi_alloc_report_buf(struct quickspi_device *qsdev) max_input_len = max(le16_to_cpu(qsdev->dev_desc.rep_desc_len), le16_to_cpu(qsdev->dev_desc.max_input_len)); - qsdev->input_buf = devm_kzalloc(qsdev->dev, max_input_len, GFP_KERNEL); + qsdev->input_buf = devm_kzalloc(qsdev->dev, + HIDSPI_INPUT_BODY_SIZE(max_input_len), GFP_KERNEL); if (!qsdev->input_buf) return -ENOMEM; From af2fb3ee6679b517e2fdbd3ba16ec276a8b74312 Mon Sep 17 00:00:00 2001 From: "Harry Yoo (Meta)" Date: Tue, 22 Sep 2026 12:56:43 +0100 Subject: [PATCH 222/535] mm/slab: do not wake up kswapd in __kfree_rcu_sheaf() Since kfree_rcu() can be called under pi_lock (a raw spinlock in the scheduler), kfree_rcu() itself should never allocate memory with __GFP_KSWAPD_RECLAIM as waking up kswapd ends up acquiring pi_lock, which leads to a deadlock. Reproducing the issue even intentionally was not straightforward. The set_cpus_allowed_force() path that is called under pi_lock is exercised very rarely, and competing tasks that allocate memory will most likely wake kswapd up. Therefore the existence of the deadlock was verified with a modified kernel that has a lockdep map for waking up kswapd: ====================================================== WARNING: possible circular locking dependency detected 7.2.0-rc1-slab-for-next+ #10 Not tainted ------------------------------------------------------ git/6660 is trying to acquire lock: ffff8e524533c550 (&p->pi_lock){-.-.}-{2:2}, at: _raw_spin_lock_irqsave+0x12/0x20 but task is already holding lock: ffff8e57bffff1a0 (&pgdat->kswapd_wait){....}-{3:3}, at: _raw_spin_lock_irqsave+0x12/0x20 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #2 (&pgdat->kswapd_wait){....}-{3:3}: __lock_acquire+0x5a4/0xc50 lock_acquire.part.0+0xb7/0x240 lock_acquire+0x70/0x170 __raw_spin_lock_irqsave+0x44/0x80 _raw_spin_lock_irqsave+0x12/0x20 __wake_up_common_lock+0x31/0xa0 __wake_up+0x20/0x40 kswapd_wakeup_wake+0x7e/0x110 wakeup_kswapd+0x295/0x320 wake_all_kswapds+0xac/0x1a0 __alloc_pages_slowpath.constprop.0+0x282/0xf80 __alloc_frozen_pages_noprof+0x32a/0x360 alloc_slab_page+0x2e/0x160 allocate_slab+0x82/0x420 new_slab+0x52/0xb0 refill_objects+0x13d/0x190 refill_sheaf+0x5d/0xd0 __pcs_replace_empty_main+0x230/0xb10 [...] -> #1 (kswapd_wakeup){-.-.}-{0:0}: __lock_acquire+0x5a4/0xc50 lock_sync.part.0+0x75/0x100 lock_sync+0x36/0x70 might_wakeup_kswapd+0x61/0xa0 __kfree_rcu_sheaf+0x33/0xd90 kvfree_call_rcu+0x1d4/0x3b0 set_cpus_allowed_force+0x163/0x220 cpuset_cpus_allowed_fallback+0x18b/0x240 select_fallback_rq+0x1e6/0x250 [...] -> #0 (&p->pi_lock){-.-.}-{2:2}: check_prev_add+0xe6/0xe00 validate_chain+0x51e/0x6e0 __lock_acquire+0x5a4/0xc50 lock_acquire.part.0+0xb7/0x240 lock_acquire+0x70/0x170 __raw_spin_lock_irqsave+0x44/0x80 _raw_spin_lock_irqsave+0x12/0x20 try_to_wake_up+0x77/0xa90 default_wake_function+0x27/0x60 autoremove_wake_function+0x23/0xb0 __wake_up_common+0xb8/0x170 __wake_up_common_lock+0x51/0xa0 __wake_up+0x20/0x40 kswapd_wakeup_wake+0x7e/0x110 wakeup_kswapd+0x295/0x320 wake_all_kswapds+0xac/0x1a0 __alloc_pages_slowpath.constprop.0+0x282/0xf80 __alloc_frozen_pages_noprof+0x32a/0x360 alloc_slab_page+0x2e/0x160 allocate_slab+0x82/0x420 new_slab+0x52/0xb0 refill_objects+0x13d/0x190 refill_sheaf+0x5d/0xd0 __pcs_replace_empty_main+0x230/0xb10 [...] other info that might help us debug this: Chain exists of: &p->pi_lock --> kswapd_wakeup --> &pgdat->kswapd_wait Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&pgdat->kswapd_wait); lock(kswapd_wakeup); lock(&pgdat->kswapd_wait); lock(&p->pi_lock); *** DEADLOCK *** 3 locks held by git/6660: #0: ffff8e519f0e9e20 (&type->i_mutex_dir_key#6){++++}-{4:4}, at: lookup_slow+0x2d/0x60 #1: ffffffff8a31c460 (kswapd_wakeup){-.-.}-{0:0}, at: kswapd_wakeup_wake+0x4d/0x110 #2: ffff8e57bffff1a0 (&pgdat->kswapd_wait){....}-{3:3}, at: _raw_spin_lock_irqsave+0x12/0x20 [...] Fix this by always avoiding waking up kswapd in __kfree_rcu_sheaf(). Note that there are two paths that might wake up kswapd: 1) __kfree_rcu_sheaf() // __GFP_KSWAPD_RECLAIM might wake up kswapd -> alloc_empty_sheaf(GFP_NOWAIT) 2) __kfree_rcu_sheaf() // Let's say __kfree_rcu_sheaf() doesn't pass GFP_NOWAIT -> alloc_empty_sheaf(__GFP_NOWARN) -> kmalloc_flags() -> slab_alloc_node() -> alloc_from_pcs() -> __pcs_replace_empty_main() // Free a sheaf in an allocation path when the sheaf becomes empty // and refilling the sheaf fails -> free_empty_sheaf() -> slab_free() -> free_to_pcs() -> __pcs_replace_full_main() // However free path always assumes it's safe to wake up kswapd -> alloc_empty_sheaf(GFP_NOWAIT) Drop __GFP_KSWAPD_RECLAIM in both cases. Note that the kfree_rcu() is not the only user of free_to_pcs() path, but it should be fixed as it can be invoked under pi_lock. Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260831-b4-kfree_rcu_hotfix-v1-1-4f0fb882638b%40kernel.org Fixes: ec66e0d59952 ("slab: add sheaf support for batching kfree_rcu() operations") Link: https://lore.kernel.org/linux-mm/20260831143500.x-saxdAs@linutronix.de Assisted-by: LLM # dicovery and verification Signed-off-by: Harry Yoo (Meta) Link: https://patch.msgid.link/20260922-kfree-rcu-dont-wakeup-kswapd-v1-1-42d7e2636f9e@kernel.org Signed-off-by: Vlastimil Babka (SUSE) --- mm/slub.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 54ec12503357..544cff39762c 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5969,7 +5969,8 @@ __pcs_replace_full_main(struct kmem_cache *s, struct slub_percpu_sheaves *pcs, if (!allow_spin) return NULL; - empty = alloc_empty_sheaf(s, GFP_NOWAIT, SLAB_ALLOC_DEFAULT); + /* Don't wake up kswapd, it will cause deadlock under pi_lock */ + empty = alloc_empty_sheaf(s, __GFP_NOWARN, SLAB_ALLOC_DEFAULT); if (empty) goto got_empty; @@ -6128,7 +6129,6 @@ bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj, unsigned int free_flags) struct slab_sheaf *empty; struct node_barn *barn; unsigned int alloc_flags = to_alloc_flags(free_flags); - gfp_t gfp = allow_spin ? GFP_NOWAIT : __GFP_NOWARN; /* Bootstrap or debug cache, fall back */ if (unlikely(!cache_has_sheaves(s))) { @@ -6157,7 +6157,8 @@ bool __kfree_rcu_sheaf(struct kmem_cache *s, void *obj, unsigned int free_flags) local_unlock(&s->cpu_sheaves->lock); - empty = alloc_empty_sheaf(s, gfp, alloc_flags); + /* Don't wake up kswapd, it will cause deadlock under pi_lock */ + empty = alloc_empty_sheaf(s, __GFP_NOWARN, alloc_flags); if (!empty) goto fail; From 1fef81669147d63eb8c5d3627d54eadc21173a0b Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Wed, 23 Sep 2026 16:36:32 +0200 Subject: [PATCH 223/535] arm64: mm: Fix the break-before-make flush range for erratum 2645198 modify_prot_start_ptes() performs the break-before-make TLB invalidation required by erratum 2645198 with __flush_tlb_range(), whose third argument is the end address of the range. It passes nr * PAGE_SIZE instead of addr + nr * PAGE_SIZE, so __do_flush_tlb_range() computes the page count as (nr * PAGE_SIZE - addr) >> PAGE_SHIFT. For addr > nr * PAGE_SIZE that subtraction underflows, the page count exceeds the batching limit and the flush degenerates to flush_tlb_mm(), so a single-page mprotect broadcasts an ASID-wide invalidation and a full-range mmu notifier call. For addr <= nr * PAGE_SIZE only [addr, nr * PAGE_SIZE) is invalidated, and when the cleared batch starts below nr * PAGE_SIZE the tail is left in the TLB. The workaround then no longer covers the whole batch, and for addr == nr * PAGE_SIZE the flush is empty. On affected Cortex-A715 CPUs, this can corrupt ESR_ELx and FAR_ELx on the next instruction abort caused by a permission fault. Pass addr + nr * PAGE_SIZE as the end address. Fixes: 7efa1cd5f89b5 ("arm64: add batched versions of ptep_modify_prot_start/commit") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Andrea Parri Reviewed-by: Dev Jain Signed-off-by: Will Deacon --- arch/arm64/mm/mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c index 79d90226fd5d..d4384131e10d 100644 --- a/arch/arm64/mm/mmu.c +++ b/arch/arm64/mm/mmu.c @@ -2295,7 +2295,7 @@ pte_t modify_prot_start_ptes(struct vm_area_struct *vma, unsigned long addr, * in cases where cpu is affected with errata #2645198. */ if (pte_accessible(vma->vm_mm, pte) && pte_user_exec(pte)) - __flush_tlb_range(vma, addr, nr * PAGE_SIZE, + __flush_tlb_range(vma, addr, addr + nr * PAGE_SIZE, PAGE_SIZE, 3, TLBF_NOWALKCACHE); } From 3872cc6b92940af0f73c6f9a45d65300492d648d Mon Sep 17 00:00:00 2001 From: Oleg Keri Date: Wed, 23 Sep 2026 14:56:04 +0200 Subject: [PATCH 224/535] arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz arch_freq_get_on_cpu() computes the product of the frequency scale and the reference frequency as a u64, but assigns it to an unsigned int before shifting it back down: freq = scale * arch_scale_freq_ref(cpu); freq >>= SCHED_CAPACITY_SHIFT; The product is truncated to 32 bits before the shift, so the result wraps once arch_scale_freq_ref() exceeds 2^32 / SCHED_CAPACITY_SCALE, i.e. 4194304 kHz. On a Snapdragon X2 Elite (Glymur) laptop, whose boost OPP is 4723200 kHz, cpuinfo_avg_freq reports 524283 kHz instead of ~4723200 kHz while the CPU demonstrably runs at the boost frequency: a fixed workload completes in 1.72 s at the 4723200 kHz OPP versus 2.01 s at 4032000 kHz, matching the 1.171 frequency ratio. Shift the u64 product and narrow only at the return. Fixes: 16d1e27475f6 ("arm64: Provide an AMU-based version of arch_freq_get_on_cpu") Reviewed-by: Dietmar Eggemann Signed-off-by: Oleg Keri Signed-off-by: Will Deacon --- arch/arm64/kernel/topology.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index d28438f8b83f..1773dac6fdf8 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -186,7 +186,6 @@ int arch_freq_get_on_cpu(int cpu) struct amu_cntr_sample *amu_sample; unsigned int start_cpu = cpu; unsigned long last_update; - unsigned int freq = 0; u64 scale; if (!amu_fie_cpu_supported(cpu) || !arch_scale_freq_ref(cpu)) @@ -245,9 +244,8 @@ int arch_freq_get_on_cpu(int cpu) * (see amu_scale_freq_tick for details) */ scale = arch_scale_freq_capacity(cpu); - freq = scale * arch_scale_freq_ref(cpu); - freq >>= SCHED_CAPACITY_SHIFT; - return freq; + + return (scale * arch_scale_freq_ref(cpu)) >> SCHED_CAPACITY_SHIFT; } static void amu_fie_setup(const struct cpumask *cpus) From 47c73a43c1de51ed54b621d570c5cdf20549c5b7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 22 Sep 2026 13:59:39 +0300 Subject: [PATCH 225/535] thunderbolt: stream: Announce support for FMODE_NOWAIT Commit 42bc6935339b ("thunderbolt: stream: Support IOCB_NOWAIT in non-blocking I/O as well") added support for IOCB_NOWAIT but forgot to actually announce it as part of the file->f_mode. Add this now so users such as io_uring can actually take advantage of IOCB_NOWAIT. Fixes: 42bc6935339b ("thunderbolt: stream: Support IOCB_NOWAIT in non-blocking I/O as well") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c737dd0ca6e7..8ef4064b5939 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) } mutex_unlock(&sdev->lock); + + /* Stream handles IOCB_NOWAIT just fine */ + file->f_mode |= FMODE_NOWAIT; return 0; err_unlock: From b16610e3770ef22fc8fcce818db0cd955332abf4 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 24 Sep 2026 10:54:57 +0100 Subject: [PATCH 226/535] ASoC: SDCA: Set suspended flag after resuming within system suspend drv->suspended path was executed before system suspension instead of after, move it lower to correct it. Fixes: 7a5214f769c7 ("ASoC: SDCA: Add basic system suspend support") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260924095458.2683755-2-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index cc7045dc26e6..5039230280ad 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -490,8 +490,6 @@ static int class_function_suspend(struct device *dev) struct class_function_drv *drv = auxiliary_get_drvdata(auxdev); int ret; - drv->suspended = true; - /* Ensure runtime resume runs on resume */ ret = pm_runtime_resume_and_get(dev); if (ret) { @@ -499,6 +497,8 @@ static int class_function_suspend(struct device *dev) return ret; } + drv->suspended = true; + sdca_irq_disable(drv->function, drv->core->irq_info); ret = pm_runtime_force_suspend(dev); From 91094f9c86216898ce92141c26c8fec7e97544c0 Mon Sep 17 00:00:00 2001 From: Maciej Strozek Date: Thu, 24 Sep 2026 10:54:58 +0100 Subject: [PATCH 227/535] ASoC: SDCA: Add better pm_runtime error handling in func probe Add a common error path in probe that balances the runtime PM reference. Fixes: 3af1815a2f9c ("ASoC: SDCA: Add basic SDCA function driver") Signed-off-by: Maciej Strozek Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20260924095458.2683755-3-mstrozek@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/sdca/sdca_class_function.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 5039230280ad..610931ae5531 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -388,20 +388,27 @@ static int class_function_probe(struct auxiliary_device *auxdev, ret = devm_pm_runtime_enable(dev); if (ret) - return ret; + goto err_pm; ret = class_function_boot(drv); if (ret) - return ret; + goto err_pm; ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais); - if (ret) - return dev_err_probe(dev, ret, "failed to register component\n"); + if (ret) { + dev_err_probe(dev, ret, "failed to register component\n"); + goto err_pm; + } pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; + +err_pm: + pm_runtime_put_sync(dev); + + return ret; } static void class_function_remove(struct auxiliary_device *auxdev) From d431e03d831c043707e22311c4bdea1907166c34 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 19 May 2026 22:30:49 +0200 Subject: [PATCH 228/535] drbd: remove unused drbd_nl_mcgrps[] array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the rework, two files have a copy of drbd_nl_mcgrps[], but one of them has no references: drivers/block/drbd/drbd_nl_gen.c:641:42: error: 'drbd_nl_mcgrps' defined but not used [-Werror=unused-const-variable=] 641 | static const struct genl_multicast_group drbd_nl_mcgrps[] = { | ^~~~~~~~~~~~~~ At the default warning level, -Wunused-const-variables is turned off, so this has gone unnoticed. Remove the extra variable. Fixes: 8098eeb693c4 ("drbd: replace genl_magic with explicit netlink serialization") Signed-off-by: Arnd Bergmann Reviewed-by: Christoph Böhmwalder Link: https://patch.msgid.link/20260519203057.1340528-1-arnd@kernel.org Signed-off-by: Jens Axboe --- drivers/block/drbd/drbd_nl_gen.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/block/drbd/drbd_nl_gen.c b/drivers/block/drbd/drbd_nl_gen.c index fb44b948cec8..e133e8415205 100644 --- a/drivers/block/drbd/drbd_nl_gen.c +++ b/drivers/block/drbd/drbd_nl_gen.c @@ -638,10 +638,6 @@ const struct genl_split_ops drbd_nl_ops[32] = { }, }; -static const struct genl_multicast_group drbd_nl_mcgrps[] = { - [DRBD_NLGRP_EVENTS] = { "events", }, -}; - static int __drbd_cfg_context_from_attrs(struct drbd_cfg_context *s, struct nlattr ***ret_nested_attribute_table, struct genl_info *info) From 11536ee3d3e0b1bd35b6f3f8df55a6053eb0c71d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 23 Sep 2026 08:32:03 +0000 Subject: [PATCH 229/535] MAINTAINERS: update Eric Dumazet's email address My @google.com address is going away soon, let's switch to @kernel.org. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260923083203.383923-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- .mailmap | 2 ++ MAINTAINERS | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.mailmap b/.mailmap index 389b94a0124e..b8d0dc185714 100644 --- a/.mailmap +++ b/.mailmap @@ -286,6 +286,8 @@ Ed L. Cashin Elliot Berman Enric Balletbo i Serra Enric Balletbo i Serra +Eric Dumazet +Eric Dumazet Erik Kaneda Ethan Carter Edwards Ethan Edwards Eugen Hristev diff --git a/MAINTAINERS b/MAINTAINERS index c52ec2d7d3c1..c915b114c99c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18885,7 +18885,7 @@ F: net/core/drop_monitor.c NETWORKING DRIVERS M: Andrew Lunn M: "David S. Miller" -M: Eric Dumazet +M: Eric Dumazet M: Jakub Kicinski M: Paolo Abeni L: netdev@vger.kernel.org @@ -19002,7 +19002,7 @@ K: struct\s+phy_port|phy_port_ NETWORKING [GENERAL] M: "David S. Miller" -M: Eric Dumazet +M: Eric Dumazet M: Jakub Kicinski M: Paolo Abeni R: Simon Horman @@ -19228,7 +19228,7 @@ F: net/ipv6/seg6* F: tools/testing/selftests/net/srv6* NETWORKING [TCP] -M: Eric Dumazet +M: Eric Dumazet M: Neal Cardwell R: Kuniyuki Iwashima L: netdev@vger.kernel.org @@ -19261,7 +19261,7 @@ F: net/tls/ F: tools/testing/selftests/net/tls.c NETWORKING [SOCKETS] -M: Eric Dumazet +M: Eric Dumazet M: Kuniyuki Iwashima M: Paolo Abeni M: Willem de Bruijn From ad139384ecddb6f3e7e3c3c12765aafae0e39670 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Thu, 24 Sep 2026 18:23:53 +0000 Subject: [PATCH 230/535] bpf: Fix overflow of jump offset in constant blinding bpf_jit_blind_insn() rewrites 'if rX op imm goto off' into: AX = imm ^ rnd AX ^= rnd if rX op AX goto off and subtracts 2 from off when the jump is backward. off is s16, so -32767 and -32768 wrap and the jump goes 32767 insns forward, past the end of the prog. The jump is inside the patch, hence bpf_adj_branches() doesn't check it. The kernel crashes when such prog runs with bpf_jit_harden=2. Return -ERANGE in such case. bpf_jit_blind_constants() fails the same way when blinding of other insns moves a jump out of range. Fixes: 4f3446bb809f ("bpf: add generic constant blinding for use in jits") Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260924182353.277516-1-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 2e3bf8113ae9..7f11555a5070 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1362,7 +1362,7 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, { struct bpf_insn *to = to_buff; u32 imm_rnd = get_random_u32(); - s16 off; + int off; BUILD_BUG_ON(BPF_REG_PARAMS + 2 != MAX_BPF_JIT_REG); BUILD_BUG_ON(BPF_REG_AX + 1 != MAX_BPF_JIT_REG); @@ -1438,6 +1438,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, off = from->off; if (off < 0) off -= 2; + if (off < S16_MIN) + return -ERANGE; *to++ = BPF_ALU64_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm); *to++ = BPF_ALU64_IMM(BPF_XOR, BPF_REG_AX, imm_rnd); *to++ = BPF_JMP_REG(from->code, from->dst_reg, BPF_REG_AX, off); @@ -1458,6 +1460,8 @@ static int bpf_jit_blind_insn(const struct bpf_insn *from, off = from->off; if (off < 0) off -= 2; + if (off < S16_MIN) + return -ERANGE; *to++ = BPF_ALU32_IMM(BPF_MOV, BPF_REG_AX, imm_rnd ^ from->imm); *to++ = BPF_ALU32_IMM(BPF_XOR, BPF_REG_AX, imm_rnd); *to++ = BPF_JMP32_REG(from->code, from->dst_reg, BPF_REG_AX, @@ -1606,7 +1610,9 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp if (!rewritten) continue; - if (env) + if (rewritten < 0) + tmp = ERR_PTR(rewritten); + else if (env) tmp = bpf_patch_insn_data(env, i, insn_buff, rewritten); else tmp = bpf_patch_insn_single(clone, i, insn_buff, rewritten); From f050c3e61d2a1aaece3170d459e4ce5fa2486c12 Mon Sep 17 00:00:00 2001 From: Vineet Gupta Date: Wed, 23 Sep 2026 15:01:38 -0700 Subject: [PATCH 231/535] ARC: arch_cmpxchg_relaxed to use size of pointed type not pointer Bradley spotted this snafu when working on two-byte cmpxchg emulation. He originally folded this fix into his series but the fix itself uncovered a pre-existing issue, missing arch_atomic64_cmpxchg_relaxed which just needed to be wired up. Fixes: e188f3330a13 ("ARC: cmpxchg/xchg: rewrite as macros to make type safe") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609240539.AkulvjVe-lkp@intel.com/ Suggested-by: Bradley Morgan Signed-off-by: Vineet Gupta --- arch/arc/include/asm/atomic64-arcv2.h | 6 ++++++ arch/arc/include/asm/cmpxchg.h | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/arc/include/asm/atomic64-arcv2.h b/arch/arc/include/asm/atomic64-arcv2.h index 73080a664369..1ca4a1047fb3 100644 --- a/arch/arc/include/asm/atomic64-arcv2.h +++ b/arch/arc/include/asm/atomic64-arcv2.h @@ -156,6 +156,12 @@ static inline u64 __arch_cmpxchg64_relaxed(volatile void *ptr, u64 old, u64 new) } #define arch_cmpxchg64_relaxed __arch_cmpxchg64_relaxed +static inline s64 arch_atomic64_cmpxchg_relaxed(atomic64_t *v, s64 old, s64 new) +{ + return arch_cmpxchg64_relaxed(&v->counter, old, new); +} +#define arch_atomic64_cmpxchg_relaxed arch_atomic64_cmpxchg_relaxed + static inline s64 arch_atomic64_xchg(atomic64_t *ptr, s64 new) { s64 prev; diff --git a/arch/arc/include/asm/cmpxchg.h b/arch/arc/include/asm/cmpxchg.h index 76f43db0890f..66b3743c9400 100644 --- a/arch/arc/include/asm/cmpxchg.h +++ b/arch/arc/include/asm/cmpxchg.h @@ -46,7 +46,7 @@ __typeof__(*(ptr)) _n_ = (new); \ __typeof__(*(ptr)) _prev_; \ \ - switch(sizeof((_p_))) { \ + switch (sizeof(*(_p_))) { \ case 1: \ _prev_ = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *__force)_p_, (uintptr_t)_o_, (uintptr_t)_n_); \ break; \ @@ -102,7 +102,7 @@ __typeof__(ptr) _p_ = (ptr); \ __typeof__(*(ptr)) _val_ = (val); \ \ - switch(sizeof(*(_p_))) { \ + switch (sizeof(*(_p_))) { \ case 4: \ _val_ = __arch_xchg(_p_, _val_); \ break; \ From 3afefbfe55c2a8a0c4bdf6f4cc1f773da120027c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 15 Sep 2026 17:46:57 +0200 Subject: [PATCH 232/535] HID: bpf: cast size to ssize_t when checking hid_bpf_hw_request As reported by Sashiko: If a transport driver encounters a hardware error and returns a negative error code such as -EPIPE, ret is implicitly promoted to size_t when compared against size. This causes the negative error code to evaluate as a large positive number, making the (ret > size) condition true. This silently converts the hardware error into a success return value and copies the unmodified buffer back, which could leave BPF programs operating on uninitialized or stale data. Fix this by casting size into ssize_t to return the actual negative error code. Link: https://lore.kernel.org/all/20260904130354.A79471F00A3D@smtp.kernel.org/ Fixes: 2b658c1c442e ("HID: bpf: prevent buffer overflow in hid_hw_request") Cc: stable@vger.kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- drivers/hid/bpf/hid_bpf_dispatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index b1de1dd0f21d..ece2c7c45ea6 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -453,7 +453,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, (u64)(long)ctx, true); /* prevent infinite recursions */ - if (ret > size) + if (ret > (ssize_t)size) ret = size; if (ret > 0) memcpy(buf, dma_data, ret); From 3f35b678a1d6b4c2dc773ad73623b1515cfc5bc5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 15 Sep 2026 17:46:58 +0200 Subject: [PATCH 233/535] selftest/hid: add test for negative return codes for hid_bpf_hw_request If the transport driver returns an error, we should ensure we actually get the error, not a success value. Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_bpf.c | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 32d81ba15a25..8c41ebfb85e4 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -546,6 +546,37 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) ASSERT_EQ(args.data[1], 2); } +/* + * Call hid_hw_raw_request against the given uhid device, + * check that the program is called and does the expected. + */ +TEST_F(hid_bpf, test_hid_user_raw_request_call_eio) +{ + struct hid_hw_request_syscall_args args = { + .retval = -1, + .type = HID_FEATURE_REPORT, + .request_type = HID_REQ_GET_PROTOCOL, + .size = MAX_BUF_SIZE, + }; + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, + .ctx_in = &args, + .ctx_size_in = sizeof(args), + ); + int err, prog_fd; + + LOAD_BPF; + + args.hid = self->hid.hid_id; + args.data[0] = 1; /* report ID */ + + prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request); + + err = bpf_prog_test_run_opts(prog_fd, &tattrs); + ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); + + ASSERT_EQ(args.retval, -EIO); +} + /* * Call hid_hw_raw_request against the given uhid device, * check that the program is called and prevents the From 5dad87615c9861cfa366ca984b52f581e861df20 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 25 Sep 2026 11:42:22 +0200 Subject: [PATCH 234/535] tty: add break_wait kernel-doc Add the missing kernel-doc description for the new break_wait field in struct tty_struct. Fixes: 845a2737e0f5 ("tty: abort break signalling on hangup") Reported-by: Randy Dunlap Link: https://lore.kernel.org/20d75a7a-2e7a-484f-a267-c21db82dd922@infradead.org Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260925094222.18303-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/tty.h b/include/linux/tty.h index ad4e4a835f90..833bb7b97ceb 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -168,6 +168,7 @@ struct tty_operations; * @write_wait: concurrent writers are waiting in this queue until they are * allowed to write * @read_wait: readers wait for data in this queue + * @break_wait: wait queue for timed breaks * @hangup_work: normally a work to perform a hangup (do_tty_hangup()); while * freeing the tty, (re)used to release_one_tty() * @disc_data: pointer to @ldisc's private data (e.g. to &struct n_tty_data) From 1c1a342aceec79528b3ba51f376eca2be5928fe7 Mon Sep 17 00:00:00 2001 From: Sagnik Sasmal Date: Thu, 10 Sep 2026 22:44:05 +0000 Subject: [PATCH 235/535] mtd: spinand: Do not update the QE bit on devices without one Commit be0b86c648bf ("mtd: spinand: Gather all the bus interface steps in one single function") moved quad-enable setup into spinand_configure_chip(). The new code only determines whether quad mode is needed when SPINAND_HAS_QE_BIT is set, but calls spinand_init_quad_enable() unconditionally. This clears configuration register bit 0 on devices without a QE bit. That bit is not universally a QE bit. On the Winbond W25N02KV it is H-DIS, which disables the active-low HOLD function. Clearing H-DIS enables HOLD during single and dual I/O operations. If IO3 is not kept high, the flash can pause a command and ignore clock and data. H-DIS is not restored by the FFh reset command, allowing the incorrect state to survive an SoC warm reboot while the flash remains powered. Before the refactoring, spinand_init_quad_enable() returned without touching the configuration register on devices without SPINAND_HAS_QE_BIT. Restore that behavior by only calling the helper when the flag is set. Return zero explicitly once SSDR configuration completes, as all errors are returned immediately. This avoids returning an uninitialized value when neither optional configuration step runs. The regression was reproduced on a JioRouter JIDU6401 with an MT7986 SoC and a W25N02KV. With Linux 6.18.44, sysupgrade failed and the following warm reboot hung in BL2. With this change applied, both sysupgrade and warm reboot completed successfully. Fixes: be0b86c648bf ("mtd: spinand: Gather all the bus interface steps in one single function") Cc: stable@vger.kernel.org Suggested-by: Miquel Raynal Assisted-by: LLM Signed-off-by: Sagnik Sasmal Signed-off-by: Miquel Raynal --- drivers/mtd/nand/spi/core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 43bbe2629b85..95353777d7f7 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1813,11 +1813,11 @@ static int spinand_configure_chip(struct spinand_device *spinand) spinand->ssdr_op_templates.write_cache->data.buswidth == 4 || spinand->ssdr_op_templates.update_cache->data.buswidth == 4) quad_enable = true; - } - ret = spinand_init_quad_enable(spinand, quad_enable); - if (ret) - return ret; + ret = spinand_init_quad_enable(spinand, quad_enable); + if (ret) + return ret; + } if (spinand->configure_chip) { ret = spinand->configure_chip(spinand, SSDR); @@ -1825,7 +1825,7 @@ static int spinand_configure_chip(struct spinand_device *spinand) return ret; } - return ret; + return 0; } static int spinand_init_flash(struct spinand_device *spinand) From 7fc61cdab13004a5e6dff5b279918b19b62eb200 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Sep 2026 16:48:52 +0900 Subject: [PATCH 236/535] Revert "nvme-tcp: lockdep: use dynamic lockdep keys per socket instance" This reverts commit 19bdb70c77d3b24239a453291299b64040bdba86. The commit 19bdb70c77d3 ("nvme-tcp: lockdep: use dynamic lockdep keys per socket instance") addressed the lockdep WARN caused by the circular lock dependency among six locks: set->srcu -> sk_lock -> cpu_hotplug_lock -> fs_reclaim -> q_usage_counter -> elevator_lock -> set->srcu As its title says, the commit cut the dependency by introducing the dynamic lockdep keys per socket instance. However, as described in the Link tag URL, the commit made a wrong assumption: it assumed that __fput_sync(queue->sock->file) in nvme_tcp_free_queue() would synchronously destroy the socket. This is wrong: when in-flight packets cause delayed free of the socket, the prematurely freed lockdep key is referred to and causes another WARN. The commit is an imperfect fix. Hence revert it. To address the circular dependency among the six locks, another solution is required. It is provided by the commit 0ba6912f7e97 ("Revert "once: don't use a work queue to reset sleepable static key""). It cuts the dependency between sk_lock and cpu_hotplug_lock. This solution is simpler, and reduces the complexity in nvme-tcp. Link: https://lore.kernel.org/lkml/CANn89i+wnTLC==UnXCpjsS4YxvEfhe5oK0N7fttbqr1zKyqdug@mail.gmail.com/ Reviewed-by: Eric Dumazet Reviewed-by: Nilay Shroff Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 921934028e0b..0666bb469c0a 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -144,11 +144,6 @@ struct nvme_tcp_queue { void (*state_change)(struct sock *); void (*data_ready)(struct sock *); void (*write_space)(struct sock *); - -#ifdef CONFIG_DEBUG_LOCK_ALLOC - struct lock_class_key nvme_tcp_sk_key; - struct lock_class_key nvme_tcp_slock_key; -#endif }; static DEFINE_MUTEX(nvme_tcp_ctrl_mutex); @@ -185,9 +180,12 @@ static int nvme_tcp_try_send(struct nvme_tcp_queue *queue); * a separate class prevents lockdep from conflating nvme-tcp socket use with * user-space socket API use. */ -static void nvme_tcp_reclassify_socket(struct nvme_tcp_queue *queue) +static struct lock_class_key nvme_tcp_sk_key[2]; +static struct lock_class_key nvme_tcp_slock_key[2]; + +static void nvme_tcp_reclassify_socket(struct socket *sock) { - struct sock *sk = queue->sock->sk; + struct sock *sk = sock->sk; if (WARN_ON_ONCE(!sock_allow_reclassification(sk))) return; @@ -195,20 +193,22 @@ static void nvme_tcp_reclassify_socket(struct nvme_tcp_queue *queue) switch (sk->sk_family) { case AF_INET: sock_lock_init_class_and_name(sk, "slock-AF_INET-NVME", - &queue->nvme_tcp_slock_key, + &nvme_tcp_slock_key[0], "sk_lock-AF_INET-NVME", - &queue->nvme_tcp_sk_key); + &nvme_tcp_sk_key[0]); break; case AF_INET6: sock_lock_init_class_and_name(sk, "slock-AF_INET6-NVME", - &queue->nvme_tcp_slock_key, + &nvme_tcp_slock_key[1], "sk_lock-AF_INET6-NVME", - &queue->nvme_tcp_sk_key); + &nvme_tcp_sk_key[1]); break; default: WARN_ON_ONCE(1); } } +#else +static void nvme_tcp_reclassify_socket(struct socket *sock) { } #endif static inline struct nvme_tcp_ctrl *to_tcp_ctrl(struct nvme_ctrl *ctrl) @@ -1523,11 +1523,6 @@ static void nvme_tcp_free_queue(struct nvme_ctrl *nctrl, int qid) mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); mutex_destroy(&queue->pf_cache_lock); - -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_unregister_key(&queue->nvme_tcp_sk_key); - lockdep_unregister_key(&queue->nvme_tcp_slock_key); -#endif } static int nvme_tcp_init_connection(struct nvme_tcp_queue *queue) @@ -1874,12 +1869,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, } sk_net_refcnt_upgrade(queue->sock->sk); - -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_register_key(&queue->nvme_tcp_sk_key); - lockdep_register_key(&queue->nvme_tcp_slock_key); - nvme_tcp_reclassify_socket(queue); -#endif + nvme_tcp_reclassify_socket(queue->sock); /* Single syn retry */ tcp_sock_set_syncnt(queue->sock->sk, 1); @@ -1984,10 +1974,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, /* Use sync variant - see nvme_tcp_free_queue() for explanation */ __fput_sync(queue->sock->file); queue->sock = NULL; -#ifdef CONFIG_DEBUG_LOCK_ALLOC - lockdep_unregister_key(&queue->nvme_tcp_sk_key); - lockdep_unregister_key(&queue->nvme_tcp_slock_key); -#endif err_destroy_mutex: mutex_destroy(&queue->send_mutex); mutex_destroy(&queue->queue_lock); From 447a7a3026eda85ac2738cbe6ee5ab066a39dad0 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 14 Sep 2026 16:48:53 +0900 Subject: [PATCH 237/535] nvme-tcp: delay nvme_tcp_reclassify_socket() Commit 841aee4d75f1 ("nvme-tcp: lockdep: annotate in-kernel sockets") introduced nvme_tcp_reclassify_socket() to resolve a lockdep WARN by distinguishing userspace sockets from nvme-tcp sockets. After that, TLS encryption support was introduced to nvme-tcp and it added another lock dependency on userspace socket handling by tlshd, which resulted in a new lockdep WARN. This WARN was hidden by the commit 19bdb70c77d3 ("nvme-tcp: lockdep: use dynamic lockdep keys per socket instance"). However, it turned out the commit 19bdb70c77d3 has a bug of lockdep key lifetime management, and it is to be reverted by another patch in this series. After the revert, the WARN was unveiled and observed at the blktests test case nvme/062: WARNING: possible circular locking dependency detected 7.3.0-rc2+ #455 Tainted: G W ------------------------------------------------------ tlshd/75867 is trying to acquire lock: ffffffff91ada5e0 (fs_reclaim){+.+.}-{0:0}, at: __kmalloc_cache_noprof+0x64/0x6d0 but task is already holding lock: ffff888197808258 (sk_lock-AF_INET-NVME){+.+.}-{0:0}, at: do_tcp_setsockopt+0x499/0x26a0 which lock already depends on the new lock. To fix the lockdep WARN caused by the TLS encryption support, delay the reclassification of the nvme-tcp sockets. Currently the reclassification happens before the TLS handshake starts, which pulls in the additional lock dependencies related to tlshd. Reclassify the sockets after the TLS handshake instead, to cut that dependency. Link: https://lore.kernel.org/lkml/CANn89i+wnTLC==UnXCpjsS4YxvEfhe5oK0N7fttbqr1zKyqdug@mail.gmail.com/ Reviewed-by: Nilay Shroff Signed-off-by: Eric Dumazet Signed-off-by: Shin'ichiro Kawasaki Signed-off-by: Keith Busch --- drivers/nvme/host/tcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c index 0666bb469c0a..7eec983c0ead 100644 --- a/drivers/nvme/host/tcp.c +++ b/drivers/nvme/host/tcp.c @@ -1869,7 +1869,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, } sk_net_refcnt_upgrade(queue->sock->sk); - nvme_tcp_reclassify_socket(queue->sock); /* Single syn retry */ tcp_sock_set_syncnt(queue->sock->sk, 1); @@ -1958,6 +1957,8 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid, goto err_init_connect; } + nvme_tcp_reclassify_socket(queue->sock); + ret = nvme_tcp_init_connection(queue); if (ret) goto err_init_connect; From c20bba234f3e1dcd81f7a2ec21ea7bf01a4cf35f Mon Sep 17 00:00:00 2001 From: Maurizio Lombardi Date: Tue, 15 Sep 2026 11:58:27 +0200 Subject: [PATCH 238/535] nvme: do not reset controllers in NVME_CTRL_NEW state During NVMe controller creation, the controller is exposed to sysfs via nvme_add_ctrl() while it is still in the NVME_CTRL_NEW state. This creates a narrow race window where a userspace process can write to the reset_controller sysfs node before the initialization thread transitions the state to NVME_CTRL_CONNECTING. If a reset is triggered during this window, the state machine allows the transition from NVME_CTRL_NEW to NVME_CTRL_RESETTING, and the reset work is queued. However, the original creation thread continues its execution, subsequently moving the state to NVME_CTRL_CONNECTING and finally to NVME_CTRL_LIVE. When the delayed reset work finally executes, it attempts to tear down the controller and transition the state to NVME_CTRL_CONNECTING. Because the state is now NVME_CTRL_LIVE, this transition fails, triggering a WARN_ON in the reset work. Fix this by removing NVME_CTRL_NEW from the allowed prior states for the NVME_CTRL_RESETTING transition. Fixes: 8bfc3b4c6f9d ("nvmet: switch loopback target state to connecting when resetting") Reported-by: syzbot+4a1d521d19d6321f5aad@syzkaller.appspotmail.com Reviewed-by: Daniel Wagner Signed-off-by: Maurizio Lombardi Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 758245c799a1..76f1fbe8d9b6 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -598,7 +598,6 @@ bool nvme_change_ctrl_state(struct nvme_ctrl *ctrl, break; case NVME_CTRL_RESETTING: switch (old_state) { - case NVME_CTRL_NEW: case NVME_CTRL_LIVE: changed = true; atomic_long_inc(&ctrl->nr_reset); From 6b8a5367e9e73a17561b8ce584ae77a43f712007 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 16 Sep 2026 13:44:51 +0200 Subject: [PATCH 239/535] nvme: work around -Wformat-security warning Passing a string variable into dev_set_name() causes a warning when building with -Wformat-security enabled: drivers/nvme/host/core.c: In function 'nvme_cdev_add': drivers/nvme/host/core.c:3911:9: error: format not a string literal and no format arguments [-Werror=format-security] 3911 | ret = dev_set_name(cdev_device, name); Remove the temporary strings and let dev_set_name() do the same thing internally. Fixes: 26acdaa357cd ("nvme: fix crash and memory leak during invalid cdev teardown") Reviewed-by: Nilay Shroff Reviewed-by: John Garry Reviewed-by: Damien Le Moal Signed-off-by: Arnd Bergmann Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 21 +++++++++------------ drivers/nvme/host/multipath.c | 12 +++++------- drivers/nvme/host/nvme.h | 6 +++--- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 76f1fbe8d9b6..0b61fc8634e5 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3931,9 +3931,9 @@ void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device) put_device(cdev_device); } -int nvme_cdev_add(const char *name, struct cdev *cdev, - struct device *cdev_device, - const struct file_operations *fops, struct module *owner) +int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, + const struct file_operations *fops, struct module *owner, + int ctrl, int head) { int minor, ret; @@ -3941,7 +3941,7 @@ int nvme_cdev_add(const char *name, struct cdev *cdev, if (minor < 0) return minor; - ret = dev_set_name(cdev_device, name); + ret = dev_set_name(cdev_device, "ng%dn%d", ctrl, head); if (ret) { ida_free(&nvme_ns_chr_minor_ida, minor); return ret; @@ -3982,17 +3982,14 @@ static const struct file_operations nvme_ns_chr_fops = { static void nvme_add_ns_cdev(struct nvme_ns *ns) { - char name[32]; - ns->cdev_device.parent = ns->ctrl->device; - snprintf(name, sizeof(name), "ng%dn%d", ns->ctrl->instance, - ns->head->instance); nvme_get_ns(ns); /* Undone in nvme_cdev_rel() */ - if (nvme_cdev_add(name, &ns->cdev, &ns->cdev_device, - &nvme_ns_chr_fops, ns->ctrl->ops->module)) { - dev_err(ns->ctrl->device, "Unable to create the %s device\n", - name); + if (nvme_cdev_add(&ns->cdev, &ns->cdev_device, + &nvme_ns_chr_fops, ns->ctrl->ops->module, + ns->ctrl->instance, ns->head->instance)) { + dev_err(ns->ctrl->device, "Unable to create the ng%dn%d device\n", + ns->ctrl->instance, ns->head->instance); nvme_put_ns(ns); return; } diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 75dbb58286a3..3d46c4f28a47 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -646,17 +646,15 @@ static const struct file_operations nvme_ns_head_chr_fops = { static void nvme_add_ns_head_cdev(struct nvme_ns_head *head) { - char name[32]; - head->cdev_device.parent = &head->subsys->dev; - snprintf(name, sizeof(name), "ng%dn%d", head->subsys->instance, - head->instance); nvme_get_ns_head(head); /* Undone in nvme_cdev_rel() */ - if (nvme_cdev_add(name, &head->cdev, &head->cdev_device, - &nvme_ns_head_chr_fops, THIS_MODULE)) { + if (nvme_cdev_add(&head->cdev, &head->cdev_device, + &nvme_ns_head_chr_fops, THIS_MODULE, + head->subsys->instance, head->instance)) { dev_err(disk_to_dev(head->disk), - "Unable to create the %s device\n", name); + "Unable to create the ng%dn%d device\n", + head->subsys->instance, head->instance); nvme_put_ns_head(head); return; } diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index 2cff9fcbf740..e0260f4d24fd 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -1013,9 +1013,9 @@ int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, void nvme_get_ns_head(struct nvme_ns_head *head); bool nvme_tryget_ns_head(struct nvme_ns_head *head); void nvme_put_ns_head(struct nvme_ns_head *head); -int nvme_cdev_add(const char *name, struct cdev *cdev, - struct device *cdev_device, - const struct file_operations *fops, struct module *owner); +int nvme_cdev_add(struct cdev *cdev, struct device *cdev_device, + const struct file_operations *fops, struct module *owner, + int ctrl, int head); void nvme_cdev_del(struct cdev *cdev, struct device *cdev_device); int nvme_ioctl(struct block_device *bdev, blk_mode_t mode, unsigned int cmd, unsigned long arg); From 7f4d0c359bbac4ba8691bf9b4213bf9ab07cd2a4 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 17 Sep 2026 08:04:03 +0200 Subject: [PATCH 240/535] nvme: work around all -Wformat-security warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sysfs code passes two string variables into sysfs_emit(), which is safe in this instance but causes the compiler to warn when -Wformat-security is enabled: host/sysfs.c: In function ‘cntrltype_show’: host/sysfs.c:682:9: error: format not a string literal and no format arguments [-Werror=format-security] 682 | return sysfs_emit(buf, type[ctrl->cntrltype]); Print these using a "%s" format like all other instances in the same file. Reviewed-by: Nilay Shroff Signed-off-by: Arnd Bergmann Signed-off-by: Keith Busch --- drivers/nvme/host/sysfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c index 02a2490a9ed7..e1e3dcfd084b 100644 --- a/drivers/nvme/host/sysfs.c +++ b/drivers/nvme/host/sysfs.c @@ -679,7 +679,7 @@ static ssize_t cntrltype_show(struct device *dev, if (ctrl->cntrltype > NVME_CTRL_ADMIN || !type[ctrl->cntrltype]) return sysfs_emit(buf, "reserved\n"); - return sysfs_emit(buf, type[ctrl->cntrltype]); + return sysfs_emit(buf, "%s", type[ctrl->cntrltype]); } static DEVICE_ATTR_RO(cntrltype); @@ -696,7 +696,7 @@ static ssize_t dctype_show(struct device *dev, if (ctrl->dctype > NVME_DCTYPE_CDC || !type[ctrl->dctype]) return sysfs_emit(buf, "reserved\n"); - return sysfs_emit(buf, type[ctrl->dctype]); + return sysfs_emit(buf, "%s", type[ctrl->dctype]); } static DEVICE_ATTR_RO(dctype); From 63fa2d9c9c37020bd2703fe2a6a38027b5abeb15 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 15 Sep 2026 11:19:56 +0900 Subject: [PATCH 241/535] nvme-multipath: fix underflow in ANA log bounds checks The number of ANA groups advertised through Identify Controller sizes the ANA log buffer. The ANA log header and group descriptors independently supply the number of groups and namespace IDs to parse. Both bounds checks subtract an untrusted object size from ana_log_size before comparing the current offset. If the object is larger than the buffer, the size_t subtraction underflows and lets the parser read beyond ana_log_buf. Check the current offset before the first subtraction and compare each object size with the remaining buffer instead. This rejects inconsistent ANA data before dereferencing a truncated group descriptor or walking an oversized namespace ID array. Fixes: 0d0b660f214d ("nvme: add ANA support") Cc: stable@vger.kernel.org Assisted-by: LLM Reviewed-by: Christoph Hellwig Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 3d46c4f28a47..7db6850f98d4 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -843,7 +843,8 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, u32 nr_nsids; size_t nsid_buf_size; - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - sizeof(*desc))) + if (WARN_ON_ONCE(offset > ctrl->ana_log_size || + sizeof(*desc) > ctrl->ana_log_size - offset)) return -EINVAL; nr_nsids = le32_to_cpu(desc->nnsids); @@ -859,7 +860,7 @@ static int nvme_parse_ana_log(struct nvme_ctrl *ctrl, void *data, return -EINVAL; offset += sizeof(*desc); - if (WARN_ON_ONCE(offset > ctrl->ana_log_size - nsid_buf_size)) + if (WARN_ON_ONCE(nsid_buf_size > ctrl->ana_log_size - offset)) return -EINVAL; error = cb(ctrl, desc, data); From 5d5fbfbbacd1efb13a9bf91317d4fde859ede4b9 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 15 Sep 2026 12:51:23 +0900 Subject: [PATCH 242/535] nvmet: pci-epf: reject too-short SGL segments The host controls an SGL segment's length. A nonzero length smaller than one SGL descriptor makes nr_descs zero, so nvmet_pci_epf_get_sgl_segment() reads the type byte of sgls[-1] outside the allocated buffer. If that byte contains a segment descriptor type, the function also copies the preceding 16 bytes and returns a negative descriptor count. The read was reproduced in 3/3 KASAN boots with only the host transfer mocked by a KUnit test: BUG: KASAN: slab-out-of-bounds in nvmet_pci_epf_get_sgl_segment Read of size 1 by task kunit_try_catch Call Trace: nvmet_pci_epf_get_sgl_segment nvmet_pci_epf_short_sgl_test Kernel panic - not syncing: KASAN: panic_on_warn set ... Reject segments that cannot hold one descriptor before allocating or transferring their contents. Fixes: 0faa0fe6f90e ("nvmet: New NVMe PCI endpoint function target driver") Cc: stable@vger.kernel.org Assisted-by: LLM Reviewed-by: Christoph Hellwig Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Signed-off-by: Keith Busch --- drivers/nvme/target/pci-epf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/nvme/target/pci-epf.c b/drivers/nvme/target/pci-epf.c index 9c28367435a5..c683450df422 100644 --- a/drivers/nvme/target/pci-epf.c +++ b/drivers/nvme/target/pci-epf.c @@ -978,6 +978,9 @@ nvmet_pci_epf_get_sgl_segment(struct nvmet_pci_epf_ctrl *ctrl, int nr_descs, ret; void *buf; + if (length < sizeof(struct nvme_sgl_desc)) + return NULL; + buf = kmalloc(length, GFP_KERNEL); if (!buf) return NULL; From fa6778e030bed9841ddcdf96bc7018da9ac03562 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Sun, 13 Sep 2026 01:10:11 +0800 Subject: [PATCH 243/535] nvmet-auth: fix out-of-bounds write in nvmet_auth_challenge() nvmet_auth_challenge() receives its output buffer as a void pointer. sizeof(*d) therefore evaluates to one with GCC and undercounts the fixed challenge header by 15 bytes. A short AUTH_RECEIVE buffer can pass the check before the challenge is copied past the end of its allocation. Use the typed challenge pointer when calculating the response size. BUG: KASAN: slab-out-of-bounds in nvmet_execute_auth_receive (drivers/nvme/target/fabrics-cmd-auth.c:442 drivers/nvme/target/fabrics-cmd-auth.c:569) Write of size 32 by task kworker/1:1H/64 Workqueue: nvmet_tcp_wq nvmet_tcp_io_work Call Trace: nvmet_execute_auth_receive (drivers/nvme/target/fabrics-cmd-auth.c:442 drivers/nvme/target/fabrics-cmd-auth.c:569) nvmet_tcp_try_recv_pdu (drivers/nvme/target/tcp.c:1119 drivers/nvme/target/tcp.c:1243) nvmet_tcp_io_work (drivers/nvme/target/tcp.c:1350 drivers/nvme/target/tcp.c:1382 drivers/nvme/target/tcp.c:1445) process_one_work (kernel/workqueue.c:3322) worker_thread (kernel/workqueue.c:3405 kernel/workqueue.c:3486) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) The buggy address is located 16 bytes inside of allocated 33-byte region [ffff888000554e80, ffff888000554ea1) Kernel panic - not syncing: KASAN: panic_on_warn set ... Cc: stable@vger.kernel.org Fixes: db1312dd9548 ("nvmet: implement basic In-Band Authentication") Reported-by: co+77553a7fc66ac133@bugs.sh Closes: https://lore.kernel.org/all/Ah6QavQXwvshtqyUcYD1P1R7XE0ND9fB7lbV@bugs.sh/ Assisted-by: Codex:gpt-5.6 Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Weiming Shi Signed-off-by: Keith Busch --- drivers/nvme/target/fabrics-cmd-auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvme/target/fabrics-cmd-auth.c b/drivers/nvme/target/fabrics-cmd-auth.c index 92f8a76f10ff..dccdb55a367c 100644 --- a/drivers/nvme/target/fabrics-cmd-auth.c +++ b/drivers/nvme/target/fabrics-cmd-auth.c @@ -427,7 +427,7 @@ static int nvmet_auth_challenge(struct nvmet_req *req, void *d, int al) struct nvmet_ctrl *ctrl = req->sq->ctrl; int ret = 0; int hash_len = nvme_auth_hmac_hash_len(ctrl->shash_id); - int data_size = sizeof(*d) + hash_len; + int data_size = sizeof(*data) + hash_len; if (ctrl->dh_tfm) data_size += ctrl->dh_keysize; From 5211fed41d7ec90e9ab7239dc4bffffe4d0b2b62 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Wed, 16 Sep 2026 19:42:54 +0800 Subject: [PATCH 244/535] nvmet: copy the hostid into the ctrl before creating PR pc_refs Commit 6202783184bf ("nvmet: Improve nvmet_alloc_ctrl() interface and implementation") added a second uuid_copy() of args->hostid near the end of nvmet_alloc_ctrl(), and commit 7b658153f1b8 ("nvmet: Remove duplicate uuid_copy") removed the original copy that sat before nvmet_ctrl_init_pr() instead of the new one. Since then nvmet_ctrl_init_pr() snapshots ctrl->hostid into the per-controller per-namespace reservation refs while the uuid_copy() from the connect data runs later, after the controller is published. The ctrl is allocated with kzalloc(), so every pc_ref created on this path stores the nil UUID. pc_ref->hostid has a single consumer: nvmet_pr_set_ctrl_to_abort() matches it against the preempted registrant's hostid to kill and drain the victim's in-flight I/O for Preempt and Abort. The match can never hit with the nil UUID, so whenever a namespace with reservations enabled exists before a host connects, which includes every reconnect, Preempt and Abort silently degrades into a plain Preempt: the preempting host sees success while the victim's in-flight I/O is still in the air. Copy the hostid where the rest of the connect data is consumed, before the controller is published and before nvmet_ctrl_init_pr() takes its snapshot. Fixes: 7b658153f1b8 ("nvmet: Remove duplicate uuid_copy") Cc: stable@vger.kernel.org Reviewed-by: Nilay Shroff Reviewed-by: Christoph Hellwig Reviewed-by: Hannes Reinecke Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 43871a8f56ca..8c8c8627871f 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -1648,6 +1648,8 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) INIT_DELAYED_WORK(&ctrl->ka_work, nvmet_keep_alive_timer); memcpy(ctrl->hostnqn, args->hostnqn, NVMF_NQN_SIZE); + if (args->hostid) + uuid_copy(&ctrl->hostid, args->hostid); kref_init(&ctrl->ref); ctrl->subsys = subsys; @@ -1706,9 +1708,6 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args) nvmet_start_keep_alive_timer(ctrl); - if (args->hostid) - uuid_copy(&ctrl->hostid, args->hostid); - dhchap_status = nvmet_setup_auth(ctrl, args->sq, false); if (dhchap_status) { pr_err("Failed to setup authentication, dhchap status %u\n", From 42b0f5196dc2f2113368993317eccbfd1127f298 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Fri, 18 Sep 2026 21:57:30 +0530 Subject: [PATCH 245/535] nvmet: defer setting ns->enabled to false in nvmet_ns_disable() nvmet_ns_disable() currently clears ns->enabled before draining in-flight I/O references. This allows namespace configuration to be changed while existing I/O requests can still hold a reference to the namespace. This can race with configuration of namespace attributes such as the device path, UUID, NGUID etc. These attributes can be accessed by I/O requests without holding subsys->lock and must not be modified while such requests are still using the namespace. In nvmet_ns_disable(), keep ns->enabled set while existing namespace references are being drained, so namespace configuration remains blocked until all in-flight I/O has completed. Set ns->enabled to false only after the namespace references have been drained and the namespace device has been disabled. Introduce the NVMET_NS_IO_LIVE flag, which is set after the namespace is successfully enabled in nvmet_ns_enable(). When nvmet_ns_disable() starts, clear NVMET_NS_IO_LIVE so that the I/O path stops admitting new I/O once the flag is cleared. Using test_and_clear_bit() in nvmet_ns_disable() also prevents concurrent callers from starting a second disable operation. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 14 ++++++++------ drivers/nvme/target/nvmet.h | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 8c8c8627871f..059b14da845a 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -448,7 +448,8 @@ u16 nvmet_req_find_ns(struct nvmet_req *req) struct nvmet_subsys *subsys = nvmet_req_subsys(req); req->ns = xa_load(&subsys->namespaces, nsid); - if (unlikely(!req->ns || !req->ns->enabled)) { + if (unlikely(!req->ns) || + !test_bit(NVMET_NS_IO_LIVE, &req->ns->flags)) { req->error_loc = offsetof(struct nvme_common_command, nsid); if (!req->ns) /* ns doesn't exist! */ return NVME_SC_INVALID_NS | NVME_STATUS_DNR; @@ -623,6 +624,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns) ns->enabled = true; xa_set_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); nvmet_debugfs_ns_setup(ns); + set_bit(NVMET_NS_IO_LIVE, &ns->flags); ret = 0; out_unlock: mutex_unlock(&subsys->lock); @@ -643,11 +645,11 @@ void nvmet_ns_disable(struct nvmet_ns *ns) struct nvmet_subsys *subsys = ns->subsys; struct nvmet_ctrl *ctrl; - mutex_lock(&subsys->lock); - if (!ns->enabled) - goto out_unlock; + if (!test_and_clear_bit(NVMET_NS_IO_LIVE, &ns->flags)) + return; + + mutex_lock(&subsys->lock); - ns->enabled = false; xa_clear_mark(&subsys->namespaces, ns->nsid, NVMET_NS_ENABLED); nvmet_debugfs_ns_free(ns); @@ -675,7 +677,7 @@ void nvmet_ns_disable(struct nvmet_ns *ns) mutex_lock(&subsys->lock); nvmet_ns_changed(subsys, ns->nsid); nvmet_ns_dev_disable(ns); -out_unlock: + ns->enabled = false; mutex_unlock(&subsys->lock); } diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h index dbda55895f4f..162e2fdd848e 100644 --- a/drivers/nvme/target/nvmet.h +++ b/drivers/nvme/target/nvmet.h @@ -112,6 +112,8 @@ struct nvmet_ns { bool buffered_io; bool enabled; +#define NVMET_NS_IO_LIVE 0 + unsigned long flags; struct nvmet_subsys *subsys; const char *device_path; From 3fdd281e0a049a804b2512c4cb3d685e5358aee8 Mon Sep 17 00:00:00 2001 From: Nilay Shroff Date: Fri, 18 Sep 2026 21:57:31 +0530 Subject: [PATCH 246/535] nvmet: don't allow I/O admission after percpu ns reference is killed nvmet_req_find_ns() uses percpu_ref_get() to obtain a reference to the namespace. However, percpu_ref_get() can acquire a reference even after the namespace reference has been killed (or marked DEAD). This is undesirable during namespace disable because nvmet_ns_disable() kills the namespace reference and then waits for all outstanding references to drain. Acquiring a new reference after the reference is killed can therefore extend the namespace drain period. Replace percpu_ref_get() in nvmet_req_find_ns() with percpu_ref_tryget_live_rcu(), which only acquires a reference while the namespace reference is still live. This handles the race where nvmet_req_find_ns() finds ns is admitting I/O (or it's live) but before it acquires the reference to ns, its reference is killed in nvmet_ns_disable(). For instance check this race: CPU0 CPU1 nvmet_req_find_ns(): nvmet_ns_disable(): xa_load() -> ns IO_LIVE == set xa_clear_mark() percpu_ref_kill() // DEAD percpu_ref_get() synchronize_rcu() | wait_for_completion() +-- succeed Replacing percpu_ref_get() with percpu_ref_tryget_live_rcu() prevents the I/O request from acquiring a namespace reference once the reference has been marked DEAD. Perform the namespace lookup and reference acquisition in nvmet_req_find_ns() within an RCU read-side critical section. nvmet_ns_disable() uses synchronize_rcu() before draining and exiting the namespace reference, ensuring that RCU readers which may be acquiring the namespace reference have completed before the reference is exited. Reviewed-by: Christoph Hellwig Signed-off-by: Nilay Shroff Signed-off-by: Keith Busch --- drivers/nvme/target/core.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c index 059b14da845a..8eea0a504308 100644 --- a/drivers/nvme/target/core.c +++ b/drivers/nvme/target/core.c @@ -446,21 +446,27 @@ u16 nvmet_req_find_ns(struct nvmet_req *req) { u32 nsid = le32_to_cpu(req->cmd->common.nsid); struct nvmet_subsys *subsys = nvmet_req_subsys(req); + u16 status = NVME_SC_SUCCESS; + rcu_read_lock(); req->ns = xa_load(&subsys->namespaces, nsid); if (unlikely(!req->ns) || - !test_bit(NVMET_NS_IO_LIVE, &req->ns->flags)) { + !test_bit(NVMET_NS_IO_LIVE, &req->ns->flags) || + !percpu_ref_tryget_live_rcu(&req->ns->ref)) { req->error_loc = offsetof(struct nvme_common_command, nsid); - if (!req->ns) /* ns doesn't exist! */ - return NVME_SC_INVALID_NS | NVME_STATUS_DNR; + if (!req->ns) { /* ns doesn't exist! */ + status = NVME_SC_INVALID_NS | NVME_STATUS_DNR; + goto unlock; + } /* ns exists but it's disabled */ req->ns = NULL; - return NVME_SC_INTERNAL_PATH_ERROR; + status = NVME_SC_INTERNAL_PATH_ERROR; } +unlock: + rcu_read_unlock(); - percpu_ref_get(&req->ns->ref); - return NVME_SC_SUCCESS; + return status; } static void nvmet_destroy_namespace(struct percpu_ref *ref) From 72ece656abd4de8645a421fb482d7a11e6533a45 Mon Sep 17 00:00:00 2001 From: Yao Sang Date: Tue, 22 Sep 2026 15:37:30 +0800 Subject: [PATCH 247/535] nvme: fix command effects log lifetime for multipath heads KASAN reported a use-after-free when an I/O passthrough command was sent through a multipath namespace head after the controller path that first created the head had been removed: BUG: KASAN: slab-use-after-free in nvme_command_effects+0x192/0x200 [nvme_core] Read of size 4 at addr ffff888141b14400 by task nvme/19811 nvme_command_effects+0x192/0x200 [nvme_core] nvme_cmd_allowed+0x7e/0x1b0 [nvme_core] nvme_user_cmd.constprop.0+0x1b5/0x450 [nvme_core] nvme_ns_head_chr_ioctl+0xf4/0x2a0 [nvme_core] Move the log cache to the subsystem, with one entry per command set. Both controllers and namespace heads hold subsystem references, keeping their log pointers valid until subsystem release. Fixes: be93e87e7802 ("nvme: support for multiple Command Sets Supported and Effects log pages") Reviewed-by: Christoph Hellwig Signed-off-by: Yao Sang Signed-off-by: Keith Busch --- drivers/nvme/host/core.c | 41 +++++++++++++++++++--------------------- drivers/nvme/host/nvme.h | 3 ++- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c index 0b61fc8634e5..ee7d09030c18 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -3195,9 +3195,16 @@ static void nvme_release_subsystem(struct device *dev) { struct nvme_subsystem *subsys = container_of(dev, struct nvme_subsystem, dev); + struct nvme_effects_log *cel; + unsigned long i; if (subsys->instance >= 0) ida_free(&nvme_instance_ida, subsys->instance); + xa_for_each(&subsys->cels, i, cel) { + xa_erase(&subsys->cels, i); + kfree(cel); + } + xa_destroy(&subsys->cels); kfree(subsys); } @@ -3311,6 +3318,7 @@ static int nvme_init_subsystem(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) kref_init(&subsys->ref); INIT_LIST_HEAD(&subsys->ctrls); INIT_LIST_HEAD(&subsys->nsheads); + xa_init(&subsys->cels); nvme_init_subnqn(subsys, ctrl, id); memcpy(subsys->serial, id->sn, sizeof(subsys->serial)); memcpy(subsys->model, id->mn, sizeof(subsys->model)); @@ -3413,7 +3421,7 @@ int nvme_get_log(struct nvme_ctrl *ctrl, u32 nsid, u8 log_page, u8 lsp, u8 csi, static int nvme_get_effects_log(struct nvme_ctrl *ctrl, u8 csi, struct nvme_effects_log **log) { - struct nvme_effects_log *old, *cel = xa_load(&ctrl->cels, csi); + struct nvme_effects_log *old, *cel = xa_load(&ctrl->subsys->cels, csi); int ret; if (cel) @@ -3430,7 +3438,7 @@ static int nvme_get_effects_log(struct nvme_ctrl *ctrl, u8 csi, return ret; } - old = xa_store(&ctrl->cels, csi, cel, GFP_KERNEL); + old = xa_store(&ctrl->subsys->cels, csi, cel, GFP_KERNEL); if (xa_is_err(old)) { kfree(cel); return xa_err(old); @@ -3505,7 +3513,7 @@ static int nvme_init_effects_log(struct nvme_ctrl *ctrl, if (!effects) return -ENOMEM; - old = xa_store(&ctrl->cels, csi, effects, GFP_KERNEL); + old = xa_store(&ctrl->subsys->cels, csi, effects, GFP_KERNEL); if (xa_is_err(old)) { kfree(effects); return xa_err(old); @@ -3551,23 +3559,27 @@ static int nvme_init_effects(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) { int ret = 0; + mutex_lock(&ctrl->subsys->lock); + ctrl->effects = xa_load(&ctrl->subsys->cels, NVME_CSI_NVM); if (ctrl->effects) - return 0; + goto out_unlock; if (id->lpa & NVME_CTRL_LPA_CMD_EFFECTS_LOG) { ret = nvme_get_effects_log(ctrl, NVME_CSI_NVM, &ctrl->effects); if (ret < 0) - return ret; + goto out_unlock; } if (!ctrl->effects) { ret = nvme_init_effects_log(ctrl, NVME_CSI_NVM, &ctrl->effects); if (ret < 0) - return ret; + goto out_unlock; } nvme_init_known_nvm_effects(ctrl); - return 0; +out_unlock: + mutex_unlock(&ctrl->subsys->lock); + return ret; } static int nvme_check_ctrl_fabric_info(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id) @@ -5152,19 +5164,6 @@ void nvme_uninit_ctrl(struct nvme_ctrl *ctrl) } EXPORT_SYMBOL_GPL(nvme_uninit_ctrl); -static void nvme_free_cels(struct nvme_ctrl *ctrl) -{ - struct nvme_effects_log *cel; - unsigned long i; - - xa_for_each(&ctrl->cels, i, cel) { - xa_erase(&ctrl->cels, i); - kfree(cel); - } - - xa_destroy(&ctrl->cels); -} - static void nvme_free_ctrl(struct device *dev) { struct nvme_ctrl *ctrl = @@ -5177,7 +5176,6 @@ static void nvme_free_ctrl(struct device *dev) blk_put_queue(ctrl->fabrics_q); if (!subsys || ctrl->instance != subsys->instance) ida_free(&nvme_instance_ida, ctrl->instance); - nvme_free_cels(ctrl); nvme_mpath_uninit(ctrl); cleanup_srcu_struct(&ctrl->srcu); nvme_auth_stop(ctrl); @@ -5223,7 +5221,6 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev, mutex_init(&ctrl->scan_lock); INIT_LIST_HEAD(&ctrl->namespaces); - xa_init(&ctrl->cels); ctrl->dev = dev; ctrl->ops = ops; ctrl->quirks = quirks; diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h index e0260f4d24fd..a643c4723a69 100644 --- a/drivers/nvme/host/nvme.h +++ b/drivers/nvme/host/nvme.h @@ -415,7 +415,6 @@ struct nvme_ctrl { unsigned long quirks; struct nvme_id_power_state psd[32]; struct nvme_effects_log *effects; - struct xarray cels; struct work_struct scan_work; struct work_struct async_event_work; struct delayed_work ka_work; @@ -509,6 +508,8 @@ struct nvme_subsystem { __guarded_by(&nvme_subsystems_lock); struct list_head nsheads __guarded_by(&lock); + /* Command effects logs, indexed by CSI and protected by lock. */ + struct xarray cels; char subnqn[NVMF_NQN_SIZE]; char serial[20]; char model[40]; From 4a5e49ba0abb8b4328d6318c9aef0c0121f95507 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Mon, 14 Sep 2026 18:57:13 +0800 Subject: [PATCH 248/535] nvme-multipath: set BLK_FEAT_ZONED only after the zone info is known The namespace head is marked zoned at allocation time based only on the command set identifier. If the zone info query reports a zero zone size, the path namespace is registered without zoned limits while the head still advertises the zoned capability with a zone size of zero, and reporting zones or writing to the head then shifts by ilog2(0). Drop the zoned feature from the head allocation and let the head limits refresh stack it in from the path namespace instead. Fixes: 28982ad73d6a ("nvme: set BLK_FEAT_ZONED for ZNS multipath disks") Fixes: 3838e80fcfb3 ("nvme: skip the zoned limits update if the zone info query failed") Cc: stable@vger.kernel.org Signed-off-by: Guixin Liu Signed-off-by: Keith Busch --- drivers/nvme/host/multipath.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c index 7db6850f98d4..99ecc73393e5 100644 --- a/drivers/nvme/host/multipath.c +++ b/drivers/nvme/host/multipath.c @@ -761,8 +761,6 @@ int nvme_mpath_alloc_disk(struct nvme_ctrl *ctrl, struct nvme_ns_head *head) lim.dma_alignment = 3; lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT | BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES | BLK_FEAT_PCI_P2PDMA; - if (head->ids.csi == NVME_CSI_ZNS) - lim.features |= BLK_FEAT_ZONED; head->disk = blk_alloc_disk(&lim, ctrl->numa_node); if (IS_ERR(head->disk)) From c5a8fc2abe05cf6db71f57ff0177fddc59861a1a Mon Sep 17 00:00:00 2001 From: Jasper Firth Date: Fri, 25 Sep 2026 23:06:25 +1000 Subject: [PATCH 249/535] ASoC: Intel: bytcr_rt5651: Add quirk for Chuwi Hi8 with generic DMI strings Some Chuwi Hi8 tablets with model number CWI513 ship with a BIOS (American Megatrends 5.11, dated 05/21/2016) whose DMI sys_vendor and product_name are both "Default string". The tested unit is labelled just "Hi8", but its model number is the same as the existing "Chuwi Hi8 Pro (CWI513)" entry, and it needs the same quirks. That entry matches on sys_vendor "Hampoo" and product_name "X1D3_C806N", so it never applies to these units. Without its quirks the internal speaker, whose amplifier has a differential input wired across LOUT left and right, cancels to a faint distorted residual for any centred (L == R) content, and the headphone channels are swapped. Add an entry matching board_vendor "Hampoo" and board_name "Cherry Trail CR", plus the BIOS date since those two are too generic on their own. drivers/platform/x86/touchscreen_dmi.c already matches the Chuwi HiBook the same way. The quirks are those of the existing Hi8 Pro entry, except for the over-current detection scale factor: 1.0 instead of the 0.75 from BYT_RT5651_DEFAULT_QUIRKS. With 0.75 and a CTIA headset (inline mic and one button), loud speech into the mic, and headphone playback coupling onto the mic line, were reported as spurious KEY_PLAYPAUSE presses, one of them held for a whole 8 second speech window. With 1.0 the same test gave no spurious events, and 3 real button presses were reported as exactly 3 press/release pairs. Tested on a unit with the generic DMI strings, with a module built from this patch and no quirk module parameter. The quirks were applied via the new entry, the card components read "cfg-spk:1 cfg-mic:in2 cfg-hp:lrswap", and the following all work: speaker, headphone left/right order, jack and headset-mic detection, headset button, and the internal and headset mics. The problem was diagnosed with the help of an AI coding assistant, which also drafted this changelog. Assisted-by: Claude Opus 5.5 Signed-off-by: Jasper Firth Link: https://patch.msgid.link/20260925-hi8-quirk-v1-1-ffd00b0447f9@hotmail.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/bytcr_rt5651.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 62cb4856c797..c9b1053205de 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -410,6 +410,23 @@ static const struct dmi_system_id byt_rt5651_quirk_table[] = { BYT_RT5651_HP_LR_SWAPPED | BYT_RT5651_MONO_SPEAKER), }, + { + /* Chuwi Hi8 (CWI513) with a BIOS using generic DMI strings */ + .callback = byt_rt5651_quirk_cb, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + /* Above matches are too generic, add bios-date match */ + DMI_MATCH(DMI_BIOS_DATE, "05/21/2016"), + }, + .driver_data = (void *)(BYT_RT5651_MCLK_EN | + BYT_RT5651_JD1_1 | + BYT_RT5651_OVCD_TH_2000UA | + BYT_RT5651_OVCD_SF_1P0 | + BYT_RT5651_IN2_MAP | + BYT_RT5651_HP_LR_SWAPPED | + BYT_RT5651_MONO_SPEAKER), + }, { /* Chuwi Vi8 Plus (CWI519) */ .callback = byt_rt5651_quirk_cb, From ab39974240a0cff765f3bb9cce81d8001ffdb144 Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Fri, 25 Sep 2026 08:46:51 +0200 Subject: [PATCH 250/535] bpf: Hold map BTF for the memory allocator destructor record bpf_ma_set_dtor() duplicates map->record so that the bpf_mem_alloc destructor can release the special fields of hash and rhash map elements once the allocator frees them for good. btf_record_dup() only acquires references on kernel and module BTF. Fields whose types live in the map BTF keep pointing into it: a kptr to a local type refers to map->btf, and a list_head or rb_root field carries a value_rec owned by the struct meta table of map->btf. That borrowed state can outlive the map. When RCU callbacks are still in flight, bpf_mem_alloc_destroy() copies the allocator and defers the final drain, together with the destructor context, to a workqueue. bpf_map_free() then frees map->record and drops the map's BTF reference as soon as map_free() returns. Once the program that loaded the BTF is gone as well, the BTF is freed while the worker still uses the duplicate. The worker dereferences it through btf_is_kernel() and btf_find_struct_meta() in bpf_obj_free_fields() when destroying elements that were freed under RCU, and through btf_is_kernel() in btf_record_free() when releasing the context itself: BUG: KASAN: slab-use-after-free in btf_is_kernel+0x19/0x30 Read of size 1 at addr ffff88811505dce8 by task kworker/u16:2/50 Workqueue: events_unbound free_mem_alloc_deferred Call Trace: btf_is_kernel+0x19/0x30 btf_record_free+0xc7/0xf0 htab_dtor_ctx_free+0x1a/0x30 free_mem_alloc_no_barrier+0x39/0x230 free_mem_alloc_deferred+0x2a/0x40 process_scheduled_works+0x5ce/0x9d0 worker_thread+0x42a/0x5e0 kthread+0x1f5/0x230 ret_from_fork+0x1dd/0x390 ret_from_fork_asm+0x1a/0x30 Allocated by task 358: __kmalloc_cache_noprof+0x287/0x510 btf_new_fd+0xf7/0x3d0 __sys_bpf+0x487/0x840 Freed by task 0: kfree+0x186/0x560 rcu_core+0x6f6/0xd40 Freeing a map that still holds more elements than the allocator's high watermark is enough to get there, because the bulk free during teardown queues the RCU callback that makes bpf_mem_alloc_destroy() defer. Non-preallocated hash maps and resizable hash maps both register the destructor and are affected alike. Keep one reference on the map BTF in the destructor context. Every non-kernel pointer in the duplicated record is either map->btf or memory owned by it, so a single reference covers kptrs, list heads, and rb roots, and mirrors what bpf_map_meta_alloc() does for the duplicated record of an inner map. Release it only after the duplicated record has been freed: the worker is preemptible and not in an RCU read-side critical section, so dropping the last reference first would let the BTF be freed while btf_record_free() still reads its fields. Fixes: 1df97a7453ee ("bpf: Register dtor for freeing special fields") Reported-by: Yuan Chen Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260901062845.1379760-3-chenyuan_fl@163.com Link: https://patch.msgid.link/20260925064652.2261885-1-memxor@gmail.com --- kernel/bpf/hashtab.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index f9464e566f10..548eb1cc7c45 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -128,6 +128,7 @@ struct htab_elem { struct htab_btf_record { struct btf_record *record; + struct btf *btf; u32 key_size; }; @@ -497,8 +498,13 @@ static void htab_dtor_ctx_free(void *ctx) { struct htab_btf_record *hrec = ctx; + /* + * The duplicated record still points into the map BTF, so free it + * before dropping the reference that keeps that BTF alive. + */ btf_record_free(hrec->record); - kfree(ctx); + btf_put(hrec->btf); + kfree(hrec); } static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, @@ -521,6 +527,15 @@ static int bpf_ma_set_dtor(struct bpf_map *map, struct bpf_mem_alloc *ma, kfree(hrec); return err; } + /* + * btf_record_dup() only acquires kernel and module BTF. Fields whose + * types live in the map BTF keep pointing into it: kptrs to local + * types refer to map->btf, and graph roots carry a value record owned + * by its struct meta table. The context can outlive the map when the + * allocator defers its teardown, so hold a reference of our own. + */ + hrec->btf = map->btf; + btf_get(hrec->btf); bpf_mem_alloc_set_dtor(ma, dtor, htab_dtor_ctx_free, hrec); return 0; } From aa650d87a498844060eecb1d336f4d46f4d1b0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Tue, 22 Sep 2026 20:27:35 +0000 Subject: [PATCH 251/535] audit: fix exe mark UAF in kill_rules() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kill_rules() removes mixed AUDIT_DIR and AUDIT_EXE rules when an audit tree is pruned. It drops entry->rule.exe before removing the rule from the RCU-visible filter lists. After a rule has been installed with AUDIT_ADD_RULE, which requires CAP_AUDIT_CONTROL, removing the watched directory can race with another task that is still evaluating the rule. In that case, fsnotify can free the executable mark before the reader reaches audit_mark_compare(), causing a use-after-free. KASAN reports: BUG: KASAN: slab-use-after-free in audit_mark_compare+0x8d/0xa0 Unlink the published rules from the RCU-visible lists and retain them on tree->rules for cleanup. If any rule has an executable mark, wait for a single RCU grace period before removing the marks and scheduling the entries for freeing. Otherwise, call_rcu() already provides the required deferred freeing without a synchronous wait. Cc: stable@vger.kernel.org Fixes: 34d99af52ad4 ("audit: implement audit by executable") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Reviewed-by: Ricardo Robaina Tested-by: Ricardo Robaina Reviewed-by: Bradley Morgan Signed-off-by: Paul Moore --- kernel/audit_tree.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c index 1ed19b775912..f2e81be8265e 100644 --- a/kernel/audit_tree.c +++ b/kernel/audit_tree.c @@ -545,21 +545,37 @@ static void kill_rules(struct audit_context *context, struct audit_tree *tree) { struct audit_krule *rule, *next; struct audit_entry *entry; + bool need_sync = false; + + list_for_each_entry_safe(rule, next, &tree->rules, rlist) { + entry = container_of(rule, struct audit_entry, rule); + + if (rule->tree) { + /* not a half-baked one */ + audit_tree_log_remove_rule(context, rule); + rule->tree = NULL; + list_del_rcu(&entry->list); + list_del(&entry->rule.list); + if (entry->rule.exe) + need_sync = true; + } else { + list_del_init(&rule->rlist); + } + } + + if (list_empty(&tree->rules)) + return; + + if (need_sync) + synchronize_rcu(); list_for_each_entry_safe(rule, next, &tree->rules, rlist) { entry = container_of(rule, struct audit_entry, rule); list_del_init(&rule->rlist); - if (rule->tree) { - /* not a half-baked one */ - audit_tree_log_remove_rule(context, rule); - if (entry->rule.exe) - audit_remove_mark(entry->rule.exe); - rule->tree = NULL; - list_del_rcu(&entry->list); - list_del(&entry->rule.list); - call_rcu(&entry->rcu, audit_free_rule_rcu); - } + if (entry->rule.exe) + audit_remove_mark(entry->rule.exe); + call_rcu(&entry->rcu, audit_free_rule_rcu); } } From aadf655e8ea9a8ffa24bb8d69f504572e8d18181 Mon Sep 17 00:00:00 2001 From: Binbin Deng <18983559317@163.com> Date: Tue, 22 Sep 2026 21:19:19 +0800 Subject: [PATCH 252/535] net/mctp: publish the mctp_dev only after it is initialised mctp_add_dev() links the new mctp_dev into dev->mctp_ptr before mdev->dev is assigned, so a reader that picks the device up in that window dereferences a net_device pointer that is still NULL: KASAN: null-ptr-deref in range [0x00000000000000b0-0x00000000000000b7] Call Trace: ? __pfx_mctp_sendmsg (./include/linux/sockptr.h:49) ? __pfx_mctp_dst_output (net/mctp/route.c:41) ? selinux_socket_sendmsg (security/selinux/hooks.c:5278) ____sys_sendmsg (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2684 (discriminator 1)) ? __pfx_____sys_sendmsg (net/socket.c:1131) ? __pfx_copy_msghdr_from_user (net/socket.c:2590) ? update_cfs_rq_load_avg (kernel/sched/fair.c:5478) ___sys_sendmsg (net/socket.c:2738) ? __pfx____sys_sendmsg (net/socket.c:2625) ? perf_event_task_tick (./include/linux/rcupdate.h:838) ? sched_tick (kernel/sched/core.c:5818) ? clockevents_program_event (kernel/time/clockevents.c:372) ? fdget (./include/linux/rcupdate.h:873 fs/file.c:1100) __sys_sendmsg (net/socket.c:2770) ? __pfx___sys_sendmsg (net/socket.c:2751) do_syscall_64 (arch/x86/entry/syscall_64.c:63) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) Fix by publishing only once the object is fully initialised: __mctp_dev_get() runs under rcu_read_lock() only and hands this object to readers as soon as mctp_ptr is assigned, so they must never observe mdev->dev == NULL. Fixes: 583be982d934 ("mctp: Add device handling and netlink interface") Signed-off-by: Binbin Deng <18983559317@163.com> Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/729830b8.130.1a0c945579d.Coremail.18983559317@163.com Signed-off-by: Jakub Kicinski --- net/mctp/device.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/mctp/device.c b/net/mctp/device.c index 822120e860c8..9f76ff092559 100644 --- a/net/mctp/device.c +++ b/net/mctp/device.c @@ -344,13 +344,13 @@ static struct mctp_dev *mctp_add_dev(struct net_device *dev) mdev->net = mctp_default_net(dev_net(dev)); - /* associate to net_device */ refcount_set(&mdev->refs, 1); - rcu_assign_pointer(dev->mctp_ptr, mdev); - dev_hold(dev); mdev->dev = dev; + /* Associate to net_device, publish the mdev. */ + rcu_assign_pointer(dev->mctp_ptr, mdev); + return mdev; } From 0bb8eab29b55045281a4963d4557f2776cdf8cfa Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Thu, 24 Sep 2026 04:32:49 -0400 Subject: [PATCH 253/535] net/sched: cls_api: reclaim an empty proto on the error path Two racing tc filter add requests on the same chain/prio of an unlocked classifier both run change() on the shared proto and both can fail: the winner's tcf_chain_tp_delete_empty() attempt gives up because the loser's handle is still in the idr, and the loser's error path drops only its own reference without a second reclamation attempt. The empty proto stays linked in the chain, holding the chain reference, a block reference and the classifier module reference until the chain or block is torn down. Reclaim the proto on the error path of any failed request that holds a proto reference. The reclamation is emptiness-gated: tcf_chain_tp_delete_empty() unlinks the proto only when delete_empty() admits it is empty, so a live shared proto is never unlinked. A proto the request created is reclaimed unconditionally - it is the only owner, so marking it for deletion is safe even without a delete_empty callback. Classifiers without one (the check marks the proto unconditionally) are rtnl-serialized, so the raced window this guard closes cannot arise for them. This is a follow-up to commit d4e359b3608a ("net/sched: cls_api: fix teardown of an adopted proto on insert-race loss"), which stopped the loser of the insert race from unlinking the winner's live proto but left the empty-proto residual in place. Conditions to recreate: - CONFIG_NET_CLS_FLOWER=y; veth pair - tc qdisc add dev veth0 ingress - two concurrent `tc filter add dev veth0 ingress protocol ip pref 1 flower skip_sw ... action drop` (both fail in fl_hw_replace_filter after publishing their handle in the idr); repeat in a loop - an empty flower tp stays linked after both requests fail; visible as a bare `filter protocol ip pref 1 flower chain 0` header in `tc filter show` with no filter entries - CAP_NET_ADMIN (namespace-local via unshare -Urn suffices) Fixes: 8b64678e0af8 ("net: sched: refactor tp insert/delete for concurrent execution") Reported-by: Sashiko (nipa) Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260805134049.927864-1-victor@mojatatu.com Signed-off-by: Jamal Hadi Salim Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-JCOT.v1.20260910090924@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_api.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index c47d2ee13641..a9f54988561f 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -2463,7 +2463,20 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, } errout: - if (err && tp_state == TP_CREATED) + if (err && !IS_ERR_OR_NULL(tp) && + (tp_state == TP_CREATED || tp->ops->delete_empty)) + /* + * The request is dropping its reference to tp. If it was + * the last user (the idr is empty now), reclaim the proto. + * A tp this request created is reclaimed unconditionally: + * it is the only owner, so marking it for deletion is + * safe. Otherwise only classifiers with a delete_empty + * callback are reclaimed -- the callback admits an empty + * proto only, so a live shared proto is never unlinked. + * Classifiers without one (deleting is set + * unconditionally) are rtnl-serialized, so the raced + * window this guard closes cannot arise for them. + */ tcf_chain_tp_delete_empty(chain, tp, rtnl_held, NULL); errout_tp: if (chain) { From 0e7fe2b0ca45343b53345edc174898b448ccdaa4 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 24 Sep 2026 12:19:21 +0200 Subject: [PATCH 254/535] net: bcmgenet: allocate RX buffers as page fragments Since the page_pool conversion every RX buffer is a whole page and the skb truesize is the page, although the hardware writes at most 2 KiB of it. On 64 KiB pages a packet therefore counts 65792 bytes against the socket buffer where it used to count 2752. As a result a UDP socket with the default buffer starts to drop after three packets, and the RX rings pin 16 MiB for 512 KiB of buffers. Fix this and allocate the buffers as page fragments, so the truesize is what a packet occupies. 4 KiB pages stay one buffer per page. Sync each buffer in the refill path, as page_pool can only sync a whole page on recycle. On 4 KiB pages that is twice what the hardware wrote. Fixes: 7bc054c2d4ed ("net: bcmgenet: convert RX path to page_pool") Reported-by: Karl Mehltretter Closes: https://lore.kernel.org/all/20260924065839.56793-1-kmehltretter@gmail.com/ Signed-off-by: Nicolai Buchwitz Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260924101922.2675127-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- .../net/ethernet/broadcom/genet/bcmgenet.c | 29 ++++++++++++++----- .../net/ethernet/broadcom/genet/bcmgenet.h | 2 ++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 21668e41b696..f725d26e6020 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -57,6 +57,9 @@ */ #define GENET_RSB_PAD (sizeof(struct status_64) + 2) +/* RX buffer plus the skb_shared_info napi_build_skb() places behind it */ +#define GENET_RX_BUF_SIZE SKB_HEAD_ALIGN(RX_BUF_LENGTH) + /* Tx/Rx DMA register offset, skip 256 descriptors */ #define WORDS_PER_BD(p) (p->hw_params->words_per_bd) #define DMA_DESC_SIZE (WORDS_PER_BD(priv) * sizeof(u32)) @@ -2254,11 +2257,12 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring, struct enet_cb *cb) { struct bcmgenet_priv *priv = ring->priv; + unsigned int size = GENET_RX_BUF_SIZE; + unsigned int offset; dma_addr_t mapping; struct page *page; - page = page_pool_alloc_pages(ring->page_pool, - GFP_ATOMIC); + page = page_pool_dev_alloc(ring->page_pool, &offset, &size); if (!page) { priv->mib.alloc_rx_buff_failed++; netif_err(priv, rx_err, priv->dev, @@ -2267,9 +2271,13 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring, } /* page_pool handles DMA mapping via PP_FLAG_DMA_MAP */ - mapping = page_pool_get_dma_addr(page); + mapping = page_pool_get_dma_addr(page) + offset; + dma_sync_single_for_device(&priv->pdev->dev, mapping, RX_BUF_LENGTH, + DMA_FROM_DEVICE); cb->rx_page = page; + cb->rx_offset = offset; + cb->rx_size = size; dmadesc_set_addr(priv, cb->bd_addr, mapping); return 0; @@ -2323,6 +2331,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, while ((rxpktprocessed < rxpkttoprocess) && (rxpktprocessed < budget)) { + unsigned int rx_offset, rx_size; struct status_64 *status; struct page *rx_page; void *hard_start; @@ -2332,6 +2341,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Save the received page before refilling */ rx_page = cb->rx_page; + rx_offset = cb->rx_offset; + rx_size = cb->rx_size; if (bcmgenet_rx_refill(ring, cb)) { BCMGENET_STATS64_INC(stats, dropped); @@ -2341,10 +2352,10 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Sync the full buffer; the HW may have written anywhere * up to RX_BUF_LENGTH. */ - page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, 0, + page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, rx_offset, RX_BUF_LENGTH); - hard_start = page_address(rx_page); + hard_start = page_address(rx_page) + rx_offset; status = (struct status_64 *)hard_start; dma_length_status = status->length_status; @@ -2410,7 +2421,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, /* Build SKB from the page - data starts at hard_start, * frame begins after RSB(64) + pad(2) = 66 bytes. */ - skb = napi_build_skb(hard_start, PAGE_SIZE); + skb = napi_build_skb(hard_start, rx_size); if (unlikely(!skb)) { BCMGENET_STATS64_INC(stats, dropped); page_pool_put_full_page(ring->page_pool, rx_page, @@ -2762,14 +2773,16 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv, struct bcmgenet_rx_ring *ring) { + /* Buffers share a page. bcmgenet_rx_refill() syncs each one for the + * device, PP_FLAG_DMA_SYNC_DEV would sync the whole page. + */ struct page_pool_params pp_params = { .order = 0, - .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV, + .flags = PP_FLAG_DMA_MAP, .pool_size = ring->size, .nid = NUMA_NO_NODE, .dev = &priv->pdev->dev, .dma_dir = DMA_FROM_DEVICE, - .max_len = RX_BUF_LENGTH, }; int err; diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 22a958ba9902..86f2aed20dbe 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -470,6 +470,8 @@ struct bcmgenet_rx_stats64 { struct enet_cb { struct sk_buff *skb; struct page *rx_page; + unsigned int rx_offset; + unsigned int rx_size; void __iomem *bd_addr; DEFINE_DMA_UNMAP_ADDR(dma_addr); DEFINE_DMA_UNMAP_LEN(dma_len); From a7bfaba4823e3c165bb2004c74eff7c096672bc7 Mon Sep 17 00:00:00 2001 From: Qishuai Liu Date: Wed, 23 Sep 2026 13:20:48 +0000 Subject: [PATCH 255/535] ipv6: fix prefix route expiry in modify_prefix_route() modify_prefix_route() is given the lifetime in clock_t relative to now, but fib6_set_expires() wants an absolute jiffies value. So when a permanent address is changed to a finite valid_lft, the prefix route ends up already expired and GC removes it. Steps to reproduce: ip link add dummy9 type dummy ip link set dummy9 up ip -6 addr add 2001:db8:9::1/64 dev dummy9 ip -6 addr change 2001:db8:9::1/64 dev dummy9 valid_lft 3600 preferred_lft 3600 ip -6 route show dev dummy9 # expires is negative Fixes: 8308f3ff1753 ("net/ipv6: Add support for specifying metric of connected routes") Signed-off-by: Qishuai Liu Reviewed-by: Hangbin Liu Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260923132048.3272878-1-lqs@lqs.me Signed-off-by: Jakub Kicinski --- net/ipv6/addrconf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 9d89be7e0544..c90ee6dd7446 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -4874,7 +4874,7 @@ static int modify_prefix_route(struct net *net, struct inet6_ifaddr *ifp, fib6_clean_expires(f6i); fib6_may_remove_gc_list(net, f6i); } else { - fib6_set_expires(f6i, expires); + fib6_set_expires(f6i, jiffies + clock_t_to_jiffies(expires)); fib6_add_gc_list(f6i); } From 19a4f1a6ed00286d70229f4fd5f690cc2fb033dc Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 11 Sep 2026 07:06:24 -0500 Subject: [PATCH 256/535] EDAC/altera: Do not allow driver unbinding The driver must remain bound; unbinding and re-binding it would erase active system memory. Remove the .remove functions because they will not ever get used. Fixes: 588cb03ea208 ("EDAC, altera: Add Arria10 L2 Cache ECC handling") Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260911120627.2634225-2-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 68846f583eee..fe501c89dd0d 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -453,15 +453,6 @@ static int altr_sdram_probe(struct platform_device *pdev) return res; } -static void altr_sdram_remove(struct platform_device *pdev) -{ - struct mem_ctl_info *mci = platform_get_drvdata(pdev); - - edac_mc_del_mc(&pdev->dev); - edac_mc_free(mci); - platform_set_drvdata(pdev, NULL); -} - /* * If you want to suspend, need to disable EDAC by removing it * from the device tree or defconfig. @@ -481,13 +472,13 @@ static const struct dev_pm_ops altr_sdram_pm_ops = { static struct platform_driver altr_sdram_edac_driver = { .probe = altr_sdram_probe, - .remove = altr_sdram_remove, .driver = { .name = "altr_sdram_edac", #ifdef CONFIG_PM .pm = &altr_sdram_pm_ops, #endif .of_match_table = altr_sdram_ctrl_of_match, + .suppress_bind_attrs = true, }, }; @@ -517,6 +508,7 @@ static struct platform_driver altr_edac_driver = { .driver = { .name = "socfpga_ecc_manager", .of_match_table = altr_edac_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_driver); @@ -803,22 +795,12 @@ static int altr_edac_device_probe(struct platform_device *pdev) return res; } -static void altr_edac_device_remove(struct platform_device *pdev) -{ - struct edac_device_ctl_info *dci = platform_get_drvdata(pdev); - struct altr_edac_device_dev *drvdata = dci->pvt_info; - - debugfs_remove_recursive(drvdata->debugfs_dir); - edac_device_del_device(&pdev->dev); - edac_device_free_ctl_info(dci); -} - static struct platform_driver altr_edac_device_driver = { .probe = altr_edac_device_probe, - .remove = altr_edac_device_remove, .driver = { .name = "altr_edac_device", .of_match_table = altr_edac_device_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_device_driver); @@ -2214,6 +2196,7 @@ static struct platform_driver altr_edac_a10_driver = { .driver = { .name = "socfpga_a10_ecc_manager", .of_match_table = altr_edac_a10_of_match, + .suppress_bind_attrs = true, }, }; module_platform_driver(altr_edac_a10_driver); From b62a264163ca51bee04785c581344751812395df Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 11 Sep 2026 07:06:25 -0500 Subject: [PATCH 257/535] EDAC/altera: Drop __init from ECC setup paths for re-probe safety Sashiko reports: "Does suppressing sysfs unbinding fully prevent the execution of freed __init memory? If altr_sysmgr_regmap_lookup_by_phandle() returns -EPROBE_DEFER, the probe is deferred until after __init memory is freed." The a10 EDAC .setup callbacks (sdmmc, ethernet, nand, dma, usb, qspi) and their helpers (altr_init_a10_ecc_device_type, altr_init_a10_ecc_block) were marked __init. These run from the probe path, which may execute after init memory is freed -- e.g. a probe deferred via -EPROBE_DEFER that only succeeds once a late/module dependency appears, or a manual unbind/rebind. Calling __init code then dereferences freed memory. Remove __init so these functions remain valid at runtime. Fixes: 788586efd116 ("EDAC/altera: Initialize peripheral FIFOs in probe()") Assisted-by: LLM Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260911120627.2634225-3-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index fe501c89dd0d..d8a3438fb5f4 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -926,7 +926,7 @@ static int __maybe_unused altr_init_memory_port(void __iomem *ioaddr, int port) return ret; } -static __init int __maybe_unused +static int __maybe_unused altr_init_a10_ecc_block(struct device_node *np, u32 irq_mask, u32 ecc_ctrl_en_mask, bool dual_port) { @@ -1001,7 +1001,7 @@ altr_init_a10_ecc_block(struct device_node *np, u32 irq_mask, static int validate_parent_available(struct device_node *np); static const struct of_device_id altr_edac_a10_device_of_match[]; -static int __init __maybe_unused altr_init_a10_ecc_device_type(char *compat) +static int __maybe_unused altr_init_a10_ecc_device_type(char *compat) { int irq; struct device_node *child, *np; @@ -1330,7 +1330,7 @@ static const struct edac_device_prv_data a10_l2ecc_data = { #ifdef CONFIG_EDAC_ALTERA_ETHERNET -static int __init socfpga_init_ethernet_ecc(struct altr_edac_device_dev *dev) +static int socfpga_init_ethernet_ecc(struct altr_edac_device_dev *dev) { int ret; @@ -1360,7 +1360,7 @@ static const struct edac_device_prv_data a10_enetecc_data = { #ifdef CONFIG_EDAC_ALTERA_NAND -static int __init socfpga_init_nand_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_nand_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1390,7 +1390,7 @@ static const struct edac_device_prv_data a10_nandecc_data = { #ifdef CONFIG_EDAC_ALTERA_DMA -static int __init socfpga_init_dma_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_dma_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1420,7 +1420,7 @@ static const struct edac_device_prv_data a10_dmaecc_data = { #ifdef CONFIG_EDAC_ALTERA_USB -static int __init socfpga_init_usb_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_usb_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1450,7 +1450,7 @@ static const struct edac_device_prv_data a10_usbecc_data = { #ifdef CONFIG_EDAC_ALTERA_QSPI -static int __init socfpga_init_qspi_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_qspi_ecc(struct altr_edac_device_dev *device) { int ret; @@ -1598,7 +1598,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) return rc; } -static int __init socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) +static int socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) { int rc = -ENODEV; struct device_node *child; From 3e4a10a4718e3d963ee4d6c5f26bc5ad57c1d2b1 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 11 Sep 2026 07:06:26 -0500 Subject: [PATCH 258/535] EDAC/altera: Fix memory leak on dci allocation failure Sashiko reports: "If devres_open_group() fails, the function returns -ENOMEM without freeing the dci structure allocated earlier with edac_device_alloc_ctl_info()." Free the dci structure if devres_open_group() fails. Fixes: c3eea1942a16 ("EDAC, altera: Add Altera L2 cache and OCRAM support") Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260911120627.2634225-4-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index d8a3438fb5f4..ccdfb35178b6 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1516,8 +1516,10 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) altdev = dci->pvt_info; *altdev = *device; - if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) + if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) { + edac_device_free_ctl_info(dci); return -ENOMEM; + } /* Update PortB specific values */ altdev->edac_dev_name = ecc_name; From eef3b67f9c6782127163b631c9043011a68016e2 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 11 Sep 2026 07:06:27 -0500 Subject: [PATCH 259/535] EDAC/altera: Fix use-after-free in error paths In both altr_edac_a10_device_add() and altr_portb_setup(), the error path freed the dci structure before releasing the devres group. Since the managed single and double bit IRQ handlers use altdev(dci->pvt_info) as their data, an IRQ firing between freeing dci and unregistering the IRQs could dereference the freed memory. Release the devres group first so the managed IRQs are unregistered before the dci structure is freed. Fixes: 911049845d70 ("EDAC, altera: Add Arria10 SD-MMC EDAC support") Fixes: 588cb03ea208 ("EDAC, altera: Add Arria10 L2 Cache ECC handling") Closes: https://sashiko.dev/#/patchset/20260719211238.589402-1-rosenp%40gmail.com Assisted-by: LLM Signed-off-by: Dinh Nguyen Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org ## 6.18+ Link: https://patch.msgid.link/20260911120627.2634225-5-dinguyen@kernel.org --- drivers/edac/altera_edac.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index ccdfb35178b6..4c20fb926160 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1593,8 +1593,13 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) return 0; err_release_group_1: - edac_device_free_ctl_info(dci); + /* + * Release the devres group first so the managed IRQs are + * unregistered before dci (which contains the IRQ handler's + * data via dci->pvt_info) is freed, avoiding a use-after-free. + */ devres_release_group(device->edac->dev, altr_portb_setup); + edac_device_free_ctl_info(dci); edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); return rc; @@ -1997,9 +2002,17 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, return 0; err_release_group1: + /* + * Release the devres group first so the managed IRQs are + * unregistered before dci (which contains the IRQ handler's + * data via dci->pvt_info) is freed, avoiding a use-after-free. + */ + devres_release_group(edac->dev, NULL); edac_device_free_ctl_info(dci); + goto err_print; err_release_group: devres_release_group(edac->dev, NULL); +err_print: edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); From 8264c0d73f07f6c3e6768d5a41b56c621f912a37 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:39 -0700 Subject: [PATCH 260/535] KVM: Reject attempts to lock all vCPUs if vCPU creation is in-progress Reject locking of all vCPUs if vCPU creation is in-progress, i.e. if the number of "created" vCPUs doesn't match the number of "onlined" vCPUs. It's simply not possible to guarantee that KVM has truly locked all vCPUs if one or more vCPUs are actively being created. Holding kvm->lock does prevent in-flight vCPUs from being fully onlined, but it's infeasible for common KVM to know whether or not that provides sufficient protection. In practice, this is likely a minor bug fix for the ARM and RISC-V usage of kvm_trylock_all_vcpus(), and a glorified nop for everything else. E.g. ARM's kvm_timer_vcpu_init() can race kvm_vm_ioctl_set_counter_offset() with respect to observing KVM_ARCH_FLAG_VM_COUNTER_OFFSET. Opportunistically drop x86's existing manual checks on vCPU creation being in-progress as all of x86's checks immediately precede or follow locking of all vCPUs. Leave arm64 and RISC-V alone for the moment, as their checks aren't as obviously redundant/equivalent. Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-2-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 10 ---------- arch/x86/kvm/vmx/tdx.c | 5 ----- virt/kvm/kvm_main.c | 6 ++++++ 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 63eb2155a774..6aa86a78711e 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -1125,9 +1125,6 @@ static int sev_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp) if (!sev_es_guest(kvm)) return -ENOTTY; - if (kvm_is_vcpu_creation_in_progress(kvm)) - return -EBUSY; - ret = kvm_lock_all_vcpus(kvm); if (ret) return ret; @@ -2121,10 +2118,6 @@ static int sev_check_source_vcpus(struct kvm *dst, struct kvm *src) struct kvm_vcpu *src_vcpu; unsigned long i; - if (kvm_is_vcpu_creation_in_progress(src) || - kvm_is_vcpu_creation_in_progress(dst)) - return -EBUSY; - if (!sev_es_guest(src)) return 0; @@ -2520,9 +2513,6 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp) unsigned long i; int ret; - if (kvm_is_vcpu_creation_in_progress(kvm)) - return -EBUSY; - ret = kvm_lock_all_vcpus(kvm); if (ret) return ret; diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 6c842e9191a5..00850a3a77d6 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -2728,11 +2728,6 @@ static tdx_vm_state_guard_t tdx_acquire_vm_state_locks(struct kvm *kvm) mutex_lock(&kvm->lock); - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) { - r = -EBUSY; - goto out_err; - } - r = kvm_lock_all_vcpus(kvm); if (r) goto out_err; diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 85f42289748d..aad8cf00a1c8 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1363,6 +1363,9 @@ int kvm_trylock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); + if (kvm_is_vcpu_creation_in_progress(kvm)) + return -EBUSY; + kvm_for_each_vcpu(i, vcpu, kvm) if (!mutex_trylock_nest_lock(&vcpu->mutex, &kvm->lock)) goto out_unlock; @@ -1386,6 +1389,9 @@ int kvm_lock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); + if (kvm_is_vcpu_creation_in_progress(kvm)) + return -EBUSY; + kvm_for_each_vcpu(i, vcpu, kvm) { r = mutex_lock_killable_nest_lock(&vcpu->mutex, &kvm->lock); if (r) From e3995b05e95d03953954dd60ba803ebfa7755253 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:40 -0700 Subject: [PATCH 261/535] KVM: arm64: vgic: Rely on vCPU creation check in "trylock all vCPUs" Now that KVM's APIs for locking all vCPUs return -EBUSY if vCPU creation is in-progress, drop the manual check for the same from vGIC creation, and update the comments accordingly. Note, while KVM arm64 guards many vGIC operations with its arch-specific config_lock, holding kvm->lock is sufficient to guarantee a stable result for "is vCPU creation in-progress". So, no functional change intended. Note #2, the open coded check in vgic_init() is racy when called without kvm->lock held, e.g. via vgic_lazy_init(). I.e. that check needs to stay open coded to avoid triggering a lockdep assert. Whether or not the race is "fine" is a problem for a different day. Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/arm64/kvm/vgic/vgic-init.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c index 4012df6002ea..a58575df36e9 100644 --- a/arch/arm64/kvm/vgic/vgic-init.c +++ b/arch/arm64/kvm/vgic/vgic-init.c @@ -97,6 +97,9 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) /* * - Acquiring the vCPU mutex for every *online* vCPU to prevent * concurrent vCPU ioctls for vCPUs already visible to userspace. + * This also ensures KVM isn't in the middle of creating a vCPU, + * i.e. that there are no vCPUs that have been created but aren't + * yet fully online. */ ret = -EBUSY; if (kvm_trylock_all_vcpus(kvm)) @@ -105,18 +108,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type) /* * - Taking the config_lock which protects VGIC data structures such * as the per-vCPU arrays of private IRQs (SGIs, PPIs). - */ - mutex_lock(&kvm->arch.config_lock); - - /* - * - Bailing on the entire thing if a vCPU is in the middle of creation, - * dropped the kvm->lock, but hasn't reached kvm_arch_vcpu_create(). * * The whole combination of this guarantees that no vCPU can get into * KVM with a VGIC configuration inconsistent with the VM's VGIC. */ - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) - goto out_unlock; + mutex_lock(&kvm->arch.config_lock); if (irqchip_in_kernel(kvm)) { ret = -EEXIST; From 10f4082b754f18ed82f94d47f0e959ef80038895 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:41 -0700 Subject: [PATCH 262/535] KVM: RISC-V: Use kvm_is_vcpu_creation_in_progress() instead of open-coded equivalent Use kvm_is_vcpu_creation_in_progress() instead of an open-coded equivalent during AIA initialization. Unlike similar vGIC code in arm64, the relevant RISC-V code runs under kvm->lock, i.e. can use the standard API without hitting lockdep false positive. No functional change intended. Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-4-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/riscv/kvm/aia_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/kvm/aia_device.c b/arch/riscv/kvm/aia_device.c index efc7c0bcfba9..97833a04268a 100644 --- a/arch/riscv/kvm/aia_device.c +++ b/arch/riscv/kvm/aia_device.c @@ -237,7 +237,7 @@ static int aia_init(struct kvm *kvm) return -EBUSY; /* We might be in the middle of creating a VCPU? */ - if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) + if (kvm_is_vcpu_creation_in_progress(kvm)) return -EBUSY; /* Number of sources should be less than or equals number of IDs */ From 6418b715d5a24fa952e8c4baa587dd812dd4765b Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:42 -0700 Subject: [PATCH 263/535] KVM: Protect all of kvm_vm_ioctl_create_vcpu() with kvm->lock When creating a vCPU, don't drop kvm->lock to when doing the bulk of actual vCPU creation, as allowing multiple vCPUs to be created in parallel adds significant complexity in KVM (as evidenced by the many related bugs), and all known VMMs fully serialize vCPU creation. Remove all manually locking of kvm->lock from kvm_arch_vcpu_{post,}create() for obvious reasons. For many years, "everyone" has assumed that dropping kvm->lock was done for performance reasons optimization, e.g. to allow userspace to create all vCPUs concurrently for latency purposes. But as above, no known VMM does that. Looking at the history of this code, before commit 11ec28047118 ("KVM: Convert vm lock to a mutex"), kvm->lock was a spinlock. I.e. KVM *had* to drop kvm->lock when doing the bulk of vCPU creation, otherwise KVM couldn't do normal memory allocations. When kvm->lock got turned into a mutex for unrelated reasons, no one took advantage updated of the change to simplify vCPU creation. And 19 years later, everyone just assumed that KVM continued to deal with the complexity for performance reasons. Furthermore, naively parallelizing vCPU creation in userspace is likely a net negative due to the overheads of task creation. Unless a VMM carefully avoids the extra overhead related to parallelization, e.g. spawns each vCPU's thread before creating the vCPU, creating vCPUs concurrently is a net *negative* up until about ~64 vCPUs, after which the times are a wash. The absolute speed of light _is_ faster if KVM doesn't hold kvm-lock, but at vCPU counts of ~16 or less, it's probably in the noise when considering total VM creation time, as the added latency is less than 1ms up until 16 or so vCPUs. On top of all that, KVM has had a *lot* of fatal bugs (most often found by syzkaller) related to vCPUs being created while trying to do per-VM operations (basically, see every flow that locks all vCPUs). I.e. the parallel vCPU creation "support" is actively harmful as the only "use case" is for misbehaving userspace to exploit KVM bugs. Serializing vCPU creation will allow reverting commit 97d65b544f48 ("KVM: Check for duplicate vcpu_id as early as possible"), which had "minor" math error: the worst case scenario isn't "256 bytes per VM", it's "256 unsigned longs per VM", i.e. 2048 bytes per VM, which doubles the size of each VM and pushes several architectures into order-1 allocations. Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-5-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/powerpc/kvm/book3s_hv.c | 2 -- arch/s390/kvm/s390/s390.c | 5 +---- virt/kvm/kvm_main.c | 22 +++++----------------- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c index dbac3573b2c8..de05d721edc4 100644 --- a/arch/powerpc/kvm/book3s_hv.c +++ b/arch/powerpc/kvm/book3s_hv.c @@ -3058,7 +3058,6 @@ static int kvmppc_core_vcpu_create_hv(struct kvm_vcpu *vcpu) init_waitqueue_head(&vcpu->arch.cpu_run); - mutex_lock(&kvm->lock); vcore = NULL; err = -EINVAL; if (cpu_has_feature(CPU_FTR_ARCH_300)) { @@ -3091,7 +3090,6 @@ static int kvmppc_core_vcpu_create_hv(struct kvm_vcpu *vcpu) mutex_unlock(&kvm->arch.mmu_setup_lock); } } - mutex_unlock(&kvm->lock); if (!vcore) return err; diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index efd77b9cd36c..e4912db67adc 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -3579,12 +3579,11 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu) void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) { - mutex_lock(&vcpu->kvm->lock); preempt_disable(); vcpu->arch.sie_block->epoch = vcpu->kvm->arch.epoch; vcpu->arch.sie_block->epdx = vcpu->kvm->arch.epdx; preempt_enable(); - mutex_unlock(&vcpu->kvm->lock); + if (!kvm_is_ucontrol(vcpu->kvm)) { vcpu->arch.gmap = vcpu->kvm->arch.gmap; sca_add_vcpu(vcpu); @@ -3757,13 +3756,11 @@ static int kvm_s390_vcpu_setup(struct kvm_vcpu *vcpu) kvm_s390_vcpu_pci_setup(vcpu); - mutex_lock(&vcpu->kvm->lock); if (kvm_s390_pv_is_protected(vcpu->kvm)) { rc = kvm_s390_pv_create_cpu(vcpu, &uvrc, &uvrrc); if (rc) kvm_s390_vcpu_unsetup_cmma(vcpu); } - mutex_unlock(&vcpu->kvm->lock); return rc; } diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index aad8cf00a1c8..cae0a9cdc33e 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4188,6 +4188,8 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) struct kvm_vcpu *vcpu; struct page *page; + guard(mutex)(&kvm->lock); + /* * KVM tracks vCPU IDs as 'int', be kind to userspace and reject * too-large values instead of silently truncating. @@ -4200,26 +4202,18 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) if (id >= KVM_MAX_VCPU_IDS) return -EINVAL; - mutex_lock(&kvm->lock); - if (kvm->created_vcpus >= kvm->max_vcpus) { - mutex_unlock(&kvm->lock); + if (kvm->created_vcpus >= kvm->max_vcpus) return -EINVAL; - } - if (test_bit(id, kvm->vcpu_ids)) { - mutex_unlock(&kvm->lock); + if (test_bit(id, kvm->vcpu_ids)) return -EEXIST; - } r = kvm_arch_vcpu_precreate(kvm, id); - if (r) { - mutex_unlock(&kvm->lock); + if (r) return r; - } kvm->created_vcpus++; __set_bit(id, kvm->vcpu_ids); - mutex_unlock(&kvm->lock); vcpu = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL_ACCOUNT); if (!vcpu) { @@ -4250,8 +4244,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) goto arch_vcpu_destroy; } - mutex_lock(&kvm->lock); - if (WARN_ON_ONCE(kvm_get_vcpu_by_id(kvm, id))) { r = -EEXIST; goto unlock_vcpu_destroy; @@ -4290,7 +4282,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) atomic_inc(&kvm->online_vcpus); mutex_unlock(&vcpu->mutex); - mutex_unlock(&kvm->lock); kvm_arch_vcpu_postcreate(vcpu); kvm_create_vcpu_debugfs(vcpu); return r; @@ -4301,7 +4292,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) xa_erase(&kvm->vcpu_array, vcpu->vcpu_idx); unlock_vcpu_destroy: vcpu->vcpu_idx = -1; - mutex_unlock(&kvm->lock); kvm_dirty_ring_free(&vcpu->dirty_ring); arch_vcpu_destroy: kvm_arch_vcpu_destroy(vcpu); @@ -4310,10 +4300,8 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) vcpu_free: kmem_cache_free(kvm_vcpu_cache, vcpu); vcpu_decrement: - mutex_lock(&kvm->lock); kvm->created_vcpus--; __clear_bit(id, kvm->vcpu_ids); - mutex_unlock(&kvm->lock); return r; } From c5866d42f7116ab609e3ab6b4b699282e8287e3f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:43 -0700 Subject: [PATCH 264/535] KVM: Move check for existing vCPU ID to the top of vCPU creation Now that kvm->lock is held for the entirety of vCPU creation, check for a conflicting vCPU ID at the begnning of vCPU creation, before the arch precreate() hook is invoked. This will allow reverting commit 97d65b544f48 ("KVM: Check for duplicate vcpu_id as early as possible"). For now, keep the redundant vcpu_ids tracking as a sanity check. No functional change intended (absent KVM bugs, checking vcpu_ids and walking kvm_get_vcpu_by_id() should yield the same result). Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-6-seanjc@google.com> Signed-off-by: Paolo Bonzini --- virt/kvm/kvm_main.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index cae0a9cdc33e..0e5b639559ba 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4205,7 +4205,10 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) if (kvm->created_vcpus >= kvm->max_vcpus) return -EINVAL; - if (test_bit(id, kvm->vcpu_ids)) + if (kvm_get_vcpu_by_id(kvm, id)) + return -EEXIST; + + if (WARN_ON_ONCE(test_bit(id, kvm->vcpu_ids))) return -EEXIST; r = kvm_arch_vcpu_precreate(kvm, id); @@ -4244,11 +4247,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) goto arch_vcpu_destroy; } - if (WARN_ON_ONCE(kvm_get_vcpu_by_id(kvm, id))) { - r = -EEXIST; - goto unlock_vcpu_destroy; - } - /* * Set the vCPU's index *before* the vCPU is reachable by other tasks. * Unwind the index back to -1 on failure so that KVM can use the index From 700f8d24436c45cb3cc8956f35c90a1b6a6487c2 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:44 -0700 Subject: [PATCH 265/535] Revert "KVM: Check for duplicate vcpu_id as early as possible" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that KVM uses kvm_get_vcpu_by_id() to check for an existing vCPU ID before doing any meaningful work, which was made possible by holding kvm->lock for the entirety of vCPU creation, revert the now-redundant "early" vCPU ID tracking. The claims about the impact of kvm->vcpu_ids on the memory footprint were a wee bit wrong: the worst case scenario isn't 256 bytes per VM, it's 256 "unsigned longs" per VM, i.e. 2048 bytes per VM. Increasing the size of "struct kvm" by 2048 nearly doubled the total size on many architectures, and tripped x86's KVM_SANITY_CHECK_VM_STRUCT_SIZE, which was added to detect this *exact* scenario, where a single change significantly increased the size of "struct kvm". I.e. attempting to build KVM with CONFIG_DEBUG_KERNEL=n fails on x86 (the build failures got missed because all build bots apparently test only CONFIG_DEBUG_KERNEL=y kernels, and maintainers' test flows were similarly lacking). This reverts commit 97d65b544f48b2ee49f6aea32145e3e7969955dc. Fixes: 97d65b544f48 ("KVM: Check for duplicate vcpu_id as early as possible") Reported-by: Jean-Christophe Guillain Closes: https://lore.kernel.org/all/56a4bc35ee605588b7cc36c8e45c12b5f3b506cb.camel@guillain.net Reported-by: Paweł S Closes: https://lore.kernel.org/all/CABD%3DWFOS4j4hDv%2BpW-eEM9HAM2q2GY_iYdAG%2BqvYcUEinUrcQQ@mail.gmail.com Tested-by: Jean-Christophe Guillain Signed-off-by: Sean Christopherson Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-7-seanjc@google.com> Signed-off-by: Paolo Bonzini --- include/linux/kvm_host.h | 1 - virt/kvm/kvm_main.c | 5 ----- 2 files changed, 6 deletions(-) diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index cf7fe835c4ad..c844def6dd95 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -791,7 +791,6 @@ struct kvm { /* The current active memslot set for each address space */ struct kvm_memslots __rcu *memslots[KVM_MAX_NR_ADDRESS_SPACES]; struct xarray vcpu_array; - DECLARE_BITMAP(vcpu_ids, KVM_MAX_VCPU_IDS); /* * Protected by slots_lock, but can be read outside if an * incorrect answer is acceptable. diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 0e5b639559ba..02bab6923e03 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4208,15 +4208,11 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) if (kvm_get_vcpu_by_id(kvm, id)) return -EEXIST; - if (WARN_ON_ONCE(test_bit(id, kvm->vcpu_ids))) - return -EEXIST; - r = kvm_arch_vcpu_precreate(kvm, id); if (r) return r; kvm->created_vcpus++; - __set_bit(id, kvm->vcpu_ids); vcpu = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL_ACCOUNT); if (!vcpu) { @@ -4299,7 +4295,6 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id) kmem_cache_free(kvm_vcpu_cache, vcpu); vcpu_decrement: kvm->created_vcpus--; - __clear_bit(id, kvm->vcpu_ids); return r; } From 41e005e2c5126ccb906d6374b3b97fcb223f7f8f Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 10:44:45 -0700 Subject: [PATCH 266/535] KVM: WARN if vCPU creation is in-progress when locking all vCPUs Now that KVM holds kvm->lock for the entirety of vCPU creation, from when created_vcpus is incremented until the new vCPU is fully onlined, WARN if the impossible happens and KVM somehow sees a discrepancy between the number of vCPUs "created" and "onlined". Signed-off-by: Sean Christopherson Tested-by: Jean-Christophe Guillain Tested-by: Naveen N Rao (AMD) Message-ID: <20260921174445.911676-8-seanjc@google.com> Signed-off-by: Paolo Bonzini --- virt/kvm/kvm_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 02bab6923e03..5a782849521c 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1363,7 +1363,7 @@ int kvm_trylock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); - if (kvm_is_vcpu_creation_in_progress(kvm)) + if (WARN_ON_ONCE(kvm_is_vcpu_creation_in_progress(kvm))) return -EBUSY; kvm_for_each_vcpu(i, vcpu, kvm) @@ -1389,7 +1389,7 @@ int kvm_lock_all_vcpus(struct kvm *kvm) lockdep_assert_held(&kvm->lock); - if (kvm_is_vcpu_creation_in_progress(kvm)) + if (WARN_ON_ONCE(kvm_is_vcpu_creation_in_progress(kvm))) return -EBUSY; kvm_for_each_vcpu(i, vcpu, kvm) { From b6249c1d5665098c8bf12f8d3e4712bf04c775bc Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 25 Sep 2026 16:42:56 -0700 Subject: [PATCH 267/535] KVM: x86/mmu: Bail from shadow walks if the root is invalid or a dummy When walking shadow page tables, immediately terminate the walk if the root is a "dummy" root, i.e. a root whose top-level page table is backed by the zero page, but otherwise doesn't exist. If memslot creation races with a stage-2 page fault (EPT violation or #NPF) from L2, then if the stars align, KVM will attempt to walk shadow page tables using the zero page and hit a NULL pointer deref. BUG: kernel NULL pointer dereference, address: 0000000000000021 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP CPU: 30 UID: 1000 PID: 941 Comm: qemu Not tainted 7.2.0-rc2-1b731e5ded48-next-vm #1741 PREEMPTLAZY Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:__kvm_mmu_invalidate_addr+0xea/0x210 [kvm] Call Trace: kvm_mmu_invalidate_addr+0x92/0xe0 [kvm] __kvm_inject_emulated_page_fault+0x67/0x80 [kvm] ept_page_fault+0x160/0x850 [kvm] kvm_mmu_do_page_fault+0x102/0x1f0 [kvm] kvm_mmu_page_fault+0x8e/0x6b0 [kvm] vmx_handle_exit+0x163/0x640 [kvm_intel] kvm_arch_vcpu_ioctl_run+0x960/0x2120 [kvm] kvm_vcpu_ioctl+0x2c7/0x970 [kvm] __x64_sys_ioctl+0x90/0xd0 do_syscall_64+0x67/0x5f0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7f606d0b53bb Opportunistically harden the shadow walks against fully invalid roots, but WARN, as all callers are expected/required to pre-check for a valid root. Don't WARN in the dummy root case as the whole point of using a dummy root is to provide a root that's valid enough to enter the guest, i.e. it should Just Work for all flows except those that *need* to know about dummy roots. Alternatively, KVM could allocate a dedicated page and associated shadow page structure for the dummy root, which is very tempting as it such an approach should be more resilient against unexpected behavior. But that would be a much larger and thus riskier change than simply terminating walks of dummy roots. Fixes: 0e3223d8d00a ("KVM: x86/mmu: Use dummy root, backed by zero page, for !visible guest roots") Reported-by: Gabriel Schneider Cc: stable@vger.kernel.org Signed-off-by: Sean Christopherson Message-ID: <20260925234256.2384816-1-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/mmu/mmu.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 8e62476e477b..a0d608e3fceb 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -2512,6 +2512,12 @@ static void shadow_walk_init_using_root(struct kvm_shadow_walk_iterator *iterato iterator->addr = addr; iterator->shadow_addr = root; + + if (WARN_ON_ONCE(!VALID_PAGE(root)) || kvm_mmu_is_dummy_root(root)) { + iterator->level = 0; + return; + } + iterator->level = vcpu->arch.mmu->root_role.level; if (iterator->level >= PT64_ROOT_4LEVEL && From 60d93c27859fb3ec5b5a689e24de303f166477d0 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 4 Sep 2026 10:06:39 -0700 Subject: [PATCH 268/535] KVM: SVM: Preserve TLB control (i.e. pending TLB flush) on failed VMRUN Don't reset the VMCB's TLB control back to "do nothing" on a failed VMRUN, as empirical testing shows that the CPU performs the requested TLB flush if and only if VMRUN is successful, i.e. clearing TLB control on a failed VMRUN effectively drops a TLB flush. Explicitly track the need to flush all ASIDs on a per-CPU basis, as the ASID reuse condition is tied to the pCPU, not to the vCPU. As a bonus, this also obviates the need to avoid clobbering FLUSH_ALL_ASID with TLB_CONTROL_FLUSH_ASID, e.g. in svm_flush_tlb_asid(). Deliberately don't bother saving/restoring the "old" tlb_ctl on failure, in quotes because it's not exactly the old tlb_ctl, it's the tlb_ctl from after pre_svm_run(), but before updating tlb_ctl for flush_all_asids. If VMRUN fails and TLB_CONTROL_FLUSH_ALL_ASID is forced, then the next successful run of the VMCB *may* unnecessarily flush all ASIDs, which strictly speaking could result in noisy neighbor issues. However, the fact that new_asid() is already guest-triggerable, because of KVM's flawed behavior of clearing the ASID on emulated INIT, means that a guest can already trigger a flush of all ASIDs at roughly the same rate. And once KVM stops clobbering the ASID on emulated INIT, *or* assigns a static ASID to each vCPU, this flaw goes away. Fixes: 38e5e92fe8c0 ("KVM: SVM: Implement Flush-By-Asid feature") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Suggested-by: Yosry Ahmed Cc: Tom Lendacky Cc: Jim Mattson Signed-off-by: Sean Christopherson Message-ID: <20260904170642.3291466-2-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/svm.c | 13 ++++++++++--- arch/x86/kvm/svm/svm.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 7d59d301e1e5..afbaaaab84ed 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -1902,8 +1902,7 @@ static void new_asid(struct vcpu_svm *svm, struct svm_cpu_data *sd) if (sd->next_asid > sd->max_asid) { ++sd->asid_generation; sd->next_asid = sd->min_asid; - svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; - vmcb_mark_dirty(svm->vmcb, VMCB_ASID); + sd->flush_all_asids = true; } svm->current_vmcb->asid_generation = sd->asid_generation; @@ -4528,6 +4527,11 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) svm->vmcb->control.asid = svm->asid; vmcb_mark_dirty(svm->vmcb, VMCB_ASID); } + if (this_cpu_ptr(&svm_data)->flush_all_asids) { + svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; + vmcb_mark_dirty(svm->vmcb, VMCB_ASID); + } + svm->vmcb->save.cr2 = vcpu->arch.cr2; if (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS) && @@ -4618,7 +4622,10 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) vcpu->arch.nested_run_pending = 0; } - svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING; + if (!svm_is_vmrun_failure(svm->vmcb->control.exit_code)) { + this_cpu_ptr(&svm_data)->flush_all_asids = false; + svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING; + } /* * Unconditionally mask off the CLEAR_RAP bit, the AND is just as cheap diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index e958943b8162..84f19026d3e8 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -376,6 +376,7 @@ struct svm_cpu_data { u32 next_asid; u32 min_asid; + bool flush_all_asids; bool bp_spec_reduce_set; struct vmcb *save_area; From e27fc915c8f575ab5630e27d97465c2077d4b9d1 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 4 Sep 2026 10:06:40 -0700 Subject: [PATCH 269/535] KVM: SVM: Update control fields on #VMEXIT if and only if VMRUN succeeded Leave control.erap_ctl and control.clean as-is in the VMCS if VMRUN fails, because as per AMD: there's no explicit architectural guarantee about the behavior in the presence of VMRUN failures. So the best thing to do would be to assume that if VMRUN fails, the actions requested in the control fields may not have been performed. Cc: stable@vger.kernel.org Signed-off-by: Sean Christopherson Message-ID: <20260904170642.3291466-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/svm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index afbaaaab84ed..b63e7c69aa1c 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4625,17 +4625,17 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) if (!svm_is_vmrun_failure(svm->vmcb->control.exit_code)) { this_cpu_ptr(&svm_data)->flush_all_asids = false; svm->vmcb->control.tlb_ctl = TLB_CONTROL_DO_NOTHING; + + /* + * Unconditionally mask off the CLEAR_RAP bit, the AND is just + * as cheap as the TEST+Jcc to avoid it. + */ + if (cpu_feature_enabled(X86_FEATURE_ERAPS)) + svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP; + + vmcb_mark_all_clean(svm->vmcb); } - /* - * Unconditionally mask off the CLEAR_RAP bit, the AND is just as cheap - * as the TEST+Jcc to avoid it. - */ - if (cpu_feature_enabled(X86_FEATURE_ERAPS)) - svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP; - - vmcb_mark_all_clean(svm->vmcb); - /* if exit due to PF check for async PF */ if (svm->vmcb->control.exit_code == SVM_EXIT_EXCP_BASE + PF_VECTOR) vcpu->arch.apf.host_apf_flags = From 64c5b15af71ba3c26b2088bdbb0bbab3dbe58e2c Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 4 Sep 2026 10:06:41 -0700 Subject: [PATCH 270/535] KVM: SVM: Don't mark ASID fields as dirty when setting control.tlb_ctl Don't mark the ASID as dirty in the VMCB when requesting a TLB flush via control.tlb_ctl. Per "15.15.3 VMCB Clean Field" of the July 2026, Revision 3.45 version of the APM: The following are explicitly not cached and not represented by Clean bits: * TLB_Control Fixes: 7e8e6eed75e2 ("KVM: SVM: Move asid to vcpu_svm") Suggested-by: Yosry Ahmed Signed-off-by: Sean Christopherson Message-ID: <20260904170642.3291466-4-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 1 - arch/x86/kvm/svm/svm.c | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 6aa86a78711e..3448d56520c6 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3620,7 +3620,6 @@ int pre_sev_run(struct vcpu_svm *svm, int cpu) sd->sev_vmcbs[asid] = svm->vmcb; svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ASID; - vmcb_mark_dirty(svm->vmcb, VMCB_ASID); return 0; } diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index b63e7c69aa1c..830ace75e986 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4527,10 +4527,8 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) svm->vmcb->control.asid = svm->asid; vmcb_mark_dirty(svm->vmcb, VMCB_ASID); } - if (this_cpu_ptr(&svm_data)->flush_all_asids) { + if (this_cpu_ptr(&svm_data)->flush_all_asids) svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID; - vmcb_mark_dirty(svm->vmcb, VMCB_ASID); - } svm->vmcb->save.cr2 = vcpu->arch.cr2; From 73392706d0e0f3aff299321de41d73abe3f53d9d Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 4 Sep 2026 10:06:42 -0700 Subject: [PATCH 271/535] KVM: SVM: Sync guest's PERF_CNTR_GLOBAL_CTL from h/w only on successful VMRUN Don't (re)read PERF_CNTR_GLOBAL_CTL from hardware on a failed VMRUN, as the purpose of the read is to synchronize KVM's cache with any writes done by the guest, and the guest can't possibly have modified the MSR if it never got a chance to run. Signed-off-by: Sean Christopherson Message-ID: <20260904170642.3291466-5-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/svm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index 830ace75e986..f4f488328ea4 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -4632,6 +4632,10 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) svm->vmcb->control.erap_ctl &= ~ERAP_CONTROL_CLEAR_RAP; vmcb_mark_all_clean(svm->vmcb); + + if (!msr_write_intercepted(svm, MSR_AMD64_PERF_CNTR_GLOBAL_CTL)) + rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, + vcpu_to_pmu(vcpu)->global_ctrl); } /* if exit due to PF check for async PF */ @@ -4641,9 +4645,6 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) kvm_clear_available_registers(vcpu, SVM_REGS_LAZY_LOAD_SET); - if (!msr_write_intercepted(svm, MSR_AMD64_PERF_CNTR_GLOBAL_CTL)) - rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, vcpu_to_pmu(vcpu)->global_ctrl); - trace_kvm_exit(vcpu, KVM_ISA_SVM); svm_complete_interrupts(vcpu); From 9e06bbb9ade7cdf28d4a3b5b14e5812881c8df85 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Wed, 26 Aug 2026 12:58:33 -0700 Subject: [PATCH 272/535] KVM: SVM: Use the active VMCB's MSR bitmap when checking if MSR is intercepted Use the MSR permission bitmap of the active VMCB instead of assuming that KVM is always using vmcb02's bitmap when L2 is active, as KVM uses msrpm02 if and only if L1 wants to intercept MSR accesses, i.e. if and only if KVM needs to merge msprm01 with msrpm12. Don't bother tracking the virtual address of the bitmap that's being used, as __va() is cheap on x86, and caching the virtual address would introduce yet another source of potentially stale information. Fixes: b2ac58f90540 ("KVM/SVM: Allow direct access to MSR_IA32_SPEC_CTRL") Cc: stable@vger.kernel.org Reported-by: Stefan Teodorescu Signed-off-by: Sean Christopherson Message-ID: <20260826195833.844526-1-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/svm.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index f4f488328ea4..fe0cba4731b2 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -673,16 +673,7 @@ static void clr_dr_intercepts(struct vcpu_svm *svm) static bool msr_write_intercepted(struct vcpu_svm *svm, u32 msr) { - /* - * For non-nested case: - * If the L01 MSR bitmap does not intercept the MSR, then we need to - * save it. - * - * For nested case: - * If the L02 MSR bitmap does not intercept the MSR, then we need to - * save it. - */ - void *msrpm = is_guest_mode(&svm->vcpu) ? svm->nested.msrpm : svm->msrpm; + void *msrpm = __va(__sme_clr(svm->vmcb->control.msrpm_base_pa)); return svm_test_msr_bitmap_write(msrpm, msr); } From 2abb25273c82061dd4670690c81c319c03740bf6 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Aug 2026 14:44:03 -0700 Subject: [PATCH 273/535] KVM: nVMX: Force MSR bitmap refresh if runtime eVMCS controls are modified Force a refresh of the vmcs02 MSR bitmap during nested VM-Enter if the runtime eVMCS controls (pin, primary, secondary, etc.) are being updated. If L1 isn't intercepting TPR writes, runs L2 with TPR virtualization, and then runs the same L2 with TPR virtualization disabled, KVM will fail to refresh msr_bitmap02 and leave TPR in passthrough mode even though TPR virtualization is disabled. I.e. failure to refresh the bitmap lets L2 (or L1 by proxy) read and write L0's TPR. Fixes: 502d2bf5f2fd ("KVM: nVMX: Implement Enlightened MSR Bitmap feature") Cc: stable@vger.kernel.org Reviewed-by: Vitaly Kuznetsov Signed-off-by: Sean Christopherson Signed-off-by: Paolo Bonzini --- arch/x86/kvm/vmx/nested.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 40c1a5f6fa8a..b25216862740 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -1754,6 +1754,9 @@ static void copy_vmcs12_to_shadow(struct vcpu_vmx *vmx) static void copy_enlightened_to_vmcs12(struct vcpu_vmx *vmx, u32 hv_clean_fields) { #ifdef CONFIG_KVM_HYPERV + const u64 runtime_controls = HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1 | + HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2 | + HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_PROC; struct vmcs12 *vmcs12 = vmx->nested.cached_vmcs12; struct hv_enlightened_vmcs *evmcs = nested_vmx_evmcs(vmx); struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(&vmx->vcpu); @@ -1762,6 +1765,9 @@ static void copy_enlightened_to_vmcs12(struct vcpu_vmx *vmx, u32 hv_clean_fields vmcs12->tpr_threshold = evmcs->tpr_threshold; vmcs12->guest_rip = evmcs->guest_rip; + if ((hv_clean_fields & runtime_controls) != runtime_controls) + vmx->nested.force_msr_bitmap_recalc = true; + if (unlikely(!(hv_clean_fields & HV_VMX_ENLIGHTENED_CLEAN_FIELD_ENLIGHTENMENTSCONTROL))) { hv_vcpu->nested.pa_page_gpa = evmcs->partition_assist_page; From 8916c7a87455370b25523d5b1b9015b5244d18c8 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 10 Jul 2026 09:20:52 -0700 Subject: [PATCH 274/535] KVM: selftests: Add x2APIC MSR test for inhibiting APICv while nested Add a selftest to verify that KVM intercepts x2APIC MSR accesses for L1 after APICv is inhibited while L2 is active. This is a regression test for an AVIC bug where KVM would skip updating x2APIC MSR intercepts while L2 is active, thus giving L1 access to a wide swath of L0's x2APIC surface. Signed-off-by: Sean Christopherson Message-ID: <20260710162052.2188574-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/x86/nested_x2apic_test.c | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/nested_x2apic_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 6a1482e3a286..752f81b070fa 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -103,6 +103,7 @@ TEST_GEN_PROGS_x86 += x86/nested_tdp_fault_test TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test TEST_GEN_PROGS_x86 += x86/nested_vmsave_vmload_test +TEST_GEN_PROGS_x86 += x86/nested_x2apic_test TEST_GEN_PROGS_x86 += x86/platform_info_test TEST_GEN_PROGS_x86 += x86/pmu_counters_test TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c new file mode 100644 index 000000000000..e77b4c347272 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "vmx.h" +#include "svm_util.h" + +/* + * Use the kernel's posted interrupt vectors to minimize the risk of crashing + * the host if KVM is buggy. Note, the vectors aren't set in stone, ideally + * these will be kept up-to-date if the kernel vectors change, but it's "fine" + * if they are stale. + */ +#define POSTED_INTR_VECTOR 0xf2 +#define POSTED_INTR_WAKEUP_VECTOR 0xf1 +#define POSTED_INTR_NESTED_VECTOR 0xf0 + +static volatile unsigned int nr_irqs; + +static void guest_irq_handler(struct ex_regs *regs) +{ + nr_irqs++; + x2apic_write_reg(APIC_EOI, 0); +} + +static void l2_guest_code(void) +{ + wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); + asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); +} + +static void l1_svm_code(struct svm_test_data *svm) +{ + struct vmcb_control_area *ctrl = &svm->vmcb->control; + + generic_svm_setup(svm, l2_guest_code); + ctrl->intercept |= BIT_ULL(INTERCEPT_CPUID) | BIT_ULL(INTERCEPT_MSR_PROT); + + run_guest(svm->vmcb, svm->vmcb_gpa); + GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_CPUID); + + stgi(); +} + +static void l1_vmx_code(struct vmx_pages *vmx) +{ + u64 control; + + GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true); + GUEST_ASSERT_EQ(load_vmcs(vmx), true); + + prepare_vmcs(vmx, NULL); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, (unsigned long)l2_guest_code), 0); + + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); + control |= CPU_BASED_USE_MSR_BITMAPS; + GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); + + GUEST_ASSERT(!vmlaunch()); + GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); +} + +static void l1_guest_code(void *test_data) +{ + x2apic_enable(); + + if (this_cpu_has(X86_FEATURE_SVM)) + l1_svm_code(test_data); + else + l1_vmx_code(test_data); + + sti_nop(); + + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_VECTOR); + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_WAKEUP_VECTOR); + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_NESTED_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 3); + GUEST_DONE(); +} + +int main(int argc, char *argv[]) +{ + gva_t nested_test_data_gva; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); + + vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); + vm_install_exception_handler(vm, POSTED_INTR_VECTOR, guest_irq_handler); + vm_install_exception_handler(vm, POSTED_INTR_WAKEUP_VECTOR, guest_irq_handler); + vm_install_exception_handler(vm, POSTED_INTR_NESTED_VECTOR, guest_irq_handler); + + if (kvm_cpu_has(X86_FEATURE_SVM)) + vcpu_alloc_svm(vm, &nested_test_data_gva); + else + vcpu_alloc_vmx(vm, &nested_test_data_gva); + + vcpu_args_set(vcpu, 1, nested_test_data_gva); + + vcpu_run(vcpu); + + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_DONE: + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Expected DONE, got unexpected ucall %lu", uc.cmd); + } + + kvm_vm_free(vm); +} From 0c19bc0ef11d8ea7f929a3ec3ce549cfe0781859 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Aug 2026 15:36:07 -0700 Subject: [PATCH 275/535] KVM: selftests: Run the nested x2APIC with and without APICv being inhibited in L2 Signed-off-by: Sean Christopherson Message-ID: <20260813223610.2043560-4-seanjc@google.com> Signed-off-by: Paolo Bonzini --- .../selftests/kvm/x86/nested_x2apic_test.c | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c index e77b4c347272..a1072bf499ee 100644 --- a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -15,6 +15,7 @@ #define POSTED_INTR_WAKEUP_VECTOR 0xf1 #define POSTED_INTR_NESTED_VECTOR 0xf0 +static bool inhibit_apicv; static volatile unsigned int nr_irqs; static void guest_irq_handler(struct ex_regs *regs) @@ -25,7 +26,8 @@ static void guest_irq_handler(struct ex_regs *regs) static void l2_guest_code(void) { - wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); + if (inhibit_apicv) + wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); } @@ -78,20 +80,20 @@ static void l1_guest_code(void *test_data) GUEST_DONE(); } -int main(int argc, char *argv[]) +static void __test_x2apic_intercepts(void) { gva_t nested_test_data_gva; struct kvm_vcpu *vcpu; struct kvm_vm *vm; struct ucall uc; - TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); - vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); vm_install_exception_handler(vm, POSTED_INTR_VECTOR, guest_irq_handler); vm_install_exception_handler(vm, POSTED_INTR_WAKEUP_VECTOR, guest_irq_handler); vm_install_exception_handler(vm, POSTED_INTR_NESTED_VECTOR, guest_irq_handler); + sync_global_to_guest(vm, inhibit_apicv); + if (kvm_cpu_has(X86_FEATURE_SVM)) vcpu_alloc_svm(vm, &nested_test_data_gva); else @@ -115,3 +117,18 @@ int main(int argc, char *argv[]) kvm_vm_free(vm); } + +#define test_x2apic_intercepts(inhibit_apic_setting) \ +do { \ + inhibit_apic_setting; \ + \ + __test_x2apic_intercepts(); \ +} while (0) + +int main(int argc, char *argv[]) +{ + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); + + test_x2apic_intercepts(inhibit_apicv = true); + test_x2apic_intercepts(inhibit_apicv = false); +} From 2f00ba93cc285092cd3b7ffebbc9abb6004f55e7 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Aug 2026 15:36:08 -0700 Subject: [PATCH 276/535] KVM: selftests: Verify that L0's TPR doesn't get clobbered Signed-off-by: Sean Christopherson Message-ID: <20260813223610.2043560-5-seanjc@google.com> Signed-off-by: Paolo Bonzini --- .../selftests/kvm/x86/nested_x2apic_test.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c index a1072bf499ee..3b59ba3e3342 100644 --- a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -28,6 +28,10 @@ static void l2_guest_code(void) { if (inhibit_apicv) wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); + + x2apic_write_reg(APIC_TASKPRI, 0xf0); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); + asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); } @@ -73,10 +77,22 @@ static void l1_guest_code(void *test_data) sti_nop(); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 0); + + x2apic_write_reg(APIC_TASKPRI, 0xff); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xff); x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_WAKEUP_VECTOR); + GUEST_ASSERT_EQ(nr_irqs, 0); + + x2apic_write_reg(APIC_TASKPRI, 0); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); + x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_NESTED_VECTOR); GUEST_ASSERT_EQ(nr_irqs, 3); + GUEST_DONE(); } From 7d5a36a5490d496e17823085fbe7ec6c0a7bf43d Mon Sep 17 00:00:00 2001 From: Rounak Das Date: Sat, 26 Sep 2026 17:38:46 +0530 Subject: [PATCH 277/535] EDAC/altera: Fix device node reference leaks in the SDMMC ECC setup Under altr_portb_setup() and socfpga_init_sdmmc_ecc(), of_find_compatible_node() was being used to look up the sdmmc-ecc node. This node wasn't being dropped using of_node_put(). altr_portb_setup() did not drop its reference under its success path or on any error path. socfpga_init_sdmmc_ecc() did an early return thereby skipping the common exit label and thus leaking the reference. Add the missing of_node_put() calls in altr_portb_setup(), and route socfpga_init_sdmmc_ecc()'s success path through the common exit label. Fixes: 911049845d70 ("EDAC, altera: Add Arria10 SD-MMC EDAC support") Fixes: 788586efd116 ("EDAC/altera: Initialize peripheral FIFOs in probe()") Closes: https://sashiko.dev/#/patchset/20260708091135.94114-1-rounakdas2025%40gmail.com Signed-off-by: Rounak Das Signed-off-by: Borislav Petkov (AMD) Acked-by: Dinh Nguyen Cc: stable@vger.kernel.org # 6.18+ Link: https://patch.msgid.link/20260926120846.35716-1-rounakdas2025@gmail.com --- drivers/edac/altera_edac.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 4c20fb926160..bb95dab847b3 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1506,6 +1506,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name, 1, ecc_name, 1, 0, edac_idx); if (!dci) { + of_node_put(np); edac_printk(KERN_ERR, EDAC_DEVICE, "%s: Unable to allocate PortB EDAC device\n", ecc_name); @@ -1518,6 +1519,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) if (!devres_open_group(device->edac->dev, altr_portb_setup, GFP_KERNEL)) { edac_device_free_ctl_info(dci); + of_node_put(np); return -ENOMEM; } @@ -1584,6 +1586,8 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) rc = -ENOMEM; goto err_release_group_1; } + of_node_put(np); + altr_create_edacdev_dbgfs(dci, prv); list_add(&altdev->next, &altdev->edac->a10_ecc_devices); @@ -1600,6 +1604,7 @@ static int altr_portb_setup(struct altr_edac_device_dev *device) */ devres_release_group(device->edac->dev, altr_portb_setup); edac_device_free_ctl_info(dci); + of_node_put(np); edac_printk(KERN_ERR, EDAC_DEVICE, "%s:Error setting up EDAC device: %d\n", ecc_name, rc); return rc; @@ -1627,7 +1632,7 @@ static int socfpga_init_sdmmc_ecc(struct altr_edac_device_dev *device) goto exit; /* Setup portB */ - return altr_portb_setup(device); + rc = altr_portb_setup(device); exit: of_node_put(child); From c79cf15eb35a4c703bf18f8c33c47e80dd660f8e Mon Sep 17 00:00:00 2001 From: Hao Ge Date: Thu, 27 Aug 2026 11:05:03 +0800 Subject: [PATCH 278/535] module: fix lost error code from codetag_load_module() If codetag_load_module() fails, err is not set to reflect the failure and load_module() returns 0 after the module has been torn down. Also, if the module is a livepatch, mod->klp_info allocated by copy_module_elf() leaks on this error path. Free it via a new livepatch_cleanup label. Link: https://lore.kernel.org/20260827030503.49171-1-hao.ge@linux.dev Fixes: 044d2aee6c57 ("alloc_tag: handle module codetag load errors as module load failures") Signed-off-by: Hao Ge Signed-off-by: Andrew Morton Reported-by: Sashiko Suggested-by: Petr Pavlu Reviewed-by: Bradley Morgan Cc: Aaron Tomlin Cc: Luis Chamberalin Cc: Sami Tolvanen Cc: Suren Baghdasaryan Cc: --- kernel/module/main.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/module/main.c b/kernel/module/main.c index d0e1e0bd2ad0..c1b34dc1e89a 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -3581,8 +3581,9 @@ static int load_module(struct load_info *info, const char __user *uargs, goto sysfs_cleanup; } - if (codetag_load_module(mod)) - goto sysfs_cleanup; + err = codetag_load_module(mod); + if (err) + goto livepatch_cleanup; /* Get rid of temporary copy. */ free_copy(info, flags); @@ -3592,6 +3593,9 @@ static int load_module(struct load_info *info, const char __user *uargs, return do_init_module(mod); + livepatch_cleanup: + if (is_livepatch_module(mod)) + free_module_elf(mod); sysfs_cleanup: mod_sysfs_teardown(mod); coming_cleanup: From 72c3d682c165b836b54a3d07a01f72e39c47022c Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Mon, 14 Sep 2026 10:12:41 +0800 Subject: [PATCH 279/535] mm: shmem: ignore sysfs configs for shmem forced collapse According to Documentation/mm/transhuge.rst, MADV_COLLAPSE is expected to ignore any THP or mTHP interface settings. However, after commit 26c7d8413aaf ("mm: thp: support "THPeligible" semantics for mTHP with anonymous shmem"), performing MADV_COLLAPSE on shmem will depend on /sys/.../hugepages-2048kB/shmem_enabled being set to "inherit" (although that is the default), which can cause MADV_COLLAPSE to fail unexpectedly. This could cause a userspace-visible performance regression. Fix this by returning the result of shmem_huge_global_enabled() directly when MADV_COLLAPSE is requested, allowing PMD-order collapse while ignoring shmem THP/mTHP settings. Link: https://lore.kernel.org/063f655b4d6c4234f3aa27ed6ecab10283ecb880.1789351825.git.baolin.wang@linux.alibaba.com Fixes: 26c7d8413aaf ("mm: thp: support "THPeligible" semantics for mTHP with anonymous shmem") Signed-off-by: Baolin Wang Signed-off-by: Andrew Morton Reported-by: Kiryl Shutsemau (Meta) Closes: https://lore.kernel.org/all/20260908125105.1510704-7-kirill@shutemov.name/ Reviewed-by: Kiryl Shutsemau (Meta) Acked-by: Zi Yan Reviewed-by: Lance Yang Reviewed-by: Barry Song Acked-by: Qi Zheng Acked-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes (ARM) Cc: Dev Jain Cc: Hugh Dickins Cc: Liam R. Howlett Cc: Ryan Roberts Cc: --- mm/shmem.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mm/shmem.c b/mm/shmem.c index 897fa2b61346..848316eaa7f4 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1847,8 +1847,11 @@ unsigned long shmem_allowable_huge_orders(struct inode *inode, global_orders = shmem_huge_global_enabled(inode, index, write_end, shmem_huge_force, vma, vm_flags); - /* Tmpfs huge pages allocation */ - if (!vma || !vma_is_anon_shmem(vma)) + /* + * Tmpfs huge pages allocation or forced collapse ignores + * sysfs configs. + */ + if (!vma || !vma_is_anon_shmem(vma) || shmem_huge_force) return global_orders; /* From 347c6ed8cf7768883252e106994de62c7947f0a0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:23:47 +0200 Subject: [PATCH 280/535] alloc_tag: avoid implicit padding in uapi The implied padding causes a harmless warning when testing the uapi headers with -Wpadded that could in theory indicate incompatibilities or data leaks: ./usr/include/linux/alloc_tag.h:41:1: error: padding struct size to alignment boundary with 7 bytes [-Werror=padded] The code here is fine, but it's better to make the padding explicit and avoid the warning here. Link: https://lore.kernel.org/20260916065830.1619425-1-arnd@kernel.org Link: https://lore.kernel.org/20260915202404.3568029-1-arnd@kernel.org Fixes: 1d581ab2348c ("alloc_tag: add ioctl to /proc/allocinfo") Signed-off-by: Arnd Bergmann Signed-off-by: Andrew Morton Acked-by: Suren Baghdasaryan Acked-by: SJ Park Acked-by: Hao Ge --- include/uapi/linux/alloc_tag.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index e3ad94444864..7d4618bea043 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -38,6 +38,7 @@ struct allocinfo_counter { __u64 bytes; __u64 calls; __u8 accurate; + __u8 pad[7]; } __attribute__((aligned(8))); struct allocinfo_tag_data { From 52ae167ce16609c7e6fffef588b3c2c26de2db19 Mon Sep 17 00:00:00 2001 From: SJ Park Date: Wed, 16 Sep 2026 06:50:19 -0700 Subject: [PATCH 281/535] mm/damon/core: don't skip damos_adjust_quota() while esz is not zero DAMOS could unexpectedly stop working when a user disables quota using the online parameters commit feature. Fix it by correcting a wrong quota unset check in damos_adjust_quota(). DAMON users could disable all quotas by unsetting time and size quotas, and removing all quota goals. The intention of disabling quotas would be making DAMOS run at full speed. When such quota disabled setup is detected, damos_adjust_quota() skips all its work. The skipped works include effective size quota (damos_quota->esz) updates and charged quota amount (damos_quota->charged_sz) resets. The intention is to avoid doing unnecessary work when quotas are disabled. However, users could do the setup while effective size quota is non-zero, by doing the disabling with the online DAMON parameters commit feature. In this case, because the effective size quota exists, DAMOS will keep working with the quota until it is fully charged. After the effective quota is fully charged, the charged quota amount (damos_quota->charged_sz) cannot be reset because damos_adjust_quota() skips it. Then, DAMOS stops working until the quota is newly set or DAMON is entirely restarted. The problem happens because damos_adjust_quota() assumes the user setup for disabling quota immediately disabled it. In reality, the quota is still working until the effective size quota is also updated to zero. Other logic for catching that uses damos_quota_is_set(), which understands the fact and therefore checks the effective size quota in addition to the user setup. Fix the issue by using damos_quota_is_set() in damos_adjust_quota() to determine if its works should be skipped. The user impact is a non-deterministic and unexpected DAMOS stop behavior. That is, users would disable quotas using the online parameters commit feature, expecting DAMOS will run at full speed. However, depending on the timing, the setup can be updated while the effective size quota is non-zero. Due to the above mentioned internal mechanism, DAMOS stops working instead of running at full speed. It is unexpected behavior. It is also non-deterministic because sometimes the setup is done when the effective size quota is zero, depending on the timing. It doesn't cause critical issues like crashes or leaks. Users can simply set a reasonable quota again, or restart DAMON. But definitely it is an unexpected and non-deterministic behavior that makes it difficult to reliably use. Also investigating the root cause of the behavior would be quite difficult. Link: https://lore.kernel.org/20260916135020.86483-1-sj@kernel.org Fixes: da87878010e5 ("mm/damon/sysfs: support online inputs update") Signed-off-by: SJ Park Signed-off-by: Andrew Morton Reviewed-by: Kunwu Chan Cc: # 5.19.x --- mm/damon/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/damon/core.c b/mm/damon/core.c index 1764620903f2..ab3c4d754964 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -3147,7 +3147,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) unsigned long cumulated_sz, cached_esz; unsigned int score, max_score = 0; - if (!quota->ms && !quota->sz && list_empty("a->goals)) + if (!damos_quota_is_set(quota)) return; /* First charge window */ From 22ab0647764c38924669673634ae333578d77768 Mon Sep 17 00:00:00 2001 From: Andrey Ryabinin Date: Wed, 16 Sep 2026 19:51:13 +0200 Subject: [PATCH 282/535] kasan: unpoison task stack below watermark only in generic mode CPU resume and BPF exception handling can discard stack frames without running their compiler-generated epilogues. Generic KASAN needs kasan_unpoison_task_stack_below() to clear the redzones left behind by those frames before the stack is reused. CONFIG_KASAN_STACK also enables this helper for SW_TAGS. The helper derives the stack base from an untagged stack pointer, so kasan_unpoison() writes KASAN_TAG_KERNEL (0xff) into the shadow for [base, watermark). For a vmapped task stack, vm_area->addr still carries the original random allocation tag. This creates a tag mismatch at the stack base even if that memory has never held an instrumented stack object. When the task exits and its stack is not cached, thread_stack_free_rcu() passes vm_area->addr to vfree(). In RCU callback context this reaches vfree_atomic(), whose llist_add() writes to the allocation base through the tagged pointer and triggers a false KASAN invalid-access report: BUG: KASAN: invalid-access in vfree_atomic+0x90/0x150 Write of size 8 at addr c2ffffc0a8f70000 by task rcuop/7/75 Pointer tag: [c2], memory tag: [ff] Call trace: __hwasan_store8_noabort+0xe8/0xf8 vfree_atomic+0x90/0x150 vfree+0x220/0x298 thread_stack_free_rcu+0x3c/0x4c rcu_do_batch+0x308/0xaf0 rcu_nocb_cb_kthread+0x33c/0x708 The deferred-free path started using the tagged vm_area->addr in commit 449e0b4ed5a1 ("fork: clean-up naming of vm_stack/vm_struct variables in vmap stacks code"). Previously it freed the untagged pointer derived from tsk->stack, so the write was never tag-checked. SW_TAGS does not need the blanket shadow reset to make subsequent stack accesses valid: untagged kernel pointers have the match-all 0xff tag, and the compiler initializes the shadow tags of instrumented stack objects before use. Return early unless CONFIG_KASAN_GENERIC is enabled. Keep the mode check in the common helper so it covers both resume and bpf_throw(). Link: https://lore.kernel.org/20260916175113.1327454-1-ryabinin.a.a@gmail.com Fixes: 449e0b4ed5a1 ("fork: clean-up naming of vm_stack/vm_struct variables in vmap stacks code") Signed-off-by: Andrey Ryabinin Signed-off-by: Andrew Morton Reported-by: Shaobo Huang Closes: https://lore.kernel.org/all/20260806123020.90869-1-huangshaobo3@xiaomi.com/ Assisted-by: LLM Cc: Alexander Potapenko Cc: Andrey Konovalov Cc: David Hildenbrand Cc: Dmitry Vyukov Cc: Lorenzo Stoakes Cc: Vincenzo Frascino Cc: --- mm/kasan/common.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mm/kasan/common.c b/mm/kasan/common.c index b7d05c2a6d93..7d21c4db68d2 100644 --- a/mm/kasan/common.c +++ b/mm/kasan/common.c @@ -114,12 +114,23 @@ void kasan_unpoison_task_stack(struct task_struct *task) /* Unpoison the stack for the current task beyond a watermark sp value. */ asmlinkage void kasan_unpoison_task_stack_below(const void *watermark) { + void *base; + + /* + * Only the generic mode needs this. In the tag-based mode the + * compiler fully initializes the shadow of stack variables on + * function entry, so stale tags left below the watermark are + * harmless. + */ + if (!IS_ENABLED(CONFIG_KASAN_GENERIC)) + return; + /* * Calculate the task stack base address. Avoid using 'current' * because this function is called by early resume code which hasn't * yet set up the percpu register (%gs). */ - void *base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1)); + base = (void *)((unsigned long)watermark & ~(THREAD_SIZE - 1)); kasan_unpoison(base, watermark - base, false); } From 711d886fe76e28854814759f51ee175e279ef8c7 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Fri, 18 Sep 2026 18:34:24 +0200 Subject: [PATCH 283/535] MAINTAINERS: split up MEMORY MANAGEMENT - MEMORY POLICY AND MIGRATION Patch series "MAINTAINERS: rework MEMORY MANAGEMENT - MEMORY POLICY". Let's rework MEMORY MANAGEMENT - MEMORY POLICY AND MIGRATION. As I can use some maintenance help in that area, add Gregory as a new co-maintainer for the split out MEMORY MANAGEMENT - NUMA PLACEMENT section. This patch (of 3): Let's split it up into MIGRATION and NUMA PLACEMENT. The latter is a better fitting description for mempolicy.c. Make a best guess about which pieces existing reviewers are interested in. Move the split sections to keep alphabetical order. Link: https://lore.kernel.org/20260918-maintainers-mempolicy-v1-1-9a94cac6d135@kernel.org Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Andrew Morton Acked-by: Lorenzo Stoakes (ARM) Acked-by: Zi Yan Reviewed-by: Joshua Hahn Reviewed-by: SJ Park Acked-by: Vlastimil Babka (SUSE) Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Liam R. Howlett Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan --- MAINTAINERS | 62 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 140eafcbbd78..3f70a28df4b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17208,31 +17208,6 @@ F: include/trace/events/ksm.h F: mm/ksm.c F: mm/mm_slot.h -MEMORY MANAGEMENT - MEMORY POLICY AND MIGRATION -M: Andrew Morton -M: David Hildenbrand -R: Zi Yan -R: Matthew Brost -R: Joshua Hahn -R: Rakie Kim -R: Byungchul Park -R: Gregory Price -R: Ying Huang -R: Alistair Popple -L: linux-mm@kvack.org -S: Maintained -W: http://www.linux-mm.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm -F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy -F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave -F: include/linux/mempolicy.h -F: include/uapi/linux/mempolicy.h -F: include/linux/migrate.h -F: include/linux/migrate_mode.h -F: mm/mempolicy.c -F: mm/migrate.c -F: mm/migrate_device.c - MEMORY MANAGEMENT - MGLRU (MULTI-GEN LRU) M: Andrew Morton R: Kairui Song @@ -17255,6 +17230,25 @@ F: include/linux/mmzone.h F: mm/vmscan.c F: mm/workingset.c +MEMORY MANAGEMENT - MIGRATION +M: Andrew Morton +M: David Hildenbrand +R: Zi Yan +R: Matthew Brost +R: Joshua Hahn +R: Byungchul Park +R: Gregory Price +R: Ying Huang +R: Alistair Popple +L: linux-mm@kvack.org +S: Maintained +W: http://www.linux-mm.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: include/linux/migrate.h +F: include/linux/migrate_mode.h +F: mm/migrate.c +F: mm/migrate_device.c + MEMORY MANAGEMENT - MISC M: Andrew Morton M: David Hildenbrand @@ -17307,6 +17301,24 @@ F: mm/numa.c F: mm/numa_emulation.c F: mm/numa_memblks.c +MEMORY MANAGEMENT - NUMA PLACEMENT +M: Andrew Morton +M: David Hildenbrand +R: Zi Yan +R: Joshua Hahn +R: Rakie Kim +R: Gregory Price +R: Ying Huang +L: linux-mm@kvack.org +S: Maintained +W: http://www.linux-mm.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy +F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave +F: include/linux/mempolicy.h +F: include/uapi/linux/mempolicy.h +F: mm/mempolicy.c + MEMORY MANAGEMENT - OOM KILLER M: Michal Hocko R: David Rientjes From 4050a73a90f456887dc35d5bbfa4f4790eb5c2cf Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Fri, 18 Sep 2026 18:34:25 +0200 Subject: [PATCH 284/535] MAINTAINERS: move memory tiering under MEMORY MANAGEMENT - NUMA PLACEMENT NUMA PLACEMENT is a better place for memory tiering. My best guess is that existing MISC reviewers are not that interested in memory tiering, so don't carry any over. Link: https://lore.kernel.org/20260918-maintainers-mempolicy-v1-2-9a94cac6d135@kernel.org Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Andrew Morton Acked-by: Lorenzo Stoakes (ARM) Acked-by: Zi Yan Reviewed-by: Joshua Hahn Reviewed-byt: SJ Park Acked-by: Vlastimil Babka (SUSE) Cc: Alistair Popple Cc: Byungchul Park Cc: Gregory Price Cc: "Huang, Ying" Cc: Liam R. Howlett Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan --- MAINTAINERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 3f70a28df4b1..d69f112e5abb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17264,7 +17264,6 @@ W: http://www.linux-mm.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm F: Documentation/ABI/testing/sysfs-kernel-mm F: Documentation/ABI/testing/sysfs-kernel-mm-cma -F: Documentation/ABI/testing/sysfs-kernel-mm-memory-tiers F: Documentation/ABI/testing/sysfs-kernel-mm-numa F: Documentation/admin-guide/mm/ F: Documentation/mm/ @@ -17272,7 +17271,6 @@ F: drivers/char/mem.c F: include/linux/cma.h F: include/linux/dmapool.h F: include/linux/ioremap.h -F: include/linux/memory-tiers.h F: include/linux/page_idle.h F: mm/backing-dev.c F: mm/cma.c @@ -17284,7 +17282,6 @@ F: mm/early_ioremap.c F: mm/fadvise.c F: mm/ioremap.c F: mm/mapping_dirty_helpers.c -F: mm/memory-tiers.c F: mm/page_idle.c F: mm/pgalloc-track.h F: mm/process_vm_access.c @@ -17313,10 +17310,13 @@ L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm +F: Documentation/ABI/testing/sysfs-kernel-mm-memory-tiers F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy F: Documentation/ABI/testing/sysfs-kernel-mm-mempolicy-weighted-interleave +F: include/linux/memory-tiers.h F: include/linux/mempolicy.h F: include/uapi/linux/mempolicy.h +F: mm/memory-tiers.c F: mm/mempolicy.c MEMORY MANAGEMENT - OOM KILLER From b3f0c1a4e41f356c3126a676ad55c0f6881e7514 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" Date: Fri, 18 Sep 2026 18:34:26 +0200 Subject: [PATCH 285/535] MAINTAINERS: make Gregory a co-maintainer of MEMORY MANAGEMENT - NUMA PLACEMENT Gregory is extremely familiar with NUMA placement handling and volunteer to help co-maintain the numa placement bits. So add him as a co-maintainer. Link: https://lore.kernel.org/20260918-maintainers-mempolicy-v1-3-9a94cac6d135@kernel.org Signed-off-by: David Hildenbrand (Arm) Signed-off-by: Andrew Morton Acked-by: Lorenzo Stoakes (ARM) Acked-by: Gregory Price (Meta) Acked-by: Zi Yan Reviewed-by: Joshua Hahn Acked-by: SJ Park Acked-by: Vlastimil Babka (SUSE) Cc: Alistair Popple Cc: Byungchul Park Cc: "Huang, Ying" Cc: Liam R. Howlett Cc: Matthew Brost Cc: Michal Hocko Cc: Mike Rapoport Cc: Rakie Kim Cc: Suren Baghdasaryan --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index d69f112e5abb..9fcda07a32a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17301,10 +17301,10 @@ F: mm/numa_memblks.c MEMORY MANAGEMENT - NUMA PLACEMENT M: Andrew Morton M: David Hildenbrand +M: Gregory Price R: Zi Yan R: Joshua Hahn R: Rakie Kim -R: Gregory Price R: Ying Huang L: linux-mm@kvack.org S: Maintained From 6cc8f549f6888e36036ab14d84a8ec46b4a393c0 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Wed, 23 Sep 2026 08:39:20 +0800 Subject: [PATCH 286/535] MAINTAINERS: add Heming Zhao as ocfs2 reviewer Heming Zhao has contributed ocfs2 a lot recent years, both as a author and reviewer. So add him as ocfs2 reviewer. Link: https://lore.kernel.org/20260923003920.2382398-1-joseph.qi@linux.alibaba.com Signed-off-by: Joseph Qi Signed-off-by: Andrew Morton Acked-by: Mark Fasheh Cc: Heming Zhao Cc: Joel Becker --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9fcda07a32a0..23de9fc4c2a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20568,6 +20568,7 @@ ORACLE CLUSTER FILESYSTEM 2 (OCFS2) M: Mark Fasheh M: Joel Becker M: Joseph Qi +R: Heming Zhao L: ocfs2-devel@lists.linux.dev S: Supported W: http://ocfs2.wiki.kernel.org From 976d5e0ddac885b0219252f004f6c561c344df9d Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:45:41 +0100 Subject: [PATCH 287/535] mm/vma: predicate setting mmap_prepare VMA fields on new vma alloc It only makes sense to manipulate VMA fields if a new VMA was allocated, rather than merged. VMA merging does not compare vm_ops or vm_private_data, so a merged VMA keeps its own, which is also what the legacy f_op->mmap path does since it never touches an existing VMA. Currently, these fields will get overwritten by whatever state is established in the mmap_prepare hook, and if the VMA was merged, vm_ops->mapped will not have been called, so this could destructively clear existing state without replacing it with anything valid. There is an implicit requirement that vm_private_data and vm_ops are fungible across VMAs which means that losing the 'new' state is fine. However in this case the 'old' state is being overwritten by potentially invalid 'new' state, so this must be rectified. Additionally constify have_mmap_prepare while here. All existing in-tree users either derive state from the file or are unmergeable due to VMA flags, so this has no direct impact. Link: https://lore.kernel.org/20260923-fix-mmap-prepare-overwrite-v1-1-3b3f1bfcdf5e@kernel.org Fixes: c84bf6dd2b83 ("mm: introduce new .mmap_prepare() file callback") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reviewed-by: Suren Baghdasaryan Reviewed-by: Gregory Price (Meta) Acked-by: Zi Yan Acked-by: Vlastimil Babka (SUSE) Cc: Liam R. Howlett Cc: Jann Horn Cc: Pedro Falcato Cc: --- mm/vma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/vma.c b/mm/vma.c index 9f0a0acf694a..6cde67883fb0 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -2849,7 +2849,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, { struct mm_struct *mm = current->mm; struct vm_area_struct *vma = NULL; - bool have_mmap_prepare = file && file->f_op->mmap_prepare; + const bool have_mmap_prepare = file && file->f_op->mmap_prepare; VMA_ITERATOR(vmi, mm, addr); const pgoff_t anon_pgoff = addr >> PAGE_SHIFT; MMAP_STATE(map, mm, &vmi, addr, len, pgoff, anon_pgoff, vma_flags, file); @@ -2892,7 +2892,7 @@ static unsigned long __mmap_region(struct file *file, unsigned long addr, allocated_new = true; } - if (have_mmap_prepare) + if (have_mmap_prepare && allocated_new) set_vma_user_defined_fields(vma, &map); __mmap_complete(&map, vma); From a442c8a89fb9c531f9d6ab86120c31decd778d51 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 4 Sep 2026 20:03:07 +0200 Subject: [PATCH 288/535] netfilter: ipset: do not update comments from kernel-side adds 'Fixes' commit stopped calling ip_set_init_comment() for hash types from kernel-side-adds (xtables .. -j SET). ip_set_init_comment() says: "The kadt functions don't use the comment extensions in any way." But bitmap set type calls the function from kadt cb too. While this appears to be safe (serialized via the set spinlock), it seems better to not call the init function either, least of all to keep behaviour consistent. ip_set_list calls ip_set_init_comment() only from uadt cb, it can be kept as-is. This was triggered by yet another LLM review, hinting that the existing rcu_dereference_protected() cannot be downgraded to only check if the nfnl mutex is held. Fixes: f30415929be8 ("netfilter: ipset: do not update comments from kernel-side hash adds") Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipset/ip_set_bitmap_gen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h index d6a7e6604542..ae376fa3e7a3 100644 --- a/net/netfilter/ipset/ip_set_bitmap_gen.h +++ b/net/netfilter/ipset/ip_set_bitmap_gen.h @@ -159,7 +159,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, if (SET_WITH_COUNTER(set)) ip_set_init_counter(ext_counter(x, set), ext); - if (SET_WITH_COMMENT(set)) + if (SET_WITH_COMMENT(set) && !ext->target) ip_set_init_comment(set, ext_comment(x, set), ext); if (SET_WITH_SKBINFO(set)) ip_set_init_skbinfo(ext_skbinfo(x, set), ext); From 5f43a2d35d5e748c09a50a98fc766dc95bb3a6bd Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 13 Sep 2026 13:35:32 +0800 Subject: [PATCH 289/535] EDAC/versalnet: Drop remote processor handle refcount on driver removal mc_probe() acquires a reference to the remote processor with rproc_get_by_phandle(), but mc_remove() does not release the reference. rproc_shutdown() only balances the power reference acquired by rproc_boot(); it does not drop the device reference acquired by rproc_get_by_phandle(). As a result, successful driver removal leaves the remoteproc reference unbalanced. Call rproc_put() during removal to release the reference acquired in mc_probe(). [ bp: Massage commit message. ] Fixes: d5fe2fec6c40d ("EDAC: Add a driver for the AMD Versal NET DDR controller") Signed-off-by: Guangshuo Li Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Radhey Shyam Pandey Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260913053532.1324671-1-lgs201920130244@gmail.com --- drivers/edac/versalnet_edac.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/edac/versalnet_edac.c b/drivers/edac/versalnet_edac.c index 9e65c4b1d99d..eae9fea60db1 100644 --- a/drivers/edac/versalnet_edac.c +++ b/drivers/edac/versalnet_edac.c @@ -953,6 +953,7 @@ static void mc_remove(struct platform_device *pdev) remove_versalnet(priv); rproc_shutdown(priv->mcdi->r5_rproc); cdx_mcdi_finish(priv->mcdi); + rproc_put(priv->mcdi->r5_rproc); kfree(priv->mcdi); } From b7e6df2f52ed5c02838832f53c171a5645f9b376 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Thu, 24 Sep 2026 17:10:35 -0700 Subject: [PATCH 290/535] i2c: xiic: preserve PEC byte length in SMBus block read setup xiic_smbus_block_read_setup() recalculates i2c->rx_msg->len based on the length byte returned by the device, but historically clobbered the PEC byte expectation the SMBus core had baked into msg->len. That dropped the PEC byte from the caller's buffer on the normal and chunked receive-fifo branches. Compute pec_len up-front as (i2c->rx_msg->len - 1) -- the trailing bytes the caller has already accounted for beyond the length byte, 1 when the SMBus core enabled PEC, and possibly more for an I2C_M_RECV_LEN request coming from i2c-dev -- and add it to the new length in every branch: - chunked: the trailing bytes do not fit in the Rx FIFO, so drain in chunks. The guard becomes (rxmsg_len + pec_len > IIC_RX_FIFO_DEPTH) rather than rxmsg_len alone, both because pec_len bytes also have to fit and because it is what bounds rfd_set in the else branch below to the 4 bits of XIIC_RFD_REG_OFFSET. - padded (1 + rxmsg_len + pec_len < SMBUS_BLOCK_READ_MIN_LEN): the hardware needs at least 3 bytes on the bus to exit the read cleanly (the second byte is already being clocked in by the time the ISR reads the length byte and is too late to NACK), so we still pad rx_msg->len up to SMBUS_BLOCK_READ_MIN_LEN. The dummy trailing byte that gets drained must then be trimmed off before handing the message back to the SMBus core; otherwise i2c_smbus_check_pec() reads buf[len-1] (= dummy) instead of the real PEC byte at buf[1] and rejects every clean zero-length block read with -EBADMSG. Record the true valid byte count in a new field i2c->smbus_actual_len and trim rx_msg->len down to it in xiic_smbus_trim_len(), called from both completion sites that clear rx_msg: xiic_process()'s RX_FULL branch and xiic_recv_atomic(), which drains the FIFO with interrupts off. smbus_actual_len is per-receive state, so xiic_start_recv() clears it before every receive. Only the padded branch ever sets it, and a block read aborted by arbitration loss or a TX error never reaches the completion site, so without that clear a stale value would trim the length of an unrelated later read. The condition is expressed in total bytes rather than the old "(rxmsg_len == 1) || (rxmsg_len == 0)" so that a request carrying more than one trailing byte does not get padded: padding records a length the drain never reaches, which would hand the caller a byte that was never received. - normal: all trailing bytes fit in one FIFO fill. rfd_set gains pec_len for the same reason the length does. Because the padded branch above has already taken every case with fewer than SMBUS_BLOCK_READ_MIN_LEN total bytes, rxmsg_len + pec_len is at least 2 here and the subtraction cannot underflow the u8. Fixes: e4c1ff772e1a ("i2c: xiic: Add smbus_block_read functionality") Signed-off-by: Abdurrahman Hussain Cc: # v6.3+ Acked-by: Michal Simek Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260924-i2c-xiic-v7-1-df7e752332ef@nexthop.ai --- drivers/i2c/busses/i2c-xiic.c | 54 ++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 3e7735e1dae0..0777de45bdf4 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -73,6 +73,9 @@ enum i2c_scl_freq { * @prev_msg_tx: Previous message is Tx * @quirks: To hold platform specific bug info * @smbus_block_read: Flag to handle block read + * @smbus_actual_len: Valid byte count (length + payload + optional PEC) of a + * padded SMBus block read. msg->len is trimmed to this on completion. + * Zero when no trimming is needed. * @input_clk: Input clock to I2C controller * @i2c_clk: I2C SCL frequency * @atomic: Mode of transfer @@ -98,6 +101,7 @@ struct xiic_i2c { bool prev_msg_tx; u32 quirks; bool smbus_block_read; + unsigned int smbus_actual_len; unsigned long input_clk; unsigned int i2c_clk; bool atomic; @@ -539,30 +543,38 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c) /* Check if received length is valid */ if (rxmsg_len <= I2C_SMBUS_BLOCK_MAX) { + unsigned int pec_len = i2c->rx_msg->len - 1; + /* Set Receive fifo depth */ - if (rxmsg_len > IIC_RX_FIFO_DEPTH) { + if (rxmsg_len + pec_len > IIC_RX_FIFO_DEPTH) { /* - * When Rx msg len greater than or equal to Rx fifo capacity - * Receive fifo depth should set to Rx fifo capacity minus 1 + * Trailing bytes (payload plus any PEC) exceed Rx FIFO + * capacity, so drain in chunks. This also keeps the + * else branch below from pushing rfd_set past the + * 4-bit XIIC_RFD_REG_OFFSET field. */ rfd_set = IIC_RX_FIFO_DEPTH - 1; - i2c->rx_msg->len = rxmsg_len + 1; - } else if ((rxmsg_len == 1) || - (rxmsg_len == 0)) { + i2c->rx_msg->len = rxmsg_len + 1 + pec_len; + } else if (1 + rxmsg_len + pec_len < SMBUS_BLOCK_READ_MIN_LEN) { /* - * Minimum of 3 bytes required to exit cleanly. 1 byte - * already received, Second byte is being received. Have - * to set NACK in read_rx before receiving the last byte + * The HW needs SMBUS_BLOCK_READ_MIN_LEN bytes on the + * bus to exit cleanly: by the time the ISR reads the + * length byte the second byte is already being clocked + * in, too late to NACK. Pad the drain target and record + * the real length, trimmed back on completion so the + * PEC check sees the right byte. */ rfd_set = 0; i2c->rx_msg->len = SMBUS_BLOCK_READ_MIN_LEN; + i2c->smbus_actual_len = 1 + rxmsg_len + pec_len; } else { /* - * When Rx msg len less than Rx fifo capacity - * Receive fifo depth should set to Rx msg len minus 2 + * All trailing bytes fit in the Rx FIFO. The widened + * condition above guarantees rxmsg_len + pec_len >= 2, + * so this cannot underflow. */ - rfd_set = rxmsg_len - 2; - i2c->rx_msg->len = rxmsg_len + 1; + rfd_set = rxmsg_len + pec_len - 2; + i2c->rx_msg->len = rxmsg_len + 1 + pec_len; } xiic_setreg8(i2c, XIIC_RFD_REG_OFFSET, rfd_set); @@ -575,6 +587,16 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c) dev_err(i2c->adap.dev.parent, "smbus_block_read Invalid msg length\n"); } +/* + * Undo the setup-time padding of a short SMBus block read before rx_msg is + * cleared, so the PEC check sees the right byte. + */ +static void xiic_smbus_trim_len(struct xiic_i2c *i2c) +{ + if (i2c->rx_msg && i2c->smbus_actual_len) + i2c->rx_msg->len = i2c->smbus_actual_len; +} + static void xiic_read_rx(struct xiic_i2c *i2c) { u8 bytes_in_fifo, cr = 0, bytes_to_read = 0; @@ -797,6 +819,8 @@ static irqreturn_t xiic_process(int irq, void *dev_id) xiic_read_rx(i2c); if (xiic_rx_space(i2c) == 0) { + xiic_smbus_trim_len(i2c); + /* this is the last part of the message */ i2c->rx_msg = NULL; @@ -938,6 +962,7 @@ static void xiic_recv_atomic(struct xiic_i2c *i2c) return; } + xiic_smbus_trim_len(i2c); i2c->rx_msg = NULL; xiic_irq_clr_en(i2c, XIIC_INTR_TX_ERROR_MASK); @@ -955,6 +980,9 @@ static void xiic_start_recv(struct xiic_i2c *i2c) u8 cr = 0, rfd_set = 0; struct i2c_msg *msg = i2c->rx_msg = i2c->tx_msg; + /* A stale value from an aborted block read would truncate this msg. */ + i2c->smbus_actual_len = 0; + if (!i2c->atomic) dev_dbg(i2c->adap.dev.parent, "%s entry, ISR: 0x%x, CR: 0x%x\n", __func__, xiic_getreg32(i2c, XIIC_IISR_OFFSET), From e6fe3ea04f0113fe4d47c03d76e03805a4768ca7 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Thu, 24 Sep 2026 17:10:36 -0700 Subject: [PATCH 291/535] i2c: xiic: defer RX_FULL until all trailing bytes are in FIFO For the normal path of xiic_smbus_block_read_setup() -- the trailing bytes all fit in one Rx FIFO fill -- RFD was programmed two below the byte count, which fires the RX_FULL interrupt while the last byte is still in flight. xiic_read_rx() then lands in its bytes_rem == 1 branch and sets NACK on a byte still on the wire, truncating the read. Without PEC this is harmless: the truncated byte is the dummy one the caller never looks at. With PEC enabled it is the PEC byte itself, and i2c_smbus_check_pec() fails the transfer with -EBADMSG. Raise the threshold by one so RX_FULL fires only once every remaining byte is already buffered. That routes the drain through xiic_read_rx()'s bytes_rem == 0 path, which reads everything out and emits the stop cleanly. The only change for the non-PEC case is that the controller waits one extra byte-time before servicing the interrupt. rfd_set stays inside the 4 bits of XIIC_RFD_REG_OFFSET: this branch is only reached when rxmsg_len + pec_len <= IIC_RX_FIFO_DEPTH, so the value is at most IIC_RX_FIFO_DEPTH - 1. Fixes: e4c1ff772e1a ("i2c: xiic: Add smbus_block_read functionality") Signed-off-by: Abdurrahman Hussain Cc: # v6.3+ Acked-by: Michal Simek Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260924-i2c-xiic-v7-2-df7e752332ef@nexthop.ai --- drivers/i2c/busses/i2c-xiic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 0777de45bdf4..5cd737c7608f 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -569,11 +569,11 @@ static void xiic_smbus_block_read_setup(struct xiic_i2c *i2c) i2c->smbus_actual_len = 1 + rxmsg_len + pec_len; } else { /* - * All trailing bytes fit in the Rx FIFO. The widened - * condition above guarantees rxmsg_len + pec_len >= 2, - * so this cannot underflow. + * All trailing bytes fit in the Rx FIFO. Defer RX_FULL + * until every one of them is buffered, so the drain + * takes xiic_read_rx()'s bytes_rem == 0 path. */ - rfd_set = rxmsg_len + pec_len - 2; + rfd_set = rxmsg_len + pec_len - 1; i2c->rx_msg->len = rxmsg_len + 1 + pec_len; } xiic_setreg8(i2c, XIIC_RFD_REG_OFFSET, rfd_set); From 840d8acc87925deb3c75566fde9ca81d2f47f98c Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Thu, 24 Sep 2026 17:10:37 -0700 Subject: [PATCH 292/535] i2c: xiic: don't clobber msg->len to signal block-read completion At the end of a SMBus block read the BNB handler force-set tx_msg->len = 1 to push xiic_tx_space() to zero so the STATE_DONE branch would fire. Two problems: 1. tx_msg and rx_msg alias the same i2c_msg struct during a receive (see xiic_start_recv), so overwriting tx_msg->len also changes rx_msg->len. The i2c core's i2c_smbus_check_pec() then reads the PEC from the wrong offset -- buf[0] instead of buf[rxmsg_len + 1] -- and either mis-validates or returns -EBADMSG. 2. xiic_start_recv sets tx_pos = msg->len (typically 2 when PEC is enabled). xiic_tx_space() is unsigned msg->len - tx_pos, so setting msg->len = 1 with tx_pos = 2 underflows to 0xFFFFFFFF and xiic_tx_space() never compares equal to 0 -- the STATE_DONE check falls through to STATE_ERROR, giving -EIO. Instead, advance tx_pos up to msg->len. That drives tx_space to 0 without touching msg->len, preserving the buffer length that xiic_smbus_block_read_setup() already grew to cover the length byte, the payload and the optional PEC byte. Fixes: e4c1ff772e1a ("i2c: xiic: Add smbus_block_read functionality") Signed-off-by: Abdurrahman Hussain Cc: # v6.3+ Acked-by: Michal Simek Reviewed-by: Shubhrajyoti Datta Signed-off-by: Andi Shyti Link: https://patch.msgid.link/20260924-i2c-xiic-v7-3-df7e752332ef@nexthop.ai --- drivers/i2c/busses/i2c-xiic.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 5cd737c7608f..5e397a7e63f6 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -889,8 +889,11 @@ static irqreturn_t xiic_process(int irq, void *dev_id) if (i2c->tx_msg && i2c->smbus_block_read) { i2c->smbus_block_read = false; - /* Set requested message len=1 to indicate STATE_DONE */ - i2c->tx_msg->len = 1; + /* + * Drive xiic_tx_space() to 0 to signal STATE_DONE + * without truncating the rx_msg length. + */ + i2c->tx_pos = i2c->tx_msg->len; } if (!i2c->tx_msg) From d2457a7e2727dc747a61a50d65c2f259afce8c45 Mon Sep 17 00:00:00 2001 From: Laurent Wandrebeck Date: Tue, 22 Sep 2026 10:50:32 +0200 Subject: [PATCH 293/535] x86/mm: Don't apply va_align to hugetlb mappings on AMD F15h get_align_mask() returns huge_page_mask_align() for hugetlbfs, but get_align_bits() adds va_align.bits regardless, so vm_unmapped_area() returns an address off the huge page boundary and __unmap_hugepage_range() hits BUG_ON(start & ~huge_page_mask(h)) at teardown. This can be triggered on Carrizo and FX-8370E, both hstates. Pass the file to get_align_bits() and skip the randomisation for hugetlbfs. [ bp: Massage commit message. ] Fixes: 1317a5e7f7b1 ("arch/x86: teach arch_get_unmapped_area_vmflags to handle hugetlb mappings") Suggested-by: Dave Hansen Acked-by: Dave Hansen Signed-off-by: Laurent Wandrebeck Signed-off-by: Borislav Petkov (AMD) Cc: stable@vger.kernel.org # 6.13+ Link: https://patch.msgid.link/20260922085032.46144-1-l.wandrebeck@quelquesmots.fr --- arch/x86/kernel/sys_x86_64.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c index 776ae6fa7f2d..4c078827f26a 100644 --- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -50,9 +50,16 @@ static unsigned long get_align_mask(struct file *filp) * value before calling vm_unmapped_area() or ORed directly to the * address. */ -static unsigned long get_align_bits(void) +static unsigned long get_align_bits(struct file *filp) { - return va_align.bits & get_align_mask(NULL); + /* + * va_align.bits is smaller than the huge page size and will + * lead to misaligned huge pages. Ignore it for huge mappings. + */ + if (is_file_hugepages(filp)) + return 0; + + return va_align.bits & get_align_mask(filp); } static int __init control_va_addr_alignment(char *str) @@ -157,7 +164,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, } if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + info.align_offset += get_align_bits(filp); } return vm_unmapped_area(&info); @@ -222,7 +229,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0, if (filp) { info.align_mask = get_align_mask(filp); - info.align_offset += get_align_bits(); + info.align_offset += get_align_bits(filp); } addr = vm_unmapped_area(&info); if (!(addr & ~PAGE_MASK)) From f0fd8993082ca229faa3f9844f1c9fe182eca20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Larocque?= Date: Sat, 26 Sep 2026 20:58:10 -0400 Subject: [PATCH 294/535] Input: synaptics - limit T440p InterTouch quirk to LEN0036 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ThinkPad L440 also uses board id 2722, but does not have the SMBus initialization problem seen on the T440p. On the L440, the SMBus companion becomes available shortly after psmouse probes, allowing the touchpad to use SMBus/RMI4 normally. The T440p's quirk is currently applied to all Synaptics touchpads with board id 2722, unnecessarily disabling SMBus InterTouch on the L440, as reported by Daniel Salmun. Restrict the quirk to PNP ID LEN0036, which identifies the T440p, so that other devices sharing board id 2722 retain their normal SMBus/RMI4 operation. Reported-by: Daniel Salmun Link: https://lore.kernel.org/all/20260925230039.236786-1-salmundani@gmail.com/ Fixes: 26eb3d92c7a4 ("Input: synaptics - disable InterTouch on ThinkPad T440p (board id 2722)") Cc: stable@vger.kernel.org Signed-off-by: Raphaël Larocque Link: https://patch.msgid.link/20260927005810.85971-1-rlarocque@disroot.org Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/synaptics.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index 444285e58f41..1ed9261bff2b 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -1840,8 +1840,10 @@ static int synaptics_setup_intertouch(struct psmouse *psmouse, return -ENXIO; } - /* Disable intertouch on known-broken board revisions */ - if (info->board_id == 2722) { + /* Disable intertouch on known-broken T440p board revisions */ + if (info->board_id == 2722 && + psmouse_matches_pnp_id(psmouse, + (const char * const []){"LEN0036", NULL})) { psmouse_info(psmouse, "Disabling intertouch for board id %u\n", info->board_id); From a215720959c5450b3699e243904b39c3f8f68888 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Sat, 26 Sep 2026 22:26:47 +0200 Subject: [PATCH 295/535] Input: s6sy761 - fix resume ordering and restore sensing System suspend powers the controller off and resume powers it back on, but the resume path enables the interrupt before s6sy761_power_on() checks the boot. The firmware raises its boot-complete event on the interrupt line, the threaded handler consumes it, s6sy761_power_on() then reads an empty event and resume fails with -ENODEV, skipping the touch function setup: s6sy761 2-0048: PM: dpm_run_callback(): s6sy761_resume [s6sy761] returns -19 Power the chip on first and only then unmask the interrupt. Once resume completes the boot handshake the chip comes back with sensing off, as at probe where input_open() turns it on, so the touchscreen stays dead after resume. Send SENSE_ON again when the input device is open. Tested on a Pixel 3 XL over several s2idle cycles: resume succeeds and the touch function and sense status match the pre-suspend state. Assisted-by: LLM Cc: stable@vger.kernel.org Fixes: 0145a7141e59 ("Input: add support for the Samsung S6SY761 touchscreen") Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260926-s6sy761-suspend-v2-1-8f00a96ee6e8@ixit.cz Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/s6sy761.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/input/touchscreen/s6sy761.c b/drivers/input/touchscreen/s6sy761.c index 0f24a9b73063..899c179fb2d3 100644 --- a/drivers/input/touchscreen/s6sy761.c +++ b/drivers/input/touchscreen/s6sy761.c @@ -500,10 +500,22 @@ static int s6sy761_suspend(struct device *dev) static int s6sy761_resume(struct device *dev) { struct s6sy761_data *sdata = dev_get_drvdata(dev); + int err; + + err = s6sy761_power_on(sdata); + if (err) + return err; enable_irq(sdata->client->irq); - return s6sy761_power_on(sdata); + guard(mutex)(&sdata->input->mutex); + + if (!input_device_enabled(sdata->input)) + return 0; + + err = i2c_smbus_write_byte(sdata->client, S6SY761_SENSE_ON); + + return err; } static const struct dev_pm_ops s6sy761_pm_ops = { From 4febc02cd02f021f2b6d0768332be643ef14a12d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 26 Sep 2026 14:02:16 +0200 Subject: [PATCH 296/535] wifi: mac80211: set info->band for 802.3 encap offload frames Unlike the 802.11 TX paths, ieee80211_8023_xmit() does not set info->band from the channel context on non-MLD interfaces. It stays at 0, so ieee80211_get_tx_rates() uses the wrong band for these frames. Set it the same way as ieee80211_build_hdr(): drop the frame if there is no channel context, and leave the band at 0 on MLD interfaces. Reported-by: Andrea Pesaresi Fixes: 50ff477a8639 ("mac80211: add 802.11 encapsulation offloading support") Cc: stable@vger.kernel.org Signed-off-by: Felix Fietkau Link: https://patch.msgid.link/20260926120216.4054712-1-nbd@nbd.name Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c8217f6d146e..b88acb18e04d 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4735,6 +4735,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, { struct ieee80211_tx_info *info; struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *chanctx_conf; struct tid_ampdu_tx *tid_tx = NULL; struct sk_buff *seg, *next; unsigned int skbs = 0, len = 0; @@ -4784,6 +4785,16 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); + /* MLD transmissions must not rely on the band */ + if (!ieee80211_vif_is_mld(&sdata->vif)) { + chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); + if (unlikely(!chanctx_conf)) { + kfree_skb_list(skb); + return; + } + info->band = chanctx_conf->def.chan->band; + } + info->flags |= IEEE80211_TX_CTL_HW_80211_ENCAP; info->control.vif = &sdata->vif; From 56e236759f0a8dc910c2ae7cd3c161f298ee5794 Mon Sep 17 00:00:00 2001 From: Ruide Cao Date: Wed, 23 Sep 2026 02:02:23 +0800 Subject: [PATCH 297/535] wifi: mac80211: reject invalid 320 MHz CSA bandwidth The HT/VHT channel definition validator can receive a 320 MHz bandwidth indication from a received CSA frame on a non-6 GHz link. It warns for this unsupported width but continues with an uninitialized vht_operation.chan_width, which is then read by ieee80211_chandef_vht_oper(). With panic_on_warn enabled, this lets a received frame panic the kernel. Reject the channel definition before entering the VHT operation conversion. This preserves the existing CSA fallback while avoiding both the warning and the uninitialized read. Fixes: 21c3f8f95554 ("wifi: mac80211: refactor STA CSA parsing flows") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Ruide Cao Signed-off-by: Ren Wei Link: https://patch.msgid.link/bd68e360e06135c750397cb6be003a01a834a179.1787222001.git.vega.cover-letter@nebusec.ai Signed-off-by: Johannes Berg --- net/mac80211/spectmgmt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c index 880f4625775d..f5e4970869b6 100644 --- a/net/mac80211/spectmgmt.c +++ b/net/mac80211/spectmgmt.c @@ -111,8 +111,8 @@ validate_chandef_by_ht_vht_oper(struct ieee80211_sub_if_data *sdata, switch (chan_width) { case NL80211_CHAN_WIDTH_320: - WARN_ON(1); - break; + chandef->chan = NULL; + return; case NL80211_CHAN_WIDTH_160: vht_oper.chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ; vht_oper.center_freq_seg1_idx = vht_oper.center_freq_seg0_idx; From 6f63e919fe1e335b8abcb3a28bfd4804a98d875a Mon Sep 17 00:00:00 2001 From: "Cen Zhang (Microsoft)" Date: Thu, 24 Sep 2026 22:44:08 -0400 Subject: [PATCH 298/535] wifi: mac80211: fix slab-out-of-bounds read in ieee80211_monitor_select_queue() ieee80211_monitor_select_queue() validates the skb length using skb->len before dereferencing pointers into skb->data to access the 802.11 header. However, skb->len includes data in both the linear head buffer and non-linear fragments/pages. When AF_PACKET sends a packet large enough to become non-linear, the 802.11 header at skb->data + len_rthdr may extend past the linear head buffer even though skb->len appears sufficient. This leads to a slab-out-of-bounds read when accessing hdr->frame_control, as the kernel reads beyond the allocated skb head buffer: BUG: KASAN: slab-out-of-bounds in ieee80211_monitor_select_queue+0x1ed/0x220 syzbot has hit the same issues. Fix this by checking skb_headlen(skb) (which gives the length of the linear data region) instead of skb->len before dereferencing skb->data pointers. This ensures the required bytes are actually present in the linear portion of the skb. Apply the same fix to ieee80211_validate_radiotap_len() which has the same class of bug: it uses skb->len to validate accesses to skb->data, and to the corresponding checks in ieee80211_monitor_start_xmit(): for drivers advertising NETIF_F_SG the skb is not linearized before ndo_start_xmit, so the 802.11 header can be read past the linear buffer there as well. Fixes: cf0277e714a0 ("mac80211: fix skb buffering issue") Fixes: 9b8a74e3482f ("[MAC80211]: Improve sanity checks on injected packets") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: syzbot+610e40369bc02181bad0@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=610e40369bc02181bad0 Reported-by: syzbot+878643e0580bc580f883@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=878643e0580bc580f883 Assisted-by: GitHub-Copilot:claude-opus-4.6 Signed-off-by: Cen Zhang (Microsoft) Reviewed-by: Francis Perron Link: https://patch.msgid.link/20260925024408.32143-1-cenzhang@linux.microsoft.com Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 4 ++-- net/mac80211/tx.c | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 889c32fd8de1..88942317fec8 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -998,8 +998,8 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, len_rthdr = ieee80211_get_radiotap_len(skb->data); hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr); - if (skb->len < len_rthdr + 2 || - skb->len < len_rthdr + ieee80211_hdrlen(hdr->frame_control)) + if (skb_headlen(skb) < len_rthdr + 2 || + skb_headlen(skb) < len_rthdr + ieee80211_hdrlen(hdr->frame_control)) return 0; /* doesn't matter, frame will be dropped */ return ieee80211_select_queue_80211(sdata, skb, hdr); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index b88acb18e04d..d51f810dd2c5 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2091,7 +2091,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) (struct ieee80211_radiotap_header *)skb->data; /* check for not even having the fixed radiotap header part */ - if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header))) + if (unlikely(skb_headlen(skb) < sizeof(struct ieee80211_radiotap_header))) return false; /* too short to be possibly valid */ /* is it a header version we can trust to find length from? */ @@ -2099,7 +2099,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) return false; /* only version 0 is supported */ /* does the skb contain enough to deliver on the alleged length? */ - if (unlikely(skb->len < ieee80211_get_radiotap_len(skb->data))) + if (unlikely(skb_headlen(skb) < ieee80211_get_radiotap_len(skb->data))) return false; /* skb too short for claimed rt header extent */ return true; @@ -2388,13 +2388,13 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, skb_set_network_header(skb, len_rthdr); skb_set_transport_header(skb, len_rthdr); - if (skb->len < len_rthdr + 2) + if (skb_headlen(skb) < len_rthdr + 2) goto fail; hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr); hdrlen = ieee80211_hdrlen(hdr->frame_control); - if (skb->len < len_rthdr + hdrlen) + if (skb_headlen(skb) < len_rthdr + hdrlen) goto fail; /* @@ -2402,7 +2402,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * carrying a rfc1042 header */ if (ieee80211_is_data(hdr->frame_control) && - skb->len >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) { + skb_headlen(skb) >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) { u8 *payload = (u8 *)hdr + hdrlen; if (ether_addr_equal(payload, rfc1042_header)) From c034e8a46e4cb703018fe7e10fe5a3a974d6a7c3 Mon Sep 17 00:00:00 2001 From: Mitul Golani Date: Thu, 17 Sep 2026 13:13:22 +0530 Subject: [PATCH 299/535] drm/i915/vrr: Disable DC balance by default Disable VRR DC balance by default due to timing issues observed on some panel/TCON combinations. Keep the module parameter to enable DC balance during debugging and to isolate DC balance effects from underlying VRR/display timing issues. --v2: - Make enable_dc_balance a bool and keep it disabled by default; fix the parameter type/value mismatch and correct the description (Chaitanya Kumar Borah, Jani Nikula) - Explain in the commit message why the feature is gated and why a module parameter is used (Jani Nikula) --v3: - Commit message update (Jani Nikula) Fixes: 555819270707 ("drm/i915/vrr: Enable DC Balance") Cc: # v7.0+ Signed-off-by: Mitul Golani Reviewed-by: Ankit Nautiyal Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260917074322.2606738-1-mitulkumar.ajitkumar.golani@intel.com (cherry picked from commit d1ef78f0581e856c4238c751e9ae2884ce58c275) Signed-off-by: Jani Nikula --- drivers/gpu/drm/i915/display/intel_display_params.c | 4 ++++ drivers/gpu/drm/i915/display/intel_display_params.h | 1 + drivers/gpu/drm/i915/display/intel_vrr.c | 4 +++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/display/intel_display_params.c b/drivers/gpu/drm/i915/display/intel_display_params.c index 2aed110c5b09..ca0ef466bb10 100644 --- a/drivers/gpu/drm/i915/display/intel_display_params.c +++ b/drivers/gpu/drm/i915/display/intel_display_params.c @@ -120,6 +120,10 @@ intel_display_param_named_unsafe(enable_psr, int, 0400, "(0=disabled, 1=enable up to PSR1, 2=enable up to PSR2) " "Default: -1 (use per-chip default)"); +intel_display_param_named_unsafe(enable_dc_balance, bool, 0400, + "Enable VRR DC balance (0=disabled, 1=enabled). " + "Default: 0 (disabled)"); + intel_display_param_named_unsafe(enable_panel_replay, int, 0400, "Enable Panel Replay (0=disabled, 1=enabled). Default: -1 (use per-chip default)"); diff --git a/drivers/gpu/drm/i915/display/intel_display_params.h b/drivers/gpu/drm/i915/display/intel_display_params.h index b95ecf728daa..16759e94c025 100644 --- a/drivers/gpu/drm/i915/display/intel_display_params.h +++ b/drivers/gpu/drm/i915/display/intel_display_params.h @@ -46,6 +46,7 @@ struct drm_printer; param(bool, enable_dp_mst, true, 0600) \ param(int, enable_fbc, -1, 0600) \ param(int, enable_psr, -1, 0600) \ + param(bool, enable_dc_balance, false, 0600) \ param(int, enable_panel_replay, -1, 0600) \ param(bool, psr_safest_params, false, 0400) \ param(bool, enable_psr2_sel_fetch, true, 0400) \ diff --git a/drivers/gpu/drm/i915/display/intel_vrr.c b/drivers/gpu/drm/i915/display/intel_vrr.c index 51e4f3309b8b..cfc0cfbff017 100644 --- a/drivers/gpu/drm/i915/display/intel_vrr.c +++ b/drivers/gpu/drm/i915/display/intel_vrr.c @@ -386,10 +386,12 @@ static bool intel_vrr_dc_balance_possible(const struct intel_crtc_state *crtc_st static void intel_vrr_dc_balance_compute_config(struct intel_crtc_state *crtc_state) { + struct intel_display *display = to_intel_display(crtc_state); int guardband_usec, adjustment_usec; struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; - if (!intel_vrr_dc_balance_possible(crtc_state) || !crtc_state->vrr.enable) + if (!intel_vrr_dc_balance_possible(crtc_state) || + !crtc_state->vrr.enable || !display->params.enable_dc_balance) return; crtc_state->vrr.dc_balance.vmax = crtc_state->vrr.vmax; From 395e9f2967a7ac6898026e8f136c369805dc2fd9 Mon Sep 17 00:00:00 2001 From: Kurt Lieber Date: Thu, 24 Sep 2026 12:26:37 +0000 Subject: [PATCH 300/535] thunderbolt: Disable CL states for the Anker Prime TB5 dock The Anker Prime TB5 dock identifies itself in the DROM as 01ea:83b5. Its router config space is an Intel Barlow Ridge 80G hub (8087:5786). The upstream lane adapter advertises CL0s, CL1, and CL2. With CLx left enabled, TMU uni-directional LowRes setup fails with -ENOTCONN, the USB3 and DisplayPort tunnels are aborted, and the router reconnects in a loop. Loading thunderbolt with clx=0 keeps the link up on this machine. Match the DROM id together with the Barlow Ridge hub id and disable CL states for this dock only. QUIRK_NO_CLX takes the same early-out as the module parameter, without disabling CLx for every other router. Closes: https://lore.kernel.org/linux-usb/SJ0PR15MB4696D491504DB667AA8E0617A3812@SJ0PR15MB4696.namprd15.prod.outlook.com/ Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Kurt Lieber Signed-off-by: Mika Westerberg --- drivers/thunderbolt/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 9f7914ac2f48..96b7fe0386e3 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { quirk_usb3_maximum_bandwidth }, { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, quirk_usb3_maximum_bandwidth }, + /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, + quirk_clx_disable }, /* * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host * controllers. From e0cb0570d0ebce6f452ab492e8743f6f7eb2558e Mon Sep 17 00:00:00 2001 From: Jianyu Zhou <997536907@qq.com> Date: Thu, 17 Sep 2026 14:58:29 +0800 Subject: [PATCH 301/535] ALSA: hda/realtek: Add quirk for IPASON SmartBook S1 From 2adfedabfdd9f5ec800462b1f1c0859a198e63aa Mon Sep 17 00:00:00 2001 From: Jianyu Zhou <997536907@qq.com> Date: Thu, 17 Sep 2026 14:34:46 +0800 Subject: [PATCH v2] ALSA: hda/realtek: Add quirk for IPASON SmartBook S1 The internal speaker of the IPASON SmartBook S1 (an Emdoor ODM laptop) stays silent under Linux, while the Bluetooth audio and the headset output work fine. The same machine plays sound under Windows with the vendor driver, so the hardware and the amplifier are known good. The BIOS reports the pin default of the actual internal speaker pin (NID 0x1b) as 0x411111f0, i.e. the default association 0xf, which means "unconnected". The pin parser therefore drops that node already at the pin-configuration time, hence no mixer control is ever created for it and the pin is never driven, no matter how the output pin 0x14 is set up. Enabling 0x1b makes the speaker work immediately. The existing ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO quirk makes the speaker work, too, but it additionally rewrites the pins 0x14, 0x19 and 0x1a with Honor-specific values, which creates a bogus "Dock Mic" device that does not exist on this hardware. Add a minimal fixup that only enables 0x1b as the internal speaker. Touching 0x14 is not needed: with the pin configuration overridden at runtime, both keeping its BIOS value (0x90170110) and disabling it (0x411111f0) leave the speaker working, so 0x1b alone decides the output. Codec: Realtek ALC256 (0x10ec0256), SSID 0x2782:0x0206 BIOS pincfg: 0x14 0x90170110 0x18 0x411111f0 0x19 0x02a19040 0x1a 0x411111f0 0x1b 0x411111f0 0x1d 0x4067b945 0x1e 0x411111f0 0x21 0x02214020 Tested on 7.2.0-1-MANJARO and 6.18.45-1-MANJARO, with both the SOF and the legacy HDA driver paths. Signed-off-by: Jianyu Zhou <997536907@qq.com> Link: https://patch.msgid.link/tencent_3AB9214FB134949B31E9C471BC3669117F05@qq.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5127a111c59c..fdca7e5db3a9 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4384,6 +4384,7 @@ enum { ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET, ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, + ALC256_FIXUP_IPASON_SMARTBOOK_S1, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7136,6 +7137,13 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_TXNW2781_I2C, }, + [ALC256_FIXUP_IPASON_SMARTBOOK_S1] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1b, 0x90170110 }, /* the real internal speaker */ + { } + }, + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -8354,6 +8362,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), SND_PCI_QUIRK(0x2145, 0x0001, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), + SND_PCI_QUIRK(0x2782, 0x0206, "IPASON SmartBook S1", ALC256_FIXUP_IPASON_SMARTBOOK_S1), SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13), SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO), From ae0f12706433995336784a0966d42d2f7f7b5dac Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Fri, 18 Sep 2026 19:03:01 +0800 Subject: [PATCH 302/535] ALSA: hda/realtek: Add quirk for Acer Nitro ANV16-41 The Acer Nitro ANV16-41 requires the ALC2XX_FIXUP_HEADSET_MIC quirk to make the headset microphone work properly. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221953 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260918110301.664172-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fdca7e5db3a9..30972cbe5f03 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7204,6 +7204,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x171e, "Acer Nitro ANV15-51", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), From 86f86e6fdedc33c5b8d38b9208b350f8accf57ee Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Fri, 18 Sep 2026 15:57:53 -0700 Subject: [PATCH 303/535] ALSA: ua101: reject mismatched capture/playback packet sizes detect_usb_format() cross-checks bSubframeSize, bBitResolution and tSamFreq between the capture and playback interfaces, but never relates the two endpoints' wMaxPacketSize and bNrChannels. Each playback URB gets a buffer of ua->playback.max_packet_bytes, while the number of bytes written into it is derived from the capture stream: capture_urb_complete() computes frames from the received capture packet and capture.frame_bytes, and start_usb_playback() and playback_work() multiply that by playback.frame_bytes. A device declaring a large capture wMaxPacketSize with few capture channels and a small playback wMaxPacketSize with many playback channels therefore memset()s and memcpy()s past the end of the playback buffer, in open() of the PCM node the driver registers during probe. usb_submit_urb() rejects the over-long iso_frame_desc[0].length with -EMSGSIZE, but only after the write. Reject such descriptors at probe time. Genuine UA-101/UA-1000 hardware declares proportional packet sizes and is unaffected. BUG: KASAN: slab-out-of-bounds in start_usb_playback (sound/usb/misc/ua101.c:586) Write of size 2048 at addr ffff8881098f3c00 by task exploit/5021 Call Trace: __asan_memset (mm/kasan/shadow.c:84) start_usb_playback (sound/usb/misc/ua101.c:586) playback_pcm_open (sound/usb/misc/ua101.c:679) snd_pcm_open_substream (sound/core/pcm_native.c:2829) snd_pcm_open (sound/core/pcm_native.c:2865 sound/core/pcm_native.c:2932) snd_pcm_playback_open (sound/core/pcm_native.c:2891) snd_open (sound/core/sound.c:166) chrdev_open (fs/char_dev.c:411) do_dentry_open (fs/open.c:996) vfs_open (fs/open.c:1101) path_openat (fs/namei.c:4837 fs/namei.c:5000) do_file_open (fs/namei.c:5029) do_sys_openat2 (fs/open.c:1417) __x64_sys_openat (fs/open.c:1423 fs/open.c:1439 fs/open.c:1434) do_syscall_64 (arch/x86/entry/syscall_64.c:61 arch/x86/entry/syscall_64.c:84) entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) The buggy address belongs to the object at ffff8881098f3c00 which belongs to the cache kmalloc-192 of size 192 The buggy address is located 0 bytes inside of allocated 168-byte region [ffff8881098f3c00, ffff8881098f3ca8) Cc: stable@vger.kernel.org Fixes: 63978ab3e3e9 ("sound: add Edirol UA-101 support") Reported-by: Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260918225753.1278505-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/misc/ua101.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c index 860a62a3d74b..58d8a3cdc8b9 100644 --- a/sound/usb/misc/ua101.c +++ b/sound/usb/misc/ua101.c @@ -1007,6 +1007,15 @@ static int detect_usb_format(struct ua101 *ua) } ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); ua->playback.max_packet_bytes = usb_endpoint_maxp(epd); + + if (ua->capture.max_packet_bytes / ua->capture.frame_bytes * + ua->playback.frame_bytes > ua->playback.max_packet_bytes) { + dev_err(&ua->dev->dev, + "playback packet size %u too small for %u capture frames\n", + ua->playback.max_packet_bytes, + ua->capture.max_packet_bytes / ua->capture.frame_bytes); + return -ENXIO; + } return 0; } From 61b0a5a114fa4552892da5c0ff2f42e63bf08c6b Mon Sep 17 00:00:00 2001 From: Xiang Mei Date: Fri, 18 Sep 2026 16:40:14 -0700 Subject: [PATCH 304/535] ALSA: line6: reject oversized playback packets Each playback URB is handed a slice of out.buffer that is exactly LINE6_ISO_PACKETS * max_packet_size_out bytes, sized from the OUT endpoint, but the number of bytes written into that slice is never compared against it. submit_audio_out_urb() takes the frame count from prev_fsize, which audio_in_callback() derived from the *IN* endpoint's received packet length, and rescales it with the playback frame size; when prev_fsize is still zero it synthesizes a length from the sample rate instead. On a device declaring a large IN wMaxPacketSize and a small OUT wMaxPacketSize, the resulting memcpy(), or the memset() when the playback stream is idle, runs past its slice and, as the reproducer below shows, beyond the allocation. usb_submit_urb() is not a backstop. max_packet_size_out comes from usb_maxpacket(), which returns only the low 11 bits of wMaxPacketSize, while USB core validates a high-speed isochronous length against that base scaled by usb_endpoint_maxp_mult(). A length of up to three times the allocated slice is therefore accepted, and even a rejected URB is only rejected after the write. Reject a packet that does not fit the slice the driver allocated for it. Clamping it instead would silently shorten the capture-derived rate feedback and desynchronize the two streams. BUG: KASAN: slab-out-of-bounds in submit_audio_out_urb (sound/usb/line6/playback.c:229) Write of size 1024 at addr ffff888100bbd400 by task kworker/1:2/5002 Workqueue: events line6_startup_work Call Trace: __asan_memcpy (mm/kasan/shadow.c:106) submit_audio_out_urb (sound/usb/line6/playback.c:229) line6_submit_audio_out_all_urbs (sound/usb/line6/playback.c:291) line6_stream_start (sound/usb/line6/pcm.c:194) line6_pcm_acquire (sound/usb/line6/pcm.c:337) line6_startup_work (sound/usb/line6/driver.c:728) process_one_work (kernel/workqueue.c:3396) worker_thread (kernel/workqueue.c:3479 kernel/workqueue.c:3560) kthread (kernel/kthread.c:436) ret_from_fork (arch/x86/kernel/process.c:158) ret_from_fork_asm (arch/x86/entry/entry_64.S:245) The buggy address belongs to the object at ffff888100bbd400 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 0 bytes inside of allocated 256-byte region [ffff888100bbd400, ffff888100bbd500) Cc: stable@vger.kernel.org Fixes: 7a0f55aeeb8f ("ALSA: line6: Support assymetrical in/out configurations") Reported-by: Assisted-by: LLM Signed-off-by: Xiang Mei Link: https://patch.msgid.link/20260918234014.1318325-1-xmei5@asu.edu Signed-off-by: Takashi Iwai --- sound/usb/line6/playback.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index 7ebaf125f969..aa6ddf8746a0 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -182,6 +182,13 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) fsize *= bytes_per_frame; + if (fsize > line6pcm->max_packet_size_out) { + dev_err(line6pcm->line6->ifcdev, + "playback packet too large: %d > %d\n", + fsize, line6pcm->max_packet_size_out); + return -EMSGSIZE; + } + fout->offset = urb_size; fout->length = fsize; urb_size += fsize; From f4b506f735e3c24c4fd4d3a60239e0b1b241600e Mon Sep 17 00:00:00 2001 From: Matt Barr Date: Mon, 21 Sep 2026 17:48:12 -0400 Subject: [PATCH 305/535] ALSA: hda/realtek: Drop Yoga Pro 9 16IAH10 PCI SSID quirk The PCI SSID quirk added for the Lenovo Yoga Pro 9 16IAH10 (PCI SSID 17aa:3846, codec SSID 17aa:3920) also matches every other machine that shares PCI SSID 17aa:3846. snd_hda_pick_fixup() tries all PCI SSID entries before it falls back to the codec SSID, so those machines now get ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1 and never reach their own codec-SSID quirk. The Lenovo Legion Pro 7 16IRX8H (product 82WQ) is one such machine: PCI SSID 17aa:3846, codec SSID 17aa:3884. Before this quirk it picked ALC287_FIXUP_TAS2781_I2C via the 17aa:3884 codec SSID fallback and bound its TAS2781 amplifier. With it, the codec never binds the amplifier and the internal speakers are silent: snd_hda_codec_alc269 hdaudioC0D0: ALC287: picked fixup for PCI SSID 17aa:3846 versus, on a kernel without the quirk: snd_hda_codec_alc269 hdaudioC0D0: ALC287: picked fixup for codec SSID 17aa:3884 snd_hda_codec_alc269 hdaudioC0D0: bound i2c-TIAS2781:00 (ops tas2781_hda_comp_ops ...) Drop the PCI SSID entry. The Yoga Pro 9 16IAH10 has the same amplifier as the Yoga S990-16 (codec SSID 17aa:3920), so it still gets a fixup through the codec SSID fallback, using the existing 17aa:3920 entry. Switch that entry from ALC287_FIXUP_TXNW2781_I2C to ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, which applies the DAC routing fix and then chains to ALC287_FIXUP_TXNW2781_I2C, so the amplifier setup is unchanged. Fixes: 41d60cbfde10 ("ALSA: hda/realtek: Fix bass speaker DAC routing for Lenovo Yoga Pro 9 16IAH10") Link: https://bugzilla.kernel.org/show_bug.cgi?id=220540 Suggested-by: Zhang Heng Signed-off-by: Matt Barr Link: https://patch.msgid.link/20260921214812.22139-1-matthewjaybarr@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 30972cbe5f03..b4164db7ecbc 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8173,7 +8173,6 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), - SND_PCI_QUIRK(0x17aa, 0x3846, "Lenovo Yoga Pro 9 16IAH10", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them */ @@ -8267,7 +8266,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), - SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), From d24b03248ac6f02fb87feff6beec0b5b1da5dafd Mon Sep 17 00:00:00 2001 From: Achilles Zhang Date: Mon, 21 Sep 2026 10:43:43 +0800 Subject: [PATCH 306/535] ALSA: usb-audio: Add native DSD quirk for HiBy FC4 The HiBy FC4 with USB ID 32bb:0004 advertises its native DSD stream as UAC2 RAW_DATA with a 4-byte, 32-bit subslot. Without a quirk snd-usb-audio leaves this alternate setting as SNDRV_PCM_FORMAT_SPECIAL, so userspace cannot select a native DSD format. Mark the device with QUIRK_FLAG_DSD_RAW so the existing generic raw DSD handling exposes the stream as DSD_U32_BE. Tested on an FC4 at DSD256. With the quirk the playback alternate setting changes from SPECIAL to DSD_U32_BE and reports DOP=0, bitrev=0; native DSD256 playback works correctly. Signed-off-by: Achilles Zhang Link: https://patch.msgid.link/20260921024344.391912-1-bnqzzdf@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index c5870901a8d4..924d930a9f0e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2542,6 +2542,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x3255, 0x0000, /* Luxman D-10X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), + DEVICE_FLG(0x32bb, 0x0004, /* HiBy FC4 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x3302, 0x17c2, /* TTGK Technology USB-C Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x339b, 0x3a07, /* Synaptics HONOR USB-C HEADSET */ From 786fb6b3f513937f712db5a9ba6a12cbc6982ba9 Mon Sep 17 00:00:00 2001 From: Hernan Ganzo Date: Mon, 21 Sep 2026 21:55:38 +0100 Subject: [PATCH 307/535] ALSA: hda/realtek: Limit internal mic boost on HP Pavilion 15 (103c:2164) The HP Pavilion 15 with the ALC3227 codec (SSID 103c:2164) exposes the full four-step internal mic boost on pin 0x12: nsteps=3 with stepsize=0x2f, that is 0, 12, 24 and 36 dB. Combined with the ADC capture gain of +30 dB the analogue cascade reaches 66 dB, and the internal microphone clips on the ambient noise of a quiet room; the upper two steps are unusable at any useful input level. This is the defect class ALC269_FIXUP_LIMIT_INT_MIC_BOOST exists for. The machine matches no SSID quirk and falls through to the pin table, which selects ALC269_FIXUP_HP_MUTE_LED_MIC1 through a pin signature shared with several other HP models. Add an SSID entry instead, using ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED: the existing fixup that applies alc269_fixup_limit_int_mic_boost() and chains to the same mute LED fixup, as already done for 103c:218b. The boost becomes 0 or 12 dB and the LED handling is unchanged. Tested on the affected machine by selecting the same fixup through the model string (snd-hda-intel.model), which bypasses the SSID and pin tables, with the driver confirming the selection: snd_hda_codec_alc269 hdaudioC1D0: ALC3227: picked fixup limit-mic-boost (model specified) With that, Internal Mic Boost reports Limits 0 - 1 (0 or 12 dB) instead of 0 - 3 (0 to 36 dB), so alc269_fixup_limit_int_mic_boost() clamps this codec's internal mic pin as intended. Note that /proc/asound/card1/codec#0 keeps printing the unclamped hardware caps (nsteps=0x03): that file reads codec parameters uncached by design, while the fixup overrides the driver's cached parameter that the control is built from. This does not by itself make the default capture volume sane -- the remaining 12 dB on top of the +30 dB ADC gain is still hotter than this machine's usable operating point. It removes the two unusable steps from the hardware range, which is what a quirk of this class can do. Signed-off-by: Hernan Ganzo Link: https://patch.msgid.link/20260921205538.41192-1-gp@sneemgp.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b4164db7ecbc..b94b0e29bda5 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7312,6 +7312,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x164b, "Dell", ALC293_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1586, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC2), SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x2164, "HP Pavilion 15 Notebook PC", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), From 775aa96c310d59f3d15a6e5a9bd1796fd1ba95ec Mon Sep 17 00:00:00 2001 From: Luis Banha Date: Tue, 22 Sep 2026 00:01:05 +0100 Subject: [PATCH 308/535] ALSA: hda/realtek: Add mute LED quirk for HP Pavilion 15-ec1xxx The mute LED on the HP Pavilion Gaming Laptop 15-ec1xxx (103c:87b2) does not work automatically. Add the ALC285_FIXUP_HP_MUTE_LED quirk to fix this behavior. Signed-off-by: Luis Banha Link: https://patch.msgid.link/20260921230105.16844-1-luisbanha26@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b94b0e29bda5..8c29688d8e41 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7433,6 +7433,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x87b2, "HP Pavilion Gaming Laptop 15-ec1xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x87b7, "HP Laptop 14-fq0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x87cb, "HP Pavilion 15-eg0xxx", ALC287_FIXUP_HP_GPIO_LED), From 3867e38b2d4b844d89076f988c7b9741c87a9858 Mon Sep 17 00:00:00 2001 From: KIM MinWoo Date: Wed, 23 Sep 2026 10:37:56 +0900 Subject: [PATCH 309/535] ALSA: hda/realtek: Add quirk for Lenovo ThinkBook 16p Gen 3 ARH (21EK) The ThinkBook 16p Gen 3 ARH (SSID 17aa:3871) has a TI TAS2563 amplifier device on I2C, exposed by ACPI as INT8866, the same arrangement as the Yoga 7 14ARB7 (SSID 17aa:3870). Without a quirk, the codec falls back to the vendor-wide Lenovo quirk (ALC269_FIXUP_LENOVO_XPAD_ACPI), the amplifier is never bound, and the internal speakers stay silent while the headphone output works. Reuse the Yoga 7 14ARB7 fixup, which binds the INT8866 device as a side codec. ALC287_FIXUP_TAS2781_I2C does not work on this machine because it looks for a TIAS2781 device. Assisted-by: LLM Signed-off-by: KIM MinWoo Link: https://patch.msgid.link/20260923013756.106033-1-phrimm136@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 8c29688d8e41..de478d01430e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8199,6 +8199,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), + SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), From d49f8d9680ee3f7f4e0339467cf0a244158c1adb Mon Sep 17 00:00:00 2001 From: Roman Bolotov Date: Wed, 23 Sep 2026 09:45:27 +0300 Subject: [PATCH 310/535] ALSA: usb-audio: Add quirk flag for ASUS SupremeFX Hi-Fi The ASUS SupremeFX Hi-Fi (0b05:1826, 0b05:1827) does not survive USB autosuspend. After a suspend/resume cycle the firmware degrades: HID probes begin to fail and the descriptors it returns become corrupted, until the device stops responding entirely and needs a physical power cycle to recover. Add QUIRK_FLAG_DISABLE_AUTOSUSPEND for both product IDs. Verified on 7.2.6 through the quirk_flags module parameter, with no code change, and with the local udev rules that had been forcing power/control commented out, so the parameter was the only mechanism in play. Both IDs were passed as 0b05:1827:disable_autosuspend;0b05:1826:disable_autosuspend The device then stayed awake across 2.5 hours (runtime_suspended_time remained 0), survived a system suspend/resume cycle, and continued to work afterwards with no failed probes and no power cycle needed. Signed-off-by: Roman Bolotov Link: https://patch.msgid.link/20260923064527.434441-1-hadros@ikeepitoblique.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 924d930a9f0e..294c7026b93c 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2366,6 +2366,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x0a73, 0x003a, /* Mackie DLZ Creator XS */ QUIRK_FLAG_ALWAYS_SET_RATE), + DEVICE_FLG(0x0b05, 0x1826, /* ASUS SupremeFX Hi-Fi */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), + DEVICE_FLG(0x0b05, 0x1827, /* ASUS SupremeFX Hi-Fi */ + QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x0b05, 0x18a6, /* ASUSTek Computer, Inc. */ QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), DEVICE_FLG(0x0b0e, 0x0349, /* Jabra 550a */ From a01a223bd685be42e908d970de32eeb3646097f3 Mon Sep 17 00:00:00 2001 From: Tarun Noonemunthala Date: Wed, 23 Sep 2026 15:10:14 +0530 Subject: [PATCH 311/535] ALSA: hda/realtek: Add quirk for HP ENVY 13-aq1xxx The HP ENVY Laptop 13-aq1xxx (PCI SSID 103c:86ad) uses an ALC285 codec with two speaker pairs: front (pin 0x14) and bottom (pin 0x17). The BIOS leaves pin 0x14 as [N/A] ("not connected"), so the generic parser never enables the front pair and only the bottom speakers are audible. The vendor driver (RTKVHD64.sys) programs that pin with the value 0x90170150 and runs the HP amplifier initialization, which is already implemented in the driver as ALC285_FIXUP_HP_GPIO_AMP_INIT. Add a board fixup that applies the pin configuration for 0x14 and chains to the existing amplifier init fixup. The pin value was recovered from the vendor driver's own configuration data. The patch and this changelog were prepared with an AI coding assistant (see the Assisted-by tag below); the change was reviewed and tested by the submitter on the affected machine. Tested on an HP ENVY 13-aq1xxx by rebuilding snd-hda-codec-alc269 from the Fedora 7.1.10 source and loading it: the codec now reports "line_outs=2 (0x14/0x17)", pin 0x14 comes up as an output (Pin-ctls 0x40) with its amplifier unmuted, and both speaker pairs play. Assisted-by: OpenCode:deepseek-v4.1-flash checkpatch Signed-off-by: Tarun Noonemunthala Link: https://patch.msgid.link/20260923094014.58867-1-ntarun2000@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index de478d01430e..004f6a3eadfe 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4124,6 +4124,7 @@ enum { ALC285_FIXUP_HP_SPECTRE_X360_EB1, ALC285_FIXUP_HP_SPECTRE_X360_DF1, ALC285_FIXUP_HP_ENVY_X360, + ALC285_FIXUP_HP_ENVY_13AQ, ALC288_FIXUP_DELL_HEADSET_MODE, ALC288_FIXUP_DELL1_MIC_NO_PRESENCE, ALC288_FIXUP_DELL_XPS_13, @@ -6401,6 +6402,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, }, + [ALC285_FIXUP_HP_ENVY_13AQ] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170150 }, /* front speakers */ + { } + }, + .chained = true, + .chain_id = ALC285_FIXUP_HP_GPIO_AMP_INIT, + }, [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_ideapad_s740_coef, @@ -7405,6 +7415,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x864f, "HP Laptop 15-dy0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x86ad, "HP ENVY 13-aq1xxx", ALC285_FIXUP_HP_ENVY_13AQ), SND_PCI_QUIRK(0x103c, 0x86c1, "HP Laptop 15-da3001TU", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), SND_PCI_QUIRK(0x103c, 0x86c8, "HP Laptop 14s-dr1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), From cfc0a117d773eb585ca7e87d20c50d1c415058e5 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Wed, 23 Sep 2026 20:16:40 +0800 Subject: [PATCH 312/535] ALSA: hda/realtek: Fix square wave output on ASUS Strix G615LR ASUS ROG Strix G16 G615LR (PCI SSID 1043:3f20) outputs a loud full-scale square wave from internal speakers since the TAS2781 amplifier driver started loading. The quirk was using ALC287_FIXUP_TXNW2781_I2C which chains to the ThinkPad headset jack fixup, but this is an ASUS device that should use ALC287_FIXUP_TXNW2781_I2C_ASUS which chains to ALC294_FIXUP_ASUS_SPK for proper EAPD and pin configuration. Fixes: f7cede182c96 ("ALSA: hda/realtek: Add Asus quirk for TAS amplifiers") Cc: stable@vger.kernel.org Cc: Baojun Xu Cc: Antheas Kapenekakis Reported-by: Aleksei Arsenev Link: https://bugzilla.kernel.org/show_bug.cgi?id=222045 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260923121640.272509-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 004f6a3eadfe..b5cb9fa6e6d4 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7938,7 +7938,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x3ef0, "ASUS Strix G635LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3f00, "ASUS Strix G815LH_LM_LP", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3f10, "ASUS Strix G835LR_LW_LX", ALC287_FIXUP_TXNW2781_I2C), - SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C), + SND_PCI_QUIRK(0x1043, 0x3f20, "ASUS Strix G615LR_LW", ALC287_FIXUP_TXNW2781_I2C_ASUS), SND_PCI_QUIRK(0x1043, 0x3f30, "ASUS Strix G815LR_LW", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x1043, 0x3fd0, "ASUS B3605CVA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3ff0, "ASUS B5405CVA", ALC245_FIXUP_CS35L41_SPI_2), From 2a8ee6897302ca12ff39e8eb180503956797c60c Mon Sep 17 00:00:00 2001 From: Yu-Hsiang Tseng Date: Wed, 23 Sep 2026 22:13:44 +0800 Subject: [PATCH 313/535] ALSA: hda/realtek: Fix silent speaker on Higole F9B The Higole F9B is an Alder Lake-N mini PC with a 7" touchscreen and an ALC269VC codec (subsystem ID 0x10ec111e). Its DMI strings are all "Default string", so the SSID is the only way to identify it. The internal speaker is silent out of the box. The generic parser assigns DAC 0x03 to the speaker pin 0x14 and DAC 0x02 to the headphone pin 0x15, but the speaker only reproduces what DAC 0x02 plays, whatever its connection selection says. Checked one path at a time, only the gain of DAC 0x02 and the mute of pin 0x14 change what comes out of the speaker. User space only knows the path it was told the speaker uses, so selecting the speaker turns the path that actually reaches it all the way down. Route both 0x14 and 0x15 through mixer 0x0c (DAC 0x02), which is what ALC290_FIXUP_MONO_SPEAKERS already does. Tested on the device: the speaker plays. Cc: Assisted-by: LLM Signed-off-by: Yu-Hsiang Tseng Link: https://patch.msgid.link/20260923141344.1543158-1-asas1asas200@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index b5cb9fa6e6d4..502844dc4e26 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7961,6 +7961,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x10cf, 0x1757, "Lifebook E752", ALC269_FIXUP_LIFEBOOK_HP_PIN), SND_PCI_QUIRK(0x10cf, 0x1845, "Lifebook U904", ALC269_FIXUP_LIFEBOOK_EXTMIC), SND_PCI_QUIRK(0x10ec, 0x10f2, "Intel Reference board", ALC700_FIXUP_INTEL_REFERENCE), + SND_PCI_QUIRK(0x10ec, 0x111e, "Higole F9B", ALC290_FIXUP_MONO_SPEAKERS), SND_PCI_QUIRK(0x10ec, 0x118c, "Medion EE4254 MD62100", ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE), SND_PCI_QUIRK(0x10ec, 0x119e, "Positivo SU C1400", ALC269_FIXUP_ASPIRE_HEADSET_MIC), SND_PCI_QUIRK(0x10ec, 0x11bc, "VAIO VJFE-IL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), From 7b457ae8cf711831f19d20963f5ed2fdf4842d3a Mon Sep 17 00:00:00 2001 From: Tomasz Bojanowski Date: Wed, 23 Sep 2026 20:49:57 +0200 Subject: [PATCH 314/535] ALSA: usb-audio: Add mixer mapping for MSI MAG B850M MORTAR WIFI The USB audio device 0db0:cc78 (Realtek ALC4080) on the MSI MAG B850M MORTAR WIFI motherboard exposes all playback controls as "PCM", which makes it hard to tell the outputs apart. Its mixer layout matches the one of the MSI MPG X570S Carbon Max Wifi (units 29, 30 and 32), so reuse msi_mpg_x570s_carbon_max_wifi_alc4080_map for this device too. With the mapping applied, the controls show up as "Speaker", "Front Headphone" and "IEC958". Tested with a patched snd-usb-audio module on a 7.2.7 kernel. Signed-off-by: Tomasz Bojanowski Link: https://patch.msgid.link/20260923184957.6687-1-tomasz@bojanowski.me Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 41454cb403d0..f8e1f78039e1 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -749,6 +749,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x0db0, 0xa073), .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, }, + { /* MSI MAG B850M MORTAR WIFI */ + .id = USB_ID(0x0db0, 0xcc78), + .map = msi_mpg_x570s_carbon_max_wifi_alc4080_map, + }, { /* MSI TRX40 */ .id = USB_ID(0x0db0, 0x543d), .map = trx40_mobo_map, From 0b7bd20f6379bc5e431fd74d866390f5b5b63f3c Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sat, 26 Sep 2026 00:07:17 +0700 Subject: [PATCH 315/535] ALSA: caiaq: unregister the input device when probe fails setup_card() registers the input device, whose name and phys point into struct snd_usb_caiaqdev, and then goes on to snd_card_register() and snd_usb_caiaq_control_init(). If either fails, snd_probe() calls snd_card_free(), which frees the device state but leaves the input device registered: card_free() only clears the pointer, and the input device is unregistered from snd_disconnect() alone. Reading its "uevent" attribute afterwards dereferences freed memory: BUG: KASAN: slab-use-after-free in string+0x4a9/0x4f0 Read of size 1 at addr ffff8880208f5043 by task caiaq/4967 add_uevent_var+0x183/0x3a0 input_dev_uevent+0x162/0x900 dev_uevent+0x2f1/0x870 uevent_show+0x1ca/0x3a0 ... Unregister the input device on the probe error path, as snd_disconnect() does. snd_usb_caiaq_input_disconnect() is a no-op when no input device was registered. Reproduced with a raw-gadget Audio Kontrol 1 and a temporary hack that makes snd_usb_caiaq_control_init() fail. Fixes: 28abd224db4a ("ALSA: caiaq: Handle probe errors properly") Cc: stable@vger.kernel.org Reported-by: syzbot+2a123f6269da57ffefaa@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2a123f6269da57ffefaa Signed-off-by: Nguyen Ngoc Thang Link: https://patch.msgid.link/20260925170717.22462-1-ngocthang2710.1999@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/caiaq/device.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index 3d821fde4582..f604846ec6e6 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -562,6 +562,9 @@ static int snd_probe(struct usb_interface *intf, ret = init_card(caiaqdev(card)); if (ret < 0) { dev_err(&usb_dev->dev, "unable to init card! (ret=%d)\n", ret); +#ifdef CONFIG_SND_USB_CAIAQ_INPUT + snd_usb_caiaq_input_disconnect(caiaqdev(card)); +#endif snd_card_free(card); return ret; } From ca51771c8efde5df05476ac98dd1b2af4e2b149f Mon Sep 17 00:00:00 2001 From: Zimeng Li Date: Sun, 27 Sep 2026 09:09:11 +0800 Subject: [PATCH 316/535] ASoC: qcom: lpass-cpu: use DAI table index for playback constraints The DAI ID is a hardware port ID and is not necessarily the index of the corresponding entry in variant->dai_driver. When configuring a DAI with LPAIF_I2SCTL_MODE_QUAD01, the probe code currently uses dai_id to index variant->dai_driver. This is incorrect for platforms where DAI IDs are sparse. For example, the IPQ806x MI2S DAI has ID 4 while it is the only entry in the DAI driver table. If that DAI is configured for QUAD01 and probe reaches this branch, the code writes beyond that single entry via dai_driver[4] instead of updating the DAI being processed. Use the loop index i when updating the current DAI's playback channel constraints, while retaining dai_id for indexing the hardware-port specific playback SD-line mode array. This fixes the incorrect DAI table access for platforms where the DAI ID does not match its position in the driver table. Fixes: c223f41c1a52 ("ASoC: qcom: Add four speaker support on MI2S secondary") Cc: stable@vger.kernel.org Signed-off-by: Zimeng Li Assisted-by: LLM-assisted source analysis Reviewed-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260927010911.51980-1-me@lizi.moe Signed-off-by: Mark Brown --- sound/soc/qcom/lpass-cpu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 242bc16da36d..079177a6d81e 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -1234,8 +1234,8 @@ int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) } if (drvdata->mi2s_playback_sd_mode[dai_id] == LPAIF_I2SCTL_MODE_QUAD01) { - variant->dai_driver[dai_id].playback.channels_min = 4; - variant->dai_driver[dai_id].playback.channels_max = 4; + variant->dai_driver[i].playback.channels_min = 4; + variant->dai_driver[i].playback.channels_max = 4; } } From 89365897f4210979966aa808d7b013edf4080558 Mon Sep 17 00:00:00 2001 From: Aaron Kling Date: Sat, 26 Sep 2026 02:24:19 -0500 Subject: [PATCH 317/535] ALSA: hda/tegra: Re-enable SDO workaround for Tegra194 This workaround was originally added for Tegra194, but no longer gets applied for it. Commit 615d43540043 ("ALSA: hda/tegra: fix tegra-hda on tegra30 soc") changed the guard to tegra30-hda, which worked because all affected archs had the fallback compatible. However, commit 7f0ea5acfc19 ("arm64: tegra: Use correct compatible string for Tegra194 HDA") removed the fallback compatible for Tegra194, causing this to break. Fixes: 7f0ea5acfc19 ("arm64: tegra: Use correct compatible string for Tegra194 HDA") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Aaron Kling Link: https://patch.msgid.link/20260926-tegra194-hda-sdo-v1-1-f7a636a07a58@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/controllers/tegra.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c index 31c14c4bbe68..674cc0f79c3c 100644 --- a/sound/hda/controllers/tegra.c +++ b/sound/hda/controllers/tegra.c @@ -374,7 +374,8 @@ static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev) * in powers of 2, next available ratio is 16 which can be * used as a limiting factor here. */ - if (of_device_is_compatible(np, "nvidia,tegra30-hda")) + if (of_device_is_compatible(np, "nvidia,tegra30-hda") || + of_device_is_compatible(np, "nvidia,tegra194-hda")) chip->bus.core.sdo_limit = 16; /* codec detection */ From 608c0c8947f03069b651ace54538a115f5b23123 Mon Sep 17 00:00:00 2001 From: Kailang Yang Date: Thu, 17 Sep 2026 14:53:26 +0800 Subject: [PATCH 318/535] ALSA: hda/realtek - Add headset mode for Dell Pro QC1255 It lost its headset microphone functionality, and this patch will restore it. Fixes: 97272a5704bf ("ALSA: hda/realtek - Fixed Headphone noise issue for Dell QCM1255") Signed-off-by: Kailang Yang Link: https://lore.kernel.org/3c251e40a2944ae4ae479e4c2e4e63a4@realtek.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 502844dc4e26..fb3a223bf9fa 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7098,6 +7098,8 @@ static const struct hda_fixup alc269_fixups[] = { [ALC236_FIXUP_DELL_HP_POP_NOISE] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_invalidate_dacs, + .chained = true, + .chain_id = ALC255_FIXUP_DELL1_MIC_NO_PRESENCE }, [ALC274_FIXUP_HP_89E9_GPIO] = { .type = HDA_FIXUP_FUNC, From 4322ac247f4ce7932edbd46aa37f3dae62cde424 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 21 Sep 2026 20:44:49 +0800 Subject: [PATCH 319/535] ALSA: hda/realtek: Add quirk for Lenovo ThinkBook G8+ Fix Bass Speaker volume control on Lenovo ThinkBook G8+ by adding quirk 17aa:38dc with ALC285_FIXUP_SPEAKER2_TO_DAC1. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221799 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260921124450.639601-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fb3a223bf9fa..2b1fa3d4585b 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8264,6 +8264,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x38d5, "Yoga S990-16 Pro IMH YC Quad", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38d6, "Yoga S990-16 Pro IMH VECO Quad", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38d7, "Lenovo Yoga 9 14IMH9", ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x38dc, "Lenovo ThinkBook G8", ALC285_FIXUP_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x38df, "Yoga Y990 Intel YC Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C), From 0d90a929836344f1d3b39f22f08ccecc62378a59 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 21 Sep 2026 20:44:50 +0800 Subject: [PATCH 320/535] ALSA: hda/realtek: Add quirk for Acer Nitro AN515-45 The Acer Nitro AN515-45 requires ALC2XX_FIXUP_HEADSET_MIC to make the headset microphone work properly. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221925 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260921124450.639601-2-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2b1fa3d4585b..4bd37a15206e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7203,6 +7203,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), + SND_PCI_QUIRK(0x1025, 0x151e, "Acer Nitro AN515-45", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1534, "Acer Predator PH315-54", ALC255_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1539, "Acer Nitro 5 AN515-57", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x159c, "Acer Nitro 5 AN515-58", ALC287_FIXUP_ACER_MICMUTE_LED), From c748c42abb9b549fc79c41ea55e1f220eadb60ec Mon Sep 17 00:00:00 2001 From: Krish Gulati Date: Mon, 21 Sep 2026 18:47:03 +0530 Subject: [PATCH 321/535] ALSA: hda/realtek: Add mute LED quirk for HP Victus 15-fb2xxx The HP Victus 15-fb2xxx (PCI SSID 103c:8c2f) with the ALC245 codec does not turn on the mute LED when audio is muted. Add a quirk entry to enable it. Reported-by: erph.briones27@gmail.com Link: https://bugzilla.kernel.org/show_bug.cgi?id=221946 Signed-off-by: Krish Gulati Tested-by: Raphael Briones Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260921131728.8999-1-krishgulati7@gmail.com --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 4bd37a15206e..1db335b18c4e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7628,6 +7628,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8c17, "HP Spectre x360 2-in-1 Laptop 16-aa0xxx", ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX), SND_PCI_QUIRK(0x103c, 0x8c21, "HP Pavilion Plus Laptop 14-ey0XXX", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8c2d, "HP Victus 15-fa1xxx (MB 8C2D)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8c2f, "HP Victus 15-fb2xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c30, "HP Victus 15-fb1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c3f, "HP Victus 15-fa1xxx (MB 8C3F)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8c46, "HP EliteBook 830 G11", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), From c26d18964a9352e83cb5fdd7b327d9a2566abddf Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 28 Sep 2026 11:56:10 +0100 Subject: [PATCH 322/535] ASoC: Intel: Add quirk to block match table for Lenovo Yoga Slim 7 A variant of the Lenovo Yoga Slim 7 exists on Lunar Lake that doesn't have a physical jack socket. Cirrus codecs still use match tables on LNL but the match table makes no distinction between a system with or without a jack. This leads to the system failing probe as the jack in the topology file is not matched by a DAI link as the machine driver is constructed. Add a new quirk to block using match tables for specific devices, and rely on function topologies which can handle this difference seamlessly. This is slightly preferred over moving everything to function topologies due to the lower chance of causing regressions on other systems. Signed-off-by: Charles Keepax Link: https://patch.msgid.link/20260928105610.242687-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- .../soc/intel/common/soc-acpi-intel-lnl-match.c | 1 + .../intel/common/soc-acpi-intel-sdca-quirks.c | 16 ++++++++++++++++ .../intel/common/soc-acpi-intel-sdca-quirks.h | 1 + 3 files changed, 18 insertions(+) diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c index 3b9758f73238..62b8811395cb 100644 --- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c @@ -745,6 +745,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { .links = lnl_cs42l43_l0, .drv_name = "sof_sdw", .sof_tplg_filename = "sof-lnl-cs42l43-l0.tplg", + .machine_check = snd_soc_acpi_intel_force_function_topology, .get_function_tplg_files = sof_sdw_get_tplg_files, }, { diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c index 9fdeb6ad96ac..e62ef81214ff 100644 --- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c +++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.c @@ -53,6 +53,22 @@ bool snd_soc_acpi_intel_no_function_topology(void *arg) } EXPORT_SYMBOL_NS(snd_soc_acpi_intel_no_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); +static const struct dmi_system_id force_function_topology_quirk_table[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83R0"), + }, + }, + {} +}; + +bool snd_soc_acpi_intel_force_function_topology(void *arg) +{ + return !dmi_check_system(force_function_topology_quirk_table); +} +EXPORT_SYMBOL_NS(snd_soc_acpi_intel_force_function_topology, "SND_SOC_ACPI_INTEL_SDCA_QUIRKS"); + bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg) { return snd_soc_acpi_intel_sdca_is_device_rt712_vb(arg) && diff --git a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h index 60b665ab5924..10179004c86a 100644 --- a/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h +++ b/sound/soc/intel/common/soc-acpi-intel-sdca-quirks.h @@ -11,6 +11,7 @@ bool snd_soc_acpi_intel_sdca_is_device_rt712_vb(void *arg); bool snd_soc_acpi_intel_no_function_topology(void *arg); +bool snd_soc_acpi_intel_force_function_topology(void *arg); bool snd_soc_acpi_intel_rt712_vb_no_function_topology(void *arg); #endif From f090e7ff26fe08be4cd57e0950872ede4af5351a Mon Sep 17 00:00:00 2001 From: Bartosz Brom Date: Mon, 28 Sep 2026 15:43:35 +0200 Subject: [PATCH 323/535] ALSA: hda/realtek: Add mute LED quirks for HP Pavilion 15-eh3174nw and HP Laptop 15-dw1xxx The HP Pavilion 15-eh3174nw with an ALC287 codec requires the ALC287_FIXUP_HP_GPIO_LED fixup for its mute LED to work correctly. Add subsystem ID 0x103c:0x8bc7 to the quirk table to apply the existing fixup. Tested on an HP Pavilion 15-eh3174nw; the mute LED now follows the speaker mute state. The HP Laptop 15-dw1xxx with an ALC236 codec requires the ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO fixup for its mute LED to work correctly. Add subsystem ID 0x103c:0x85f1 to the quirk table to apply the existing fixup. Tested on an HP Laptop 15-dw1xxx; the mute LED now follows the speaker mute state. Signed-off-by: Bartosz Brom Link: https://patch.msgid.link/20260928134335.1875-1-bartosz.brom06@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 1db335b18c4e..193e0fa3606d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7411,6 +7411,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x85f1, "HP Laptop 15-dw1xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x85f3, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x8603, "HP Omen 17-cb0xxx", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x860c, "HP ZBook 17 G6", ALC285_FIXUP_HP_GPIO_AMP_INIT), @@ -7606,6 +7607,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8bb4, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8bb6, "HP Laptop 15-fd0039nt", ALC236_FIXUP_HP_15_FD0XXX), SND_PCI_QUIRK(0x103c, 0x8bbe, "HP Victus 16-r0xxx (MB 8BBE)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), + SND_PCI_QUIRK(0x103c, 0x8bc7, "HP Pavilion 15-eh3174nw", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8bc8, "HP Victus 15-fa1xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bcd, "HP Omen 16-xd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bd4, "HP Victus 16-s0xxx (MB 8BD4)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), From 74dae5f33c0c64d3ca12710bfd845721d73fe2e5 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Fri, 11 Sep 2026 20:23:06 +0200 Subject: [PATCH 324/535] drm/xe/pf: Refactor VF LMEM BAR resize helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve and move diagnostics messages to the helper function to keep the caller function tidy. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260911182306.14973-1-michal.wajdeczko@intel.com (cherry picked from commit 10628c52a3732a10426499a3d462cc2e6bc371ae) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_pci_sriov.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pci_sriov.c b/drivers/gpu/drm/xe/xe_pci_sriov.c index 35e6b53e18ce..c023a2a1fc17 100644 --- a/drivers/gpu/drm/xe/xe_pci_sriov.c +++ b/drivers/gpu/drm/xe/xe_pci_sriov.c @@ -82,16 +82,33 @@ static void pf_engine_activity_stats(struct xe_device *xe, unsigned int num_vfs, } } -static int resize_vf_vram_bar(struct xe_device *xe, int num_vfs) +static int pf_resize_vf_vram_bar(struct xe_device *xe, int num_vfs) { struct pci_dev *pdev = to_pci_dev(xe->drm.dev); + char buf[10]; u32 sizes; + int size; + int err; + + if (!IS_DGFX(xe)) + return 0; sizes = pci_iov_vf_bar_get_sizes(pdev, VF_LMEM_BAR, num_vfs); if (!sizes) return 0; - return pci_iov_vf_bar_set_size(pdev, VF_LMEM_BAR, __fls(sizes)); + size = __fls(sizes); + string_get_size(pci_rebar_size_to_bytes(size), 1, STRING_UNITS_2, buf, sizeof(buf)); + + err = pci_iov_vf_bar_set_size(pdev, VF_LMEM_BAR, size); + if (err) { + xe_sriov_notice(xe, "Failed to resize VF BAR %u to %s (%pe)\n", + VF_LMEM_BAR, buf, ERR_PTR(err)); + return err; + } + + xe_sriov_dbg(xe, "VF LMEM BAR resized to %s\n", buf); + return 0; } static int pf_prepare_vfs_enabling(struct xe_device *xe) @@ -142,11 +159,7 @@ static int pf_enable_vfs(struct xe_device *xe, int num_vfs) if (err < 0) goto failed; - if (IS_DGFX(xe)) { - err = resize_vf_vram_bar(xe, num_vfs); - if (err) - xe_sriov_info(xe, "Failed to set VF LMEM BAR size: %d\n", err); - } + pf_resize_vf_vram_bar(xe, num_vfs); err = pci_enable_sriov(pdev, num_vfs); if (err < 0) From 72207463f7a4a6818a88f4ef57802b42da534301 Mon Sep 17 00:00:00 2001 From: Marcin Bernatowicz Date: Fri, 18 Sep 2026 13:01:30 +0200 Subject: [PATCH 325/535] drm/xe/pf: Keep VF LMEM BAR size low if no VFs enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When VFs are enabled on dGFX the driver resizes the PF VF_LMEM_BAR to fit the requested layout. After VFs are disabled the PF VF BAR size is left as-is. On platforms with tight MMIO apertures a subsequent unplug/rescan followed by another enable may fail with: "VF BAR …: can't assign; no space" because the PCI core reserves address space based on the (now large) VF template, often multiplied by totalvfs. Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/issues/5937 Fixes: 94eae6ee4c2d ("drm/xe/pf: Set VF LMEM BAR size") Signed-off-by: Marcin Bernatowicz Cc: Michał Wajdeczko Cc: Michał Winiarski Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260918110130.700332-1-marcin.bernatowicz@linux.intel.com Signed-off-by: Rodrigo Vivi (cherry picked from commit 0646547a67d25c407f5a4ac71b4eefe8b941202b) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_pci_sriov.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pci_sriov.c b/drivers/gpu/drm/xe/xe_pci_sriov.c index c023a2a1fc17..31abcee55211 100644 --- a/drivers/gpu/drm/xe/xe_pci_sriov.c +++ b/drivers/gpu/drm/xe/xe_pci_sriov.c @@ -177,6 +177,7 @@ static int pf_enable_vfs(struct xe_device *xe, int num_vfs) return num_vfs; failed: + pf_resize_vf_vram_bar(xe, xe->sriov.pf.device_total_vfs); xe_sriov_pf_unprovision_vfs(xe, num_vfs); xe_pm_runtime_put(xe); pf_finish_vfs_enabling(xe); @@ -204,6 +205,8 @@ static int pf_disable_vfs(struct xe_device *xe) pci_disable_sriov(pdev); + pf_resize_vf_vram_bar(xe, xe->sriov.pf.device_total_vfs); + xe_sriov_pf_reprovision_default(xe); pf_reset_vfs(xe, num_vfs); From 98ad5b63f5041dc354a6e694f4a9436f74e0681b Mon Sep 17 00:00:00 2001 From: Junyuan Feng Date: Wed, 23 Sep 2026 09:20:44 +0000 Subject: [PATCH 326/535] io_uring/zcrx: requeue multishot receives stopped by a local resource A multishot RECV_ZC can go idle with unread TCP data when a receiver-local resource runs out. An empty copy-fallback niov freelist returns -ENOMEM; a full CQ returns -ENOSPC. After partial progress the stop reason is lost: io_zcrx_copy_chunk() and io_zcrx_recv_skb() report the copied bytes, and tcp_read_sock() drops an error from a later call once earlier data was consumed. The edge-triggered request is then not requeued, so it cannot consume the remaining data until another socket event arrives. Record in io_zcrx_recv_skb() when a walk stops before consuming the length it was offered, and requeue if the pass still returned data. The failed chunk is first on the next pass. The resource is usually still exhausted by then, so that pass fails at once and ends the request with an error CQE, which the application must handle by re-arming. The request does not spin, and the stall becomes a visible error. Keep the existing skb limit and SOCK_DONE handling. Fixes: 11ed914bbf94 ("io_uring/zcrx: add io_recvzc request") Fixes: bc57c7d36c4c ("io_uring/zcrx: add copy fallback") Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-6-sol Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Junyuan Feng Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/010001a0cd914529-23df2f94-bbe0-49cd-ae5d-05922356fa12-000000@email.amazonses.com Signed-off-by: Jens Axboe --- io_uring/zcrx.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 1b3b11405dac..beb35077f3ce 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -374,6 +374,7 @@ struct io_zcrx_args { struct io_kiocb *req; struct io_zcrx_ifq *ifq; unsigned nr_skbs; + bool stopped_early; }; static const struct memory_provider_ops io_uring_pp_zc_ops; @@ -1899,6 +1900,9 @@ io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb, } out: + /* Bytes left in len mean an error stopped the walk early. */ + if (len) + args->stopped_early = true; if (offset == start_off) return ret; desc->count -= (offset - start_off); @@ -1935,8 +1939,9 @@ static int io_zcrx_tcp_recvmsg(struct io_kiocb *req, struct io_zcrx_ifq *ifq, ret = -ENOTCONN; else ret = -EAGAIN; - } else if (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) && - (issue_flags & IO_URING_F_MULTISHOT)) { + } else if ((issue_flags & IO_URING_F_MULTISHOT) && + (unlikely(args.nr_skbs > IO_SKBS_PER_CALL_LIMIT) || + args.stopped_early)) { ret = IOU_REQUEUE; } else if (sock_flag(sk, SOCK_DONE)) { /* Make it to retry until it finally gets 0. */ From 3a3d93070c5ec8b8049d57025987ba313420bda9 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Sun, 27 Sep 2026 15:42:06 +0800 Subject: [PATCH 327/535] io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full SOCKET_URING_OP_TX_TIMESTAMP arms an EPOLLERR apoll multishot and, on each error queue edge, posts one 32 byte aux CQE per timestamp skb. Aux CQEs have no overflow backing, so io_uring_cmd_post_mshot_cqe32() fails once the CQ ring is full. The loop then stops, splices the unprocessed skbs back onto sk_error_queue and returns -EAGAIN. -EAGAIN is IOU_RETRY, so the request goes idle until the next poll event. io_arm_apoll() forces EPOLLET and draining the CQ does not re-run the command, so the spliced-back timestamps stay queued until an unrelated later timestamp produces a new edge. End the multishot when a CQE cannot be posted, as multishot poll and recv already do. There is no partial result to report, so complete with -ENOBUFS: no CQ space is left for the timestamp CQEs. This command does not use provided buffers, so the value cannot be confused with a buffer ring running empty. The terminal CQE still reaches userspace because request completions can go to the overflow list. The application sees a CQE without IORING_CQE_F_MORE, which it already has to handle, and the first issue of the re-armed request delivers the queued timestamps. A timestamp that cannot be extracted keeps its current behaviour. This was found with LLM assistance while reviewing io_uring multishot handlers for the pattern behind a RECV_ZC stall reported earlier (see Link), and confirmed with a reproducer on v7.3-rc2 and an A/B run of this patch. Fixes: 9e4ed359b8ef ("io_uring/netcmd: add tx timestamping cmd support") Link: https://lore.kernel.org/all/010001a0cd914529-23df2f94-bbe0-49cd-ae5d-05922356fa12-000000@email.amazonses.com/ Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-6-sol Assisted-by: Claude:claude-opus-5-5 Signed-off-by: lollipopkit Reviewed-by: Pavel Begunkov Link: https://patch.msgid.link/20260927074206.65427-1-a@lolli.tech Signed-off-by: Jens Axboe --- io_uring/cmd_net.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/io_uring/cmd_net.c b/io_uring/cmd_net.c index 7cd411fc4f33..90d4ec7cc761 100644 --- a/io_uring/cmd_net.c +++ b/io_uring/cmd_net.c @@ -69,8 +69,8 @@ static inline int io_uring_cmd_setsockopt(struct socket *sock, optlen); } -static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, - struct sk_buff *skb, unsigned issue_flags) +static int io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, + struct sk_buff *skb, unsigned int issue_flags) { struct sock_exterr_skb *serr = SKB_EXT_ERR(skb); struct io_uring_cqe cqe[2]; @@ -83,7 +83,7 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, ret = skb_get_tx_timestamp(skb, sk, &ts); if (ret < 0) - return false; + return ret; tskey = serr->ee.ee_data; tstype = serr->ee.ee_info; @@ -98,7 +98,9 @@ static bool io_process_timestamp_skb(struct io_uring_cmd *cmd, struct sock *sk, iots = (struct io_timespec *)&cqe[1]; iots->tv_sec = ts.tv_sec; iots->tv_nsec = ts.tv_nsec; - return io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe); + if (!io_uring_cmd_post_mshot_cqe32(cmd, issue_flags, cqe)) + return -ENOBUFS; + return 0; } static int io_uring_cmd_timestamp(struct socket *sock, @@ -135,7 +137,8 @@ static int io_uring_cmd_timestamp(struct socket *sock, skb = skb_peek(&list); if (!skb) break; - if (!io_process_timestamp_skb(cmd, sk, skb, issue_flags)) + ret = io_process_timestamp_skb(cmd, sk, skb, issue_flags); + if (ret) break; __skb_dequeue(&list); consume_skb(skb); @@ -145,6 +148,12 @@ static int io_uring_cmd_timestamp(struct socket *sock, scoped_guard(spinlock_irqsave, &q->lock) skb_queue_splice(&list, q); } + /* + * Aux CQEs cannot overflow and the poll is edge triggered, so nothing + * re-runs the command once the CQ drains. End the multishot instead. + */ + if (ret == -ENOBUFS) + return -ENOBUFS; return -EAGAIN; } From 024e05f73a4c1263d031614bc36700ec135eecb4 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 19:04:50 +0800 Subject: [PATCH 328/535] Bluetooth: hci_conn: Lock parent access during enhanced SCO setup Bluetooth: hci_conn: Lock parent access during enhanced SCO setup hci_enhanced_setup_sync() runs on the request workqueue without the hci_dev_lock held by its caller when setup was queued. Its CVSD capability check and find_next_esco_param() dereference conn->parent while the receive workqueue can unlink and release that parent. The following interleaving can cause a use-after-free: hci_enhanced_setup_sync() hci_disconn_complete_evt() load conn->parent hci_dev_lock() hci_conn_del(ACL parent) unlink SCO child drop link's parent reference clear child->parent release ACL parent bt_link_release() kfree(parent) hci_dev_unlock() read parent->features[0][3] The reference held for the queued SCO child does not keep its ACL parent alive after unlinking. Commit 42de40abe25d ("Bluetooth: hci_conn: fix the SCO setup context lifetime") protects the child stored in the queued context, but leaves these parent accesses unprotected. With a 40 ms diagnostic delay after loading conn->parent, an instrumented kernel based on fd179f8a05be, which already contains 42de40abe25d, reported: BUG: KASAN: slab-use-after-free in hci_enhanced_setup_sync+0xda5/0xdf0 Read of size 1 at addr ffff888102204047 by task kworker/u17:0/93 Call Trace: hci_enhanced_setup_sync+0xda5/0xdf0 hci_cmd_sync_work+0x13c/0x290 process_one_work+0x6b4/0x10e0 Allocated by task 92: __hci_conn_add+0x304/0x1df0 hci_connect_acl+0x349/0x3e0 hci_connect_sco+0x3b/0x9a0 sco_sock_connect+0x475/0xca0 Freed by task 94: kfree+0x121/0x3c0 bt_link_release+0x79/0xa0 device_release+0xc8/0x240 kobject_put+0x14d/0x280 hci_conn_del+0x524/0xe30 hci_disconn_complete_evt+0x403/0x8c0 hci_event_packet+0x71b/0xb20 hci_rx_work+0x293/0x730 The accessed address is 71 bytes into the freed ACL parent, at its features[0][3] byte; the queued SCO child is a different object. The diagnostic preserves the loaded parent across the delay, matching the unmodified compiled capability check, and does not change the parent references or teardown path. Hold hci_dev_lock() across the codec switch, including every call to find_next_esco_param(), and release it on all selection errors. Keep configure_datapath_sync() outside the critical section because it waits for HCI events. Preserve parameter selection and existing return values. Fixes: e07a06b4eb41 ("Bluetooth: Convert SCO configure_datapath to hci_sync") Cc: stable@vger.kernel.org Assisted-by: GPT-6-Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_conn.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 96195d2fd10f..cf44452e0766 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -302,11 +302,13 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); + hci_dev_lock(hdev); + switch (conn->codec.id) { case BT_CODEC_MSBC: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x05; @@ -332,7 +334,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) case BT_CODEC_TRANSPARENT: if (!find_next_esco_param(conn, esco_param_msbc, ARRAY_SIZE(esco_param_msbc))) - return -EINVAL; + goto unlock; param = &esco_param_msbc[conn->attempt - 1]; cp.tx_coding_format.id = 0x03; @@ -359,11 +361,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) if (conn->parent && lmp_esco_capable(conn->parent)) { if (!find_next_esco_param(conn, esco_param_cvsd, ARRAY_SIZE(esco_param_cvsd))) - return -EINVAL; + goto unlock; param = &esco_param_cvsd[conn->attempt - 1]; } else { if (conn->attempt > ARRAY_SIZE(sco_param_cvsd)) - return -EINVAL; + goto unlock; param = &sco_param_cvsd[conn->attempt - 1]; } cp.tx_coding_format.id = 2; @@ -386,9 +388,11 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.out_transport_unit_size = 16; break; default: - return -EINVAL; + goto unlock; } + hci_dev_unlock(hdev); + cp.retrans_effort = param->retrans_effort; cp.pkt_type = __cpu_to_le16(param->pkt_type); cp.max_latency = __cpu_to_le16(param->max_latency); @@ -397,6 +401,10 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) return -EIO; return 0; + +unlock: + hci_dev_unlock(hdev); + return -EINVAL; } static bool hci_setup_sync_conn(struct hci_conn *conn, __u16 handle) From 1a572db43c057566771b485598ad2e66cb390821 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 01:29:35 +0800 Subject: [PATCH 329/535] Bluetooth: hci_core: Serialize fragmented ISO packet queueing The fragmented path in hci_queue_iso() calls __skb_queue_tail() without holding conn->data_q.lock. The socket lock serializes senders, but the transmit worker can concurrently remove packets from this queue using skb_dequeue(). After an insertion saves the old tail pointer, hci_sched_iso() can dequeue that packet and pass it to the driver. The driver can free the packet before the insertion resumes and writes to the old tail's next pointer, causing a use-after-free. Concurrent updates can also corrupt the queue. KASAN reported: BUG: KASAN: slab-use-after-free in hci_send_iso+0xc7d/0xe10 Call Trace: hci_send_iso+0xc7d/0xe10 iso_sock_sendmsg+0x710/0x900 __sys_sendto+0x34a/0x3a0 Allocated by task 86: __alloc_skb+0xdd/0x820 alloc_skb_with_frags+0x7e/0x770 sock_alloc_send_pskb+0x67a/0x820 bt_skb_sendmsg.constprop.0+0xc0/0x6c0 iso_sock_sendmsg+0x44e/0x900 Freed by task 90: kmem_cache_free+0xcb/0x3d0 vhci_read+0x33f/0x4d0 vfs_read+0x177/0xa20 Hold the queue lock across the entire fragment batch, as hci_queue_acl() does, to serialize insertion against dequeue while preserving fragment ordering. Fixes: 26afbd826ee3 ("Bluetooth: Add initial implementation of CIS connections") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index bbe391525530..5d1cdb1e7d8b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3327,6 +3327,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, skb_shinfo(skb)->frag_list = NULL; + spin_lock_bh(&queue->lock); + __skb_queue_tail(queue, skb); do { @@ -3341,6 +3343,8 @@ static void hci_queue_iso(struct hci_conn *conn, struct sk_buff_head *queue, __skb_queue_tail(queue, skb); } while (list); + + spin_unlock_bh(&queue->lock); } bt_dev_dbg(hdev, "hcon %p queued %d", conn, skb_queue_len(queue)); From b1f24c1ab52345e810c42a1a81286cb5a7c92252 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Thang Date: Sun, 27 Sep 2026 22:18:02 +0700 Subject: [PATCH 330/535] Bluetooth: hci_sync: don't drain cmd_sync backlog on unregister hci_unregister_dev() disables cmd_work and cmd_timer, then calls hci_cmd_sync_clear(), whose cancel_work_sync() waits for hci_cmd_sync_work() to return. That worker dequeues and runs every entry on cmd_sync_work_list. With cmd_work disabled nothing reaches the controller, so each queued HCI command waits the full HCI_CMD_TIMEOUT (2s). The backlog has no bound. Userspace can keep queuing MGMT commands such as MGMT_OP_GET_CLOCK_INFO against a controller that doesn't answer. Closing /dev/vhci then blocks in vhci_release() for backlog * 2s: INFO: task syz-executor:5749 blocked for more than 143 seconds. cancel_work_sync hci_cmd_sync_clear hci_unregister_dev vhci_release In a local reproduction the backlog held more than 5000 entries, which comes to hours of hang. Stop the worker from taking new entries once HCI_UNREGISTER is set, and wake any request still waiting with -ENODEV before cancelling the work. The entries left over are destroyed with -ECANCELED by the existing sweep in hci_cmd_sync_clear(). They stay on the list until the work has stopped, so hci_cmd_sync_dequeue() and friends still see them. A callback already running may still issue another command, which delays unregister by at most one timeout per remaining command, not by the whole backlog. Fixes: 008ee9eb8a11 ("Bluetooth: hci_sync: Fix not processing all entries on cmd_sync_work") Reported-by: syzbot+217e3f1283cafe80586e@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=217e3f1283cafe80586e Signed-off-by: Nguyen Ngoc Thang Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 74e2b04c84b2..e41fc1427d34 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -312,6 +312,10 @@ static void hci_cmd_sync_work(struct work_struct *work) while (1) { struct hci_cmd_sync_work_entry *entry; + /* Leave the backlog to hci_cmd_sync_clear() */ + if (hci_dev_test_flag(hdev, HCI_UNREGISTER)) + break; + mutex_lock(&hdev->cmd_sync_work_lock); entry = list_first_entry_or_null(&hdev->cmd_sync_work_list, struct hci_cmd_sync_work_entry, @@ -658,6 +662,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) { struct hci_cmd_sync_work_entry *entry, *tmp; + /* cmd_work is disabled, the pending request can only time out */ + hci_cmd_sync_cancel_sync(hdev, ENODEV); cancel_work_sync(&hdev->cmd_sync_work); cancel_work_sync(&hdev->reenable_adv_work); From 8ed67535fdff40ca652bcd4ac7da68d11d01bd34 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 01:28:31 +0800 Subject: [PATCH 331/535] Bluetooth: hci_sync: Fix inquiry cache use-after-free The sync command worker holds hdev->req_lock, but inquiry-cache updates and flushes use hdev->lock. Both hci_acl_create_conn_sync() and hci_stop_discovery_sync() look up entries and read their fields without taking hdev->lock. After either lookup returns, a concurrent HCIINQUIRY ioctl can acquire hdev->lock and flush the cache, freeing the entry. The worker then reads the freed entry while preparing a create-connection or remote-name-cancel command. The list traversal also races with cache updates and removal. KASAN reported these accesses: BUG: KASAN: slab-use-after-free in hci_acl_create_conn_sync+0x5f1/0x650 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_acl_create_conn_sync+0x5f1/0x650 hci_cmd_sync_work+0x13c/0x290 Allocated by task 90: hci_inquiry_cache_update+0x3e6/0x7d0 hci_inquiry_result_evt+0x3cb/0x560 Freed by task 93: hci_inquiry_cache_flush+0x111/0x2b0 hci_inquiry+0x2f2/0x780 hci_sock_ioctl+0x269/0x5f0 BUG: KASAN: slab-use-after-free in hci_stop_discovery_sync+0x3b1/0x3c0 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_stop_discovery_sync+0x3b1/0x3c0 hci_cmd_sync_work+0x173/0x300 Allocated by task 86: hci_inquiry_cache_update+0x483/0x940 hci_inquiry_result_evt+0x3cb/0x560 Freed by task 91: hci_inquiry_cache_flush+0x13e/0x2f0 hci_inquiry+0x2f2/0x780 hci_sock_ioctl+0x269/0x5f0 Hold hdev->lock across each lookup and all reads from its result. Copy the remote address before unlocking so discovery cancellation does not retain a cache entry pointer. Release the lock before sending synchronous HCI commands, since their completion handlers may need the same lock. Fixes: cf75ad8b41d2 ("Bluetooth: hci_sync: Convert MGMT_SET_POWERED") Fixes: 45340097ce6e ("Bluetooth: hci_conn: Only do ACL connections sequentially") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index e41fc1427d34..c01c8b58d9e8 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5770,6 +5770,7 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) { struct discovery_state *d = &hdev->discovery; struct inquiry_entry *e; + bdaddr_t addr; int err; bt_dev_dbg(hdev, "state %u", hdev->discovery.state); @@ -5805,15 +5806,21 @@ int hci_stop_discovery_sync(struct hci_dev *hdev) return 0; if (d->state == DISCOVERY_RESOLVING || d->state == DISCOVERY_STOPPING) { + hci_dev_lock(hdev); e = hci_inquiry_cache_lookup_resolve(hdev, BDADDR_ANY, NAME_PENDING); - if (!e) + if (!e) { + hci_dev_unlock(hdev); return 0; + } + + bacpy(&addr, &e->data.bdaddr); + hci_dev_unlock(hdev); /* Ignore cancel errors since it should interfere with stopping * of the discovery. */ - hci_remote_name_cancel_sync(hdev, &e->data.bdaddr); + hci_remote_name_cancel_sync(hdev, &addr); } return 0; @@ -7242,6 +7249,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) bacpy(&cp.bdaddr, &conn->dst); cp.pscan_rep_mode = 0x02; + hci_dev_lock(hdev); ie = hci_inquiry_cache_lookup(hdev, &conn->dst); if (ie) { if (inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { @@ -7253,6 +7261,7 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data) memcpy(conn->dev_class, ie->data.dev_class, 3); } + hci_dev_unlock(hdev); cp.pkt_type = cpu_to_le16(conn->pkt_type); if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) From 2d696c1ed3468dcb62578a3911e687110d463520 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 01:27:23 +0800 Subject: [PATCH 332/535] Bluetooth: RFCOMM: Fix initial port reference race For RFCOMM_RELEASE_ONHUP devices, rfcomm_tty_install() and __rfcomm_release_dev() both use RFCOMM_TTY_OWNED to decide who drops the initial tty_port reference. The release path tests the bit separately from the install path setting it, so both can drop that reference. The synchronous hangup does not prevent this race: port->tty is not assigned until tty_port_open(), after installation. The following interleaving is possible: 1. Install and release each obtain a reference with rfcomm_dev_get(). 2. Release observes RFCOMM_TTY_OWNED clear. 3. Install sets RFCOMM_TTY_OWNED and drops the initial reference. 4. Release drops the same initial reference again. 5. TTY cleanup drops its reference and frees the device. 6. Release performs its final tty_port_put() on the freed port. KASAN reported: BUG: KASAN: slab-use-after-free in tty_port_put+0x22/0x190 Write of size 4 at addr ffff8881001d3d5c by task poc/92 Call Trace: tty_port_put+0x22/0x190 rfcomm_dev_ioctl+0x1d4/0x1930 sock_do_ioctl+0x110/0x260 sock_ioctl+0x380/0x590 __x64_sys_ioctl+0x134/0x1c0 Allocated by task 88: rfcomm_dev_ioctl+0x8f6/0x1930 sock_do_ioctl+0x110/0x260 sock_ioctl+0x380/0x590 __x64_sys_ioctl+0x134/0x1c0 Freed by task 65: kfree+0x131/0x3c0 rfcomm_dev_destruct+0x23b/0x2f0 release_one_tty+0xc1/0x370 process_one_work+0x661/0x1090 Use test_and_set_bit() in both paths so that only the caller that changes RFCOMM_TTY_OWNED from clear to set drops the initial reference. Each path keeps its own lookup reference until release or TTY cleanup, preserving the existing callback ordering and error handling. Fixes: 80ea73378af4 ("Bluetooth: Fix unreleased rfcomm_dev reference") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/tty.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index b2c1060394e6..dc3cdf614def 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -462,7 +462,7 @@ static int __rfcomm_release_dev(void __user *arg) /* Shut down TTY synchronously before freeing rfcomm_dev */ tty_port_tty_vhangup(&dev->port); - if (!test_bit(RFCOMM_TTY_OWNED, &dev->status)) + if (!test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) tty_port_put(&dev->port); tty_port_put(&dev->port); @@ -724,10 +724,9 @@ static int rfcomm_tty_install(struct tty_driver *driver, struct tty_struct *tty) * when the last process closes the tty. The behaviour is expected by * userspace. */ - if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { - set_bit(RFCOMM_TTY_OWNED, &dev->status); + if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags) && + !test_and_set_bit(RFCOMM_TTY_OWNED, &dev->status)) tty_port_put(&dev->port); - } return 0; } From 81a2345f1984f0b36d3226571bc196316a01b648 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 14:42:50 +0800 Subject: [PATCH 333/535] Bluetooth: Serialize SMP remote OOB data access build_pairing_cmd() looks up remote OOB data and copies its contents without holding hdev->lock, which serializes the list's writers. After SMP finds an entry, a concurrent management Remove Remote OOB Data command can unlink and free it before SMP reads its present flag or copies its random and confirmation values. Removal can also invalidate an entry while the lookup is still traversing the list. KASAN reported: BUG: KASAN: slab-use-after-free in build_pairing_cmd+0x948/0x9b0 Call Trace: build_pairing_cmd+0x948/0x9b0 smp_recv_cb+0x459f/0x8110 l2cap_recv_frame+0xf14/0x9190 l2cap_recv_acldata+0xa64/0xd40 hci_rx_work+0x4ca/0x730 Allocated by task 87: hci_add_remote_oob_data+0x11d/0x530 add_remote_oob_data+0x282/0x400 hci_sock_sendmsg+0x1033/0x1ea0 Freed by task 93: hci_remote_oob_data_clear+0x108/0x1c0 remove_remote_oob_data+0x198/0x220 hci_sock_sendmsg+0x1033/0x1ea0 Taking hdev->lock in build_pairing_cmd() would recurse for callers that already hold it and invert the device-to-L2CAP lock order on the receive path. Add a per-device remote_oob_lock instead, held across the SMP lookup and copies and by the add, remove and clear helpers. Cover initialization and in-place updates as well, so SMP cannot read partially initialized or updated OOB values. Release the mutex on allocation failure, preserving the existing error return. The new critical sections acquire no device, connection or channel locks. Writers retain their existing hdev->lock protection, which continues to serialize the other readers without changing their locking or behavior. Link: https://lore.kernel.org/r/00660cd3-7d71-13a4-f617-229e6defb701@gmail.com Fixes: 02b05bd8b0a6 ("Bluetooth: Set SMP OOB flag if OOB data is available") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci_core.h | 1 + net/bluetooth/hci_core.c | 12 +++++++++++- net/bluetooth/smp.c | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index ca77ad0d9393..fa2a367731b5 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -562,6 +562,7 @@ struct hci_dev { struct list_head link_keys; struct list_head long_term_keys; struct list_head identity_resolving_keys; + struct mutex remote_oob_lock; struct list_head remote_oob_data; struct list_head le_accept_list; struct list_head le_resolv_list; diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5d1cdb1e7d8b..bc662e24a8e4 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1489,8 +1489,10 @@ int hci_remove_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, BT_DBG("%s removing %pMR (%u)", hdev->name, bdaddr, bdaddr_type); + mutex_lock(&hdev->remote_oob_lock); list_del(&data->list); kfree(data); + mutex_unlock(&hdev->remote_oob_lock); return 0; } @@ -1499,10 +1501,12 @@ void hci_remote_oob_data_clear(struct hci_dev *hdev) { struct oob_data *data, *n; + mutex_lock(&hdev->remote_oob_lock); list_for_each_entry_safe(data, n, &hdev->remote_oob_data, list) { list_del(&data->list); kfree(data); } + mutex_unlock(&hdev->remote_oob_lock); } int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, @@ -1511,11 +1515,14 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, { struct oob_data *data; + mutex_lock(&hdev->remote_oob_lock); data = hci_find_remote_oob_data(hdev, bdaddr, bdaddr_type); if (!data) { data = kmalloc_obj(*data); - if (!data) + if (!data) { + mutex_unlock(&hdev->remote_oob_lock); return -ENOMEM; + } bacpy(&data->bdaddr, bdaddr); data->bdaddr_type = bdaddr_type; @@ -1548,6 +1555,8 @@ int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, BT_DBG("%s for %pMR", hdev->name, bdaddr); + mutex_unlock(&hdev->remote_oob_lock); + return 0; } @@ -2485,6 +2494,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) mutex_init(&hdev->lock); mutex_init(&hdev->req_lock); mutex_init(&hdev->mgmt_pending_lock); + mutex_init(&hdev->remote_oob_lock); ida_init(&hdev->unset_handle_ida); diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index d23f9d0729c4..2df303f6a38f 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -662,6 +662,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, else bdaddr_type = BDADDR_LE_RANDOM; + mutex_lock(&hdev->remote_oob_lock); oob_data = hci_find_remote_oob_data(hdev, &hcon->dst, bdaddr_type); if (oob_data && oob_data->present) { @@ -672,6 +673,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, SMP_DBG("OOB Remote Confirmation: %16phN", smp->pcnf); SMP_DBG("OOB Remote Random: %16phN", smp->rr); } + mutex_unlock(&hdev->remote_oob_lock); } else { authreq &= ~SMP_AUTH_SC; From f4845ab191a4f686a5da481fbba90ed73bb693b0 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 01:04:02 +0800 Subject: [PATCH 334/535] Bluetooth: hci_core: Serialize ACL scheduling with channel deletion hci_chan_sent() selects a channel under RCU but drops the read lock before accessing chan->conn and returning the channel. The ACL and LE schedulers then use its packet queue and update its transmit counters without any protection against channel deletion. After TX selects a channel and releases RCU, a disconnect command timeout on the separate request workqueue can run hci_conn_failed() and l2cap_conn_del(). hci_chan_del() can then unlink the channel, complete synchronize_rcu(), purge its queue and free it before TX resumes. This causes use-after-free both in hci_chan_sent() and in its callers. KASAN reported: BUG: KASAN: slab-use-after-free in hci_chan_sent+0x892/0x9b0 Workqueue: hci0 hci_tx_work Call Trace: hci_chan_sent+0x892/0x9b0 hci_tx_work+0x5e6/0xb70 Allocated by task 91: hci_chan_create+0xe3/0x350 l2cap_conn_add.part.0+0x12/0xa30 l2cap_chan_connect+0x110d/0x1b60 l2cap_sock_connect+0x310/0x530 Freed by task 99: hci_chan_del+0x11f/0x170 l2cap_conn_del+0x4f1/0x800 l2cap_connect_cfm+0x88c/0xd30 hci_conn_failed+0x150/0x250 hci_abort_conn_sync+0x3e3/0x800 hci_cmd_sync_run+0x7e/0xc0 hci_abort_conn+0x105/0x1f0 disconnect_sync+0x157/0x290 hci_cmd_sync_work+0x13c/0x290 Hold the existing device mutex across channel selection and transmission in both schedulers to serialize them with channel teardown. Keep timeout handling outside the critical sections because hci_link_tx_to() acquires the same mutex. This also permits the transmit path to sleep, unlike extending the RCU read-side critical section across packet submission. Fixes: 3eff45eaf817 ("Bluetooth: convert tx_task to workqueue") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index bc662e24a8e4..a92ab4db4676 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3675,6 +3675,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) __check_timeout(hdev, cnt, ACL_LINK); + hci_dev_lock(hdev); + while (hdev->acl_cnt && (chan = hci_chan_sent(hdev, ACL_LINK, "e))) { u32 priority = (skb_peek(&chan->data_q))->priority; @@ -3706,6 +3708,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) if (cnt != hdev->acl_cnt) hci_prio_recalculate(hdev, ACL_LINK); + + hci_dev_unlock(hdev); } static void hci_sched_acl(struct hci_dev *hdev) @@ -3734,6 +3738,8 @@ static void hci_sched_le(struct hci_dev *hdev) __check_timeout(hdev, *cnt, LE_LINK); + hci_dev_lock(hdev); + tmp = *cnt; while (*cnt && (chan = hci_chan_sent(hdev, LE_LINK, "e))) { u32 priority = (skb_peek(&chan->data_q))->priority; @@ -3762,6 +3768,8 @@ static void hci_sched_le(struct hci_dev *hdev) if (*cnt != tmp) hci_prio_recalculate(hdev, LE_LINK); + + hci_dev_unlock(hdev); } /* Schedule iso */ From 848a7a91c7f03675d3bd8db6f7721cbf9cb50e21 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sun, 27 Sep 2026 01:04:03 +0800 Subject: [PATCH 335/535] Bluetooth: hci_core: Serialize SCO and ISO scheduling with teardown hci_low_sent() selects a connection under RCU but drops the read lock before calculating its quota and returning it to the scheduler. The connection is then used without lifetime protection by hci_sched_iso() and hci_sched_sco(). After the TX worker drops the RCU read lock, hci_abort_conn_sync() on hdev->req_workqueue can remove the connection from the hash, complete synchronize_rcu(), purge its queues and release it. The TX worker on hdev->workqueue can then access the freed connection in hci_quote_sent() or while dequeuing packets and updating conn->sent. KASAN reported: BUG: KASAN: slab-use-after-free in hci_low_sent+0x730/0x840 Workqueue: hci0 hci_tx_work Call Trace: hci_low_sent+0x730/0x840 hci_sched_iso+0x25e/0x4d0 hci_tx_work+0x239/0xcb0 Allocated by task 93: __hci_conn_add+0x16f/0x1b40 hci_bind_bis+0x782/0x17b0 hci_connect_bis+0xa0/0x510 iso_sock_connect+0x589/0x1050 Freed by task 88: kfree+0x131/0x3c0 device_release+0xc8/0x240 kobject_put+0x14d/0x280 hci_conn_del+0x55a/0xe80 hci_disconnect_sync+0x156/0x180 hci_abort_conn_sync+0x3e7/0x940 hci_cmd_sync_work+0x13c/0x290 Hold hci_dev_lock() across connection selection and transmission in both SCO and ISO scheduling, serializing them with connection teardown. This also prevents queuing completion timestamps after the connection queues have been purged. Extending RCU across transmission would be unsafe because the transmit path can sleep. Keep the ISO timeout check outside the mutex since hci_link_tx_to() takes it itself. The preceding channel fix already holds this mutex in the ACL and LE schedulers, which call the SCO scheduler between packets. Move the SCO body to __hci_sched_sco(), assert that its caller holds the mutex, and use it directly from these locked paths. Keep a locking hci_sched_sco() wrapper for the direct calls from hci_tx_work(). This avoids recursively acquiring the device mutex while preserving the scheduling order. Remove the obsolete claim that connection removal disables TX. Fixes: bf4c63252490 ("Bluetooth: convert conn hash to RCU") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index a92ab4db4676..2076689eb302 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3418,9 +3418,6 @@ static struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, struct hci_conn *conn = NULL, *c; unsigned int num = 0, min = ~0; - /* We don't have to lock device here. Connections are always - * added and removed with TX task disabled. */ - rcu_read_lock(); list_for_each_entry_rcu(c, &h->list, list) { @@ -3625,13 +3622,15 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt, u8 type) } /* Schedule SCO */ -static void hci_sched_sco(struct hci_dev *hdev, __u8 type) +static void __hci_sched_sco(struct hci_dev *hdev, __u8 type) { struct hci_conn *conn; struct sk_buff *skb; int quote, *cnt; unsigned int pkts = hdev->sco_pkts; + lockdep_assert_held(&hdev->lock); + bt_dev_dbg(hdev, "type %u", type); if (!hci_conn_num(hdev, type) || !pkts) @@ -3666,6 +3665,13 @@ static void hci_sched_sco(struct hci_dev *hdev, __u8 type) queue_work(hdev->workqueue, &hdev->tx_work); } +static void hci_sched_sco(struct hci_dev *hdev, __u8 type) +{ + hci_dev_lock(hdev); + __hci_sched_sco(hdev, type); + hci_dev_unlock(hdev); +} + static void hci_sched_acl_pkt(struct hci_dev *hdev) { unsigned int cnt = hdev->acl_cnt; @@ -3701,8 +3707,8 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev) chan->conn->sent++; /* Send pending SCO packets right away */ - hci_sched_sco(hdev, SCO_LINK); - hci_sched_sco(hdev, ESCO_LINK); + __hci_sched_sco(hdev, SCO_LINK); + __hci_sched_sco(hdev, ESCO_LINK); } } @@ -3761,8 +3767,8 @@ static void hci_sched_le(struct hci_dev *hdev) chan->conn->sent++; /* Send pending SCO packets right away */ - hci_sched_sco(hdev, SCO_LINK); - hci_sched_sco(hdev, ESCO_LINK); + __hci_sched_sco(hdev, SCO_LINK); + __hci_sched_sco(hdev, ESCO_LINK); } } @@ -3788,6 +3794,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) __check_timeout(hdev, *cnt, type); + hci_dev_lock(hdev); + while (*cnt && (conn = hci_low_sent(hdev, type, "e))) { while (quote-- && (skb = skb_dequeue(&conn->data_q))) { BT_DBG("skb %p len %d", skb, skb->len); @@ -3801,6 +3809,8 @@ static void hci_sched_iso(struct hci_dev *hdev, __u8 type) (*cnt)--; } } + + hci_dev_unlock(hdev); } static void hci_tx_work(struct work_struct *work) From de0be324fcb97a49caad131c23ad33265dd4b0b8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 28 Sep 2026 16:39:25 +0800 Subject: [PATCH 336/535] ASoC: amd: acp-config: Add ACP70 DMI quirk for ASUS UM5406GA Add ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) to acp70_acpi_flag_override_table so that the broken BIOS acp-audio-config-flag is ignored and the default SoundWire enumeration path is used. Without this, no ASoC machine driver matches and internal speakers/mic fail to probe. Reported-by: Jannick Tobler Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222009 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260928083925.266471-1-zhangheng@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/acp-config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 4d8f86aa3d16..b3b73096ac74 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -70,6 +70,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Vivobook 18 M1807GA"), }, }, + { + /* ASUS Zenbook S14 UM5406GA (Strix Point, ACP 7.0) */ + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "Zenbook S14 UM5406GA"), + }, + }, { /* HP OmniBook X Flip 14-kc0xxx (Strix Point, ACP 7.2) */ .matches = { From e3ee38c1bc0b11a8d3e63dce7050759b61864286 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Thu, 24 Sep 2026 03:31:16 +0500 Subject: [PATCH 337/535] x86/mm: Drop unnecessary PMD page copy when freeing On a box with a discrete GPU, lockdep reports a possible deadlock as soon as kswapd shrinks the TTM page pool. The immediate cause is an x86 commit that added an mmap_read_lock() to kernel page protection munging code. The huge vmap code holds the same lock over a GFP_KERNEL allocation, which is a no-no now that reclaim can take it. That allocation is in a page table *free* path and ends up being for dubious purposes[1]. Basically, it tries to avoid hardware setting Accessed=1 in page table entries that are unreachable by the hardware, a non-issue. Remove the PMD copy. Detach the original PMD page at the PUD, flush the mid-level caches, and free the PTE tables straight from the detached PMD page. With no allocation left, the locking issue is gone. Lockdep splat/analysis: WARNING: possible circular locking dependency detected 7.3.0-rc3-f6e7b42bf05b+ #183 Tainted: G U ------------------------------------------------------ kswapd0/269 is trying to acquire lock: ((init_mm).mmap_lock){++++}-{4:4}, at: change_page_attr_set_clr+0x29a/0x4a0 but task is already holding lock: (pool_shrink_rwsem){.+.+}-{4:4}, at: ttm_pool_shrink+0xb2/0x330 [ttm] Chain exists of: (init_mm).mmap_lock --> fs_reclaim --> pool_shrink_rwsem The cycle is built from three edges: 1) pool_shrink_rwsem -> (init_mm).mmap_lock The TTM shrinker restores the caching attribute of every page it frees, while holding pool_shrink_rwsem: ttm_pool_shrink() -> ttm_pool_dispose_list() -> ttm_pool_free_page() -> set_pages_wb() -> change_page_attr_set_clr() [ init_mm mmap read lock ] 2) fs_reclaim -> pool_shrink_rwsem The same shrinker, called from reclaim. 3) (init_mm).mmap_lock -> fs_reclaim ioremap() installing a huge PUD mapping over an existing PMD table: ioremap_page_range() -> vmap_range_noflush() -> vmap_try_huge_pud() [ init_mm mmap read lock ] -> pud_free_pmd_page() -> __get_free_page(GFP_KERNEL) [ enters reclaim ] [ dhansen: Lots of changelog munging/trimming and merged comments from my version of the fix. ] Fixes: d5d8b8662e6e ("x86/mm/pat: Acquire init_mm read lock on attribute changes to avoid UAF") Suggested-by: Pedro Falcato Signed-off-by: Mikhail Gavrilov Signed-off-by: Dave Hansen Reviewed-by: Pedro Falcato Link: https://lore.kernel.org/20260916062222.27347-1-mikhail.v.gavrilov@gmail.com Link: https://lore.kernel.org/all/e11449f0-d9ad-4d1b-ab21-2be7d71fe335@intel.com/ [1] Link: https://patch.msgid.link/20260923223116.20090-1-mikhail.v.gavrilov@gmail.com Cc: stable@vger.kernel.org --- arch/x86/mm/pgtable.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index cb03f5a2b243..4a105f283cfb 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -705,47 +705,37 @@ int pmd_clear_huge(pmd_t *pmd) } #ifdef CONFIG_X86_64 -/** - * pud_free_pmd_page - Clear PUD entry and free PMD page - * @pud: Pointer to a PUD - * @addr: Virtual address associated with PUD - * - * Context: The PUD range has been unmapped and TLB purged. - * Return: 1 if clearing the entry succeeded. 0 otherwise. - * - * NOTE: Callers must allow a single page allocation. +/* + * Given a PUD poitner, detach and free the pointed-to + * PMD page and any PTE page children. The entire range + * under the PUD must not have any valid translations + * and the TLB must have already been flushed. */ int pud_free_pmd_page(pud_t *pud, unsigned long addr) { - pmd_t *pmd, *pmd_sv; struct ptdesc *pt; + pmd_t *pmd; int i; pmd = pud_pgtable(*pud); - pmd_sv = (pmd_t *)__get_free_page(GFP_KERNEL); - if (!pmd_sv) - return 0; - - for (i = 0; i < PTRS_PER_PMD; i++) { - pmd_sv[i] = pmd[i]; - if (!pmd_none(pmd[i])) - pmd_clear(&pmd[i]); - } + /* Detach the PMD page: */ pud_clear(pud); - /* INVLPG to clear all paging-structure caches */ + /* + * PMD and all its descendents are unreachable + * via normal page walks. Make them unreachable + * in cached mid-level walks too: + */ flush_tlb_kernel_range(addr, addr + PAGE_SIZE-1); for (i = 0; i < PTRS_PER_PMD; i++) { - if (!pmd_none(pmd_sv[i])) { - pt = page_ptdesc(pmd_page(pmd_sv[i])); + if (!pmd_none(pmd[i])) { + pt = page_ptdesc(pmd_page(pmd[i])); pagetable_dtor_free(pt); } } - free_page((unsigned long)pmd_sv); - pmd_free(&init_mm, pmd); return 1; From a661c34fe693c8f9ef29b45ee71cfa1fe593502b Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Fri, 21 Aug 2026 15:45:42 -0700 Subject: [PATCH 338/535] {x86,um}/uapi/ptrace: Guard register offset macros with __ASSEMBLER__ or __FRAME_OFFSETS The register offset macros in are guarded by `defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS)` for 64-bit, but were left unguarded for 32-bit. This causes havoc for userspace that happens to use identifiers colliding with these short macro names (e.g., EBX, ECX, EAX, DS, ES, FS, GS, CS, SS). Without this guard, userspace is forced to be super extra careful with include ordering to minimize the chance of collision. Wrap both the 32-bit and 64-bit register definitions under `#if defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS)`, and ensure User-Mode Linux (UML) defines `__FRAME_OFFSETS` for 32-bit as well. Closes: https://github.com/llvm/llvm-project/issues/217413 Assisted-by: LLM Signed-off-by: Nick Desaulniers Signed-off-by: Borislav Petkov (AMD) Acked-by: Oleg Nesterov Acked-by: Johannes Berg Tested-by: Elliott Hughes Link: https://patch.msgid.link/20260821-ptrace_uapi-v1-1-3de8638a29f2@google.com --- arch/x86/include/uapi/asm/ptrace-abi.h | 4 ++-- arch/x86/um/asm/ptrace.h | 4 +--- arch/x86/um/ptrace_32.c | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/arch/x86/include/uapi/asm/ptrace-abi.h b/arch/x86/include/uapi/asm/ptrace-abi.h index 5823584dea13..3656955c6faa 100644 --- a/arch/x86/include/uapi/asm/ptrace-abi.h +++ b/arch/x86/include/uapi/asm/ptrace-abi.h @@ -2,6 +2,7 @@ #ifndef _ASM_X86_PTRACE_ABI_H #define _ASM_X86_PTRACE_ABI_H +#if defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) #ifdef __i386__ #define EBX 0 @@ -25,7 +26,6 @@ #else /* __i386__ */ -#if defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) /* * C ABI says these regs are callee-preserved. They aren't saved on kernel entry * unless syscall needs a complete, fully filled "struct pt_regs". @@ -57,12 +57,12 @@ #define EFLAGS 144 #define RSP 152 #define SS 160 -#endif /* __ASSEMBLER__ */ /* top of stack page */ #define FRAME_SIZE 168 #endif /* !__i386__ */ +#endif /* defined(__ASSEMBLER__) || defined(__FRAME_OFFSETS) */ /* Arbitrarily choose the same ptrace numbers as used by the Sparc code. */ #define PTRACE_GETREGS 12 diff --git a/arch/x86/um/asm/ptrace.h b/arch/x86/um/asm/ptrace.h index 2641d28d115c..439c4151f6b7 100644 --- a/arch/x86/um/asm/ptrace.h +++ b/arch/x86/um/asm/ptrace.h @@ -13,9 +13,7 @@ enum { }; #include -#ifndef CONFIG_X86_32 -#define __FRAME_OFFSETS /* Needed to get the R* macros */ -#endif +#define __FRAME_OFFSETS /* Needed to get the register macros */ #include #define user_mode(r) UPT_IS_USER(&(r)->regs) diff --git a/arch/x86/um/ptrace_32.c b/arch/x86/um/ptrace_32.c index 3af3cb821524..9e9155b0e918 100644 --- a/arch/x86/um/ptrace_32.c +++ b/arch/x86/um/ptrace_32.c @@ -7,6 +7,7 @@ #include #include #include +#define __FRAME_OFFSETS #include #include #include From b630929bcc4c867c9abc8cd5a0ebee3bb5ce5dcd Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Sat, 26 Sep 2026 02:04:03 -0400 Subject: [PATCH 339/535] KVM: selftests: Extend nested x2APIC test to validate disabling x2APIC virt Signed-off-by: Sean Christopherson Message-ID: <20260813223610.2043560-6-seanjc@google.com> Signed-off-by: Paolo Bonzini --- .../selftests/kvm/x86/nested_x2apic_test.c | 88 ++++++++++++++----- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c index 3b59ba3e3342..964937aefe39 100644 --- a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -29,10 +29,12 @@ static void l2_guest_code(void) if (inhibit_apicv) wrmsr(MSR_IA32_APICBASE, rdmsr(MSR_IA32_APICBASE) & GENMASK_ULL(11, 0)); - x2apic_write_reg(APIC_TASKPRI, 0xf0); - GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); + for (;;) { + x2apic_write_reg(APIC_TASKPRI, 0xf0); + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0xf0); - asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); + asm volatile("cpuid" ::: "eax", "ebx", "ecx", "edx"); + } } static void l1_svm_code(struct svm_test_data *svm) @@ -46,6 +48,7 @@ static void l1_svm_code(struct svm_test_data *svm) GUEST_ASSERT_EQ(ctrl->exit_code, SVM_EXIT_CPUID); stgi(); + x2apic_write_reg(APIC_TASKPRI, 0); } static void l1_vmx_code(struct vmx_pages *vmx) @@ -58,22 +61,50 @@ static void l1_vmx_code(struct vmx_pages *vmx) prepare_vmcs(vmx, NULL); GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, (unsigned long)l2_guest_code), 0); + control = vmreadz(PIN_BASED_VM_EXEC_CONTROL); + control |= PIN_BASED_EXT_INTR_MASK; + vmwrite(PIN_BASED_VM_EXEC_CONTROL, control); + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); - control |= CPU_BASED_USE_MSR_BITMAPS; + control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_TPR_SHADOW; GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); + control = vmreadz(SECONDARY_VM_EXEC_CONTROL); + control |= SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + SECONDARY_EXEC_APIC_REGISTER_VIRT | + SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY; + control &= (rdmsr(MSR_IA32_VMX_PROCBASED_CTLS2) >> 32); + GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); + GUEST_ASSERT(!vmlaunch()); GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, + vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); } -static void l1_guest_code(void *test_data) +static void l1_vmx_code_part2(void) { - x2apic_enable(); + u64 control; - if (this_cpu_has(X86_FEATURE_SVM)) - l1_svm_code(test_data); - else - l1_vmx_code(test_data); + control = vmreadz(CPU_BASED_VM_EXEC_CONTROL); + control &= ~CPU_BASED_TPR_SHADOW; + GUEST_ASSERT_EQ(vmwrite(CPU_BASED_VM_EXEC_CONTROL, control), 0); + + control = vmreadz(SECONDARY_VM_EXEC_CONTROL); + control &= ~(SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + SECONDARY_EXEC_APIC_REGISTER_VIRT | + SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY); + GUEST_ASSERT_EQ(vmwrite(SECONDARY_VM_EXEC_CONTROL, control), 0); + + GUEST_ASSERT(!vmresume()); + GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_CPUID); + GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, + vmreadz(GUEST_RIP) + vmreadz(VM_EXIT_INSTRUCTION_LEN)), 0); +} + +static void l1_test_x2apic_intercepts(void) +{ + GUEST_ASSERT_EQ(nr_irqs, 0); sti_nop(); @@ -93,16 +124,40 @@ static void l1_guest_code(void *test_data) x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | POSTED_INTR_NESTED_VECTOR); GUEST_ASSERT_EQ(nr_irqs, 3); + nr_irqs = 0; +} + +static void l1_guest_code(void *test_data) +{ + x2apic_enable(); + + if (this_cpu_has(X86_FEATURE_SVM)) + l1_svm_code(test_data); + else + l1_vmx_code(test_data); + + GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); + x2apic_write_reg(APIC_TASKPRI, 0xf0); + + l1_test_x2apic_intercepts(); + + if (this_cpu_has(X86_FEATURE_VMX)) { + l1_vmx_code_part2(); + l1_test_x2apic_intercepts(); + } + GUEST_DONE(); } -static void __test_x2apic_intercepts(void) +static void __test_x2apic_intercepts(bool with_inhibit_apicv) { gva_t nested_test_data_gva; struct kvm_vcpu *vcpu; struct kvm_vm *vm; struct ucall uc; + inhibit_apicv = with_inhibit_apicv; + vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code); vm_install_exception_handler(vm, POSTED_INTR_VECTOR, guest_irq_handler); vm_install_exception_handler(vm, POSTED_INTR_WAKEUP_VECTOR, guest_irq_handler); @@ -134,17 +189,10 @@ static void __test_x2apic_intercepts(void) kvm_vm_free(vm); } -#define test_x2apic_intercepts(inhibit_apic_setting) \ -do { \ - inhibit_apic_setting; \ - \ - __test_x2apic_intercepts(); \ -} while (0) - int main(int argc, char *argv[]) { TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); - test_x2apic_intercepts(inhibit_apicv = true); - test_x2apic_intercepts(inhibit_apicv = false); + test_x2apic_intercepts(true); + test_x2apic_intercepts(false); } From b0bc51910f0391fea09c0a1d32352fec2acb2686 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 13 Aug 2026 15:36:10 -0700 Subject: [PATCH 340/535] KVM: selftests: Extend nested x2APIC test to validate using eVMCS for vmcs12 Signed-off-by: Sean Christopherson Message-ID: <20260813223610.2043560-7-seanjc@google.com> Signed-off-by: Paolo Bonzini --- .../selftests/kvm/x86/nested_x2apic_test.c | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c index 964937aefe39..ce204ce29a9c 100644 --- a/tools/testing/selftests/kvm/x86/nested_x2apic_test.c +++ b/tools/testing/selftests/kvm/x86/nested_x2apic_test.c @@ -51,12 +51,24 @@ static void l1_svm_code(struct svm_test_data *svm) x2apic_write_reg(APIC_TASKPRI, 0); } -static void l1_vmx_code(struct vmx_pages *vmx) +static void l1_vmx_code(struct vmx_pages *vmx, struct hyperv_test_pages *hv_pages) { u64 control; + if (hv_pages) { + wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); + enable_vp_assist(hv_pages->vp_assist_gpa, hv_pages->vp_assist); + evmcs_enable(); + } + GUEST_ASSERT_EQ(prepare_for_vmx_operation(vmx), true); - GUEST_ASSERT_EQ(load_vmcs(vmx), true); + + if (hv_pages) { + GUEST_ASSERT(load_evmcs(hv_pages)); + current_evmcs->hv_enlightenments_control.msr_bitmap = 1; + } else { + GUEST_ASSERT(load_vmcs(vmx)); + } prepare_vmcs(vmx, NULL); GUEST_ASSERT_EQ(vmwrite(GUEST_RIP, (unsigned long)l2_guest_code), 0); @@ -127,14 +139,14 @@ static void l1_test_x2apic_intercepts(void) nr_irqs = 0; } -static void l1_guest_code(void *test_data) +static void l1_guest_code(void *test_data, void *hv_pages) { x2apic_enable(); if (this_cpu_has(X86_FEATURE_SVM)) l1_svm_code(test_data); else - l1_vmx_code(test_data); + l1_vmx_code(test_data, hv_pages); GUEST_ASSERT_EQ(x2apic_read_reg(APIC_TASKPRI), 0); x2apic_write_reg(APIC_TASKPRI, 0xf0); @@ -149,9 +161,9 @@ static void l1_guest_code(void *test_data) GUEST_DONE(); } -static void __test_x2apic_intercepts(bool with_inhibit_apicv) +static void test_x2apic_intercepts(bool with_inhibit_apicv, bool use_evmcs) { - gva_t nested_test_data_gva; + gva_t nested_test_data_gva, hv_pages_gva = 0; struct kvm_vcpu *vcpu; struct kvm_vm *vm; struct ucall uc; @@ -170,7 +182,14 @@ static void __test_x2apic_intercepts(bool with_inhibit_apicv) else vcpu_alloc_vmx(vm, &nested_test_data_gva); - vcpu_args_set(vcpu, 1, nested_test_data_gva); + if (use_evmcs) { + vcpu_set_hv_cpuid(vcpu); + vcpu_enable_evmcs(vcpu); + + vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva); + } + + vcpu_args_set(vcpu, 2, nested_test_data_gva, hv_pages_gva); vcpu_run(vcpu); @@ -193,6 +212,11 @@ int main(int argc, char *argv[]) { TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) || kvm_cpu_has(X86_FEATURE_VMX)); - test_x2apic_intercepts(true); - test_x2apic_intercepts(false); + test_x2apic_intercepts(true, false); + test_x2apic_intercepts(false, false); + + if (kvm_has_cap(KVM_CAP_HYPERV_ENLIGHTENED_VMCS)) { + test_x2apic_intercepts(true, true); + test_x2apic_intercepts(false, true); + } } From 8abbc76120a74bfba1851bd97528099d715e45c6 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 28 Sep 2026 08:46:43 -0700 Subject: [PATCH 341/535] KVM: SEV: Nullify "have run CPUs" mask pointer when freeing it Nullify have_run_cpus when freeing the mask, particularly in the error path of __sev_guest_init(), so that KVM doesn't have to subtly use sev->active to track whether or not the mask has been freed. As pointed out by Sashiko, blindly freeing the mask in sev_vm_destroy() results in a double-free if the mask is freed if __sev_guest_init() fails. Throw the logic in a helper as nullifying the pointer is frustratingly difficult and weird due to have_run_cpus being a single-entry array when CPUMASK_OFFSTACK=n. Deliberately don't use CPUMASK_VAR_NULL, as it's not directly assignable when the cpumask is on-stack, e.g. requires using a local variable and a memcpy(), which is beyond ridiculous. Furthermore, while clearing the on-stack bitmask is an unnecessary and arguably unwanted side effect, KVM absolutely relies on '0' being the "null" value given that the struct is zero-allocated. Opportunistically add an alloc() helper to pair with free(); there are just enough call sites to make doing so worthwhile. Fixes: 12c1f6e03f94 ("KVM: SEV: Free have_run_cpus during VM destruction even if VM is no longer SEV") Cc: stable@vger.kernel.org Reported-by: Sashiko Bot Closes: https://lore.kernel.org/all/20260923165349.CAAF01F000FF@smtp.kernel.org Signed-off-by: Sean Christopherson Message-ID: <20260928154644.2559454-2-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 3448d56520c6..d3a2e6a51efc 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -488,6 +488,20 @@ static void snp_guest_req_cleanup(struct kvm *kvm) sev->guest_resp_buf = NULL; } +static int sev_alloc_have_run_cpus(struct kvm_sev_info *sev) +{ + if (!zalloc_cpumask_var(&sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) + return -ENOMEM; + + return 0; +} + +static void sev_free_have_run_cpus(struct kvm_sev_info *sev) +{ + free_cpumask_var(sev->have_run_cpus); + memset(&sev->have_run_cpus, 0, sizeof(sev->have_run_cpus)); +} + static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, struct kvm_sev_init *data, unsigned long vm_type) @@ -545,10 +559,9 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, if (ret) goto e_free_asid; - if (!zalloc_cpumask_var(&sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(sev); + if (ret) goto e_free_asid; - } /* This needs to happen after SEV/SNP firmware initialization. */ if (snp_active) { @@ -566,7 +579,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp, return 0; e_free: - free_cpumask_var(sev->have_run_cpus); + sev_free_have_run_cpus(sev); e_free_asid: argp->error = init_args.error; sev_asid_free(sev); @@ -2191,10 +2204,9 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) * does not, i.e. KVM could skip flushes if memory is reclaimed from * the old VM but not the new VM. */ - if (!zalloc_cpumask_var(&dst_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(dst_sev); + if (ret) goto out_source_vcpu; - } sev_migrate_from(kvm, source_kvm); kvm_vm_dead(source_kvm); @@ -2888,10 +2900,9 @@ int sev_vm_copy_enc_context_from(struct kvm *kvm, unsigned int source_fd) } mirror_sev = to_kvm_sev_info(kvm); - if (!zalloc_cpumask_var(&mirror_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { - ret = -ENOMEM; + ret = sev_alloc_have_run_cpus(mirror_sev); + if (ret) goto e_unlock; - } /* * The mirror kvm holds an enc_context_owner ref so its asid can't @@ -2984,7 +2995,7 @@ void sev_vm_destroy(struct kvm *kvm) * Free the mask even if the VM is not *currently* an SEV VM, as it may * have been an SEV VM prior to intra-host migration. */ - free_cpumask_var(sev->have_run_cpus); + sev_free_have_run_cpus(sev); if (!sev_guest(kvm)) return; From 973ea70393e885e540f714904e51bc6cac80e3d7 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 28 Sep 2026 08:46:44 -0700 Subject: [PATCH 342/535] KVM: SEV: Do cache maintenance on the source VM *before* clearing SEV state Explicitly flush caches after intra-host migration before clearing "SEV active" on the source VM, as doing cache maintenance afterwards creates a tiny window where memory reclaim could return memory to the host without performing a cache flush, e.g. as pointed out by Sashiko: CPU1 in sev_migrate_from(): src->active = false; CPU2 running concurrent unmap: Since active is false, the automatic cache flush in sev_guest_memory_reclaimed is skipped. The host frees and reallocates the page. CPU1 in sev_migrate_from(): sev_writeback_caches(src_kvm); Executes a hardware cache flush (wbnoinvd), which writes the guest's old dirty ciphertext over the new page owner's data. Fixes: 93de2a6a4b91 ("KVM: SEV: Do cache maintenance on the source VM during intra-host migration") Cc: stable@vger.kernel.org Reported-by: Sashiko Bot Closes: https://lore.kernel.org/all/20260923165304.1662E1F000FF@smtp.kernel.org Signed-off-by: Sean Christopherson Message-ID: <20260928154644.2559454-3-seanjc@google.com> Signed-off-by: Paolo Bonzini --- arch/x86/kvm/svm/sev.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index d3a2e6a51efc..0c1ebb16cec6 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2045,6 +2045,13 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) struct kvm_sev_info *mirror; unsigned long i; + /* + * Do cache maintenance on the source VM *before* clearing "SEV active", + * as memory reclaim flows won't trigger cache maintenance on the VM + * once it's no longer an SEV VM. + */ + sev_writeback_caches(src_kvm); + dst->active = true; dst->asid = src->asid; dst->handle = src->handle; @@ -2058,12 +2065,6 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->pages_locked = 0; src->es_active = false; - /* - * Do cache maintenance on the source VM as it is no longer an SEV VM, - * i.e. memory reclaim flows won't trigger cache maintenance on the VM. - */ - sev_writeback_caches(src_kvm); - list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); mutex_lock(&sev_mirror_lock); From bce7698e2e6565bb765bd0522337f29f5f312624 Mon Sep 17 00:00:00 2001 From: Haojie Li Date: Tue, 25 Aug 2026 18:08:45 +0800 Subject: [PATCH 343/535] drm/mediatek: Add missing IS_ERR check for ovl_adaptor platform device platform_device_register_data() can fail and return an ERR_PTR, but the return value is used without checking, leading to an invalid pointer being stored in ddp_comp[].dev and passed to component_match_add() and mtk_ddp_comp_init(), which could result in a kernel crash. Add an IS_ERR() check to jump to the error handling path on failure. Fixes: 0d9eee9118b7 ("drm/mediatek: Add drm ovl_adaptor sub driver for MT8195") Cc: stable@vger.kernel.org Signed-off-by: Haojie Li Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260825100845.438893-1-lihaojie@kylinos.cn/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_drm_drv.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index c86a3f54f35b..b281482ab8c8 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -1112,6 +1112,9 @@ static int mtk_drm_probe(struct platform_device *pdev) PLATFORM_DEVID_AUTO, (void *)private->mmsys_dev, sizeof(*private->mmsys_dev)); + if (IS_ERR(ovl_adaptor)) + return PTR_ERR(ovl_adaptor); + private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR].dev = &ovl_adaptor->dev; mtk_ddp_comp_init(dev, NULL, &private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR], DDP_COMPONENT_DRM_OVL_ADAPTOR); From 3e292c56bf5e338efcda7f9bfa603180855cd1be Mon Sep 17 00:00:00 2001 From: Julien Stephan Date: Fri, 28 Aug 2026 14:10:47 +0200 Subject: [PATCH 344/535] drm/mediatek: mtk_hdmi_v2: fix VID_DOWNSAMPLE_CONFIG register offset According to the datasheet, VID_DOWNSAMPLE_CONFIG is at offset 0x8f0; 0x8d0 is the VID_CSC_COEFF_0 register. Fixes: 8d0f79886273 ("drm/mediatek: Introduce HDMI/DDC v2 for MT8195/MT8188") Signed-off-by: Julien Stephan Reviewed-by: Louis-Alexis Eyraud Reviewed-by: AngeloGioacchino Del Regno Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260828-mtk-hdmi-v2-fix-register-offset-v1-1-118ad5d7ebe3@baylibre.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h index 521b35c7e14d..bf3ebf5492d1 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_regs_v2.h @@ -173,7 +173,7 @@ #define TPI_AUDIO_LOOKUP_EN BIT(2) /* Video downsampling configuration */ -#define VID_DOWNSAMPLE_CONFIG 0x8d0 +#define VID_DOWNSAMPLE_CONFIG 0x8f0 #define C444_C422_CONFIG_ENABLE BIT(0) #define C422_C420_CONFIG_ENABLE BIT(4) #define C422_C420_CONFIG_BYPASS BIT(5) From 6b2462f07cb74cd0a5cb868b36c09e9f51f56d3f Mon Sep 17 00:00:00 2001 From: Christian Melendez Date: Tue, 22 Sep 2026 23:14:39 +0200 Subject: [PATCH 345/535] MAINTAINERS: add missing entry for b53.rst Documentation/networking/dsa/b53.rst is not covered by the BROADCOM B53/SF2 ETHERNET SWITCH DRIVER entry. Signed-off-by: Christian Melendez Link: https://patch.msgid.link/20260922211439.369917-2-chrismelnu@gmail.com Signed-off-by: Jakub Kicinski --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index c915b114c99c..7d334b504bc3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5143,6 +5143,7 @@ L: netdev@vger.kernel.org L: openwrt-devel@lists.openwrt.org (subscribers-only) S: Supported F: Documentation/devicetree/bindings/net/dsa/brcm,b53.yaml +F: Documentation/networking/dsa/b53.rst F: drivers/net/dsa/b53/* F: drivers/net/dsa/bcm_sf2* F: include/linux/dsa/brcm.h From eb7b84af0ec5c71b4920e999611b466b3784496c Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 22 Sep 2026 16:24:31 -0700 Subject: [PATCH 346/535] net: systemport: Fix buffer overflow in bcm_sysport_get_stats() When running on SYSTEMPORT Lite, certain statistics are unsupported and skipped during bcm_sysport_get_stats(). The variable 'j' tracks the compacted index into the destination data buffer, whereas 'i' iterates over all elements in bcm_sysport_gstrings_stats. Because the buffer allocated by ethtool is sized only according to bcm_sysport_get_sset_count(), storing values at data[i] instead of data[j] writes past the allocated array bounds, leading to memory corruption. Fix this by writing to data[j] instead of data[i]. Fixes: 10377ba7673d ("net: systemport: Support 64bit statistics") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260922232440.598918-2-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bcmsysport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index 4d06c6ba6641..db627cd15fb7 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -482,10 +482,10 @@ static void bcm_sysport_get_stats(struct net_device *dev, s->type == BCM_SYSPORT_STAT_NETDEV64) { do { start = u64_stats_fetch_begin(syncp); - data[i] = *(u64 *)p; + data[j] = *(u64 *)p; } while (u64_stats_fetch_retry(syncp, start)); } else - data[i] = *(u32 *)p; + data[j] = *(u32 *)p; j++; } From 242935adfeb096f09b29fb2cfcf12f9eb2911fc6 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 22 Sep 2026 16:24:35 -0700 Subject: [PATCH 347/535] net: systemport: Fix RUNT MIB counter register offset calculation In UniMAC hardware, there is a 0xC byte gap between the RX MIB counters and the TX MIB counters, and a second 0xC byte gap between the TX MIB counters and the RX RUNT MIB counters. In bcm_sysport_update_mib_counters(), 'offset' was only set to UMAC_MIB_STAT_OFFSET (0xC) for all non-RX counters, omitting the second 0xC gap for BCM_SYSPORT_STAT_RUNT counters. As a result, all 4 RUNT MIB counters were read from unmapped gap register space. Fix this by setting offset to 2 * UMAC_MIB_STAT_OFFSET (0x18) when reading BCM_SYSPORT_STAT_RUNT counters. Fixes: 80105befdb4b ("net: systemport: add Broadcom SYSTEMPORT Ethernet MAC driver") Reviewed-by: Nicolai Buchwitz Signed-off-by: Florian Fainelli Link: https://patch.msgid.link/20260922232440.598918-6-florian.fainelli@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bcmsysport.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c index db627cd15fb7..78909f516823 100644 --- a/drivers/net/ethernet/broadcom/bcmsysport.c +++ b/drivers/net/ethernet/broadcom/bcmsysport.c @@ -391,8 +391,10 @@ static void bcm_sysport_update_mib_counters(struct bcm_sysport_priv *priv) if (priv->is_lite) continue; - if (s->type != BCM_SYSPORT_STAT_MIB_RX) + if (s->type == BCM_SYSPORT_STAT_MIB_TX) offset = UMAC_MIB_STAT_OFFSET; + else if (s->type == BCM_SYSPORT_STAT_RUNT) + offset = 2 * UMAC_MIB_STAT_OFFSET; val = umac_readl(priv, UMAC_MIB_START + j + offset); break; case BCM_SYSPORT_STAT_RXCHK: From 04c824940d52871c49866a4ac67b504146f7cf35 Mon Sep 17 00:00:00 2001 From: Anirudh Virdi Date: Thu, 24 Sep 2026 12:12:44 +0530 Subject: [PATCH 348/535] net/mlx5: Fix slab-out-of-bounds when handling team device events The mlx5_handle_changeupper_event() and mlx5_handle_changeinfodata_event() functions check for LAG masters (which includes both bonding and team devices) but only call bonding-specific APIs. When processing team device events, calling bond_slave_get_rcu() and bond_is_slave_inactive() on team_port structures causes KASAN to detect an out-of-bounds memory access since team_port is smaller than bond slave. Fix this by wrapping the bond-specific API calls with a check for bonding devices. This allows the function to still process LAG events for both bonding and teams but only calls bond-specific functions when dealing with actual bonding devices. For mlx5_handle_changeinfodata_event(), keep the explicit bond check as mlx5 doesn't handle state information for team ports anyway. Tested with Mellanox ConnectX-5 on Linux 7.2.0-rc6: - Bonding: PASS (no regressions) - Team device: PASS (no KASAN errors) Fixes: 54493a08e21f ("net/mlx5: Lag, record inactive state of bond device") Suggested-by: Mark Bloch Signed-off-by: Anirudh Virdi Reviewed-by: Mark Bloch Link: https://patch.msgid.link/20260924064244.94045-1-avirdi@redhat.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index dd14cdc378de..3b34bec559e0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1943,9 +1943,11 @@ static int mlx5_handle_changeupper_event(struct mlx5_lag *ldev, } } if (i < MLX5_MAX_PORTS) { - slave = bond_slave_get_rcu(ndev_tmp); - if (slave) - has_inactive |= bond_is_slave_inactive(slave); + if (netif_is_bond_master(upper)) { + slave = bond_slave_get_rcu(ndev_tmp); + if (slave) + has_inactive |= bond_is_slave_inactive(slave); + } bond_status |= (1 << idx); } @@ -2029,7 +2031,7 @@ static int mlx5_handle_changeinfodata_event(struct mlx5_lag *ldev, bool has_inactive = 0; int idx; - if (!netif_is_lag_master(ndev)) + if (!netif_is_bond_master(ndev)) return 0; rcu_read_lock(); From 056994563935e049093f100efaad7e63ad113814 Mon Sep 17 00:00:00 2001 From: Geetha sowjanya Date: Tue, 22 Sep 2026 15:51:48 +0530 Subject: [PATCH 349/535] octeontx2-pf: Fix aura BPID assignment when CONFIG_DCB is enabled Previously, BPID assignment under CONFIG_DCB assumed `queue_to_pfc_map` was always initialized. For SDP VFs this leads to invalid memory access as it was not initialized. This patch adds a NULL check for `queue_to_pfc_map` before dereferencing it. Also, simplifies the logic by always assigning a default BPID first, then conditionally overriding it if CONFIG_DCB is enabled and the map exists. Fixes: 184fb40f731b ("octeontx2-pf: Avoid adding dcbnl_ops for LBK and SDP vf") Signed-off-by: Geetha sowjanya Signed-off-by: Subrat Pandey Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260922102149.2078168-2-subratp@marvell.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/marvell/octeontx2/nic/cn20k.c | 11 +---------- .../ethernet/marvell/octeontx2/nic/otx2_common.c | 16 ++++++---------- .../ethernet/marvell/octeontx2/nic/otx2_common.h | 9 +++++++++ 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index 8e41431c7f9c..e92b77789f08 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -242,15 +242,6 @@ int cn20k_register_pfvf_mbox_intr(struct otx2_nic *pf, int numvfs) #define RQ_BP_LVL_AURA (255 - ((85 * 256) / 100)) /* BP when 85% is full */ -static u8 cn20k_aura_bpid_idx(struct otx2_nic *pfvf, int aura_id) -{ -#ifdef CONFIG_DCB - return pfvf->queue_to_pfc_map[aura_id]; -#else - return 0; -#endif -} - static int cn20k_tc_get_entry_index(struct otx2_flow_config *flow_cfg, struct otx2_tc_flow *node) { @@ -580,7 +571,7 @@ static int cn20k_aura_aq_init(struct otx2_nic *pfvf, int aura_id, if (pfvf->nix_blkaddr == BLKADDR_NIX1) aq->aura.bp_ena = 1; - bpid_idx = cn20k_aura_bpid_idx(pfvf, aura_id); + bpid_idx = otx2_get_bpid_idx(pfvf, aura_id); aq->aura.bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level for RQ's Aura */ diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 175992188c18..0f3f405d9f1b 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -1055,6 +1055,7 @@ int otx2_cq_init(struct otx2_nic *pfvf, u16 qidx) struct nix_aq_enq_req *aq; struct otx2_cq_queue *cq; struct otx2_pool *pool; + u8 bpid_idx; cq = &qset->cq[qidx]; cq->cq_idx = qidx; @@ -1132,11 +1133,8 @@ int otx2_cq_init(struct otx2_nic *pfvf, u16 qidx) if (!is_otx2_lbkvf(pfvf->pdev)) { /* Enable receive CQ backpressure */ aq->cq.bp_ena = 1; -#ifdef CONFIG_DCB - aq->cq.bpid = pfvf->bpid[pfvf->queue_to_pfc_map[qidx]]; -#else - aq->cq.bpid = pfvf->bpid[0]; -#endif + bpid_idx = otx2_get_bpid_idx(pfvf, qidx); + aq->cq.bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level is same as cq pass level */ aq->cq.bp = RQ_PASS_LVL_CQ(pfvf->hw.rq_skid, qset->rqe_cnt); @@ -1378,6 +1376,7 @@ int otx2_aura_aq_init(struct otx2_nic *pfvf, int aura_id, { struct npa_aq_enq_req *aq; struct otx2_pool *pool; + u8 bpid_idx; int err; pool = &pfvf->qset.pool[pool_id]; @@ -1433,11 +1432,8 @@ int otx2_aura_aq_init(struct otx2_nic *pfvf, int aura_id, */ if (pfvf->nix_blkaddr == BLKADDR_NIX1) aq->aura.bp_ena = 1; -#ifdef CONFIG_DCB - aq->aura.nix0_bpid = pfvf->bpid[pfvf->queue_to_pfc_map[aura_id]]; -#else - aq->aura.nix0_bpid = pfvf->bpid[0]; -#endif + bpid_idx = otx2_get_bpid_idx(pfvf, aura_id); + aq->aura.nix0_bpid = pfvf->bpid[bpid_idx]; /* Set backpressure level for RQ's Aura */ aq->aura.bp = RQ_BP_LVL_AURA; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h index eecee612b7b2..44cd56a0c2c4 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.h @@ -1029,6 +1029,15 @@ static inline int otx2_tc_flower_rule_cnt(struct otx2_nic *pfvf) return pfvf->flow_cfg->nr_flows; } +static inline u8 otx2_get_bpid_idx(struct otx2_nic *pfvf, int qidx) +{ +#ifdef CONFIG_DCB + if (pfvf->queue_to_pfc_map) + return pfvf->queue_to_pfc_map[qidx]; +#endif + return 0; +} + /* MSI-X APIs */ void otx2_free_cints(struct otx2_nic *pfvf, int n); void otx2_set_cints_affinity(struct otx2_nic *pfvf); From cdef07f3002f708a345392ee28d0d9bfac8383a1 Mon Sep 17 00:00:00 2001 From: Baha Mesleh Date: Tue, 22 Sep 2026 15:51:49 +0530 Subject: [PATCH 350/535] octeontx2-af: mcs: Fix SC resource cleanup loop The SC resource cleanup and statistics loops were incorrectly iterating over secy.max instead of sc.max, so the highest SC id could be left allocated across teardown or FLR. Use sc.max as the loop bound. Fixes: cfc14181d497 ("octeontx2-af: cn10k: mcs: Manage the MCS block hardware resources") Signed-off-by: Baha Mesleh Signed-off-by: Subrat Pandey Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260922102149.2078168-3-subratp@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/mcs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c index 211c10aa5880..30c672d526a0 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/mcs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/mcs.c @@ -314,7 +314,7 @@ int mcs_clear_all_stats(struct mcs *mcs, u16 pcifunc, int dir) } /* Clear SC stats */ - for (id = 0; id < map->secy.max; id++) { + for (id = 0; id < map->sc.max; id++) { if (map->sc2pf_map[id] != pcifunc) continue; mcs_clear_stats(mcs, MCS_SC_STATS, id, dir); @@ -738,7 +738,7 @@ int mcs_free_all_rsrc(struct mcs *mcs, int dir, u16 pcifunc) } /* free sc entries */ - for (id = 0; id < map->secy.max; id++) { + for (id = 0; id < map->sc.max; id++) { if (map->sc2pf_map[id] != pcifunc) continue; mcs_free_rsrc(&map->sc, map->sc2pf_map, id, pcifunc); From 512ccd3d0e91e791fb37442aa0a2599aca19783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Thu, 24 Sep 2026 20:21:05 +0000 Subject: [PATCH 351/535] tipc: prevent GCM nonce reuse on peer key changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TIPC can encrypt traffic between nodes using a different transmit key for each node. In this mode, the AES-GCM nonce for a packet sent to a known peer consists of a 32-bit prefix (a per-key salt XOR the peer's address) followed by a 64-bit counter. That counter is stored in the peer's RX crypto object. When the peer reports a change in which key it uses to receive packets, TIPC resets this counter. The sender can still be using the same TX key and salt, so subsequent packets reuse earlier nonces. This nonce reuse breaks confidentiality and exposes GCM's authentication key. This makes forgeries trivial: an attacker can exploit CTR malleability to alter captured ciphertexts and use the recovered authentication key to compute a valid tag for the modified ciphertext, under the same key and nonce. Use the TX key's existing aead->seqno counter instead. All encryptions using that key object share the same atomic counter, so concurrent encryptions get distinct nonce counter values. The counter survives key activation and peer reconnection, and peer key-status reports cannot reset it. This prevents those transitions from causing nonce reuse while the same TX key remains installed. The nonce format is unchanged, and receivers do not require consecutive counter values, so sharing the counter across peers remains compatible with existing receivers. A pre-existing check still invokes key revocation in the unlikely event that the counter wraps to zero. Fixes: fc1b6d6de220 ("tipc: introduce TIPC encryption & authentication") Signed-off-by: Jérémy Jean Reviewed-by: Tung Nguyen Link: https://patch.msgid.link/20260924202105.3722778-1-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Jakub Kicinski --- net/tipc/crypto.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/net/tipc/crypto.c b/net/tipc/crypto.c index 16f1ed1f6b1b..4409bdb70fe9 100644 --- a/net/tipc/crypto.c +++ b/net/tipc/crypto.c @@ -144,7 +144,7 @@ struct tipc_tfm { * @rcu: struct rcu_head * @key: the aead key * @gen: the key's generation - * @seqno: the key seqno (cluster scope) + * @seqno: the per-key TX nonce counter * @refcnt: the key reference counter */ struct tipc_aead { @@ -190,7 +190,6 @@ struct tipc_crypto_stats { * @rekeying_intv: rekeying interval (in minutes) * @stats: the crypto statistics * @name: the crypto name - * @sndnxt: the per-peer sndnxt (TX) * @timer1: general timer 1 (jiffies) * @timer2: general timer 2 (jiffies) * @working: the crypto is working or not @@ -219,7 +218,6 @@ struct tipc_crypto { struct tipc_crypto_stats __percpu *stats; char name[48]; - atomic64_t sndnxt ____cacheline_aligned; unsigned long timer1; unsigned long timer2; union { @@ -1051,14 +1049,11 @@ static int tipc_ehdr_build(struct net *net, struct tipc_aead *aead, WARN_ON(skb_headroom(skb) < ehsz); ehdr = (struct tipc_ehdr *)skb_push(skb, ehsz); - /* Obtain a seqno first: - * Use the key seqno (= cluster wise) if dest is unknown or we're in - * cluster key mode, otherwise it's better for a per-peer seqno! + /* + * Keep the nonce unique for the lifetime of the TX key, + * including key state changes and peer reconnection. */ - if (!__rx || aead->mode == CLUSTER_KEY) - seqno = atomic64_inc_return(&aead->seqno); - else - seqno = atomic64_inc_return(&__rx->sndnxt); + seqno = atomic64_inc_return(&aead->seqno); /* Revoke the key if seqno is wrapped around */ if (unlikely(!seqno)) @@ -1237,7 +1232,6 @@ void tipc_crypto_key_flush(struct tipc_crypto *c) tipc_crypto_key_set_state(c, 0, 0, 0); for (k = KEY_MIN; k <= KEY_MAX; k++) tipc_crypto_key_detach(c->aead[k], &c->lock); - atomic64_set(&c->sndnxt, 0); spin_unlock_bh(&c->lock); } @@ -1384,8 +1378,6 @@ static struct tipc_aead *tipc_crypto_key_pick_tx(struct tipc_crypto *tx, * It also considers if peer has no key, then we need to make own master key * (if any) taking over i.e. starting grace period and also trigger key * distributing process. - * - * The "per-peer" sndnxt is also reset when the peer key has switched. */ static void tipc_crypto_key_synch(struct tipc_crypto *rx, struct sk_buff *skb) { @@ -1436,7 +1428,6 @@ static void tipc_crypto_key_synch(struct tipc_crypto *rx, struct sk_buff *skb) if (cur) tipc_aead_users_dec(tx->aead[cur], 0); - atomic64_set(&rx->sndnxt, 0); /* Mark the point TX key users changed */ tx->timer1 = jiffies; @@ -1501,7 +1492,6 @@ int tipc_crypto_start(struct tipc_crypto **crypto, struct net *net, tipc_crypto_key_set_state(c, 0, 0, 0); atomic_set(&c->key_distr, 0); atomic_set(&c->peer_rx_active, 0); - atomic64_set(&c->sndnxt, 0); c->timer1 = jiffies; c->timer2 = jiffies; c->rekeying_intv = TIPC_REKEYING_INTV_DEF; From bdea4980182e3badac74f04df6c07492b91c569e Mon Sep 17 00:00:00 2001 From: Mina Almasry Date: Fri, 25 Sep 2026 14:41:11 +0000 Subject: [PATCH 352/535] net: page_pool: fix use-after-free in page_pool_recycle_ring_bulk() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With CONFIG_PAGE_POOL_STATS=y, page_pool_recycle_ring_bulk() updates the recycle stats after dropping the producer lock. If it just recycled the last inflight netmems of a pool being destroyed, page_pool_release() can pass its producer-lock barrier and free the pool before recycle_stat_add() runs. Commit fcc680a647ba7 ("page_pool: allow mixing PPs within one bulk") moved this stat update after the unlock. Commit 271683bb2cf32 ("page_pool: Fix use-after-free in page_pool_recycle_in_ring") later added the barrier, but only fixed page_pool_recycle_in_ring(). Move the update back under the lock. Fixes: fcc680a647ba7 ("page_pool: allow mixing PPs within one bulk") Link: https://lore.kernel.org/r/179028939171.2160803.706522228583664641@kernel.org Cc: Kaifeng Wang Cc: Dong Chenchen Signed-off-by: Mina Almasry Reviewed-by: Simon Horman Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260925144127.1445667-1-almasrymina@google.com Signed-off-by: Jakub Kicinski --- net/core/page_pool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 08d7f35cf608..d7c88c0b67e5 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -951,8 +951,8 @@ static void page_pool_recycle_ring_bulk(struct page_pool *pool, } } - page_pool_producer_unlock(pool, in_softirq); recycle_stat_add(pool, ring, i); + page_pool_producer_unlock(pool, in_softirq); /* Hopefully all pages were returned into ptr_ring */ if (likely(i == bulk_len)) From 88095728511e0df9c89528944f5a1762ad298983 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 23 Sep 2026 11:14:28 +0200 Subject: [PATCH 353/535] net: stmmac: fix rx Scatter-Gather support When a received frame is larger than dma_buf_sz, the DMA scatters it across multiple RX descriptors (rx Scatter-Gather). The secondary RX buffer (sec_page) was only allocated and programmed when split-header (SPH) was active, so for regular frames buffer2 was neither allocated nor backed by a valid mapping. As soon as an incoming frame overflowed buffer1, the DMA wrote the overflow into the unmapped secondary-buffer address, triggering an SMMU translation fault on IOMMU-based platforms: arm-smmu 15000000.iommu: Unhandled context fault: fsr=0x402, iova=0x00000000, fsynr=0x7f0011, cbfrsynra=0x1c90, cb=11 arm-smmu 15000000.iommu: FSR = 00000402 [Format=2 TF], SID=0x1c90 arm-smmu 15000000.iommu: FSYNR0 = 007f0011 [S1CBNDX=127 WNR PLVL=1] Enable scatter-gather for non-SPH frames on cores that can program an independent secondary RX buffer (GMAC4/XGMAC): allocate and mark buffer2 as valid in stmmac_init_rx_buffers() and stmmac_rx_refill(), and account for it in the buffer length computation. Legacy cores have no set_sec_addr op, so they keep buffer2 disabled. Since buffer2 is handed to the DMA at page offset 0, the page pool sync window is widened to cover both buffers in every mode: offset is set to 0 and max_len to dma_buf_sz + stmmac_rx_offset(). The FCS can straddle the buffer1/buffer2 boundary and a descriptor boundary, so it is now stripped from the tail of the assembled frame with pskb_trim() instead of from a single buffer, avoiding an unsigned underflow for frames that overflow a buffer by 1..3 bytes. For single-buffer frames the XDP program must not see the FCS, so it is removed from the XDP buffer before the program runs. Native XDP currently only supports single-buffer (linear) frames. An oversized frame accepted by the MAC (jumbo enabled) is received via buffer2; the XDP program only sees buffer1, so on a TX/REDIRECT verdict the frame is forwarded truncated. XDP multi-buffer support to handle this case is planned as a follow-up. AF_XDP zero-copy RX is not covered by this change: a ZC queue still programs buffer2 at DMA address 0 (and XGMAC has no buffer2-valid bit), so an oversized frame overflowing buffer1 can still trigger the same SMMU translation fault. Handling is planned as a follow-up. Fixes: 88ebe2cf7f3f ("net: stmmac: Rework stmmac_rx()") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260923-stmmac-rx-sg-fix-v3-1-ed26fea7180d@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 3f34d491c959..ec62fa7418f4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1629,6 +1629,16 @@ static void stmmac_clear_descriptors(struct stmmac_priv *priv, stmmac_clear_tx_descriptors(priv, dma_conf, queue); } +static bool stmmac_rx_check_buf2_cap(struct stmmac_priv *priv) +{ + /* Only cores that can program an independent secondary RX buffer + * (used for scatter-gather overflow or split-header payload) back + * buffer2. Legacy cores have no set_sec_addr op, so buffer2 is + * never handed to the hardware there. + */ + return priv->hw->desc && priv->hw->desc->set_sec_addr; +} + /** * stmmac_init_rx_buffers - init the RX descriptor buffer. * @priv: driver private structure @@ -1659,16 +1669,13 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, buf->page_offset = stmmac_rx_offset(priv); } - if (priv->sph_active && !buf->sec_page) { + if (stmmac_rx_check_buf2_cap(priv) && !buf->sec_page) { buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); if (!buf->sec_page) return -ENOMEM; buf->sec_addr = page_pool_get_dma_addr(buf->sec_page); stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); - } else { - buf->sec_page = NULL; - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); } buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; @@ -2256,13 +2263,8 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, pp_params.nid = dev_to_node(priv->device); pp_params.dev = priv->device; pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE; - pp_params.offset = stmmac_rx_offset(priv); - pp_params.max_len = dma_conf->dma_buf_sz; - - if (priv->sph_active) { - pp_params.offset = 0; - pp_params.max_len += stmmac_rx_offset(priv); - } + pp_params.offset = 0; + pp_params.max_len = dma_conf->dma_buf_sz + stmmac_rx_offset(priv); rx_q->page_pool = page_pool_create(&pp_params); if (IS_ERR(rx_q->page_pool)) { @@ -5099,7 +5101,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) break; } - if (priv->sph_active && !buf->sec_page) { + if (stmmac_rx_check_buf2_cap(priv) && !buf->sec_page) { buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); if (!buf->sec_page) break; @@ -5110,10 +5112,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; stmmac_set_desc_addr(priv, p, buf->addr); - if (priv->sph_active) - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); - else - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); + stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, + stmmac_rx_check_buf2_cap(priv)); stmmac_refill_desc3(priv, rx_q, p); rx_q->rx_count_frames++; @@ -5144,7 +5144,9 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, unsigned int plen = 0, hlen = 0; int coe = priv->hw->rx_csum; - /* Not first descriptor, buffer is always zero */ + /* Not first descriptor, SPH enabled: buffer1 only carries the + * split header of the first descriptor, so it is zero here. + */ if (priv->sph_active && len) return 0; @@ -5155,14 +5157,16 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, return hlen; } - /* First descriptor, not last descriptor and not split header */ + /* Not last descriptor and not split header: buffer1 is fully filled */ if (status & rx_not_ls) return priv->dma_conf.dma_buf_sz; plen = stmmac_get_rx_frame_len(priv, p, coe); - /* First descriptor and last descriptor and not split header */ - return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen); + /* Last descriptor and not split header: buffer1 holds the remaining + * bytes of the frame, up to dma_buf_sz + */ + return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen - len); } static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, @@ -5172,8 +5176,7 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, int coe = priv->hw->rx_csum; unsigned int plen = 0; - /* Not split header, buffer is not available */ - if (!priv->sph_active) + if (!stmmac_rx_check_buf2_cap(priv)) return 0; /* For GMAC4, when split header is enabled, in some rare cases, the @@ -5190,14 +5193,15 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, * Thus 'plen - len' always gives the correct length of buf2. */ - /* Not GMAC4 and not last descriptor */ - if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls)) + /* Not GMAC4, or non-SPH and not last descriptor */ + if ((priv->plat->core_type != DWMAC_CORE_GMAC4 || !priv->sph_active) && + (status & rx_not_ls)) return priv->dma_conf.dma_buf_sz; /* GMAC4 or last descriptor */ plen = stmmac_get_rx_frame_len(priv, p, coe); - return plen - len; + return plen > len ? plen - len : 0; } static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue, @@ -5720,6 +5724,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) unsigned int desc_size; struct sk_buff *skb = NULL; struct stmmac_xdp_buff ctx; + bool fcs_stripped = false; int xdp_status = 0; int bufsz; @@ -5809,24 +5814,25 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) len += buf2_len; /* ACS is disabled; strip manually. */ - if (likely(!(status & rx_not_ls))) { - if (buf2_len) { - buf2_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } else if (buf1_len) { - buf1_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } - } + if (likely(!(status & rx_not_ls))) + len -= ETH_FCS_LEN; if (!skb) { unsigned int pre_len, sync_len; + /* Each frame starts here: reset the FCS handling */ + fcs_stripped = false; + dma_sync_single_for_cpu(priv->device, buf->addr, buf1_len, dma_dir); net_prefetch(page_address(buf->page) + buf->page_offset); + if (stmmac_xdp_is_enabled(priv) && !buf2_len) { + buf1_len -= ETH_FCS_LEN; + fcs_stripped = true; + } + xdp_init_buff(&ctx.xdp, bufsz, &rx_q->xdp_rxq); xdp_prepare_buff(&ctx.xdp, page_address(buf->page), buf->page_offset, buf1_len, true); @@ -5927,6 +5933,15 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) /* Got entire packet into SKB. Finish it. */ + /* Remove FCS if needed */ + if (!fcs_stripped && pskb_trim(skb, len)) { + dev_kfree_skb_any(skb); + skb = NULL; + rx_dropped++; + count++; + continue; + } + stmmac_get_rx_hwtstamp(priv, p, np, skb); if (priv->hw->hw_vlan_en) From 8ae3f4067ba4ae3a15581392f6dac422e3d3def0 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Fri, 25 Sep 2026 15:38:07 +0200 Subject: [PATCH 354/535] seg6: ensure packet data is writable before modifying SRH and IPv6 DA advance_nextseg() modifies the SRH Segments Left field and the IPv6 destination address without ensuring the packet data is writable. seg6_next_csid_advance_arg() has the same problem when it advances the NEXT-C-SID argument in the destination address. The skb may be cloned, for example by an AF_PACKET socket receiving on the ingress device. advance_nextseg() and seg6_next_csid_advance_arg() then write into the packet data shared with the clone. A read from that socket can return the modified packet instead of the received one. The simplified path below shows this for advance_nextseg(): __netif_receive_skb_one_core __netif_receive_skb_core deliver_skb [orig: users=2, cloned=0] packet_rcv skb_clone clone queued to the AF_PACKET socket consume_skb(orig) [orig: users=1, cloned=1] ipv6_rcv ip6_rcv_core skb_share_check: no-op [orig: users=1] [...] input_action_end_core advance_nextseg writes into the data shared with the clone Call skb_ensure_writable() in advance_nextseg() and in seg6_next_csid_advance_arg() before they modify the packet data. skb_ensure_writable() may reallocate skb->head, which invalidates the pointers into the packet data taken before the call. advance_nextseg() now returns a valid SRH pointer, or NULL if skb_ensure_writable() fails. seg6_next_csid_advance_arg() takes the pointer to the destination address from the skb after skb_ensure_writable(). On failure, the callers drop the packet with SKB_DROP_REASON_NOMEM. Fixes: 140f04c33bbc ("ipv6: sr: implement several seg6local actions") Fixes: 848f3c0d4769 ("seg6: add NEXT-C-SID support for SRv6 End behavior") Signed-off-by: Andrea Mayer Link: https://patch.msgid.link/20260925133807.32-1-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_local.c | 140 +++++++++++++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 23 deletions(-) diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index d1070aec7b72..53f7b2452575 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -278,13 +278,43 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) return true; } -static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr) +/* advance the SRH to the next segment and set the IPv6 DA accordingly. + * skb pointers may change: after this call, the caller must evaluate again + * any pointer into the packet data. + * + * This function returns: + * - the SRH on success; + * - NULL when the skb cannot be made writable. In this case, the function + * sets the drop reason to SKB_DROP_REASON_NOMEM. + */ +static struct ipv6_sr_hdr *advance_nextseg(struct sk_buff *skb, + struct ipv6_sr_hdr *srh, + enum skb_drop_reason *reason) { struct in6_addr *addr; + int srhoff; + int wlen; + + srhoff = (unsigned char *)srh - skb->data; + /* we write only the Segment Left field and the IPv6 DA. The segment + * list is only read, and seg6_get_srh() already pulled it into the + * linear area. + */ + wlen = srhoff + sizeof(*srh); + + if (unlikely(skb_ensure_writable(skb, wlen))) { + *reason = SKB_DROP_REASON_NOMEM; + return NULL; + } + + /* skb_ensure_writable() may change skb pointers; evaluate srh again */ + srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); srh->segments_left--; addr = srh->segments + srh->segments_left; - *daddr = *addr; + ipv6_hdr(skb)->daddr = *addr; + + return srh; } static int @@ -382,12 +412,26 @@ static bool seg6_next_csid_is_arg_zero(const struct in6_addr *addr, return true; } -/* assume that DA.Argument length > 0 */ -static void seg6_next_csid_advance_arg(struct in6_addr *addr, - const struct seg6_flavors_info *finfo) +/* assume that DA.Argument length > 0. + * skb pointers may change: after this call, the caller must evaluate again + * any pointer into the packet data. + * + * This function returns: + * - SKB_NOT_DROPPED_YET on success; + * - SKB_DROP_REASON_NOMEM when the skb cannot be made writable. + */ +static enum skb_drop_reason +seg6_next_csid_advance_arg(struct sk_buff *skb, + const struct seg6_flavors_info *finfo) { __u8 fnc_octects = seg6_flv_lcnode_func_octects(finfo); __u8 blk_octects = seg6_flv_lcblock_octects(finfo); + struct in6_addr *addr; + + if (unlikely(skb_ensure_writable(skb, sizeof(struct ipv6hdr)))) + return SKB_DROP_REASON_NOMEM; + + addr = &ipv6_hdr(skb)->daddr; /* advance DA.Argument */ memmove(&addr->s6_addr[blk_octects], @@ -395,6 +439,8 @@ static void seg6_next_csid_advance_arg(struct in6_addr *addr, 16 - blk_octects - fnc_octects); memset(&addr->s6_addr[16 - fnc_octects], 0x00, fnc_octects); + + return SKB_NOT_DROPPED_YET; } static int input_action_end_finish(struct sk_buff *skb, @@ -408,31 +454,42 @@ static int input_action_end_finish(struct sk_buff *skb, static int input_action_end_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } return input_action_end_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } static int end_next_csid_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { const struct seg6_flavors_info *finfo = &slwt->flv_info; struct in6_addr *daddr = &ipv6_hdr(skb)->daddr; + enum skb_drop_reason reason; if (seg6_next_csid_is_arg_zero(daddr, finfo)) return input_action_end_core(skb, slwt); /* update DA */ - seg6_next_csid_advance_arg(daddr, finfo); + reason = seg6_next_csid_advance_arg(skb, finfo); + if (reason) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } return input_action_end_finish(skb, slwt); } @@ -448,19 +505,25 @@ static int input_action_end_x_finish(struct sk_buff *skb, static int input_action_end_x_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } return input_action_end_x_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } static int end_x_next_csid_core(struct sk_buff *skb, @@ -468,12 +531,17 @@ static int end_x_next_csid_core(struct sk_buff *skb, { const struct seg6_flavors_info *finfo = &slwt->flv_info; struct in6_addr *daddr = &ipv6_hdr(skb)->daddr; + enum skb_drop_reason reason; if (seg6_next_csid_is_arg_zero(daddr, finfo)) return input_action_end_x_core(skb, slwt); /* update DA */ - seg6_next_csid_advance_arg(daddr, finfo); + reason = seg6_next_csid_advance_arg(skb, finfo); + if (reason) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } return input_action_end_x_finish(skb, slwt); } @@ -760,10 +828,12 @@ static bool seg6_pop_srh(struct sk_buff *skb, int srhoff) */ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; const struct seg6_flavors_info *finfo = &slwt->flv_info; enum seg6_local_flv_action action; enum seg6_local_pktinfo pinfo; struct ipv6_sr_hdr *srh; + int err = -EINVAL; __u32 flvmask; int srhoff; @@ -787,10 +857,18 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) switch (action) { case SEG6_LOCAL_FLV_ACT_END: /* process the packet as the "standard" End behavior */ - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } break; case SEG6_LOCAL_FLV_ACT_PSP: - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } if (unlikely(!seg6_pop_srh(skb, srhoff))) goto drop; @@ -807,8 +885,8 @@ static int end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt) return input_action_end_finish(skb, slwt); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } /* regular endpoint function */ @@ -847,21 +925,27 @@ static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; + int err = -EINVAL; srh = get_and_validate_srh(skb); if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } seg6_lookup_nexthop(skb, NULL, slwt->table); return dst_input(skb); drop: - kfree_skb(skb); - return -EINVAL; + kfree_skb_reason(skb, reason); + return err; } /* decapsulate and forward inner L2 frame on specified interface */ @@ -1375,6 +1459,7 @@ static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) static int input_action_end_b6_encap(struct sk_buff *skb, struct seg6_local_lwt *slwt) { + enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct ipv6_sr_hdr *srh; int err = -EINVAL; @@ -1382,7 +1467,11 @@ static int input_action_end_b6_encap(struct sk_buff *skb, if (!srh) goto drop; - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + err = -ENOMEM; + goto drop; + } skb_reset_inner_headers(skb); skb->encapsulation = 1; @@ -1398,7 +1487,7 @@ static int input_action_end_b6_encap(struct sk_buff *skb, return dst_input(skb); drop: - kfree_skb(skb); + kfree_skb_reason(skb, reason); return err; } @@ -1434,6 +1523,7 @@ static int input_action_end_bpf(struct sk_buff *skb, struct seg6_local_lwt *slwt) { struct seg6_bpf_srh_state *srh_state; + enum skb_drop_reason reason; struct ipv6_sr_hdr *srh; int ret; @@ -1442,7 +1532,11 @@ static int input_action_end_bpf(struct sk_buff *skb, kfree_skb(skb); return -EINVAL; } - advance_nextseg(srh, &ipv6_hdr(skb)->daddr); + srh = advance_nextseg(skb, srh, &reason); + if (!srh) { + kfree_skb_reason(skb, reason); + return -ENOMEM; + } /* The access to the per-CPU buffer srh_state is protected by running * always in softirq context (with disabled BH). On PREEMPT_RT the From 036778a4fad19b54b73ae229a1901665aa8750a2 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Fri, 25 Sep 2026 15:52:36 +0200 Subject: [PATCH 355/535] net: ethtool: let tsconfig reach a PHY-only timestamp provider TSCONFIG_GET and TSCONFIG_SET reject a device that implements neither hwtstamp NDO, even when its PHY can serve the request. The ioctls they meant to replace handle it, so the two interfaces disagree on the same hardware and user space has to pick one. Accept the default timestamping PHY and an already installed provider on both sides. On the set side the test moves into ethnl_set_tsconfig() as the validate callback runs without rtnl. On the get side it stays ahead of ethnl_ops_begin(), so a device that can serve nothing keeps failing with EOPNOTSUPP and a dump still skips it rather than stopping there. A netdev provider needs ndo_hwtstamp_set to be programmed at all, so don't pick that source without it, and test for ndo_hwtstamp_get before calling it. Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config") Signed-off-by: Nicolai Buchwitz Reviewed-by: Maxime Chevallier Reviewed-by: Kory Maincent Link: https://patch.msgid.link/20260925135237.3432266-3-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- net/core/dev_ioctl.c | 3 ++- net/ethtool/tsconfig.c | 24 ++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index 164643140a52..f6029f60c1dc 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -267,7 +267,8 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, hwprov->phydev) return phy_hwtstamp_get(hwprov->phydev, cfg); - if (hwprov->source == HWTSTAMP_SOURCE_NETDEV) + if (hwprov->source == HWTSTAMP_SOURCE_NETDEV && + dev->netdev_ops->ndo_hwtstamp_get) return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); return -EOPNOTSUPP; diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c index 6be3aa5d4bc1..2db0e7ba8b9f 100644 --- a/net/ethtool/tsconfig.c +++ b/net/ethtool/tsconfig.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include @@ -42,7 +43,9 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base, struct kernel_hwtstamp_config cfg = {}; int ret; - if (!dev->netdev_ops->ndo_hwtstamp_get) + if (!dev->netdev_ops->ndo_hwtstamp_get && + !phy_is_default_hwtstamp(dev->phydev) && + !netdev_ops_lock_dereference(dev->hwprov, dev)) return -EOPNOTSUPP; ret = ethnl_ops_begin(dev); @@ -248,17 +251,6 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info) return ret; } -static int ethnl_set_tsconfig_validate(struct ethnl_req_info *req_base, - struct genl_info *info) -{ - const struct net_device_ops *ops = req_base->dev->netdev_ops; - - if (!ops->ndo_hwtstamp_set || !ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - - return 1; -} - static struct hwtstamp_provider * tsconfig_set_hwprov_from_desc(struct net_device *dev, struct genl_info *info, @@ -272,7 +264,7 @@ tsconfig_set_hwprov_from_desc(struct net_device *dev, int ret; ret = ethtool_net_get_ts_info_by_phc(dev, &ts_info, hwprov_desc); - if (!ret) { + if (!ret && dev->netdev_ops->ndo_hwtstamp_set) { /* Found */ source = HWTSTAMP_SOURCE_NETDEV; } else { @@ -313,6 +305,11 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, if (!netif_device_present(dev)) return -ENODEV; + if (!dev->netdev_ops->ndo_hwtstamp_set && + !phy_is_default_hwtstamp(dev->phydev) && + !netdev_ops_lock_dereference(dev->hwprov, dev)) + return -EOPNOTSUPP; + if (tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER]) { struct hwtstamp_provider_desc __hwprov_desc = {.index = -1}; struct hwtstamp_provider *__hwprov; @@ -459,6 +456,5 @@ const struct ethnl_request_ops ethnl_tsconfig_request_ops = { .reply_size = tsconfig_reply_size, .fill_reply = tsconfig_fill_reply, - .set_validate = ethnl_set_tsconfig_validate, .set = ethnl_set_tsconfig, }; From 37e02c42a00be692c06343e11779aadc45f45971 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 25 Sep 2026 13:52:43 +0000 Subject: [PATCH 356/535] net: restrict SO_RESERVE_MEM to TCP sockets and cap max value Commit 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM") and commit d00c8ee31729 ("net: fix possible NULL deref in sock_reserve_memory") only checked sk_has_account(sk), which is true for both TCP and UDP sockets. However, SO_RESERVE_MEM and sk_unused_reserved_mem() are currently only supported by TCP: - On UDP sockets, sk->sk_forward_alloc is protected by sk->sk_receive_queue.lock, whereas sock_reserve_memory() and sock_release_reserved_memory() only acquire lock_sock(sk). Concurrent UDP packet reception/release and setsockopt(SO_RESERVE_MEM) corrupt sk_forward_alloc and memcg accounting. - udp_rmem_release() reclaims excess sk_forward_alloc without accounting for sk_unused_reserved_mem(sk). Restrict sock_reserve_memory() to TCP sockets (sk_is_tcp(sk)) for now. Supporting SO_RESERVE_MEM for UDP (acquiring sk_receive_queue.lock and honoring sk_unused_reserved_mem() in udp_rmem_release()) can be done in a future net-next series if needed. In addition, reject val > SZ_1G with -EINVAL in sk_setsockopt(SO_RESERVE_MEM). Without an upper bound, values near INT_MAX cause sk_mem_pages(delta) and (pages << PAGE_SHIFT) to overflow 32-bit signed int, corrupting sk->sk_forward_alloc and sk->sk_reserved_mem. Using a page-aligned cap (SZ_1G) ensures that the page-rounded sk->sk_reserved_mem reported by getsockopt(SO_RESERVE_MEM) can always be passed back to setsockopt(SO_RESERVE_MEM). Fixes: 2bb2f5fb21b0 ("net: add new socket option SO_RESERVE_MEM") Reported-by: Cai Xinchen Closes: https://lore.kernel.org/netdev/5a88421d-10ef-4fca-9acb-85a27a3c1173@huawei.com/ Signed-off-by: Eric Dumazet Reviewed-by: Wei Wang Link: https://patch.msgid.link/20260925135244.3715196-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- net/core/sock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/sock.c b/net/core/sock.c index d23333bb4f3f..e8551df8330f 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -1034,7 +1034,7 @@ static int sock_reserve_memory(struct sock *sk, int bytes) bool charged; int pages; - if (!mem_cgroup_sk_enabled(sk) || !sk_has_account(sk)) + if (!mem_cgroup_sk_enabled(sk) || !sk_is_tcp(sk)) return -EOPNOTSUPP; if (!bytes) @@ -1661,7 +1661,7 @@ int sk_setsockopt(struct sock *sk, int level, int optname, { int delta; - if (val < 0) { + if (val < 0 || val > SZ_1G) { ret = -EINVAL; break; } From 27a868b40e7d18e4be57d2bbbeac7581fd1219db Mon Sep 17 00:00:00 2001 From: Preston Lam Date: Mon, 28 Sep 2026 19:16:21 -0400 Subject: [PATCH 357/535] ALSA: hda/realtek: Add mute LED quirk for HP Laptop 15-fd2xxx The HP Laptop 15-fd2xxx (PCI SSID 103c:8e43, Realtek ALC236) has a speaker mute LED on the F5 key and a microphone mute LED on the F8 key. Neither lights up, because the model has no quirk entry and only the generic HP vendor fallback (ALC269_FIXUP_HP_MUTE_LED) is applied. Probing the codec with hda-verb showed how the LEDs are wired: - mute LED: COEF index 0x07, bit 0, on NID 0x20 (set = LED on) - mic mute LED: codec GPIO0, active low (driven low = LED on) Driving GPIO1 and GPIO2 did not light the mute LED. This wiring is exactly what the existing ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO fixup implements (COEF 0x07 bit 0 for the mute LED, GPIO0 with inverted polarity for the micmute LED), so use it for this model. The sibling HP Laptop 15-fd0xxx (103c:8dd7) already uses the same fixup. Tested on this machine running Ubuntu 7.0.0-34-generic (SOF driver, the quirk built into snd-hda-codec-alc269 from Ubuntu's 7.0.0-34.34 source tree): the hda::mute and hda::micmute LEDs are created, follow the Master mute state and the Dmic0 capture switch respectively, and the F5 and F8 keys toggle them correctly. Muting the microphone also silences the capture stream. The LEDs also kept the correct state across an s2idle suspend/resume cycle (speaker muted in one run, microphone in another) and still responded to mute changes afterwards; long suspends were not tested. The desktop session was SwayFX (wlroots) with PipeWire, which only changes the mute state through the normal mixer controls; no other desktop was tried. Not tested on a mainline kernel tree directly; the change is only the quirk table entry. AI assistance: this patch, the hardware analysis behind it (probing the codec with hda-verb and choosing the existing fixup) and this commit message were written by Claude Code (Anthropic, model claude-sonnet-5-5). The submitter ran the privileged commands and reboots and reported the LED behavior; Claude Code ran the tests and read the kernel logs. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Preston Lam Link: https://patch.msgid.link/20260928231621.1672704-1-plamlam99@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 193e0fa3606d..dfb5b5ab899d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7740,6 +7740,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), From 0fb5751a6e411b1b466c06e50021818d9391df98 Mon Sep 17 00:00:00 2001 From: "Florent Revest (Anthropic)" Date: Sat, 26 Sep 2026 13:55:58 +0000 Subject: [PATCH 358/535] bpf: Wait for an RCU tasks grace period before freeing trampoline progs When a prog is detached from a trampoline, it is freed after an RCU grace period, or an RCU tasks trace one if it is sleepable. This covers the tasks that are running the prog, since the prog's enter helper takes the matching read lock before the prog is called. On a preemptible kernel, it doesn't cover a task that was preempted in the trampoline just before the enter helper. That task holds no lock yet, and it calls the prog after it was freed: BUG: KASAN: vmalloc-out-of-bounds in __bpf_prog_enter_recur+0x3a5/0x3f0 Read of size 8 at addr ffffc90000055040 by task candidate/110 CPU: 1 UID: 0 PID: 110 Comm: candidate Not tainted 7.3.0-rc2-00014-g15071f2a1263-dirty #2 PREEMPT(full) Call Trace: __bpf_prog_enter_recur+0x3a5/0x3f0 bpf_trampoline_6442509193+0x37/0xf1 __x64_sys_futex+0x9/0x410 do_syscall_64+0xb0/0x530 ... Wait for an RCU tasks grace period before the existing one when freeing a prog that was linked to a trampoline. An RCU tasks grace period only ends once the tasks that were preempted have run again, and bpf_tramp_image_put() already relies on it to free the image. It doesn't wait for tasks that sleep in the trampoline, the next commit takes care of those. Only progs that were linked to a trampoline can be called this way, so bpf_trampoline_add_prog() marks them and other progs are still freed as before. Fixes: e21aa341785c ("bpf: Fix fexit trampoline.") Reported-by: Junseo Lim Signed-off-by: Florent Revest (Anthropic) Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260926135605.1217928-2-florent.revest@linux.dev Closes: https://lore.kernel.org/bpf/aqdrwVpanH3WGurX@omen-arch/ --- include/linux/bpf.h | 1 + kernel/bpf/syscall.c | 19 ++++++++++++++++++- kernel/bpf/trampoline.c | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 1d2676782d70..a8ec85127571 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1770,6 +1770,7 @@ struct bpf_prog_aux { bool offload_requested; /* Program is bound and offloaded to the netdev. */ bool attach_btf_trace; /* true if attaching to BTF-enabled raw tp */ bool attach_tracing_prog; /* true if tracing another tracing program */ + bool tramp_linked; /* true if it was ever linked to a trampoline */ bool func_proto_unreliable; bool tail_call_reachable; bool xdp_has_frags; diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 244a939b9d2d..96217b99399d 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2448,6 +2448,21 @@ static void __bpf_prog_put_rcu(struct rcu_head *rcu) bpf_prog_free(aux->prog); } +/* + * Progs called from a trampoline can also be reached by a task that was + * preempted in the trampoline before the prog's enter helper took its RCU + * read lock, wait for those first. + */ +static void __bpf_prog_put_rcu_tasks(struct rcu_head *rcu) +{ + struct bpf_prog *prog = container_of(rcu, struct bpf_prog_aux, rcu)->prog; + + if (prog->sleepable) + call_rcu_tasks_trace(rcu, __bpf_prog_put_rcu); + else + call_rcu(rcu, __bpf_prog_put_rcu); +} + static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred) { bpf_prog_kallsyms_del_all(prog); @@ -2461,7 +2476,9 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred) btf_put(prog->aux->attach_btf); if (deferred) { - if (prog->sleepable) + if (IS_ENABLED(CONFIG_TASKS_RCU) && prog->aux->tramp_linked) + call_rcu_tasks(&prog->aux->rcu, __bpf_prog_put_rcu_tasks); + else if (prog->sleepable) call_rcu_tasks_trace(&prog->aux->rcu, __bpf_prog_put_rcu); else call_rcu(&prog->aux->rcu, __bpf_prog_put_rcu); diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 90b70ea0d370..9d69c066a817 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -907,6 +907,7 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr, } hlist_add_head(&node->tramp_hlist, prog_list); + node->link->prog->aux->tramp_linked = true; if (kind == BPF_TRAMP_FSESSION) { tr->progs_cnt[BPF_TRAMP_FENTRY]++; fexit = fsession_exit(node); From 17637e1a581a22466ac3620a91e099683ff9cc6f Mon Sep 17 00:00:00 2001 From: "Florent Revest (Anthropic)" Date: Sat, 26 Sep 2026 13:55:59 +0000 Subject: [PATCH 359/535] bpf: Skip detached progs in trampoline images that are still in use bpf_tramp_image_put() makes sure a trampoline image is not freed while a task may still be running in it, but nothing similar is done for the progs called by that image. Detach drops the last prog reference right away and the prog is freed after grace periods, on the basis that a task still in the traced function skips the fexit progs once the nop at ip_after_call is patched to a jump. That leaves out a task sleeping in a sleepable prog that runs before the detached one, which no grace period waits for: CPU 0 CPU 1 in image I, sleeping in prog S detach P from I's trampoline -> new image, bpf_tramp_image_put(I) bpf_prog_put(P), last ref grace periods, P freed back from S __bpf_prog_enter(P) call P->bpf_func If S and P are fexit progs the task is already past the patched jump, and fentry only images don't have one. On x86 this is an int3 in poisoned bpf_prog_pack memory: Oops: int3: 0000 [#1] SMP NOPTI CPU: 18 UID: 0 PID: 94573 Comm: x169 Not tainted 6.18.44 #1 PREEMPT(lazy) RIP: 0010:0xffffffffc0601d8d Call Trace: ? bpf_trampoline_6442515411+0x1a4/0x21b bpf_lsm_bprm_committed_creds+0x5/0x10 security_bprm_committed_creds+0x5f/0x70 begin_new_exec+0x2d6/0x410 ... We hit this in production when progs attached through trampolines got detached while their hooks were busy, and it was independently found with a fuzzer and KASAN. Have the JITs emit a patchable nop in front of each prog call sequence and record it in the image. When a prog is detached, patch its nop to a jump over the call sequence. Progs that stay attached keep running for the tasks that are in the image, and ip_after_call isn't needed anymore. The task can be in any image that isn't freed yet, not only in the current one. It sleeps in image I1 that calls S, P and Q, then P is detached and the trampoline moves to image I2, then Q is detached and its call is still in I1. So the trampoline keeps a list of its images until they are freed, and detaching a prog patches its nop in all of them. Images hold a reference on the trampoline for that long. On riscv and loongarch a jump of any range takes several instructions, and a task preempted in the middle of them could resume into half of the new sequence. The nop is a single instruction there, patched to a near branch through arch_bpf_trampoline_skip(). With the extra nops, BPF_MAX_TRAMP_LINKS progs no longer fit in a page on x86 and arm64 (and already didn't on powerpc), so lower the limit there like s390 does. Fixes: e21aa341785c ("bpf: Fix fexit trampoline.") Reported-by: Sechang Lim Suggested-by: Alexei Starovoitov Signed-off-by: Florent Revest (Anthropic) Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260926135605.1217928-3-florent.revest@linux.dev Closes: https://lore.kernel.org/bpf/20260815071927.147049-1-zirajs7@gmail.com/ --- arch/arm64/net/bpf_jit_comp.c | 37 +++++++++------ arch/loongarch/net/bpf_jit.c | 60 +++++++++++++++-------- arch/powerpc/net/bpf_jit_comp.c | 45 +++++++++--------- arch/riscv/net/bpf_jit_comp64.c | 56 +++++++++++++++------- arch/s390/net/bpf_jit_comp.c | 46 +++++++++++------- arch/x86/net/bpf_jit_comp.c | 26 ++++++---- include/linux/bpf.h | 44 +++++++++++++++-- kernel/bpf/trampoline.c | 84 ++++++++++++++++++++++++++------- 8 files changed, 277 insertions(+), 121 deletions(-) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c5f55d6161fe..c1279fabce47 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -2418,10 +2418,11 @@ bool bpf_jit_supports_subprog_tailcalls(void) return true; } -static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, - int bargs_off, int retval_off, int run_ctx_off, - bool save_ret) +static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_node *node, int bargs_off, + int retval_off, int run_ctx_off, bool save_ret) { + void *skip; __le32 *branch; u64 enter_prog; u64 exit_prog; @@ -2431,6 +2432,10 @@ static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, enter_prog = (u64)bpf_trampoline_enter(p); exit_prog = (u64)bpf_trampoline_exit(p); + /* nop, patched to skip this prog when it is detached */ + skip = ctx->ro_image + ctx->idx; + emit(A64_NOP, ctx); + if (node->cookie == 0) { /* if cookie is zero, one instruction is enough to store it */ emit(A64_STR64I(A64_ZR, A64_SP, run_ctx_off + cookie_off), ctx); @@ -2483,11 +2488,13 @@ static void invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *node, emit(A64_ADD_I(1, A64_R(2), A64_SP, run_ctx_off), ctx); emit_call(exit_prog, ctx); + + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_image + ctx->idx); } -static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, - int bargs_off, int retval_off, int run_ctx_off, - __le32 **branches) +static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_nodes *tn, int bargs_off, + int retval_off, int run_ctx_off, __le32 **branches) { int i; @@ -2496,7 +2503,7 @@ static void invoke_bpf_mod_ret(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, */ emit(A64_STR64I(A64_ZR, A64_SP, retval_off), ctx); for (i = 0; i < tn->nr_nodes; i++) { - invoke_bpf_prog(ctx, tn->nodes[i], bargs_off, retval_off, + invoke_bpf_prog(ctx, im, tn->nodes[i], bargs_off, retval_off, run_ctx_off, true); /* if (*(u64 *)(sp + retval_off) != 0) * goto do_fexit; @@ -2882,7 +2889,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, store_func_meta(ctx, meta, func_meta_off); cookie_bargs_off--; } - invoke_bpf_prog(ctx, fentry->nodes[i], bargs_off, + invoke_bpf_prog(ctx, im, fentry->nodes[i], bargs_off, retval_off, run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET); } @@ -2893,7 +2900,7 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, if (!branches) return -ENOMEM; - invoke_bpf_mod_ret(ctx, fmod_ret, bargs_off, retval_off, + invoke_bpf_mod_ret(ctx, im, fmod_ret, bargs_off, retval_off, run_ctx_off, branches); } @@ -2906,9 +2913,6 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, emit(A64_RET(A64_R(10)), ctx); /* store return value */ emit(A64_STR64I(A64_R(0), A64_SP, retval_off), ctx); - /* reserve a nop for bpf_tramp_image_put */ - im->ip_after_call = ctx->ro_image + ctx->idx; - emit(A64_NOP, ctx); } /* update the branches saved in invoke_bpf_mod_ret with cbnz */ @@ -2930,12 +2934,11 @@ static int prepare_trampoline(struct jit_ctx *ctx, struct bpf_tramp_image *im, store_func_meta(ctx, meta, func_meta_off); cookie_bargs_off--; } - invoke_bpf_prog(ctx, fexit->nodes[i], bargs_off, retval_off, + invoke_bpf_prog(ctx, im, fexit->nodes[i], bargs_off, retval_off, run_ctx_off, false); } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_image + ctx->idx; /* for the first pass, assume the worst case */ if (!ctx->image) ctx->idx += 4; @@ -2994,7 +2997,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, .image = NULL, .idx = 0, }; - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct arg_aux aaux; int ret; @@ -3281,6 +3284,10 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, * longer reachable, since bpf_tramp_image_put() function already * uses percpu_ref and task-based rcu to do the sync, no need to call * the sync version here, see bpf_tramp_image_put() for details. + * + * 3. when a detached prog is patched out of a trampoline, a CPU that + * still executes the old nop calls the prog before it went through + * a quiescent state, and the prog is freed after grace periods. */ ret = aarch64_insn_patch_text_nosync(ip, new_insn); out: diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 4da278900938..e78eb582c300 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1578,6 +1578,24 @@ void *bpf_arch_text_copy(void *dst, void *src, size_t len) return ret ? ERR_PTR(-EINVAL) : dst; } +int arch_bpf_trampoline_skip(void *nop, void *target) +{ + u32 old_insn = INSN_NOP; + u32 new_insn = larch_insn_gen_b((unsigned long)nop, (unsigned long)target); + int ret; + + if (memcmp(nop, &old_insn, LOONGARCH_INSN_SIZE)) + return -EFAULT; + + cpus_read_lock(); + mutex_lock(&text_mutex); + ret = larch_insn_text_copy(nop, &new_insn, LOONGARCH_INSN_SIZE); + mutex_unlock(&text_mutex); + cpus_read_unlock(); + + return ret; +} + int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, enum bpf_text_poke_type new_t, void *old_addr, void *new_addr) @@ -1696,13 +1714,18 @@ static void restore_stk_args(struct jit_ctx *ctx, int nr_stk_args, int args_off, } } -static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *n, - int args_off, int retval_off, int run_ctx_off, bool save_ret) +static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_node *n, int args_off, int retval_off, + int run_ctx_off, bool save_ret) { int ret; u32 *branch; struct bpf_prog *p = n->link->prog; int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); + void *skip = ctx->ro_image + ctx->idx; + + /* nop, patched to a b over this prog when it is detached */ + emit_insn(ctx, nop); if (n->cookie) emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, @@ -1755,13 +1778,17 @@ static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_node *n, /* arg3: &run_ctx */ emit_insn(ctx, addid, LOONGARCH_GPR_A2, LOONGARCH_GPR_FP, -run_ctx_off); ret = emit_call(ctx, (const u64)bpf_trampoline_exit(p)); + if (ret) + return ret; - return ret; + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_image + ctx->idx); + return 0; } -static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, - int args_off, int retval_off, int run_ctx_off, - int func_meta_off, bool save_ret, u64 func_meta, int cookie_off) +static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_image *im, + struct bpf_tramp_nodes *tn, int args_off, int retval_off, + int run_ctx_off, int func_meta_off, bool save_ret, + u64 func_meta, int cookie_off) { int i, cur_cookie = (cookie_off - args_off) / 8; @@ -1774,7 +1801,8 @@ static int invoke_bpf(struct jit_ctx *ctx, struct bpf_tramp_nodes *tn, emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, -func_meta_off, meta); cur_cookie--; } - err = invoke_bpf_prog(ctx, tn->nodes[i], args_off, retval_off, run_ctx_off, save_ret); + err = invoke_bpf_prog(ctx, im, tn->nodes[i], args_off, retval_off, + run_ctx_off, save_ret); if (err) return err; } @@ -2017,7 +2045,7 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i } if (fentry->nr_nodes) { - ret = invoke_bpf(ctx, fentry, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(ctx, im, fentry, args_off, retval_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off); if (ret) return ret; @@ -2029,7 +2057,7 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i emit_insn(ctx, std, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_FP, -retval_off); for (i = 0; i < fmod_ret->nr_nodes; i++) { - ret = invoke_bpf_prog(ctx, fmod_ret->nodes[i], + ret = invoke_bpf_prog(ctx, im, fmod_ret->nodes[i], args_off, retval_off, run_ctx_off, true); if (ret) goto out; @@ -2051,10 +2079,6 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i goto out; emit_insn(ctx, std, LOONGARCH_GPR_A0, LOONGARCH_GPR_FP, -retval_off); emit_insn(ctx, std, regmap[BPF_REG_0], LOONGARCH_GPR_FP, -(retval_off - 8)); - im->ip_after_call = ctx->ro_image + ctx->idx; - /* Reserve space for the move_imm + jirl instruction */ - for (i = 0; i < LOONGARCH_LONG_JUMP_NINSNS; i++) - emit_insn(ctx, nop); } for (i = 0; ctx->image && i < fmod_ret->nr_nodes; i++) { @@ -2068,14 +2092,13 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, -func_meta_off, func_meta); if (fexit->nr_nodes) { - ret = invoke_bpf(ctx, fexit, args_off, retval_off, run_ctx_off, + ret = invoke_bpf(ctx, im, fexit, args_off, retval_off, run_ctx_off, func_meta_off, false, func_meta, cookie_off); if (ret) goto out; } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_image + ctx->idx; move_addr(ctx, LOONGARCH_GPR_A0, (const u64)im); ret = emit_call(ctx, (const u64)__bpf_tramp_exit); if (ret) @@ -2177,11 +2200,8 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { int ret; - struct jit_ctx ctx; - struct bpf_tramp_image im; - - ctx.image = NULL; - ctx.idx = 0; + struct jit_ctx ctx = {}; + struct bpf_tramp_image im = {}; ret = __arch_prepare_bpf_trampoline(&ctx, &im, m, tnodes, func_addr, flags); diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c index 7b07b43575f1..7a612688e7a2 100644 --- a/arch/powerpc/net/bpf_jit_comp.c +++ b/arch/powerpc/net/bpf_jit_comp.c @@ -602,14 +602,18 @@ int arch_protect_bpf_trampoline(void *image, unsigned int size) } static int invoke_bpf_prog(u32 *image, u32 *ro_image, struct codegen_context *ctx, - struct bpf_tramp_node *n, int regs_off, int retval_off, - int run_ctx_off, bool save_ret) + struct bpf_tramp_image *im, struct bpf_tramp_node *n, + int regs_off, int retval_off, int run_ctx_off, bool save_ret) { struct bpf_prog *p = n->link->prog; ppc_inst_t branch_insn; - u32 jmp_idx; + u32 jmp_idx, skip_idx; int ret = 0; + /* nop, patched to skip this prog when it is detached */ + skip_idx = ctx->idx; + EMIT(PPC_RAW_NOP()); + /* Save cookie */ if (IS_ENABLED(CONFIG_PPC64)) { PPC_LI64(_R3, n->cookie); @@ -679,13 +683,17 @@ static int invoke_bpf_prog(u32 *image, u32 *ro_image, struct codegen_context *ct EMIT(PPC_RAW_ADDI(_R5, _R1, run_ctx_off)); ret = bpf_jit_emit_func_call_rel(image, ro_image, ctx, (unsigned long)bpf_trampoline_exit(p)); + if (ret) + return ret; - return ret; + if (ro_image) /* image is NULL for dummy pass */ + bpf_tramp_image_add_skip(im, p, &ro_image[skip_idx], &ro_image[ctx->idx]); + return 0; } static int invoke_bpf_mod_ret(u32 *image, u32 *ro_image, struct codegen_context *ctx, - struct bpf_tramp_nodes *tn, int regs_off, int retval_off, - int run_ctx_off, u32 *branches) + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tn, + int regs_off, int retval_off, int run_ctx_off, u32 *branches) { int i; @@ -696,8 +704,8 @@ static int invoke_bpf_mod_ret(u32 *image, u32 *ro_image, struct codegen_context EMIT(PPC_RAW_LI(_R3, 0)); EMIT(PPC_RAW_STL(_R3, _R1, retval_off)); for (i = 0; i < tn->nr_nodes; i++) { - if (invoke_bpf_prog(image, ro_image, ctx, tn->nodes[i], regs_off, retval_off, - run_ctx_off, true)) + if (invoke_bpf_prog(image, ro_image, ctx, im, tn->nodes[i], regs_off, + retval_off, run_ctx_off, true)) return -EINVAL; /* @@ -1043,8 +1051,8 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im cookie_ctx_off--; } - if (invoke_bpf_prog(image, ro_image, ctx, fentry->nodes[i], regs_off, retval_off, - run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET)) + if (invoke_bpf_prog(image, ro_image, ctx, im, fentry->nodes[i], regs_off, + retval_off, run_ctx_off, flags & BPF_TRAMP_F_RET_FENTRY_RET)) return -EINVAL; } @@ -1053,7 +1061,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im if (!branches) return -ENOMEM; - if (invoke_bpf_mod_ret(image, ro_image, ctx, fmod_ret, regs_off, retval_off, + if (invoke_bpf_mod_ret(image, ro_image, ctx, im, fmod_ret, regs_off, retval_off, run_ctx_off, branches)) { ret = -EINVAL; goto cleanup; @@ -1090,11 +1098,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im /* Restore updated tail_call_cnt */ if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) bpf_trampoline_restore_tail_call_cnt(image, ctx, bpf_frame_size, r4_off); - - /* Reserve space to patch branch instruction to skip fexit progs */ - if (ro_image) /* image is NULL for dummy pass */ - im->ip_after_call = &((u32 *)ro_image)[ctx->idx]; - EMIT(PPC_RAW_NOP()); } /* Update branches saved in invoke_bpf_mod_ret with address of do_fexit */ @@ -1123,16 +1126,14 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im cookie_ctx_off--; } - if (invoke_bpf_prog(image, ro_image, ctx, fexit->nodes[i], regs_off, retval_off, - run_ctx_off, false)) { + if (invoke_bpf_prog(image, ro_image, ctx, im, fexit->nodes[i], regs_off, + retval_off, run_ctx_off, false)) { ret = -EINVAL; goto cleanup; } } if (flags & BPF_TRAMP_F_CALL_ORIG) { - if (ro_image) /* image is NULL for dummy pass */ - im->ip_epilogue = &((u32 *)ro_image)[ctx->idx]; PPC_LI_ADDR(_R3, im); ret = bpf_jit_emit_func_call_rel(image, ro_image, ctx, (unsigned long)__bpf_tramp_exit); @@ -1192,7 +1193,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; int ret; ret = __arch_prepare_bpf_trampoline(&im, NULL, NULL, NULL, m, flags, tnodes, func_addr); @@ -1320,7 +1321,7 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, /* * If we are not poking at bpf prog entry, then we are simply patching in/out - * an unconditional branch instruction at im->ip_after_call + * an unconditional branch instruction in a trampoline image */ if (offset) { if (old_t == BPF_MOD_CALL || new_t == BPF_MOD_CALL) { diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c index 151031e97a24..01fe66774f02 100644 --- a/arch/riscv/net/bpf_jit_comp64.c +++ b/arch/riscv/net/bpf_jit_comp64.c @@ -822,6 +822,24 @@ static int gen_jump_or_nops(void *target, void *ip, u32 *insns, bool is_call) return emit_jump_and_link(is_call ? RV_REG_T0 : RV_REG_ZERO, rvoff, false, &ctx); } +int arch_bpf_trampoline_skip(void *nop, void *target) +{ + u32 old_insn = rv_nop(); + u32 new_insn = rv_jal(RV_REG_ZERO, (target - nop) >> 1); + int ret; + + if (memcmp(nop, &old_insn, sizeof(old_insn))) + return -EFAULT; + + cpus_read_lock(); + mutex_lock(&text_mutex); + ret = patch_text(nop, &new_insn, sizeof(new_insn)); + mutex_unlock(&text_mutex); + cpus_read_unlock(); + + return ret; +} + int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type old_t, enum bpf_text_poke_type new_t, void *old_addr, void *new_addr) @@ -904,12 +922,17 @@ static void emit_store_stack_imm64(u8 reg, int stack_off, u64 imm64, emit_sd(RV_REG_FP, stack_off, reg, ctx); } -static int invoke_bpf_prog(struct bpf_tramp_node *node, int args_off, int retval_off, - int run_ctx_off, bool save_ret, struct rv_jit_context *ctx) +static int invoke_bpf_prog(struct bpf_tramp_image *im, struct bpf_tramp_node *node, + int args_off, int retval_off, int run_ctx_off, bool save_ret, + struct rv_jit_context *ctx) { int ret, branch_off; struct bpf_prog *p = node->link->prog; int cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); + void *skip = ctx->ro_insns + ctx->ninsns; + + /* nop, patched to a jal over this prog when it is detached */ + emit(rv_nop(), ctx); if (node->cookie) emit_store_stack_imm64(RV_REG_T1, -run_ctx_off + cookie_off, node->cookie, ctx); @@ -962,13 +985,17 @@ static int invoke_bpf_prog(struct bpf_tramp_node *node, int args_off, int retval /* arg3: &run_ctx */ emit_addi(RV_REG_A2, RV_REG_FP, -run_ctx_off, ctx); ret = emit_call((const u64)bpf_trampoline_exit(p), true, ctx); + if (ret) + return ret; - return ret; + bpf_tramp_image_add_skip(im, p, skip, ctx->ro_insns + ctx->ninsns); + return 0; } -static int invoke_bpf(struct bpf_tramp_nodes *tn, int args_off, int retval_off, - int run_ctx_off, int func_meta_off, bool save_ret, u64 func_meta, - int cookie_off, struct rv_jit_context *ctx) +static int invoke_bpf(struct bpf_tramp_image *im, struct bpf_tramp_nodes *tn, + int args_off, int retval_off, int run_ctx_off, int func_meta_off, + bool save_ret, u64 func_meta, int cookie_off, + struct rv_jit_context *ctx) { int i, cur_cookie = (cookie_off - args_off) / 8; @@ -981,8 +1008,8 @@ static int invoke_bpf(struct bpf_tramp_nodes *tn, int args_off, int retval_off, emit_store_stack_imm64(RV_REG_T1, -func_meta_off, meta, ctx); cur_cookie--; } - err = invoke_bpf_prog(tn->nodes[i], args_off, retval_off, run_ctx_off, - save_ret, ctx); + err = invoke_bpf_prog(im, tn->nodes[i], args_off, retval_off, + run_ctx_off, save_ret, ctx); if (err) return err; } @@ -1170,7 +1197,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, } if (fentry->nr_nodes) { - ret = invoke_bpf(fentry, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(im, fentry, args_off, retval_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off, ctx); if (ret) return ret; @@ -1184,7 +1211,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* cleanup to avoid garbage return value confusion */ emit_sd(RV_REG_FP, -retval_off, RV_REG_ZERO, ctx); for (i = 0; i < fmod_ret->nr_nodes; i++) { - ret = invoke_bpf_prog(fmod_ret->nodes[i], args_off, retval_off, + ret = invoke_bpf_prog(im, fmod_ret->nodes[i], args_off, retval_off, run_ctx_off, true, ctx); if (ret) goto out; @@ -1211,10 +1238,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_sd(RV_REG_FP, -tcc_off, RV_REG_TCC, ctx); emit_sd(RV_REG_FP, -retval_off, RV_REG_A0, ctx); emit_sd(RV_REG_FP, -(retval_off - 8), regmap[BPF_REG_0], ctx); - im->ip_after_call = ctx->ro_insns + ctx->ninsns; - /* 2 nops reserved for auipc+jalr pair */ - emit(rv_nop(), ctx); - emit(rv_nop(), ctx); } /* update branches saved in invoke_bpf_mod_ret with bnez */ @@ -1230,14 +1253,13 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_store_stack_imm64(RV_REG_T1, -func_meta_off, func_meta, ctx); if (fexit->nr_nodes) { - ret = invoke_bpf(fexit, args_off, retval_off, run_ctx_off, func_meta_off, + ret = invoke_bpf(im, fexit, args_off, retval_off, run_ctx_off, func_meta_off, false, func_meta, cookie_off, ctx); if (ret) goto out; } if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = ctx->ro_insns + ctx->ninsns; emit_imm(RV_REG_A0, ctx->insns ? (const s64)im : RV_MAX_COUNT_IMM, ctx); ret = emit_call((const u64)__bpf_tramp_exit, true, ctx); if (ret) @@ -1299,7 +1321,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct rv_jit_context ctx; int ret; diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c index c4b47070bb59..1b2566012b63 100644 --- a/arch/s390/net/bpf_jit_comp.c +++ b/arch/s390/net/bpf_jit_comp.c @@ -2566,6 +2566,8 @@ struct bpf_tramp_jit { int r14_off; /* Offset of saved %r14, has to be at the * bottom */ int do_fexit; /* do_fexit: label */ + int skip[BPF_MAX_TRAMP_LINKS]; /* skip: labels after each prog */ + int nr_progs; }; static void load_imm64(struct bpf_jit *jit, int dst_reg, u64 val) @@ -2584,6 +2586,7 @@ static void emit_store_stack_imm64(struct bpf_jit *jit, int tmp_reg, int stack_o } static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, + struct bpf_tramp_image *im, const struct btf_func_model *m, struct bpf_tramp_node *node, bool save_ret) { @@ -2591,8 +2594,20 @@ static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, int cookie_off = tjit->run_ctx_off + offsetof(struct bpf_tramp_run_ctx, bpf_cookie); struct bpf_prog *p = node->link->prog; + void *skip = jit->prg_buf + jit->prg; + int idx = tjit->nr_progs++; int patch; + if (idx >= ARRAY_SIZE(tjit->skip)) + return -E2BIG; + + /* + * nop, patched to skip this prog when it is detached + */ + + /* brcl 0,skip */ + EMIT6_PCREL_RILC(0xc0040000, 0, tjit->skip[idx]); + /* * run_ctx.cookie = node->cookie; */ @@ -2652,10 +2667,15 @@ static int invoke_bpf_prog(struct bpf_tramp_jit *tjit, /* brasl %r14,__bpf_prog_exit */ EMIT6_PCREL_RILB_PTR(0xc0050000, REG_14, bpf_trampoline_exit(p)); + /* skip: */ + tjit->skip[idx] = jit->prg; + bpf_tramp_image_add_skip(im, p, skip, jit->prg_buf + jit->prg); + return 0; } static int invoke_bpf(struct bpf_tramp_jit *tjit, + struct bpf_tramp_image *im, const struct btf_func_model *m, struct bpf_tramp_nodes *tn, bool save_ret, u64 func_meta, int cookie_off) @@ -2670,7 +2690,7 @@ static int invoke_bpf(struct bpf_tramp_jit *tjit, emit_store_stack_imm64(jit, REG_0, tjit->func_meta_off, meta); cur_cookie--; } - if (invoke_bpf_prog(tjit, m, tn->nodes[i], save_ret)) + if (invoke_bpf_prog(tjit, im, m, tn->nodes[i], save_ret)) return -EINVAL; } @@ -2712,6 +2732,11 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, u64 func_meta; int i, j; + /* The skip labels are taken from the previous pass. */ + tjit->nr_progs = 0; + if (im) + im->nr_skips = 0; + /* Support as many stack arguments as "mvc" instruction can handle. */ nr_reg_args = min_t(int, m->nr_args, MAX_NR_REG_ARGS); nr_stack_args = m->nr_args - nr_reg_args; @@ -2875,7 +2900,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, emit_store_stack_imm64(jit, REG_0, tjit->retval_off, 0); } - if (invoke_bpf(tjit, m, fentry, flags & BPF_TRAMP_F_RET_FENTRY_RET, + if (invoke_bpf(tjit, im, m, fentry, flags & BPF_TRAMP_F_RET_FENTRY_RET, func_meta, cookie_off)) return -EINVAL; @@ -2889,7 +2914,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, 0xf000 | tjit->retval_off); for (i = 0; i < fmod_ret->nr_nodes; i++) { - if (invoke_bpf_prog(tjit, m, fmod_ret->nodes[i], true)) + if (invoke_bpf_prog(tjit, im, m, fmod_ret->nodes[i], true)) return -EINVAL; /* @@ -2943,15 +2968,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* mvc tccnt_off(%r15),tail_call_cnt(4,%r15) */ _EMIT6(0xd203f000 | tjit->tccnt_off, 0xf000 | offsetof(struct prog_frame, tail_call_cnt)); - - im->ip_after_call = jit->prg_buf + jit->prg; - - /* - * The following nop will be patched by bpf_tramp_image_put(). - */ - - /* brcl 0,im->ip_epilogue */ - EMIT6_PCREL_RILC(0xc0040000, 0, (u64)im->ip_epilogue); } /* Set the "is_return" flag for fsession. */ @@ -2962,12 +2978,10 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, /* do_fexit: */ tjit->do_fexit = jit->prg; - if (invoke_bpf(tjit, m, fexit, false, func_meta, cookie_off)) + if (invoke_bpf(tjit, im, m, fexit, false, func_meta, cookie_off)) return -EINVAL; if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = jit->prg_buf + jit->prg; - /* * __bpf_tramp_exit(im); */ @@ -3016,7 +3030,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *orig_call) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; struct bpf_tramp_jit tjit; int ret; diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index 2853e87797a7..6bca87457e87 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -3217,16 +3217,21 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog, } static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_node *node, int stack_size, int run_ctx_off, bool save_ret, void *image, void *rw_image) { u8 *prog = *pprog; - u8 *jmp_insn; + u8 *jmp_insn, *skip; int ctx_cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie); struct bpf_prog *p = node->link->prog; u64 cookie = node->cookie; + /* nop, patched to skip this prog when it is detached */ + skip = image + (prog - (u8 *)rw_image); + emit_nops(&prog, X86_PATCH_SIZE); + /* mov rdi, cookie */ emit_mov_imm64(&prog, BPF_REG_1, (long) cookie >> 32, (u32) (long) cookie); @@ -3301,6 +3306,8 @@ static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog, if (emit_rsb_call(&prog, bpf_trampoline_exit(p), image + (prog - (u8 *)rw_image))) return -EINVAL; + bpf_tramp_image_add_skip(im, p, skip, image + (prog - (u8 *)rw_image)); + *pprog = prog; return 0; } @@ -3332,6 +3339,7 @@ static int emit_cond_near_jump(u8 **pprog, void *func, void *ip, u8 jmp_cond) } static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tl, int stack_size, int run_ctx_off, int func_meta_off, bool save_ret, void *image, void *rw_image, u64 func_meta, @@ -3346,7 +3354,7 @@ static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, func_meta | (cur_cookie << BPF_TRAMP_COOKIE_INDEX_SHIFT)); cur_cookie--; } - if (invoke_bpf_prog(m, &prog, tl->nodes[i], stack_size, + if (invoke_bpf_prog(m, &prog, im, tl->nodes[i], stack_size, run_ctx_off, save_ret, image, rw_image)) return -EINVAL; } @@ -3355,6 +3363,7 @@ static int invoke_bpf(const struct btf_func_model *m, u8 **pprog, } static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog, + struct bpf_tramp_image *im, struct bpf_tramp_nodes *tl, int stack_size, int run_ctx_off, u8 **branches, void *image, void *rw_image) @@ -3368,7 +3377,7 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog, emit_mov_imm32(&prog, false, BPF_REG_0, 0); emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8); for (i = 0; i < tl->nr_nodes; i++) { - if (invoke_bpf_prog(m, &prog, tl->nodes[i], stack_size, run_ctx_off, true, + if (invoke_bpf_prog(m, &prog, im, tl->nodes[i], stack_size, run_ctx_off, true, image, rw_image)) return -EINVAL; @@ -3640,7 +3649,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im } if (fentry->nr_nodes) { - if (invoke_bpf(m, &prog, fentry, regs_off, run_ctx_off, func_meta_off, + if (invoke_bpf(m, &prog, im, fentry, regs_off, run_ctx_off, func_meta_off, flags & BPF_TRAMP_F_RET_FENTRY_RET, image, rw_image, func_meta, cookie_off)) return -EINVAL; @@ -3652,7 +3661,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im if (!branches) return -ENOMEM; - if (invoke_bpf_mod_ret(m, &prog, fmod_ret, regs_off, + if (invoke_bpf_mod_ret(m, &prog, im, fmod_ret, regs_off, run_ctx_off, branches, image, rw_image)) { ret = -EINVAL; goto cleanup; @@ -3682,8 +3691,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im } /* remember return value in a stack for bpf prog to access */ emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8); - im->ip_after_call = image + (prog - (u8 *)rw_image); - emit_nops(&prog, X86_PATCH_SIZE); } if (fmod_ret->nr_nodes) { @@ -3708,7 +3715,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im emit_store_stack_imm64(&prog, BPF_REG_0, -func_meta_off, func_meta); if (fexit->nr_nodes) { - if (invoke_bpf(m, &prog, fexit, regs_off, run_ctx_off, func_meta_off, + if (invoke_bpf(m, &prog, im, fexit, regs_off, run_ctx_off, func_meta_off, false, image, rw_image, func_meta, cookie_off)) { ret = -EINVAL; goto cleanup; @@ -3723,7 +3730,6 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im * restored to R0. */ if (flags & BPF_TRAMP_F_CALL_ORIG) { - im->ip_epilogue = image + (prog - (u8 *)rw_image); /* arg1: mov rdi, im */ emit_mov_imm64(&prog, BPF_REG_1, (long) im >> 32, (u32) (long) im); if (emit_rsb_call(&prog, __bpf_tramp_exit, image + (prog - (u8 *)rw_image))) { @@ -3811,7 +3817,7 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr) { - struct bpf_tramp_image im; + struct bpf_tramp_image im = {}; void *image; int ret; diff --git a/include/linux/bpf.h b/include/linux/bpf.h index a8ec85127571..0ecb9418dfbd 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1258,11 +1258,15 @@ struct btf_func_model { #define BPF_TRAMP_F_INDIRECT BIT(8) /* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50 - * bytes on x86. + * bytes on x86. The trampoline image has to fit in PAGE_SIZE. */ enum { -#if defined(__s390x__) +#if defined(__s390x__) || defined(__powerpc64__) BPF_MAX_TRAMP_LINKS = 27, +#elif defined(__x86_64__) + BPF_MAX_TRAMP_LINKS = 36, +#elif defined(__aarch64__) + BPF_MAX_TRAMP_LINKS = 37, #else BPF_MAX_TRAMP_LINKS = 38, #endif @@ -1315,6 +1319,7 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i void *arch_alloc_bpf_trampoline(unsigned int size); void arch_free_bpf_trampoline(void *image, unsigned int size); int __must_check arch_protect_bpf_trampoline(void *image, unsigned int size); +int arch_bpf_trampoline_skip(void *nop, void *target); int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags, struct bpf_tramp_nodes *tnodes, void *func_addr); @@ -1363,19 +1368,48 @@ enum bpf_tramp_prog_type { BPF_TRAMP_FSESSION, }; +/* + * Each prog call in a trampoline image is preceded by a nop. When the prog is + * detached, the nop is patched to a jump to target, right after the call, so + * that tasks still running in the image skip the prog. + */ +struct bpf_tramp_skip { + struct bpf_prog *prog; + void *nop; + void *target; +}; + struct bpf_tramp_image { void *image; int size; struct bpf_ksym ksym; struct percpu_ref pcref; - void *ip_after_call; - void *ip_epilogue; + bool call_orig; + /* entry in tr->images, the image holds a reference on tr */ + struct bpf_trampoline *tr; + struct list_head list; + int nr_skips; + struct bpf_tramp_skip *skips; union { struct rcu_head rcu; struct work_struct work; }; }; +static inline void bpf_tramp_image_add_skip(struct bpf_tramp_image *im, struct bpf_prog *prog, + void *nop, void *target) +{ + struct bpf_tramp_skip *skip; + + /* struct_ops trampolines and arch_bpf_trampoline_size() have no image */ + if (!im || !im->skips) + return; + skip = &im->skips[im->nr_skips++]; + skip->prog = prog; + skip->nop = nop; + skip->target = target; +} + struct bpf_trampoline { /* hlist for trampoline_key_table */ struct hlist_node hlist_key; @@ -1402,6 +1436,8 @@ struct bpf_trampoline { int progs_cnt[BPF_TRAMP_MAX]; /* Executable image of trampoline */ struct bpf_tramp_image *cur_image; + /* Images not freed yet, cur_image and older ones still in use */ + struct list_head images; /* Used as temporary old image storage for multi_attach */ struct { struct bpf_tramp_image *old_image; diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 9d69c066a817..bf4cb3dd444d 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -401,6 +401,7 @@ static struct bpf_trampoline *bpf_trampoline_lookup(u64 key, unsigned long ip) head = &trampoline_ip_table[hash_64(tr->ip, TRAMPOLINE_HASH_BITS)]; hlist_add_head(&tr->hlist_ip, head); refcount_set(&tr->refcnt, 1); + INIT_LIST_HEAD(&tr->images); for (i = 0; i < BPF_TRAMP_MAX; i++) INIT_HLIST_HEAD(&tr->progs_hlist[i]); out: @@ -565,15 +566,22 @@ static void bpf_tramp_image_free(struct bpf_tramp_image *im) arch_free_bpf_trampoline(im->image, im->size); bpf_jit_uncharge_modmem(im->size); percpu_ref_exit(&im->pcref); + kfree(im->skips); kfree_rcu(im, rcu); } static void __bpf_tramp_image_put_deferred(struct work_struct *work) { struct bpf_tramp_image *im; + struct bpf_trampoline *tr; im = container_of(work, struct bpf_tramp_image, work); + tr = im->tr; + trampoline_lock(tr); + list_del(&im->list); + trampoline_unlock(tr); bpf_tramp_image_free(im); + bpf_trampoline_put(tr); } /* callback, fexit step 3 or fentry step 2 */ @@ -601,7 +609,7 @@ static void __bpf_tramp_image_put_rcu_tasks(struct rcu_head *rcu) struct bpf_tramp_image *im; im = container_of(rcu, struct bpf_tramp_image, rcu); - if (im->ip_after_call) + if (im->call_orig) /* the case of fmod_ret/fexit trampoline and CONFIG_PREEMPTION=y */ percpu_ref_kill(&im->pcref); else @@ -621,9 +629,9 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) * * The trampoline is unreachable before bpf_tramp_image_put(). * - * First, patch the trampoline to avoid calling into fexit progs. - * The progs will be freed even if the original function is still - * executing or sleeping. + * Progs are patched out of the image when they are detached, see + * bpf_trampoline_skip_prog(), so they can be freed even if a task is + * still in the image. * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on * first few asm instructions to execute and call into * __bpf_tramp_enter->percpu_ref_get. @@ -637,11 +645,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) * percpu_ref_kill will be waiting for. Hence the first * call_rcu_tasks() is not necessary. */ - if (im->ip_after_call) { - int err = bpf_arch_text_poke(im->ip_after_call, BPF_MOD_NOP, - BPF_MOD_JUMP, NULL, - im->ip_epilogue); - WARN_ON(err); + if (im->call_orig) { if (IS_ENABLED(CONFIG_TASKS_RCU)) call_rcu_tasks(&im->rcu, __bpf_tramp_image_put_rcu_tasks); else @@ -658,7 +662,7 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im) call_rcu_tasks_trace(&im->rcu, __bpf_tramp_image_put_rcu_tasks); } -static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) +static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size, int nr_progs) { struct bpf_tramp_image *im; struct bpf_ksym *ksym; @@ -669,6 +673,10 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) if (!im) goto out; + im->skips = kzalloc_objs(*im->skips, nr_progs); + if (!im->skips) + goto out_free_im; + err = bpf_jit_charge_modmem(size); if (err) goto out_free_im; @@ -695,6 +703,7 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, int size) out_uncharge: bpf_jit_uncharge_modmem(size); out_free_im: + kfree(im->skips); kfree(im); out: return ERR_PTR(err); @@ -771,11 +780,12 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut goto out; } - im = bpf_tramp_image_alloc(tr->key, size); + im = bpf_tramp_image_alloc(tr->key, size, total); if (IS_ERR(im)) { err = PTR_ERR(im); goto out; } + im->call_orig = tr->flags & BPF_TRAMP_F_CALL_ORIG; err = arch_prepare_bpf_trampoline(im, im->image, im->image + size, &tr->func.model, tr->flags, tnodes, @@ -806,8 +816,14 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut #endif out_free: - if (err) + if (err) { bpf_tramp_image_free(im); + } else { + /* track the image until it is freed, for bpf_trampoline_skip_prog() */ + refcount_inc(&tr->refcnt); + im->tr = tr; + list_add(&im->list, &tr->images); + } out: /* If any error happens, restore previous flags */ if (err) @@ -921,6 +937,41 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr, return 0; } +/* + * Patch the nop in front of a prog call to a jump over it. A task can be + * preempted anywhere in the image, so archs that need several instructions for + * a jump of any range patch a single near branch here instead. + */ +int __weak arch_bpf_trampoline_skip(void *nop, void *target) +{ + return bpf_arch_text_poke(nop, BPF_MOD_NOP, BPF_MOD_JUMP, NULL, target); +} + +/* + * prog was detached and can be freed, but tasks may still be running in images + * that call it, sleeping in an earlier prog for example. They can be in any + * image that is not freed yet, not only in cur_image, so patch all of them to + * jump over prog. + */ +static void bpf_trampoline_skip_prog(struct bpf_trampoline *tr, struct bpf_prog *prog) +{ + struct bpf_tramp_image *im; + int i, err; + + list_for_each_entry(im, &tr->images, list) { + for (i = 0; i < im->nr_skips; i++) { + struct bpf_tramp_skip *skip = &im->skips[i]; + + if (skip->prog != prog) + continue; + err = arch_bpf_trampoline_skip(skip->nop, skip->target); + WARN_ON_ONCE(err); + /* not a nop anymore, and prog's address can be reused */ + skip->prog = NULL; + } + } +} + static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr, struct bpf_tramp_node *node) { @@ -938,6 +989,7 @@ static void bpf_trampoline_remove_prog(struct bpf_trampoline *tr, } hlist_del_init(&node->tramp_hlist); tr->progs_cnt[kind]--; + bpf_trampoline_skip_prog(tr, node->link->prog); } static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node, @@ -1246,11 +1298,9 @@ void bpf_trampoline_put(struct bpf_trampoline *tr) if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[i]))) goto out; - /* This code will be executed even when the last bpf_tramp_image - * is alive. All progs are detached from the trampoline and the - * trampoline image is patched with jmp into epilogue to skip - * fexit progs. The fentry-only trampoline will be freed via - * multiple rcu callbacks. + /* + * All progs are detached and the last image has been freed, images + * hold a reference on the trampoline until then. */ hlist_del(&tr->hlist_key); hlist_del(&tr->hlist_ip); From d5288c519bbd0df9dff9f8dce2a8ce24e2ca9c8a Mon Sep 17 00:00:00 2001 From: "Florent Revest (Anthropic)" Date: Sat, 26 Sep 2026 13:56:00 +0000 Subject: [PATCH 360/535] selftests/bpf: Detach a trampoline prog while a task sleeps before it Add a test for the use-after-free fixed by the previous commit. A sleepable prog on bpf_fentry_test1() blocks a task on a userfaultfd page, like bpf_mod_race does, while the two progs that run after it in the same trampoline are detached and freed one after the other, which the test knows from their .bss maps going away. After the first detach the task is in an image that isn't the trampoline's current one anymore. The task is then released and must not call into the freed progs. This is done with fentry progs, with fexit progs, where the task is already past the original function, and with a task sleeping in a fentry prog while fexit progs are detached, where the original function must still be called. bpf_mod_race's userfaultfd helper moves to testing_helpers.c so that both tests can use it. Without the fix, on a kernel with KASAN: BUG: KASAN: vmalloc-out-of-bounds in __bpf_prog_enter_recur+0xed/0x1e0 Read of size 8 at addr ffa0000000144040 by task test_progs/171 CPU: 6 UID: 0 PID: 171 Comm: test_progs Tainted: G OE 7.2.0+ #1 PREEMPT(full) Call Trace: __bpf_prog_enter_recur+0xed/0x1e0 bpf_trampoline_6442545468+0x72/0xe3 bpf_fentry_test1+0x9/0x20 bpf_prog_test_run_tracing+0x183/0x3e0 __sys_bpf+0xd3f/0x38f0 ... Signed-off-by: Florent Revest (Anthropic) Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260926135605.1217928-4-florent.revest@linux.dev --- .../selftests/bpf/prog_tests/bpf_mod_race.c | 34 +--- .../bpf/prog_tests/tramp_prog_detach.c | 188 ++++++++++++++++++ .../selftests/bpf/progs/tramp_prog_detach.c | 56 ++++++ tools/testing/selftests/bpf/testing_helpers.c | 28 +++ tools/testing/selftests/bpf/testing_helpers.h | 2 + 5 files changed, 275 insertions(+), 33 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c create mode 100644 tools/testing/selftests/bpf/progs/tramp_prog_detach.c diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c index ecc3d47919ad..f8497e764beb 100644 --- a/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c +++ b/tools/testing/selftests/bpf/prog_tests/bpf_mod_race.c @@ -55,38 +55,6 @@ static void *load_module_thread(void *p) return p; } -static int sys_userfaultfd(int flags) -{ - return syscall(__NR_userfaultfd, flags); -} - -static int test_setup_uffd(void *fault_addr) -{ - struct uffdio_register uffd_register = {}; - struct uffdio_api uffd_api = {}; - int uffd; - - uffd = sys_userfaultfd(O_CLOEXEC); - if (uffd < 0) - return -errno; - - uffd_api.api = UFFD_API; - uffd_api.features = 0; - if (ioctl(uffd, UFFDIO_API, &uffd_api)) { - close(uffd); - return -1; - } - - uffd_register.range.start = (unsigned long)fault_addr; - uffd_register.range.len = getpagesize(); - uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING; - if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) { - close(uffd); - return -1; - } - return uffd; -} - static void test_bpf_mod_race_config(const struct test_config *config) { void *fault_addr, *skel_fail; @@ -117,7 +85,7 @@ static void test_bpf_mod_race_config(const struct test_config *config) if (!ASSERT_OK(bpf_mod_race__attach(skel), "bpf_mod_kfunc_race__attach")) goto end_destroy; - uffd = test_setup_uffd(fault_addr); + uffd = uffd_block_page(fault_addr); if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address")) goto end_destroy; diff --git a/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c new file mode 100644 index 000000000000..1eb0d7237605 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tramp_prog_detach.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include "tramp_prog_detach.skel.h" +#include "testing_helpers.h" + +/* + * Detach and free progs while a task sleeps in the prog that runs before them + * in the same trampoline image, then let that task continue through the + * image. It must not call into the freed progs. + * + * The task is held in a sleepable prog with userfaultfd, like bpf_mod_race + * does. + */ + +static struct bpf_program *pick_prog(struct tramp_prog_detach *skel, + bool fexit, bool sleepable) +{ + if (fexit) + return sleepable ? skel->progs.fexit_sleepable : + skel->progs.fexit_victim; + return sleepable ? skel->progs.fentry_sleepable : + skel->progs.fentry_victim; +} + +static struct bpf_program *sleepable_prog; + +static void *run_sleepable(void *arg) +{ + LIBBPF_OPTS(bpf_test_run_opts, topts); + + /* calls bpf_fentry_test1() */ + return (void *)(long)bpf_prog_test_run_opts(bpf_program__fd(sleepable_prog), + &topts); +} + +static struct tramp_prog_detach *load_one(bool fexit, bool sleepable) +{ + struct tramp_prog_detach *skel; + int err; + + skel = tramp_prog_detach__open(); + if (!ASSERT_OK_PTR(skel, "open")) + return NULL; + + bpf_program__set_autoload(pick_prog(skel, fexit, sleepable), true); + err = tramp_prog_detach__load(skel); + if (!ASSERT_OK(err, "load")) + goto err; + skel->bss->pid = getpid(); + err = tramp_prog_detach__attach(skel); + if (!ASSERT_OK(err, "attach")) + goto err; + return skel; +err: + tramp_prog_detach__destroy(skel); + return NULL; +} + +/* The .bss map of a destroyed skeleton goes away when its prog is freed */ +static bool wait_for_map_free(__u32 map_id) +{ + int i, fd; + + for (i = 0; i < 100; i++) { + fd = bpf_map_get_fd_by_id(map_id); + if (fd < 0) + return true; + close(fd); + usleep(100 * 1000); + } + return false; +} + +static void test_detach(bool sleepable_fexit, bool victim_fexit) +{ + struct tramp_prog_detach *sleepable = NULL, *victims[2] = {}; + struct pollfd pfd = { .events = POLLIN }; + struct uffdio_copy uffd_copy = {}; + struct bpf_map_info map_info = {}; + __u32 map_info_len = sizeof(map_info); + struct uffd_msg uffd_msg; + void *fault_page, *src_page = MAP_FAILED; + long page_size = getpagesize(); + bool started = false; + void *thread_ret; + pthread_t thread; + int i, uffd = -1; + + fault_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!ASSERT_NEQ(fault_page, MAP_FAILED, "mmap fault_page")) + return; + src_page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!ASSERT_NEQ(src_page, MAP_FAILED, "mmap src_page")) + goto out; + + /* The most recently attached prog runs first */ + for (i = 0; i < ARRAY_SIZE(victims); i++) { + victims[i] = load_one(victim_fexit, false); + if (!victims[i]) + goto out; + } + sleepable = load_one(sleepable_fexit, true); + if (!sleepable) + goto out; + sleepable_prog = pick_prog(sleepable, sleepable_fexit, true); + + /* Not armed yet so this doesn't block, make sure sleepable runs first */ + if (!ASSERT_OK((long)run_sleepable(NULL), "dry run")) + goto out; + for (i = 0; i < ARRAY_SIZE(victims); i++) + if (!ASSERT_LT(sleepable->bss->ts, victims[i]->bss->ts, "prog order")) + goto out; + + uffd = uffd_block_page(fault_page); + if (!ASSERT_GE(uffd, 0, "userfaultfd open + register address")) + goto out; + sleepable->bss->fault_addr = fault_page; + + if (!ASSERT_OK(pthread_create(&thread, NULL, run_sleepable, NULL), + "pthread_create")) + goto out; + started = true; + + /* Wait for the thread to sleep in bpf_copy_from_user() */ + pfd.fd = uffd; + if (!ASSERT_EQ(poll(&pfd, 1, 10000), 1, "poll uffd")) + goto out; + if (!ASSERT_EQ(read(uffd, &uffd_msg, sizeof(uffd_msg)), sizeof(uffd_msg), + "read uffd")) + goto out; + if (!ASSERT_EQ(uffd_msg.event, UFFD_EVENT_PAGEFAULT, "uffd pagefault")) + goto out; + + /* + * Detach and unload the victim progs and wait for them to be freed. + * After the first one, the task is in an image that isn't the + * trampoline's current one anymore. + */ + for (i = 0; i < ARRAY_SIZE(victims); i++) { + if (!ASSERT_OK(bpf_map_get_info_by_fd(bpf_map__fd(victims[i]->maps.bss), + &map_info, &map_info_len), + "victim bss info")) + goto out; + tramp_prog_detach__destroy(victims[i]); + victims[i] = NULL; + if (!ASSERT_TRUE(wait_for_map_free(map_info.id), "victim freed")) + goto out; + } + +out: + /* Let the thread proceed with the rest of the trampoline */ + if (uffd >= 0) { + uffd_copy.dst = (unsigned long)fault_page; + uffd_copy.src = (unsigned long)src_page; + uffd_copy.len = page_size; + ASSERT_OK(ioctl(uffd, UFFDIO_COPY, &uffd_copy), "uffd copy"); + close(uffd); + } + if (started && + ASSERT_OK(pthread_join(thread, &thread_ret), "pthread_join")) + ASSERT_NULL(thread_ret, "blocking run"); + + for (i = 0; i < ARRAY_SIZE(victims); i++) + tramp_prog_detach__destroy(victims[i]); + tramp_prog_detach__destroy(sleepable); + if (src_page != MAP_FAILED) + munmap(src_page, page_size); + munmap(fault_page, page_size); +} + +void serial_test_tramp_prog_detach(void) +{ + /* a task sleeping before the original function is called */ + if (test__start_subtest("fentry")) + test_detach(false, false); + /* a task sleeping after the original function returned */ + if (test__start_subtest("fexit")) + test_detach(true, true); + /* the original function runs in between and must still be called */ + if (test__start_subtest("fentry_fexit")) + test_detach(false, true); +} diff --git a/tools/testing/selftests/bpf/progs/tramp_prog_detach.c b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c new file mode 100644 index 000000000000..507d372167ec --- /dev/null +++ b/tools/testing/selftests/bpf/progs/tramp_prog_detach.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include +#include + +char _license[] SEC("license") = "GPL"; + +int pid; +void *fault_addr; +__u64 ts; + +static int do_sleepable(void) +{ + char dst; + + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + ts = bpf_ktime_get_ns(); + /* blocks for as long as user space wants when fault_addr is armed */ + bpf_copy_from_user(&dst, sizeof(dst), fault_addr); + return 0; +} + +static int do_victim(void) +{ + if (bpf_get_current_pid_tgid() >> 32 != pid) + return 0; + + ts = bpf_ktime_get_ns(); + return 0; +} + +SEC("?fentry.s/bpf_fentry_test1") +int BPF_PROG(fentry_sleepable, int a) +{ + return do_sleepable(); +} + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(fentry_victim, int a) +{ + return do_victim(); +} + +SEC("?fexit.s/bpf_fentry_test1") +int BPF_PROG(fexit_sleepable, int a, int ret) +{ + return do_sleepable(); +} + +SEC("?fexit/bpf_fentry_test1") +int BPF_PROG(fexit_victim, int a, int ret) +{ + return do_victim(); +} diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c index c970e7793dfc..9f672d0c9dda 100644 --- a/tools/testing/selftests/bpf/testing_helpers.c +++ b/tools/testing/selftests/bpf/testing_helpers.c @@ -13,6 +13,7 @@ #include "test_progs.h" #include "testing_helpers.h" #include +#include int parse_num_list(const char *s, bool **num_set, int *num_set_len) { @@ -459,6 +460,33 @@ int kern_sync_rcu(void) return syscall(__NR_membarrier, MEMBARRIER_CMD_SHARED, 0, 0); } +int uffd_block_page(void *fault_addr) +{ + struct uffdio_register uffd_register = {}; + struct uffdio_api uffd_api = {}; + int uffd; + + uffd = syscall(__NR_userfaultfd, O_CLOEXEC); + if (uffd < 0) + return -errno; + + uffd_api.api = UFFD_API; + uffd_api.features = 0; + if (ioctl(uffd, UFFDIO_API, &uffd_api)) { + close(uffd); + return -1; + } + + uffd_register.range.start = (unsigned long)fault_addr; + uffd_register.range.len = getpagesize(); + uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING; + if (ioctl(uffd, UFFDIO_REGISTER, &uffd_register)) { + close(uffd); + return -1; + } + return uffd; +} + int get_xlated_program(int fd_prog, struct bpf_insn **buf, __u32 *cnt) { __u32 buf_element_size = sizeof(struct bpf_insn); diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h index 2edc6fb7fc52..1f83b6080716 100644 --- a/tools/testing/selftests/bpf/testing_helpers.h +++ b/tools/testing/selftests/bpf/testing_helpers.h @@ -36,6 +36,8 @@ __u64 read_perf_max_sample_freq(void); int load_bpf_testmod(bool verbose); int unload_bpf_testmod(bool verbose); int kern_sync_rcu(void); +/* returns a userfaultfd that makes accesses to fault_addr's page block */ +int uffd_block_page(void *fault_addr); int finit_module(int fd, const char *param_values, int flags); int delete_module(const char *name, int flags); int load_module(const char *path, bool verbose); From e955f14fa8467d70aaeea5830b4d1773d5b25500 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 28 Sep 2026 10:02:04 +0800 Subject: [PATCH 361/535] sysctl: Negate before converting in the int read path proc_int_k2u_conv_kop() reports the sign through *negp and the magnitude through *u_ptr, but for a negative value it hands the sign-extended int to the converter and negates the result: *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val; With div_hz() and CONFIG_HZ=1000 a stored -1000 becomes (ulong)-1000 / 1000 == 18446744073709550, and negating that wraps: # echo -1 > /proc/sys/net/ipv4/tcp_fin_timeout # cat /proc/sys/net/ipv4/tcp_fin_timeout -18428297329635842066 The magnitude is HZ dependent but the wrap is not: the negation happens after the division at every CONFIG_HZ. Take the magnitude first and convert that, which is what the open-coded version did before commit 2dc164a48e6f ("sysctl: Create converter functions with two new macros") folded it into a macro. The k_ptr_op == NULL branch was already correct. All three int converters that pass a k_ptr_op are affected: proc_dointvec_jiffies(), proc_dointvec_userhz_jiffies() and proc_dointvec_ms_jiffies(). Fixes: 2dc164a48e6f ("sysctl: Create converter functions with two new macros") Cc: stable@vger.kernel.org Reviewed-by: Bradley Morgan Signed-off-by: Zhan Xusheng Signed-off-by: Joel Granados --- kernel/sysctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f7b75985d542..38597f26b34c 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -483,7 +483,7 @@ int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, if (val < 0) { *negp = true; - *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val; + *u_ptr = k_ptr_op ? k_ptr_op(-(ulong)val) : -(ulong)val; } else { *negp = false; *u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val; From fdd6553e1e53b0421d89c7469c8a36d2eadb1115 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 28 Sep 2026 10:02:05 +0800 Subject: [PATCH 362/535] time/jiffies: Saturate in mult_hz() instead of wrapping Return ULONG_MAX for values that don't fit an unsigned long. proc_int_u2k_conv_uop() now correctly rejects a result above INT_MAX. This is the erroneous behaviour that is being fixed. The input has to exceed ULONG_MAX / HZ for the product to wrap, so the value below is specific to CONFIG_HZ=1000: # echo 18446744073709552 > /proc/sys/net/ipv4/tcp_keepalive_time # cat /proc/sys/net/ipv4/tcp_keepalive_time 0 That value is now rejected with an error. The original bound ("*u_ptr > INT_MAX / HZ") was removed in commit 2dc164a48e6f ("sysctl: Create converter functions with two new macros"). Fixes: 2dc164a48e6f ("sysctl: Create converter functions with two new macros") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 80c354811538..9b3487d40cd6 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -101,6 +101,8 @@ void __init register_refined_jiffies(long cycles_per_second) #ifdef CONFIG_SYSCTL static ulong mult_hz(const ulong val) { + if (val >= ULONG_MAX / HZ) + return ULONG_MAX; return val * HZ; } From 0f17d2e3dd9cda3ee6d94033d5e5489af168920d Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Thu, 24 Sep 2026 13:52:52 +0800 Subject: [PATCH 363/535] pfcp: fix socket lifetime on netdevice registration failure pfcp_newlink() creates the UDP socket before register_netdevice(). If registration fails after pfcp_dev_init() succeeds, the core calls pfcp_dev_uninit(), which releases the socket and clears pfcp->sk. The newlink error path then releases it again, causing a NULL pointer dereference. This was observed with failslab fault injection: FAULT_INJECTION: forcing a failure. kobject: kobject_add_internal failed for pfcp0 (error: -12 parent: net) BUG: KASAN: null-ptr-deref in udp_tunnel_sock_release+0x1c/0x50 Read of size 8 at addr 0000000000000120 by task ip/1037 Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0x78/0x90 print_report+0x468/0x5cc kasan_report+0xa4/0xf0 __asan_load8+0x7c/0xd0 udp_tunnel_sock_release+0x1c/0x50 pfcp_newlink+0x128/0x184 rtnl_newlink+0x848/0xe44 rtnetlink_rcv_msg+0x468/0x514 netlink_rcv_skb+0xc0/0x1f0 rtnetlink_rcv+0x18/0x24 netlink_unicast+0x4b8/0x558 netlink_sendmsg+0x2b8/0x584 ... Return from pfcp_del_sock() if pfcp->sk is NULL. Fixes: 76c8764ef36a5 ("pfcp: add PFCP module") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260924055252.33488-1-xuanqiang.luo@linux.dev Signed-off-by: Paolo Abeni --- drivers/net/pfcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index e1cca779d2ec..3ecd43cafdf0 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -105,6 +105,9 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) static void pfcp_del_sock(struct pfcp_dev *pfcp) { + if (!pfcp->sk) + return; + udp_tunnel_sock_release(pfcp->sk); pfcp->sk = NULL; } From 763183f76d6bc093fa603377862d6739cb7ccad6 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 29 Sep 2026 17:11:09 +0800 Subject: [PATCH 364/535] ALSA: hda/realtek: Add quirk for HP Pavilion AiO 24 speaker Add ALC225_FIXUP_HP_AIO_SPK to enable the internal speaker on HP Pavilion AiO 24 (103c:85ba). Link: https://bugzilla.kernel.org/show_bug.cgi?id=221691 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260929091109.400313-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index dfb5b5ab899d..d13cd276ec61 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4200,6 +4200,7 @@ enum { ALC225_FIXUP_DELL_WYSE_AIO_MIC_NO_PRESENCE, ALC225_FIXUP_WYSE_AUTO_MUTE, ALC225_FIXUP_WYSE_DISABLE_MIC_VREF, + ALC225_FIXUP_HP_AIO_SPK, ALC286_FIXUP_ACER_AIO_HEADSET_MIC, ALC256_FIXUP_ASUS_HEADSET_MIC, ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, @@ -5721,6 +5722,14 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC }, + [ALC225_FIXUP_HP_AIO_SPK] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0220 }, + { } + }, + }, [ALC286_FIXUP_ACER_AIO_HEADSET_MIC] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -7408,6 +7417,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x854a, "HP EliteBook 830 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x854d, "HP EliteBook 840 G6", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x856a, "HP Pavilion 15-cs1xxx", ALC295_FIXUP_HP_PAVILION_MUTE_LED_1B), + HDA_CODEC_QUIRK(0x103c, 0x85ba, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), SND_PCI_QUIRK(0x103c, 0x85c6, "HP Pavilion x360 Convertible 14-dy1xxx", ALC295_FIXUP_HP_MUTE_LED_COEFBIT11), SND_PCI_QUIRK(0x103c, 0x85de, "HP Envy x360 13-ar0xxx", ALC285_FIXUP_HP_ENVY_X360), SND_PCI_QUIRK(0x103c, 0x85f0, "HP Laptop 15-dw0xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), From 0eee030c2c09c7a44be778de5f451d9e7a2f09c1 Mon Sep 17 00:00:00 2001 From: Muhtasham Nawr al-Mahmud Date: Thu, 24 Sep 2026 20:45:12 +0600 Subject: [PATCH 365/535] ALSA: hda/realtek: Fix speakers on ASUS PM3406CHA The ASUS ExpertBook PM3406CHA uses an ALC256 codec with subsystem ID 1043:1664. On Linux boot, the internal speakers are silent even though playback is routed to the codec and the speaker pin is configured for output. The codec starts with coefficient 0x10 set to 0x1d20. Writing 0x7f20 to coefficient 0x10 immediately restores internal speaker output. This was reproduced on the affected hardware from a fresh boot using hda-verb. Reducing a larger initialization sequence showed that this coefficient write alone is sufficient; no GPIO, EAPD, pin-control, or other coefficient changes are required. After applying the coefficient in userspace, speaker output also remains functional across suspend and resume. Add a machine-specific HDA fixup that writes the required coefficient. The fixup has been compile-tested, but could not be boot-tested because the affected hardware is no longer available. Closes: https://bugs.launchpad.net/ubuntu/+source/alsa-driver/+bug/2162718 Signed-off-by: Muhtasham Nawr al-Mahmud Link: https://patch.msgid.link/20260924144512.3989-1-muhtaseem2005@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index d13cd276ec61..333689f31775 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4025,6 +4025,7 @@ enum { ALC269_FIXUP_SONY_VAIO, ALC275_FIXUP_SONY_VAIO_GPIO2, ALC269_FIXUP_DELL_M101Z, + ALC256_FIXUP_ASUS_PM3406CHA, ALC269_FIXUP_SKU_IGNORE, ALC269_FIXUP_ASUS_G73JW, ALC269_FIXUP_ASUS_N7601ZM_PINS, @@ -4484,6 +4485,14 @@ static const struct hda_fixup alc269_fixups[] = { {} } }, + [ALC256_FIXUP_ASUS_PM3406CHA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x7f20 }, + {} + } + }, [ALC269_FIXUP_SKU_IGNORE] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_sku_ignore, @@ -7853,6 +7862,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1652, "ASUS ROG Zephyrus Do 15 SE", ALC289_FIXUP_ASUS_ZEPHYRUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1662, "ASUS GV301QH", ALC294_FIXUP_ASUS_DUAL_SPK), SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), From 006c8b834aed8b02bd8fb9fb05e9e7ead37d1763 Mon Sep 17 00:00:00 2001 From: Zihan Xi Date: Thu, 24 Sep 2026 05:15:21 +0000 Subject: [PATCH 366/535] net: gso: limit recursive IP-in-IP segmentation IP-in-IP GSO can re-enter inet_gso_segment() or ipv6_gso_segment() for each nested IP header. encap_level tracks header bytes, not callback depth, so a deep chain can exhaust the kernel stack. Making inet_gso_segment() stackable introduced unbounded IPv4 nesting; IPIP GSO/TSO later made the path reachable. The IPv6 stackable path was introduced separately and uses the same guard. Count IPv4 and IPv6 GSO handler entries in skb_gso_cb, initialized once per top-level GSO operation and preserved across GRE/UDP context changes. Use the existing IP_TUNNEL_RECURSION_LIMIT for both handlers. The first five entries pass, and the sixth returns -EINVAL before dispatching another GSO callback. Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") Cc: stable@vger.kernel.org Reported-by: Vega Closes: https://lore.kernel.org/all/cover.1790157745.git.zihanx@nebusec.ai/ Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260924051521.32568-2-zihanx@nebusec.ai Signed-off-by: Paolo Abeni --- include/net/gso.h | 8 ++++++++ net/core/gso.c | 1 + net/ipv4/af_inet.c | 3 +++ net/ipv6/ip6_offload.c | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/include/net/gso.h b/include/net/gso.h index 29975440cad5..0749230d414e 100644 --- a/include/net/gso.h +++ b/include/net/gso.h @@ -19,10 +19,18 @@ struct skb_gso_cb { int encap_level; __wsum csum; __u16 csum_start; + /* Number of IPv4/IPv6 GSO handler entries for this packet. */ + u8 recursion_counter; }; #define SKB_GSO_CB_OFFSET 32 #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) +static inline bool gso_recursion_inc_test(struct sk_buff *skb, + unsigned int limit) +{ + return ++SKB_GSO_CB(skb)->recursion_counter > limit; +} + static inline int skb_tnl_header_len(const struct sk_buff *inner_skb) { return (skb_mac_header(inner_skb) - inner_skb->head) - diff --git a/net/core/gso.c b/net/core/gso.c index bcd156372f4d..e96ef6350064 100644 --- a/net/core/gso.c +++ b/net/core/gso.c @@ -117,6 +117,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb, SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb); SKB_GSO_CB(skb)->encap_level = 0; + SKB_GSO_CB(skb)->recursion_counter = 0; skb_reset_mac_header(skb); skb_reset_mac_len(skb); diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 32d006c1a8ee..4ce38c99fef9 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -1375,6 +1375,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb, int id; skb_reset_network_header(skb); + if (unlikely(gso_recursion_inc_test(skb, + IP_TUNNEL_RECURSION_LIMIT))) + goto out; nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*iph)))) goto out; diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c index 78f50c93c536..700f1861d8eb 100644 --- a/net/ipv6/ip6_offload.c +++ b/net/ipv6/ip6_offload.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "ip6_offload.h" #include "tcpv6_offload.c" @@ -104,6 +105,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, bool gso_partial; skb_reset_network_header(skb); + if (unlikely(gso_recursion_inc_test(skb, + IP_TUNNEL_RECURSION_LIMIT))) + goto out; nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h)))) goto out; From f8932b2c0c9ae6fb577bcc05a2080c8498f91c0d Mon Sep 17 00:00:00 2001 From: Hannu Varjoranta Date: Fri, 25 Sep 2026 14:52:37 +0300 Subject: [PATCH 367/535] gve: DQO: accept TSO packets with non-protocol gso_type bits Since commit 014c607f86ab ("gve: add support for UDP GSO for DQO format"), gve_prep_tso() matches shinfo->gso_type exactly. gso_type is a bitmask, though: SKB_GSO_DODGY is set on every GSO packet that comes from tun/tap, packet sockets or other untrusted sources, and SKB_GSO_TCP_ECN and SKB_GSO_TCP_FIXEDID can be set as well. Such packets hit the default case, gve_tx_add_skb_dqo() fails and gve_try_tx_skb() drops them, counting them in tx_dropped. These packets do reach the driver: tcp_gso_segment() passes DODGY skbs through unsegmented to devices that support TSO, after recomputing gso_segs, so drivers must tolerate the bit. This breaks virtual machines behind a tap on GCE instances using the DQO queue formats. Every TSO packet forwarded from a guest (gso_type SKB_GSO_TCPV4 | SKB_GSO_DODGY) is dropped, and guest uploads slow to a crawl of retransmissions or stall entirely, while the host's own TSO traffic (gso_type SKB_GSO_TCPV4) is unaffected. On an n4 instance (DQO-QPL) with a Cloud Hypervisor guest, a 64 MB upload from the guest went from a 60 s timeout at ~0.9 MB/s to 0.19 s with this change, with tx_dropped no longer increasing. Host TCP with ECN is hit as well: gve advertises NETIF_F_TSO_ECN, so a TSO packet carrying CWR has SKB_GSO_TCP_ECN set and is dropped too. Restore the bitmask test that commit 1b9f75634441 ("gve: ignore nonrelevant GSO type bits when processing TSO headers") introduced for the same problem, keeping the UDP GSO support. While here, reload shinfo after skb_cow_head(). If the head was cloned, pskb_expand_head() moves skb_shared_info to the new head, and the pointer cached at function entry can then refer to memory that another clone frees. Fixes: 014c607f86ab ("gve: add support for UDP GSO for DQO format") Signed-off-by: Hannu Varjoranta Reviewed-by: Eric Dumazet Reviewed-by: Ankit Garg Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260925115237.56465-1-hannu@varjosoft.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/google/gve/gve_tx_dqo.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 616c1921aebe..ad99cbb77e29 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -595,25 +595,25 @@ static int gve_prep_tso(struct sk_buff *skb) err = skb_cow_head(skb, 0); if (err < 0) return err; + shinfo = skb_shinfo(skb); l4_start = skb_transport_offset(skb); paylen = skb->len - l4_start; - switch (shinfo->gso_type) { - case SKB_GSO_TCPV4: - case SKB_GSO_TCPV6: + /* gso_type is a bitmask: SKB_GSO_DODGY, SKB_GSO_TCP_ECN and + * SKB_GSO_TCP_FIXEDID may be set alongside the protocol bit. + */ + if (shinfo->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) { tcp = tcp_hdr(skb); csum_replace_by_diff(&tcp->check, (__force __wsum)htonl(paylen)); header_len = skb_tcp_all_headers(skb); - break; - case SKB_GSO_UDP_L4: + } else if (shinfo->gso_type & SKB_GSO_UDP_L4) { udp = udp_hdr(skb); csum_replace_by_diff(&udp->check, (__force __wsum)htonl(paylen)); header_len = sizeof(struct udphdr) + l4_start; - break; - default: + } else { return -EINVAL; } From f341dc4d934a69ffbe6c21e3609ee725790188f1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:12:00 +0200 Subject: [PATCH 368/535] cpufreq: intel_pstate: Fix max_freq fallback in cpufreq_update_pressure() After commit d2d5c129d07e ("cpufreq: Make cpufreq_update_pressure() fall back to cpuinfo.max_freq"), cpufreq pressure appears in the CPU load balancer unexpectedly in some cases in which it was not present before, leading to confusion and uncertainty. Clearly, the scheduler assumes that cpufreq pressure will not be set unless the capacity reference frequency of the CPU is known, and the commit mentioned above violates that assumption. However, in some cases the capacity reference frequency of the CPU is in fact known even though arch_scale_freq_ref() returns 0 and in those cases it should be possible to set cpufreq pressure as appropriate. For this purpose, introduce a new cpufreq driver callback returning the CPU capacity reference frequency, .scale_freq_ref(), and make cpufreq_update_pressure() invoke it, if present, instead of falling back to cpuinfo.max_freq unconditionally. Add that callback to the intel_pstate driver and make it return 0 unless the scale-invariant capacity of the given CPU has been explicitly set, in which cases its reference frequency is always cpuinfo.max_freq. Fixes: d2d5c129d07e ("cpufreq: Make cpufreq_update_pressure() fall back to cpuinfo.max_freq") Reported-by: Jianyong Wu Closes: https://lore.kernel.org/linux-pm/20260915065747.1671965-1-wujianyong@hygon.cn/ Tested-by: Jianyong Wu Signed-off-by: Rafael J. Wysocki Tested-by: Ricardo Neri # Intel hybrid parts Tested-by: Chen Yu [ rjw: Add READ_ONCE() around a capacity_perf read ] Link: https://patch.msgid.link/12975163.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 4 ++-- drivers/cpufreq/intel_pstate.c | 10 ++++++++++ include/linux/cpufreq.h | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 96515880b4ac..54dde8419bdc 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2590,8 +2590,8 @@ static void cpufreq_update_pressure(struct cpufreq_policy *policy) cpu = cpumask_first(policy->related_cpus); max_freq = arch_scale_freq_ref(cpu); - if (!max_freq) - max_freq = policy->cpuinfo.max_freq; + if (!max_freq && cpufreq_driver->scale_freq_ref) + max_freq = cpufreq_driver->scale_freq_ref(policy); capped_freq = policy->max; diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index ceb340f7a110..17158cb25dd8 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1135,6 +1135,14 @@ static bool hybrid_clear_max_perf_cpu(void) return ret; } +static unsigned int intel_pstate_scale_freq_ref(struct cpufreq_policy *policy) +{ + if (READ_ONCE(all_cpu_data[policy->cpu]->capacity_perf)) + return policy->cpuinfo.max_freq; + + return 0; +} + static void intel_pstate_update_freq_limits(struct cpudata *cpu) { int scaling = cpu->pstate.scaling; @@ -3088,6 +3096,7 @@ static struct cpufreq_driver intel_pstate = { .offline = intel_pstate_cpu_offline, .online = intel_pstate_cpu_online, .update_limits = intel_pstate_update_limits, + .scale_freq_ref = intel_pstate_scale_freq_ref, .name = "intel_pstate", }; @@ -3411,6 +3420,7 @@ static struct cpufreq_driver intel_cpufreq = { .suspend = intel_cpufreq_suspend, .resume = intel_pstate_resume, .update_limits = intel_pstate_update_limits, + .scale_freq_ref = intel_pstate_scale_freq_ref, .name = "intel_cpufreq", }; diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 35ce665edfd8..d3d0d9d02aa4 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -420,6 +420,9 @@ struct cpufreq_driver { /* Will be called after the driver is fully initialized */ void (*ready)(struct cpufreq_policy *policy); + /* Return the capacity reference frequency for policy. */ + unsigned int (*scale_freq_ref)(struct cpufreq_policy *policy); + struct freq_attr **attr; /* platform specific boost support code */ From 0923198be4ae714b46350f742cb19ccd75bfeafa Mon Sep 17 00:00:00 2001 From: Sagar Taunk Date: Fri, 25 Sep 2026 08:55:04 +0000 Subject: [PATCH 369/535] rust: net: netlink: validate attribute length before casting to `c_int` `put()` trusted an unchecked `as` cast from `usize` to `c_int`. When the length exceeds `i32::MAX` that cast wraps around to a negative value. This ultimately resulted in a kernel panic when the reinterpreted value via `__nla_reserve()` and `skb_put()` became enormous. Validate payload and header both fit together in a `u16`, rejecting any payload that wouldn't leave room for `NLA_HDRLEN`. Fixes: 5eaa5fbb6e6c ("rust: netlink: add raw netlink abstraction") Reviewed-by: Alice Ryhl Reviewed-by: Alexandre Courbot Reviewed-by: Carlos Llamas Reviewed-by: Gary Guo Signed-off-by: Sagar Taunk Link: https://patch.msgid.link/20260925085449.22553-1-sagartaunk@proton.me Signed-off-by: Paolo Abeni --- rust/kernel/net/netlink.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rust/kernel/net/netlink.rs b/rust/kernel/net/netlink.rs index 22ef3dde36fa..2f8733a694a9 100644 --- a/rust/kernel/net/netlink.rs +++ b/rust/kernel/net/netlink.rs @@ -11,6 +11,7 @@ use kernel::{ alloc::{self, AllocError}, error::to_result, + num::casts::u16_as_usize, prelude::*, transmute::AsBytes, types::Opaque, @@ -86,9 +87,17 @@ fn put(&mut self, attrtype: c_int, value: &T) -> Result where T: ?Sized + AsBytes, { + // `nla_len` is a 16-bit field that encodes the total attribute length + // (header + payload). Subtracting the header size from `u16::MAX` gives + // the largest payload that still fits within that field. + const MAX_PAYLOAD_LEN: usize = u16_as_usize(u16::MAX) - size_of::(); + let skb = self.skb.skb.as_ptr(); let len = size_of_val(value); let ptr = core::ptr::from_ref(value).cast::(); + if len > MAX_PAYLOAD_LEN { + return Err(EMSGSIZE); + } // SAFETY: `skb` is valid by `NetlinkSkBuff` type invariants, and the provided value is // readable and initialized for its `size_of` bytes. to_result(unsafe { bindings::nla_put(skb, attrtype, len as c_int, ptr) }) From d031465acc362866f27e4f8f79cfecf1037fe2e5 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 26 Sep 2026 14:03:27 -0400 Subject: [PATCH 370/535] net/sched: fq_codel: match the no-drop threshold to the packet size commit d9ebd8f9aa8b ("net/sched: fq_codel: clamp default quantum and mtu") clamped both q->quantum and q->cparams.mtu to [256, FQ_CODEL_QUANTUM_MAX]. The two fields mean different things: quantum is a DRR credit that wants the 256 floor, but cparams.mtu is the CoDel no-drop threshold (codel_impl.h "*backlog <= params->mtu"). On a link whose maximum transmitted packet size is below 256, the floor extends CoDel's minimum-backlog exemption beyond one packet and delays drop or mark eligibility by several small packets. Split the clamp. quantum keeps [256, FQ_CODEL_QUANTUM_MAX]; cparams.mtu tracks psched_mtu() (the device MTU plus its hard-header length) with only the upper bound that guards the original overflow (psched_mtu() wrapping to ~2 GiB on a huge-MTU device). Conditions to recreate the bug: attach an fq_codel qdisc on a link whose MTU plus hard_header_len is below 256 (e.g. a CAN interface). At that MTU the no-drop threshold must equal the device MTU plus its hard-header length; before this patch it was forced to 256. Basic Testing done: with dev->mtu=100 and hard_header_len=14, a return probe on fq_codel_init() observed cparams.mtu change from 256 to 114 Fixes: d9ebd8f9aa8b ("net/sched: fq_codel: clamp default quantum and mtu") Reported-by: Sashiko (nipa) Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260819143136.57350-1-jhs@mojatatu.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-34MS.v1.20260925165535@mojatatu.com Signed-off-by: Paolo Abeni --- net/sched/sch_fq_codel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 969b2510b0b8..e6c87a32950f 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -517,8 +517,8 @@ static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, q->flows_cnt = 1024; q->memory_limit = 32 << 20; /* 32 MBytes */ q->drop_batch_size = 64; - mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, FQ_CODEL_QUANTUM_MAX); - q->quantum = mtu; + mtu = min_t(u32, psched_mtu(qdisc_dev(sch)), FQ_CODEL_QUANTUM_MAX); + q->quantum = clamp_t(u32, mtu, 256, FQ_CODEL_QUANTUM_MAX); INIT_LIST_HEAD(&q->new_flows); INIT_LIST_HEAD(&q->old_flows); codel_params_init(&q->cparams); From 54518e0e827f4ca9229ae657022c60bf60f5c1bf Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Sat, 26 Sep 2026 14:03:28 -0400 Subject: [PATCH 371/535] net/sched: sch_codel: match the no-drop threshold to the packet size commit 6439461f1618 ("net/sched: sch_codel: clamp default mtu to avoid disabling CoDel") clamped q->params.mtu to [256, 1 << 20]. The value is the CoDel no-drop threshold (codel_impl.h "*backlog <= params->mtu"), so on a link whose maximum transmitted packet size is below 256 the floor extends CoDel's minimum-backlog exemption beyond one packet and delays drop or mark eligibility by several small packets. Keep the upper bound that guards the original overflow (psched_mtu() wrapping to ~2 GiB on a huge-MTU device) but drop the 256 floor, so the threshold tracks the real device packet size. Conditions to recreate the bug: attach a codel qdisc on a link whose MTU plus hard_header_len is below 256 (e.g. a CAN interface). At that MTU the no-drop threshold must equal the device MTU plus its hard-header length; before this patch it was forced to 256. Requires CAP_NET_ADMIN in a user namespace. Fixes: 6439461f1618 ("net/sched: sch_codel: clamp default mtu to avoid disabling CoDel") Reported-by: Sashiko (nipa) Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260819143136.57350-1-jhs@mojatatu.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-34MS.v1.20260925165535@mojatatu.com.2 Signed-off-by: Paolo Abeni --- net/sched/sch_codel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index 6aa5829d6961..a1ba2e355f2d 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -205,7 +205,7 @@ static int codel_init(struct Qdisc *sch, struct nlattr *opt, codel_params_init(&q->params); codel_vars_init(&q->vars); codel_stats_init(&q->stats); - q->params.mtu = clamp_t(u32, psched_mtu(qdisc_dev(sch)), 256, 1 << 20); + q->params.mtu = min_t(u32, psched_mtu(qdisc_dev(sch)), 1 << 20); if (opt) { int err = codel_change(sch, opt, extack); From a92193c91e8839fe5fc209616ea95465ae4b919d Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 29 Sep 2026 06:52:17 -0600 Subject: [PATCH 372/535] io_uring: fix task_work add use-after-free with SQPOLL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For SQPOLL, the sqpoll thread runs task_work off its own list and doesn't need to be told about it, so it can pop and complete a request as soon as mpscq_push() has published it. If that was the last request holding the ring alive, io_ring_exit_work() then frees the ctx, stops the thread, and with it the tctx, while io_req_normal_work_add() is still looking at both after the push: BUG: KASAN: slab-use-after-free in io_req_normal_work_add+0x439/0x510 Read of size 4 at addr ff11000000ca0000 by task repro/55 (...) BUG: KASAN: slab-use-after-free in queue_work_on+0x25/0x70 Write of size 8 at addr ff11000000c3bd00 by task repro/55 Hold an RCU read lock across the add, like io_req_local_work_add() already does, and have io_ring_exit_work() wait for a grace period for SQPOLL rings as well before freeing the ctx. Reported-by: Jérémy Jean Link: https://lore.kernel.org/io-uring/20260924205056.3759980-2-Jeremy.Jean@oss.cyber.gouv.fr/ Fixes: af5d68f8892f ("io_uring/sqpoll: manage task_work privately") Cc: stable@vger.kernel.org Signed-off-by: Jens Axboe --- io_uring/io_uring.c | 4 ++-- io_uring/tw.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 2cb9b58d04dd..c2ce83c7c1f1 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2405,8 +2405,8 @@ static __cold void io_ring_exit_work(struct work_struct *work) spin_lock(&ctx->completion_lock); spin_unlock(&ctx->completion_lock); - /* pairs with RCU read section in io_req_local_work_add() */ - if (ctx->flags & IORING_SETUP_DEFER_TASKRUN) + /* pairs with the RCU read sections in the task_work add paths */ + if (ctx->flags & (IORING_SETUP_DEFER_TASKRUN | IORING_SETUP_SQPOLL)) synchronize_rcu(); io_ring_ctx_free(ctx); diff --git a/io_uring/tw.c b/io_uring/tw.c index f573bcc3af6a..f9affd68dd26 100644 --- a/io_uring/tw.c +++ b/io_uring/tw.c @@ -210,6 +210,9 @@ void io_req_normal_work_add(struct io_kiocb *req) struct io_uring_task *tctx = req->tctx; struct io_ring_ctx *ctx = req->ctx; + /* SQPOLL can retire the request on push, see io_ring_exit_work() */ + guard(rcu)(); + /* tw run already pending, nothing else to do */ if (!mpscq_push(&tctx->task_list, &req->io_task_work.node)) return; From b2047b8cadadccb1c9269ce756c399cd9aef2c82 Mon Sep 17 00:00:00 2001 From: Liviu Nicoara Date: Tue, 29 Sep 2026 07:57:55 -0400 Subject: [PATCH 373/535] ASoC: codecs: lpass-wsa-macro: rewrite the interpolator volume after enabling clocks Commit 902f497a1ff5 ("ASoC: codecs: lpass-wsa-macro: remove useless gain read/write sequence") removed the read and write of the digital volume register in wsa_macro_enable_interpolator(), on the grounds that writing back the value just read does nothing. The comment above it, "apply gain after int clk is enabled", was left in place. On the Dell XPS 13 9345 (X1E80100, four WSA8845 amplifiers on two WSA macros) the write does something: a volume change made while the path is idle does not take effect when playback starts. Lowering the digital volume from 81 to 63 with nothing playing, then playing a test tone, gave about the same level as before the change. With the rewrite restored, lowering it from 81 to 69 while idle played audibly quieter, and restoring 81 while idle brought the level back. Changes made during playback take effect with or without the rewrite. The register already holds the new value when this happens. Without the rewrite, after an idle change from 69 to 81, playback stayed at the old level while the register, read from the hardware through /dev/mem, held the new one (0xfd). Writing that same value back through /dev/mem brought the level up at once. This is the behaviour described in commit 46188db080bd ("ASoC: codecs: lpass-wsa-macro: fix compander volume hack"): "the volume registers still need to be written after enabling clocks in order for any prior updates to take effect." The value read comes from the register cache, so the write pushes the last requested volume to the hardware once its clock runs. Restore the rewrite in the interpolator's POST_PMU event only. The mix path event removed later in the same series is not brought back. Fixes: 902f497a1ff5 ("ASoC: codecs: lpass-wsa-macro: remove useless gain read/write sequence") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Liviu Nicoara Reviewed-by: Johan Hovold Link: https://patch.msgid.link/20260929115755.6096-1-lnicoara@thinkoid.org Signed-off-by: Mark Brown --- sound/soc/codecs/lpass-wsa-macro.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index cfd2ac0a6cda..38e37908baf9 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -1841,6 +1841,7 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + unsigned int gain; u16 gain_reg; u16 reg; struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); @@ -1882,6 +1883,8 @@ static int wsa_macro_enable_interpolator(struct snd_soc_dapm_widget *w, CDC_WSA_RX_PGA_HALF_DB_MASK, CDC_WSA_RX_PGA_HALF_DB_ENABLE); } + gain = snd_soc_component_read(component, gain_reg); + snd_soc_component_write(component, gain_reg, gain); wsa_macro_config_ear_spkr_gain(component, wsa, event, gain_reg); break; From b5c4823cf3e49b3419a3bc8c6715101e1ac6cc59 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:29:25 +0200 Subject: [PATCH 374/535] ALSA: usb-audio: Apply boot quirk for Behringer models generically Jan reported that a few Behringer devices became broken since the recent optimization to avoid usb_string() call at probe time at the commit b364a0d23cae ("ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co"). Interestingly, the devices seem requiring the explicit descriptor read at probing time, and the optimization above dropped it. There is already a boot quirk for another model, Behringer CM1A (1397:1234), that adds a device descriptor read, and this seems working for them, too. As the quirk is safe and cheap, just apply the same boot quirk to all Behringer devices for avoiding the pitfall again. Fixes: b364a0d23cae ("ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co") Reported-by: Jan Lentfer Closes: https://lore.kernel.org/e7087d42-5e74-4d85-b1c5-b11eff235d41@web.de Link: https://patch.msgid.link/20260929122938.1471867-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 294c7026b93c..f55b02d6dd39 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1702,10 +1702,12 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); - case USB_ID(0x1397, 0x1234): /* Behringer CM1A */ - return snd_usb_cm1a_boot_quirk(dev); } + /* Behringer devices may need explicit device descriptor read at boot */ + if (USB_ID_VENDOR(id) == 0x1397) + return snd_usb_cm1a_boot_quirk(dev); + return 0; } From a154f7b9f197b3d5e41fa3ad62083f5aa93b1fe8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 16:41:26 +0200 Subject: [PATCH 375/535] ALSA: usb-audio: Apply IGNORE_CTL_ERROR quirk to all Audient devices Faaris reported a problem with Audient EVO4 device and it turned out to be a regression by the recent fix commit 87a6f2fa6e6c ("ALSA: usb-audio: Propagate write errors in generic mixer put callbacks"). It exhibited that the hardware gives errors at accessing the mixer unit 10 on certain channels, and the change above made it a fatal error. We had already a quirk for Audient iD14 to work around such errors from the mixer controls, and we can simply apply the same for EVO4. OTOH, it's highly possible that other Audient devices suffer from the same issue; they must be using similar firmware, after all. So, in this patch, we apply the quirk generically to all devices with the vendor ID Audient (2708), instead. Ignoring the control error isn't usually less critical than overreaction to the firmware misbehavior. Fixes: 87a6f2fa6e6c ("ALSA: usb-audio: Propagate write errors in generic mixer put callbacks") Reported-and-tested-by: Faaris Ansari Closes: https://lore.kernel.org/CANBVYRCL=8QdLxGg4S6qrahrFtwJxhv-aSGpW7-1S=+iOe4ZGA@mail.gmail.com Cc: Link: https://patch.msgid.link/20260929144132.1521617-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index f55b02d6dd39..3afc49565963 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2516,8 +2516,6 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x262a, 0x9302, /* ddHiFi TC44C */ QUIRK_FLAG_DSD_RAW), - DEVICE_FLG(0x2708, 0x0002, /* Audient iD14 */ - QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x2772, 0x0502, /* Musical Fidelity M6s DAC */ 0), /* for avoiding QUIRK_FLAG_DSD_RAW with vendor match */ DEVICE_FLG(0x2912, 0x30c8, /* Audioengine D1 */ @@ -2616,6 +2614,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x2622, /* IAG Limited devices */ QUIRK_FLAG_DSD_RAW), + VENDOR_FLG(0x2708, /* Audient devices */ + QUIRK_FLAG_IGNORE_CTL_ERROR), VENDOR_FLG(0x2772, /* Musical Fidelity devices */ QUIRK_FLAG_DSD_RAW), VENDOR_FLG(0x278b, /* Rotel? */ From e079ef0b1c40c7a4a1be0c1ac5dbff95685c3beb Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 29 Sep 2026 10:35:44 +0100 Subject: [PATCH 376/535] KVM: arm64: vgic: Allow last_lr_irq to be NULL when LRs are not overflowing last_lr_irq is always populated when there is any interrupt populated in the AP list. Not only this is not necessary (it is only useful when we completely fill the LRs), but this is in the way of further fixes. Make sure last_lr_irq is kept to NULL when we LRs are not completely full. Fixes: 6da5e537f5afe ("KVM: arm64: vgic: Pick EOIcount deactivations from AP-list tail") Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260929093548.3598547-4-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-v2.c | 6 ++++-- arch/arm64/kvm/vgic/vgic-v3.c | 6 ++++-- arch/arm64/kvm/vgic/vgic.c | 8 +++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-v2.c b/arch/arm64/kvm/vgic/vgic-v2.c index 7182f63fc938..70cc53ba810a 100644 --- a/arch/arm64/kvm/vgic/vgic-v2.c +++ b/arch/arm64/kvm/vgic/vgic-v2.c @@ -122,6 +122,10 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu) for (int lr = 0; lr < vgic_cpu->vgic_v2.used_lrs; lr++) vgic_v2_fold_lr(vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs = 0; + if (!irq) + return; + /* See the GICv3 equivalent for the EOIcount handling rationale */ list_for_each_entry_continue(irq, &vgic_cpu->ap_list_head, ap_list) { u32 lr; @@ -144,8 +148,6 @@ void vgic_v2_fold_lr_state(struct kvm_vcpu *vcpu) vgic_v2_fold_lr(vcpu, lr); eoicount--; } - - cpuif->used_lrs = 0; } void vgic_v2_deactivate(struct kvm_vcpu *vcpu, u32 val) diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c index 726e20a1da6e..346bacb3198f 100644 --- a/arch/arm64/kvm/vgic/vgic-v3.c +++ b/arch/arm64/kvm/vgic/vgic-v3.c @@ -155,6 +155,10 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu) for (int lr = 0; lr < cpuif->used_lrs; lr++) vgic_v3_fold_lr(vcpu, cpuif->vgic_lr[lr]); + cpuif->used_lrs = 0; + if (!irq) + return; + /* * EOIMode=0: use EOIcount to emulate deactivation. We are * guaranteed to deactivate in reverse order of the activation, so @@ -188,8 +192,6 @@ void vgic_v3_fold_lr_state(struct kvm_vcpu *vcpu) vgic_v3_fold_lr(vcpu, lr); eoicount--; } - - cpuif->used_lrs = 0; } void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val) diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index b25303d9919f..425503e0c582 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -866,9 +866,6 @@ static void vgic_fold_state(struct kvm_vcpu *vcpu) return; } - if (!*host_data_ptr(last_lr_irq)) - return; - if (kvm_vgic_global_state.type == VGIC_V2) vgic_v2_fold_lr_state(vcpu); else @@ -1021,11 +1018,12 @@ static void vgic_flush_lr_state(struct kvm_vcpu *vcpu) scoped_guard(raw_spinlock, &irq->irq_lock) { if (likely(vgic_target_oracle(irq) == vcpu)) { vgic_populate_lr(vcpu, irq, count++); - *host_data_ptr(last_lr_irq) = irq; + if (count == kvm_vgic_global_state.nr_lr) + *host_data_ptr(last_lr_irq) = irq; } } - if (count == kvm_vgic_global_state.nr_lr) + if (*host_data_ptr(last_lr_irq)) break; } From 19bc5d79ece613f242b5ec03921a3b465321bfed Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 29 Sep 2026 10:35:45 +0100 Subject: [PATCH 377/535] KVM: arm64: vgic: Take a refcount on IRQs referenced by last_lr_irq Referencing the last interrupt inserted in an LR is rather fragile, as this interrupt can vanish if a concurrently unmapped LPI. Solve this by bumping up the refcount on the interrupt when populating last_lr_irq, and drop it at vgic_prune_ap_list() time, when LPIs are being reclaimed. Fixes: 6da5e537f5afe ("KVM: arm64: vgic: Pick EOIcount deactivations from AP-list tail") Reported-by: Yuchao Zhang Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260929093548.3598547-5-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c index 425503e0c582..5cf5a1ef86cd 100644 --- a/arch/arm64/kvm/vgic/vgic.c +++ b/arch/arm64/kvm/vgic/vgic.c @@ -853,6 +853,17 @@ static void vgic_prune_ap_list(struct kvm_vcpu *vcpu) goto retry; } + /* + * Fix the last_lr_irq refcount which was obtained while + * populating the LRs. This can also result in the LPI being + * deleted. + */ + irq = *host_data_ptr(last_lr_irq); + if (irq) { + deleted_lpis |= vgic_put_irq_norelease(vcpu->kvm, irq); + *host_data_ptr(last_lr_irq) = NULL; + } + raw_spin_unlock(&vgic_cpu->ap_list_lock); if (unlikely(deleted_lpis)) @@ -1018,8 +1029,10 @@ static void vgic_flush_lr_state(struct kvm_vcpu *vcpu) scoped_guard(raw_spinlock, &irq->irq_lock) { if (likely(vgic_target_oracle(irq) == vcpu)) { vgic_populate_lr(vcpu, irq, count++); - if (count == kvm_vgic_global_state.nr_lr) + if (count == kvm_vgic_global_state.nr_lr) { + vgic_get_irq_ref(irq); *host_data_ptr(last_lr_irq) = irq; + } } } From 751f4641560be8568dcc5af400c0e55a283abf51 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 29 Sep 2026 10:35:47 +0100 Subject: [PATCH 378/535] KVM: arm64: vgic-its: Fix MOVALL handling of source redistributor The MOVALL command moves the pending state of all LPIs targeting a given redistributor to another one. However, we seem to have lost the filtering on the source RD, which means we move all LPIs to the target. Not quite what the spec mandates. Hack update_affinity() to take an optional source vcpu that is used as a filter when non-NULL, restoring the filtering that was performed by vgic_copy_lpi_list() back in the days. Fixes: 11f4f8f3e6e06 ("KVM: arm64: vgic-its: Walk LPI xarray in vgic_its_cmd_handle_movall()") Signed-off-by: Marc Zyngier Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260929093548.3598547-7-maz@kernel.org Signed-off-by: Oliver Upton --- arch/arm64/kvm/vgic/vgic-its.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 0904ae850c35..c10a11373e10 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -319,12 +319,16 @@ static int update_lpi_config(struct kvm *kvm, struct vgic_irq *irq, return ret; } -static int update_affinity(struct vgic_irq *irq, struct kvm_vcpu *vcpu) +static int update_affinity(struct vgic_irq *irq, + struct kvm_vcpu *from_vcpu, struct kvm_vcpu *vcpu) { struct its_vlpi_map map; int ret; guard(raw_spinlock_irqsave)(&irq->irq_lock); + if (from_vcpu && irq->target_vcpu != from_vcpu) + return 0; + irq->target_vcpu = vcpu; if (!irq->hw) @@ -362,7 +366,7 @@ static void update_affinity_ite(struct kvm *kvm, struct its_ite *ite) return; vcpu = collection_to_vcpu(kvm, ite->collection); - update_affinity(ite->irq, vcpu); + update_affinity(ite->irq, NULL, vcpu); } /* @@ -856,7 +860,7 @@ static int vgic_its_cmd_handle_movi(struct kvm *kvm, struct vgic_its *its, vgic_its_invalidate_cache(its); - return update_affinity(ite->irq, vcpu); + return update_affinity(ite->irq, NULL, vcpu); } static bool __is_visible_gfn_locked(struct vgic_its *its, gpa_t gpa) @@ -1383,7 +1387,7 @@ static int vgic_its_cmd_handle_movall(struct kvm *kvm, struct vgic_its *its, if (!irq) continue; - update_affinity(irq, vcpu2); + update_affinity(irq, vcpu1, vcpu2); vgic_put_irq(kvm, irq); } From 4bdd2b3a708c1feb701cb8b62feb8ad2fd77bb07 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 29 Sep 2026 10:00:28 +0100 Subject: [PATCH 379/535] KVM: arm64: Apply the fine-grained UNDEFs without FEAT_FGT FGUs don't actually require hardware support, so don't gate fgt trap config insertion on FGT. This is the only action required to allow FGUs always, as kvm_calculate_traps() already computes the FGU bits with or without FEAT_FGT. This also fixes a spurious WARN when a TLBI OS is run on a guest in vEL1 without FEAT_TLBIOS on non FEAT_FGT hardware. In this configuration the course-grained HCR_EL2.TTLBOS trap reaches the handler handle_tlbi_el1(), which expects an EL1 TLBI only from vEL2. With FGUs enabled the UNDEF will be injected without needing the specific handler. Fixes: f5a5a406b4b8b ("KVM: arm64: Propagate and handle Fine-Grained UNDEF bits") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Reviewed-by: Wei-Lin Chang Link: https://patch.msgid.link/20260929090031.3829185-2-fuad.tabba@linux.dev [oliver: Take Wei-Lin's suggested changelog] Signed-off-by: Oliver Upton --- arch/arm64/kvm/emulate-nested.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 3806ff0920fe..f59d89d04eca 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -2386,9 +2386,6 @@ int __init populate_nv_trap_config(void) print_nv_trap_error(fgt, "FGT bit is reserved", ret); } - if (!cpus_have_final_cap(ARM64_HAS_FGT)) - continue; - prev = xa_store(&sr_forward_xa, enc, xa_mk_value(tc.val), GFP_KERNEL); From 80ae56184b57176b908cd6a7ececf274a7e4b3c7 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 29 Sep 2026 10:00:29 +0100 Subject: [PATCH 380/535] KVM: arm64: Clear HCR_EL2.RW for 32-bit non-protected vCPUs pKVM keeps its own copy of each vCPU's HCR_EL2 at EL2, initialised with RW set unconditionally. Nothing stops userspace from creating a non-protected AArch32 VM, and with RW set, entering one of its vCPUs is an illegal exception return: KVM_RUN fails with KVM_EXIT_FAIL_ENTRY. Clear RW for a vCPU that is AArch32 at EL1, when the CPU has AArch32 EL1. On a CPU without it, RW stays set and the entry still fails, rather than EL2 switching *32_EL2 registers that are UNDEFINED there. The host already rejects such a vCPU, but EL2 takes the vCPU's features from the host and doesn't rely on that. Fixes: b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260929090031.3829185-3-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/nvhe/pkvm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index bb3e0dc0676e..cd09d8888202 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -54,6 +54,15 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) else vcpu->arch.hcr_el2 |= HCR_TID2; + /* + * Without AArch32 EL1, leave RW set and let the entry fail with an + * illegal exception return: the *32_EL2 registers EL2 would otherwise + * switch are UNDEFINED there. + */ + if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_EL1_32BIT) && + cpus_have_final_cap(ARM64_HAS_32BIT_EL1)) + vcpu->arch.hcr_el2 &= ~HCR_EL2_RW; + if (vcpu_has_ptrauth(vcpu)) vcpu->arch.hcr_el2 |= (HCR_API | HCR_APK); From 04447b95c62bef2b3591486c66244c49591a3dbd Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 29 Sep 2026 10:00:30 +0100 Subject: [PATCH 381/535] KVM: arm64: Use the host's HCR_EL2 for non-protected VMs in pKVM For each vCPU, pKVM's EL2 builds its own HCR_EL2 and takes only TWI, TWE and VSE from the host's value on each entry. For a non-protected VM it has fallen behind the host's: on a CPU with MTE the guest can read GMID_EL1, on one with FEAT_EVT2 but no FGT it can execute a TLBI OS its ID registers hide, an interrupt injected without a vGIC (VI, VF) never arrives, and set/way emulation loses the TVM trap it relies on. For a non-protected VM, also take the bits the host varies with the VM's configuration or at runtime: VI, VF, TVM, TID2, TID4, TID5 and TTLBOS. They only pend interrupts for, or add traps to, a VM the host controls. The traps exit to the host, which handles them as it does without pKVM. The bits that change what EL2 does on entry and exit (E2H, RW, API/APK) or depend only on the CPU (TEA, TERR, FWB) stay EL2's. A protected VM still takes only TWI, TWE and VSE. EL2 now sets TID2 or TID4 only for a protected VM, and never sets ATA, as the host rejects KVM_CAP_ARM_MTE in pKVM. Fixes: b56680de9c648 ("KVM: arm64: Initialize trap register values in hyp in pKVM") Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260929090031.3829185-4-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 9 +++++++++ arch/arm64/kvm/hyp/nvhe/hyp-main.c | 7 ++++--- arch/arm64/kvm/hyp/nvhe/pkvm.c | 18 ++++++++---------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h index c904647d2f76..5c050f21066a 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h @@ -12,6 +12,15 @@ #include #include +/* + * HCR_EL2 bits EL2 takes from the host on each entry, per VM type. The rest + * are EL2's own and nothing the host sets there reaches the guest. + */ +#define PKVM_HCR_EL2_HOST_PVM (HCR_EL2_TWI | HCR_EL2_TWE | HCR_EL2_VSE) +#define PKVM_HCR_EL2_HOST_NPVM (PKVM_HCR_EL2_HOST_PVM | HCR_EL2_VI | HCR_EL2_VF | \ + HCR_EL2_TVM | HCR_EL2_TID2 | HCR_EL2_TID4 | \ + HCR_EL2_TID5 | HCR_EL2_TTLBOS) + /* * Holds the relevant data for maintaining the vcpu state completely at hyp. */ diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index 9a3b92e626ad..ac64a036b0a9 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -216,6 +216,7 @@ static void sync_debug_state(struct pkvm_hyp_vcpu *hyp_vcpu) static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) { struct kvm_vcpu *host_vcpu = hyp_vcpu->host_vcpu; + u64 host_hcr_mask = PKVM_HCR_EL2_HOST_PVM; fpsimd_sve_flush(); flush_debug_state(hyp_vcpu); @@ -228,6 +229,7 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) if (!pkvm_hyp_vcpu_is_protected(hyp_vcpu)) { if (vcpu_get_flag(host_vcpu, PKVM_HOST_STATE_DIRTY)) flush_hyp_vcpu_state(hyp_vcpu); + host_hcr_mask = PKVM_HCR_EL2_HOST_NPVM; } else { hyp_vcpu->vcpu.arch.ctxt = host_vcpu->arch.ctxt; } @@ -241,9 +243,8 @@ static void flush_hyp_vcpu(struct pkvm_hyp_vcpu *hyp_vcpu) * trap-control bit, so it must flow to the hyp vCPU alongside TWI/TWE * for the vSError to be delivered. sync_hyp_vcpu() reflects it back. */ - hyp_vcpu->vcpu.arch.hcr_el2 &= ~(HCR_TWI | HCR_TWE | HCR_VSE); - hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) & - (HCR_TWI | HCR_TWE | HCR_VSE); + hyp_vcpu->vcpu.arch.hcr_el2 &= ~host_hcr_mask; + hyp_vcpu->vcpu.arch.hcr_el2 |= READ_ONCE(host_vcpu->arch.hcr_el2) & host_hcr_mask; hyp_vcpu->vcpu.arch.iflags = host_vcpu->arch.iflags; diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index cd09d8888202..6290c4b62659 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -30,6 +30,7 @@ unsigned int kvm_host_sve_max_vl; */ static DEFINE_PER_CPU(struct pkvm_hyp_vcpu *, loaded_hyp_vcpu); +/* The PKVM_HCR_EL2_HOST_{PVM,NPVM} bits of this value come from the host on each entry. */ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) { vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS; @@ -47,13 +48,6 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) if (cpus_have_final_cap(ARM64_HAS_STAGE2_FWB)) vcpu->arch.hcr_el2 |= HCR_FWB; - if (cpus_have_final_cap(ARM64_HAS_EVT) && - !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) && - kvm_read_vm_id_reg(vcpu->kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0)) - vcpu->arch.hcr_el2 |= HCR_TID4; - else - vcpu->arch.hcr_el2 |= HCR_TID2; - /* * Without AArch32 EL1, leave RW set and let the entry fail with an * illegal exception return: the *32_EL2 registers EL2 would otherwise @@ -65,9 +59,6 @@ static void pkvm_vcpu_reset_hcr(struct kvm_vcpu *vcpu) if (vcpu_has_ptrauth(vcpu)) vcpu->arch.hcr_el2 |= (HCR_API | HCR_APK); - - if (kvm_has_mte(vcpu->kvm)) - vcpu->arch.hcr_el2 |= HCR_ATA; } static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu) @@ -85,6 +76,13 @@ static void pvm_init_traps_hcr(struct kvm_vcpu *vcpu) */ val |= HCR_TACR | HCR_TIDCP | HCR_TID3 | HCR_TID1; + if (cpus_have_final_cap(ARM64_HAS_EVT) && + !cpus_have_final_cap(ARM64_MISMATCHED_CACHE_TYPE) && + kvm_read_vm_id_reg(kvm, SYS_CTR_EL0) == read_cpuid(CTR_EL0)) + val |= HCR_EL2_TID4; + else + val |= HCR_EL2_TID2; + if (!kvm_has_feat(kvm, ID_AA64PFR0_EL1, RAS, IMP)) { val |= HCR_TERR | HCR_TEA; val &= ~(HCR_FIEN); From afb4334fb52c7b416bbc9c4b166f6806509f734b Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 29 Sep 2026 10:00:31 +0100 Subject: [PATCH 382/535] KVM: arm64: selftests: Check a feature hidden in an ID register is UNDEF Userspace can hide a feature from a guest by clearing its field in a writable ID register, and KVM then makes the feature's instructions UNDEFINED in the guest by trapping or disabling them. No selftest checks that. Add a test that runs the instruction of each of TLBI OS, MOPS, TCR2_EL1 and FPMR once with its field as advertised and once with it cleared, and expects an UNDEF only when cleared. A feature the vCPU doesn't advertise is skipped, as is hidden TLBI OS on a CPU with neither FGT nor FEAT_EVT2, where KVM can't trap it. Signed-off-by: Fuad Tabba Link: https://patch.msgid.link/20260929090031.3829185-5-fuad.tabba@linux.dev Signed-off-by: Oliver Upton --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/hidden_features.c | 184 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/hidden_features.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 6a1482e3a286..51b98f47044f 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -195,6 +195,7 @@ TEST_GEN_PROGS_arm64 += arm64/vgic_v5 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access TEST_GEN_PROGS_arm64 += arm64/no-vgic TEST_GEN_PROGS_arm64 += arm64/idreg-idst +TEST_GEN_PROGS_arm64 += arm64/hidden_features TEST_GEN_PROGS_arm64 += arm64/kvm-uuid TEST_GEN_PROGS_arm64 += access_tracking_perf_test TEST_GEN_PROGS_arm64 += arch_timer diff --git a/tools/testing/selftests/kvm/arm64/hidden_features.c b/tools/testing/selftests/kvm/arm64/hidden_features.c new file mode 100644 index 000000000000..194d746e7605 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/hidden_features.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * hidden_features - Check that a feature's instruction runs in the guest when + * its ID register field is advertised, and is UNDEFINED when userspace clears + * the field. + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + */ +#include "kvm_util.h" +#include "processor.h" +#include "test_util.h" + +static volatile bool undef; + +static void guest_tlbi_os(void) +{ + /* tlbi vmalle1os */ + asm volatile("sys #0, c8, c1, #0\n\tdsb ish\n\tisb" ::: "memory"); +} + +static void guest_mops(void) +{ + register u64 *d asm("x0"); + register u64 n asm("x1"); + register u64 s asm("x2"); + u64 buf[8]; + + d = buf; + n = sizeof(buf); + s = 0; + /* setp [x0]!, x1!, x2; setm; sete */ + asm volatile(".inst 0x19c20420\n\t.inst 0x19c24420\n\t.inst 0x19c28420" + : "+r"(d), "+r"(n) : "r"(s) : "cc", "memory"); +} + +static void guest_tcr2(void) +{ + read_sysreg_s(SYS_TCR2_EL1); +} + +static void guest_fpmr(void) +{ + read_sysreg_s(SYS_FPMR); +} + +struct feature { + const char *name; + u64 id_reg; + u64 mask; + u8 shift; + u64 min; + void (*insn)(void); + bool (*trappable)(struct kvm_vcpu *vcpu); +}; + +/* Without FGT, KVM traps a hidden TLBI OS only through HCR_EL2.TTLBOS (FEAT_EVT2). */ +static bool tlbi_os_trappable(struct kvm_vcpu *vcpu) +{ + u64 mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1)); + u64 mmfr2 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1)); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, FGT, mmfr0) >= ID_AA64MMFR0_EL1_FGT_IMP || + SYS_FIELD_GET(ID_AA64MMFR2_EL1, EVT, mmfr2) >= ID_AA64MMFR2_EL1_EVT_TTLBxS; +} + +#define FEATURE(n, reg, field, min_val, fn, trap) \ +{ \ + .name = n, \ + .id_reg = SYS_##reg, \ + .mask = reg##_##field##_MASK, \ + .shift = reg##_##field##_SHIFT, \ + .min = reg##_##field##_##min_val, \ + .insn = fn, \ + .trappable = trap, \ +} + +static const struct feature features[] = { + FEATURE("TLBI OS", ID_AA64ISAR0_EL1, TLB, OS, guest_tlbi_os, tlbi_os_trappable), + FEATURE("MOPS", ID_AA64ISAR2_EL1, MOPS, IMP, guest_mops, NULL), + FEATURE("TCR2_EL1", ID_AA64MMFR3_EL1, TCRX, IMP, guest_tcr2, NULL), + FEATURE("FPMR", ID_AA64PFR2_EL1, FPMR, IMP, guest_fpmr, NULL), +}; + +static void guest_code(const struct feature *feat) +{ + undef = false; + feat->insn(); + GUEST_SYNC(undef); + GUEST_DONE(); +} + +static void guest_undef_handler(struct ex_regs *regs) +{ + undef = true; + regs->pc += 4; +} + +static bool run(const struct feature *feat, bool hide) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + bool got = false; + u64 val; + + vm = vm_create_with_one_vcpu(&vcpu, (void *)guest_code); + vm_init_descriptor_tables(vm); + vcpu_init_descriptor_tables(vcpu); + vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_UNKNOWN, guest_undef_handler); + vcpu_args_set(vcpu, 1, feat); + + if (hide) { + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg)); + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg), val & ~feat->mask); + } + + for (;;) { + vcpu_run(vcpu); + switch (get_ucall(vcpu, &uc)) { + case UCALL_SYNC: + got = uc.args[1]; + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + case UCALL_DONE: + kvm_vm_free(vm); + return got; + default: + TEST_FAIL("Unknown ucall %lu", uc.cmd); + } + } +} + +static void probe_feature(const struct feature *feat, bool *present, bool *trappable) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + u64 val; + + vm = vm_create_with_one_vcpu(&vcpu, NULL); + val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(feat->id_reg)); + *present = ((val & feat->mask) >> feat->shift) >= feat->min; + *trappable = !feat->trappable || feat->trappable(vcpu); + kvm_vm_free(vm); +} + +int main(void) +{ + const struct feature *feat; + bool present, trappable; + int i; + + test_disable_default_vgic(); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(features) * 2); + + for (i = 0; i < ARRAY_SIZE(features); i++) { + feat = &features[i]; + + probe_feature(feat, &present, &trappable); + if (!present) { + ksft_test_result_skip("%s advertised, not supported\n", feat->name); + ksft_test_result_skip("%s hidden, not supported\n", feat->name); + continue; + } + + if (run(feat, false)) + ksft_test_result_fail("%s advertised, UNDEF\n", feat->name); + else + ksft_test_result_pass("%s advertised\n", feat->name); + + if (!trappable) + ksft_test_result_skip("%s hidden, not trappable\n", feat->name); + else if (run(feat, true)) + ksft_test_result_pass("%s hidden\n", feat->name); + else + ksft_test_result_fail("%s hidden, no UNDEF\n", feat->name); + } + + ksft_finished(); +} From 684b413b5483f57c890c171b9400076a0143b918 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 18 Sep 2026 16:06:42 +0200 Subject: [PATCH 383/535] virtio_blk: set the zone write granularity virtblk_read_zoned_limits() reads the write granularity that the device reports in virtio_blk_zoned_characteristics and assigns it to the physical block size and to io_min, but never to the limit that is named after it. queue_limits.zone_write_granularity is left at zero, so blk_validate_zoned_limits() raises it to the logical block size: if (lim->zone_write_granularity < lim->logical_block_size) lim->zone_write_granularity = lim->logical_block_size; A device that reports a granularity coarser than its logical block size, which is what the field exists to express, therefore has it silently reduced. A 512e host managed disk passed through to a guest reports a logical block size of 512 and a write granularity of 4096, and the guest ends up with a zone write granularity of 512. bio_split_alignment() returns lim->zone_write_granularity if it is non-zero and bio_split_io_at() may split a bio with as per bio_split_alignment(). This can real to the write getting rejected by the host drive, as the write is not aligned to the physical block size. zonefs also takes its block size from bdev_zone_write_granularity(), so it would incorrectly use 512 on a disk that requires 4096. sd_zbc_read_zones() sets the limit from the physical block size for the same reason. NVMe ZNS and null_blk leave it unset, but the fallback gives the right answer for them, as their write granularity is the logical block size. virtio carries a separate value that may exceed it. Set the zone write granularity from the value that the device reports. Fixes: 95bfec41bd3d ("virtio-blk: add support for zoned block devices") Signed-off-by: Niklas Cassel Reviewed-by: Stefan Hajnoczi Link: https://patch.msgid.link/20260918140641.2031075-2-cassel@kernel.org Signed-off-by: Jens Axboe --- drivers/block/virtio_blk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c index 32bf3ba07a9d..c8bfbf8435ac 100644 --- a/drivers/block/virtio_blk.c +++ b/drivers/block/virtio_blk.c @@ -743,6 +743,7 @@ static int virtblk_read_zoned_limits(struct virtio_blk *vblk, dev_warn(&vdev->dev, "zero write granularity reported\n"); return -ENODEV; } + lim->zone_write_granularity = wg; lim->physical_block_size = wg; lim->io_min = wg; From 1a70f0b71826fb764e8490dda55bb1b853e0d1de Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Tue, 1 Sep 2026 17:30:49 +0300 Subject: [PATCH 384/535] tpm: Fix heap buffer overflow in tpm_transmit_cmd() struct tpm_buf now uses an embedded flexible array for data, which is 6 bytes shorter than TPM_BUFSIZE due to the struct header. But tpm_transmit_cmd() still passes PAGE_SIZE to tpm_transmit(), and after clamping to TPM_BUFSIZE in tpm_try_transmit(), chip->ops->recv() can write past the end of data[] by 6 bytes. Pass buf->capacity to tpm_transmit() instead. Fixes: 3d9e043dab0a ("tpm-buf: Memory-safe allocations") Signed-off-by: Jiangshan Yi Link: https://lore.kernel.org/r/20260901062850.379870-1-yijiangshan@kylinos.cn Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index f745a098908b..1ccdbde98b69 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -268,7 +268,7 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_buf *buf, int err; ssize_t len; - len = tpm_transmit(chip, buf->data, PAGE_SIZE); + len = tpm_transmit(chip, buf->data, buf->capacity); if (len < 0) return len; From 1e2f9a611b334ce6ed1dc4a9e1587c7a11d2be30 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Wed, 2 Sep 2026 15:48:39 +0800 Subject: [PATCH 385/535] tpm: Fix auth session leak in tpm2_get_random() error path When tpm_buf_fill_hmac_session() fails in tpm2_get_random(), the error path returns without calling tpm2_end_auth_session(), leaking the auth session. All other error paths in the function handle this correctly. Fixes: 3d9e043dab0a ("tpm-buf: Memory-safe allocations") Cc: stable@vger.kernel.orga # v6.12+ Signed-off-by: Jiangshan Yi Link: https://lore.kernel.org/r/20260902074839.417419-1-yijiangshan@kylinos.cn Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm2-cmd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 48cec39995fe..96e0e06bbbf0 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -281,8 +281,10 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) } tpm_buf_append_u16(buf, num_bytes); err = tpm_buf_fill_hmac_session(chip, buf); - if (err) + if (err) { + tpm2_end_auth_session(chip); return err; + } err = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, From 9b522400bf5c082feb9a0037a053ea822a2c7e4d Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 3 Sep 2026 11:58:37 +0800 Subject: [PATCH 386/535] tpm: fix off-by-four bounds check in tpm2_get_random() When the response carries the TPM2_ST_SESSIONS tag, tpm2_get_random() skips the 4-byte parameter size field before locating the random data, but the bounds check still validates the response length against TPM_HEADER_SIZE. A truncated response can pass the check and make memcpy() read up to 4 bytes past the response end, so stale buffer contents end up in the caller's random bytes. Fix this by checking the response length against 'offset', which already includes the skipped parameter size field. Cc: stable@vger.kernel.org # v6.12+ Fixes: 1b6d7f9eb150 ("tpm: add session encryption protection to tpm2_get_random()") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260902074839.417419-1-yijiangshan%40kylinos.cn Signed-off-by: Jiangshan Yi Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260903035837.219284-1-yijiangshan@kylinos.cn Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm2-cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 96e0e06bbbf0..ae22295df798 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -307,7 +307,7 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max) out = (struct tpm2_get_random_out *)&buf->data[offset]; recd = min_t(u32, be16_to_cpu(out->size), num_bytes); if (tpm_buf_length(buf) < - TPM_HEADER_SIZE + + offset + offsetof(struct tpm2_get_random_out, buffer) + recd) { tpm2_end_auth_session(chip); From 015fb29a748342a186f37b602ac70017098c8251 Mon Sep 17 00:00:00 2001 From: Surendran Kanagaraj Date: Fri, 18 Sep 2026 00:07:31 +0000 Subject: [PATCH 387/535] tpm: Disable TPM on null key name mismatch The null key name check exists to protect against TPM reset attacks, so a mismatch should stop the device from serving further requests. Currently it does not disable the chip when it finds a mismatch. The mismatch is logged: tpm tpm0: null key integrity check failed but the chip keeps serving commands: / # tpm2_getcap -c properties-fixed TPM_PT_FAMILY_INDICATOR: as UINT32: 0x08322e3000 as string: "2.0" ... tpm2_load_null() where the null key name check is run sets the chip as disabled only if the rc is non zero. When the mismatch is seen, rc is zero at that point and it returns success. The other issue is that the caller expects the null key handle to be populated when the function returns 0 which it does here without writing the handle and proceeds assuming the null key handle is valid. During the test, I noticed that tpm2_start_auth_session() uses the uninitialized stack value as the key handle since tpm2_load_null() returns 0 despite the integrity failure and proceeds with TPM2_CC_START_AUTH_SESS with this value as salt key handle. Set rc to -ENODEV on the mismatch. The error path then disables the chip and returns the correct code to the caller. Tested in QEMU with swtpm and CONFIG_TCG_TPM2_HMAC=y by making TPM2_CC_CONTEXT_LOAD fail with TPM2_RC_INTEGRITY and changing the name of the re-created null key. The chip is now disabled on the mismatch. Fixes: cc7d8594342a ("tpm: Rollback tpm2_load_null()") Fixes: 423893fcbe7e ("tpm: Disable TPM on tpm2_create_primary() failure") Cc: stable@vger.kernel.org # v6.12+ Assisted-by: LLM Signed-off-by: Surendran Kanagaraj Link: https://lore.kernel.org/r/20260918000731.48657-1-surenkj@amazon.com Reviewed-by: Gunnar Kudrjavets Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm2-sessions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c index cf8f1fd6790b..ca1e2bf424e1 100644 --- a/drivers/char/tpm/tpm2-sessions.c +++ b/drivers/char/tpm/tpm2-sessions.c @@ -975,6 +975,7 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key) /* Deduce from the name change TPM interference: */ dev_err(&chip->dev, "null key integrity check failed\n"); tpm2_flush_context(chip, tmp_null_key); + rc = -ENODEV; err: if (rc) { From 28fa9af353bed2bb738c46e31f9b7d0347b759d1 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 19 Sep 2026 16:40:27 +0200 Subject: [PATCH 388/535] hrtimer: Use the mask to clear TIF_HRTIMER_REARM from the exit work hrtimer_rearm_deferred_user_irq() removes the rearm bit from the local copy of the TIF work with: *tif_work &= ~TIF_HRTIMER_REARM; TIF_HRTIMER_REARM is the bit number (12), not the mask. That clears TIF_NOTIFY_SIGNAL (bit 2) and TIF_MEMDIE (bit 3) from the copy and leaves bit 12 set. So the function never returns true, and the copy handed to exit_to_user_mode_loop() lacks TIF_NOTIFY_SIGNAL. If that was the only work for the loop, the task returns to user space with the task work still pending. hrtimer_interrupt() sets TIF_HRTIMER_REARM every time, so the next tick repeats that. Task work queued with TWA_SIGNAL from a hrtimer callback stays pending until the task does a syscall, takes an interrupt without deferred rearm or has to reschedule. A task which polls the io_uring completion ring in user space sees a timeout completion after 30ms (median) instead of 70us. 2 CPU QEMU guest, HZ=1000. Use the mask. Fixes: 15dd3a948855 ("hrtimer: Push reprogramming timers into the interrupt return path") Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260919144027.70250-1-kmehltretter@gmail.com --- include/linux/hrtimer_rearm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hrtimer_rearm.h b/include/linux/hrtimer_rearm.h index 17a81826bd9a..12e42721c8e0 100644 --- a/include/linux/hrtimer_rearm.h +++ b/include/linux/hrtimer_rearm.h @@ -45,7 +45,7 @@ hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif clear_thread_flag(TIF_HRTIMER_REARM); __hrtimer_rearm_deferred(); /* Don't go into the loop if HRTIMER_REARM was the only flag */ - *tif_work &= ~TIF_HRTIMER_REARM; + *tif_work &= ~_TIF_HRTIMER_REARM; return !*tif_work; } return false; From 226154ea87952ad7141ce2dcf52a6c2edd71b8fe Mon Sep 17 00:00:00 2001 From: Mohamad Raizudeen Date: Sat, 26 Sep 2026 20:29:03 +0530 Subject: [PATCH 389/535] crypto: x86/aes-gcm - fix always true check for last AAD segment In gcm_process_assoc(), a segment that is not the last one must have its length rounded down to a multiple of 16 bytes, as required by the assembly. The check for a non-last segment is `if (unlikely(assoclen)) /* Not the last segment yet? */` where assoclen is the number of AAD bytes remaining after the current segment. Since the conversion to the new scatterwalk API, assoclen is decremented at the end of the loop body rather than the beginning, so it still includes the current segment when the check executes, making the check always true. As a result the last segment was rounded down as well, causing some avoidable extra work: an additional memcpy into the temporary buffer and an additional call into the assembly after the loop. The GCM authentication tag is unaffected either way, so the self-tests pass and this went unnoticed. It is purely an efficiency issue rather than a correctness one. Fix this by moving the assoclen decrement back to the beginning of the loop body. Fixes: e9787deff49e ("crypto: x86/aes-gcm - use the new scatterwalk functions") Cc: stable@vger.kernel.org Suggested-by: Eric Biggers Signed-off-by: Mohamad Raizudeen Link: https://patch.msgid.link/20260926145903.6061-1-raizudeen.kerneldev@gmail.com Signed-off-by: Eric Biggers --- arch/x86/crypto/aesni-intel_glue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index f522fff9231e..1903ed8bbe3b 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -1293,6 +1293,7 @@ static void gcm_process_assoc(const struct aes_gcm_key *key, u8 ghash_acc[16], unsigned int len; const u8 *src = walk.addr; + assoclen -= orig_len_this_step; if (unlikely(pos)) { len = min(len_this_step, 16 - pos); memcpy(&buf[pos], src, len); @@ -1320,7 +1321,6 @@ static void gcm_process_assoc(const struct aes_gcm_key *key, u8 ghash_acc[16], kernel_fpu_end(); kernel_fpu_begin(); } - assoclen -= orig_len_this_step; } if (unlikely(pos)) aes_gcm_aad_update(key, ghash_acc, buf, pos, flags); From 2cf81fc9505b148b1b70068303ea6ea4b1212403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Lappetel=C3=A4inen?= Date: Fri, 25 Sep 2026 15:48:29 +0300 Subject: [PATCH 390/535] net/packet: prevent TX_RING byte count overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tpacket_snd() drains every SEND_REQUEST frame in a TX ring and adds each valid packet length to the signed int len_sum. Userspace can recycle completed frames concurrently, so a single blocking send call is not bounded by the ring size. After enough successful transmissions len_sum wraps into the negative range. In particular, 65536 65535-byte frames followed by a 65007-byte frame produce 0xfffffdef, or -EIOCBQUEUED. sock_sendmsg_nosec() treats that as an impossible return and triggers a BUG. On systems configured to panic on oops, a CAP_NET_RAW holder can panic the host kernel. Stop before the next frame would make the byte count exceed INT_MAX. The positive short count leaves that frame pending for a later send call. A two-thread TPACKET_V2 reproducer triggered the BUG on an arm64 build of Linux 7.3-rc4. With this change, the same reproducer returned the expected positive short count without an oops. Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap") Cc: stable@vger.kernel.org Signed-off-by: Ilkka Lappeteläinen Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260925124829.6595-1-ilkka.lappetelainen@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 7c83e01526ed..8865c1a42667 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -2874,6 +2874,9 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) vnet_hdr.hdr_len); has_vnet_hdr = true; } + /* Prevent the byte count from wrapping into a negative errno */ + if (unlikely(tp_len > INT_MAX - len_sum)) + break; copylen = max_t(int, copylen, hard_header_len); skb = sock_alloc_send_skb(&po->sk, hlen + tlen + sizeof(struct sockaddr_ll) + From e0a6249190402a28f1e2925a81acf573157d55ef Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 29 Sep 2026 09:19:12 +0900 Subject: [PATCH 391/535] fprobe: Use guard(rcu_sched_notrace) and check rcu_is_watching() unregister_fprobe() and unregister_fprobe_async() (used by BPF kprobe-multi) rely on standard RCU grace periods (synchronize_rcu() and call_rcu()) to wait until in-flight fprobe handlers complete before freeing the fprobe. However, if an fprobe handler executes while RCU is not watching (such as in the idle loop or nohz_full extended quiescent states), standard RCU does not track preemption-disabled sections. Consequently, synchronize_rcu() does not wait for those executions, which can lead to a use-after-free if the fprobe is freed immediately after unregistration. Ensure handlers exit early when !rcu_is_watching(). Furthermore, fprobe_fgraph_entry() and fprobe_ftrace_entry() previously used guard(rcu)() and rcu_read_lock(), which invoke lockdep on every hit under CONFIG_PROVE_LOCKING. This adds overhead and can cause lockdep recursion if probed functions interact with lockdep. Since rhltable_lookup() and rhl_for_each_entry_rcu() use rcu_dereference_all_check() (which checks rcu_read_lock_any_held()), holding preemption disabled via rcu_read_lock_sched_notrace() is fully valid and sufficient so long as rcu_is_watching() is true. Define and use guard(rcu_sched_notrace)() across fprobe_ftrace_entry(), fprobe_fgraph_entry(), and fprobe_return(). This eliminates fast-path rcu_read_lock() and lockdep overhead while guaranteeing safe grace period synchronization. Link: https://lore.kernel.org/all/179064115227.394389.16910234241400391996.stgit@devnote2/ Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-e46bcd68-4a56-4f19-a255-e3772980e5e3 Fixes: 657b594b2084 ("fprobe: Fix unregister_fprobe() to wait for RCU grace period") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Masami Hiramatsu (Google) Reviewed-by: Paul E. McKenney --- kernel/trace/fprobe.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index 9f2d98181779..da286619c5d8 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -47,6 +47,10 @@ static struct rhltable fprobe_ip_table; static DEFINE_MUTEX(fprobe_mutex); static struct fgraph_ops fprobe_graph_ops; +DEFINE_LOCK_GUARD_0(rcu_sched_notrace, + rcu_read_lock_sched_notrace(), + rcu_read_unlock_sched_notrace()) + static u32 fprobe_node_hashfn(const void *data, u32 len, u32 seed) { return hash_ptr(*(unsigned long **)data, 32); @@ -329,16 +333,14 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip, struct fprobe *fp; int bit; + if (!rcu_is_watching()) + return; + bit = ftrace_test_recursion_trylock(ip, parent_ip); if (bit < 0) return; - /* - * ftrace_test_recursion_trylock() disables preemption, but - * rhltable_lookup() checks whether rcu_read_lcok is held. - * So we take rcu_read_lock() here. - */ - rcu_read_lock(); + guard(rcu_sched_notrace)(); head = rhltable_lookup(&fprobe_ip_table, &ip, fprobe_rht_params); rhl_for_each_entry_rcu(node, pos, head, hlist) { @@ -353,7 +355,6 @@ static void fprobe_ftrace_entry(unsigned long ip, unsigned long parent_ip, else __fprobe_handler(ip, parent_ip, fp, fregs, NULL); } - rcu_read_unlock(); ftrace_test_recursion_unlock(bit); } NOKPROBE_SYMBOL(fprobe_ftrace_entry); @@ -567,10 +568,13 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops struct fprobe *fp; int used, ret; + if (!rcu_is_watching()) + return 0; + if (WARN_ON_ONCE(!fregs)) return 0; - guard(rcu)(); + guard(rcu_sched_notrace)(); head = rhltable_lookup(&fprobe_ip_table, &func, fprobe_rht_params); reserved_words = 0; rhl_for_each_entry_rcu(node, pos, head, hlist) { @@ -665,13 +669,16 @@ static void fprobe_return(struct ftrace_graph_ret *trace, int size, curr; int size_words; + if (!rcu_is_watching()) + return; + fgraph_data = (unsigned long *)fgraph_retrieve_data(gops->idx, &size); if (WARN_ON_ONCE(!fgraph_data)) return; size_words = SIZE_IN_LONG(size); ret_ip = ftrace_regs_get_instruction_pointer(fregs); - preempt_disable_notrace(); + guard(rcu_sched_notrace)(); curr = 0; while (size_words > curr) { @@ -687,7 +694,6 @@ static void fprobe_return(struct ftrace_graph_ret *trace, } curr += size; } - preempt_enable_notrace(); } NOKPROBE_SYMBOL(fprobe_return); From febec6a7076499d16b37f556724a942633a270a6 Mon Sep 17 00:00:00 2001 From: Alireza Asgari Date: Thu, 24 Sep 2026 13:00:24 +0000 Subject: [PATCH 392/535] net: fix checksum offsets in skb_splice_from_iter() skb_splice_from_iter() appends multiple page fragments before updating skb->len. However, skb_splice_csum_page() uses that unchanged length as the offset for every fragment checksum. If bytes already appended during the call have an odd total length, a later fragment's checksum is combined with the wrong parity. For example, prime a UDP socket with sendto(MSG_MORE) and splice two distinct pipe buffers containing "abc" and "DEFGH". Uncorking reports success, but the receiver discards the packet for a bad checksum. This also affects IPv6 and fragments beginning near a page boundary. Pass the initial skb length plus the bytes already spliced to the checksum helper. Keep the existing final length update and partial-progress error handling unchanged. CHECKSUM_PARTIAL does not use this helper and remains unaffected. Fixes: 2e910b95329c ("net: Add a function to splice pages into an skbuff for MSG_SPLICE_PAGES") Cc: stable@vger.kernel.org Signed-off-by: Alireza Asgari Link: https://patch.msgid.link/20260924-fix-udp-splice-checksum-v1-1-fe61d65447a8@asgari.net Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index c3042d822afa..4aea06d5167d 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -7409,7 +7409,8 @@ nodefer: kfree_skb_napi_cache(skb); } static void skb_splice_csum_page(struct sk_buff *skb, struct page *page, - size_t offset, size_t len) + size_t offset, size_t len, + unsigned int csum_offset) { const char *kaddr; __wsum csum; @@ -7417,7 +7418,7 @@ static void skb_splice_csum_page(struct sk_buff *skb, struct page *page, kaddr = kmap_local_page(page); csum = csum_partial(kaddr + offset, len, 0); kunmap_local(kaddr); - skb->csum = csum_block_add(skb->csum, csum, skb->len); + skb->csum = csum_block_add(skb->csum, csum, csum_offset); } /** @@ -7477,7 +7478,8 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter, } if (skb->ip_summed == CHECKSUM_NONE) - skb_splice_csum_page(skb, page, off, part); + skb_splice_csum_page(skb, page, off, part, + skb->len + spliced); off = 0; spliced += part; From 1221246d3bd08d8b5228777e1a9b3361ca946e64 Mon Sep 17 00:00:00 2001 From: Alireza Asgari Date: Thu, 24 Sep 2026 13:00:25 +0000 Subject: [PATCH 393/535] selftests: net: cover UDP splice checksum fragment alignment Exercise software UDP checksums when splice() transfers multiple pipe fragments into a corked datagram. Use separate source pipes so short writes cannot merge, and use odd fragment lengths to expose checksum-position errors between fragments. Cover IPv4, IPv6 and IPv4-mapped destinations, connected and unconnected sockets, resident prefixes, even and odd fragment lengths, and a fragment starting near a page boundary. Ordinary sends provide a control. Check the complete payload and reject extra datagrams after a successful send. Keep receive waits bounded and skip unavailable address families or pipe capacities. The test uses only loopback sockets with ephemeral ports and has no dependency on the application that exposed the regression. Signed-off-by: Alireza Asgari Link: https://patch.msgid.link/20260924-fix-udp-splice-checksum-v1-2-fe61d65447a8@asgari.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/.gitignore | 1 + tools/testing/selftests/net/Makefile | 1 + .../selftests/net/udp_splice_checksum.c | 378 ++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 tools/testing/selftests/net/udp_splice_checksum.c diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore index c9f46031ac73..dacd36ed8455 100644 --- a/tools/testing/selftests/net/.gitignore +++ b/tools/testing/selftests/net/.gitignore @@ -55,6 +55,7 @@ tools tun txring_overwrite txtimestamp +udp_splice_checksum udpgso udpgso_bench_rx udpgso_bench_tx diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 3ee3378f8b26..590b33e16d9f 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -199,6 +199,7 @@ TEST_GEN_PROGS := \ tap \ tcp_port_share \ tls \ + udp_splice_checksum \ # end of TEST_GEN_PROGS TEST_FILES := \ diff --git a/tools/testing/selftests/net/udp_splice_checksum.c b/tools/testing/selftests/net/udp_splice_checksum.c new file mode 100644 index 000000000000..dc61444e6481 --- /dev/null +++ b/tools/testing/selftests/net/udp_splice_checksum.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Exercise UDP software checksums over multiple spliced pipe fragments. + * All traffic stays on loopback; no privileges or external services are needed. + * Exit status: 0 = pass, 1 = failure, 4 = all cases skipped. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest.h" + +#define TIMEOUT_MS 200 + +struct test_case { + const char *name; + const char *body; + const char *prefix; + size_t fragments[3]; + bool ordinary; + bool page_tail; +}; + +static const struct test_case cases[] = { + { "ordinary-control", "abcDEFGH", "", { 3, 5 }, true, false }, + { "uniform-odd-fragments", "BBBBBBBB", "", { 3, 5 }, false, false }, + { "even-fragments", "abcdEFGH", "", { 4, 4 }, false, false }, + { "odd-fragments", "abcDEFGH", "", { 3, 5 }, false, false }, + { "odd-prefix-odd-fragments", "abcDEFGH", "P", { 3, 5 }, false, false }, + { "even-prefix-odd-fragments", "abcDEFGH", "PQ", { 3, 5 }, false, false }, + { "page-tail-odd-fragments", "abcDEFGH", "", { 3, 5 }, false, true }, + { "three-fragments", "abcDEFGhijkl", "", { 3, 4, 5 }, false, false }, + { "odd-prefix-three-fragments", + "abcDEFGhijkl", + "P", + { 3, 4, 5 }, + false, + false }, +}; + +static const char *const families[] = { "IPv4", "IPv6", "IPv4-mapped" }; + +static int error(const char *what) +{ + ksft_perror(what); + return 1; +} + +static bool unavailable(void) +{ + return errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT || + errno == EADDRNOTAVAIL; +} + +static long long now_ms(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + ksft_exit_fail_perror("clock_gettime"); + return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static int receive_one(int fd, const char *expected, size_t length) +{ + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + long long deadline = now_ms() + TIMEOUT_MS; + int remaining, ret; + char received[64]; + ssize_t count; + + for (;;) { + remaining = deadline - now_ms(); + if (remaining < 0) + remaining = 0; + ret = poll(&pfd, 1, remaining); + if (ret < 0) { + if (errno == EINTR) + continue; + return error("poll"); + } + if (!ret) { + ksft_print_msg("no datagram within %d ms after send\n", + TIMEOUT_MS); + return 1; + } + count = recv(fd, received, sizeof(received), + MSG_DONTWAIT | MSG_TRUNC); + if (count < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return error("recv"); + } + break; + } + if (count != (ssize_t)length || memcmp(received, expected, length)) { + ksft_print_msg("received %zd bytes; expected %zu matching\n", + count, length); + return 1; + } + count = recv(fd, received, sizeof(received), MSG_DONTWAIT | MSG_TRUNC); + if (count >= 0) { + ksft_print_msg("unexpected extra datagram (%zd bytes)\n", + count); + return 1; + } + if (errno != EAGAIN) + return error("extra-datagram check"); + return 0; +} + +/* Separate source pipes prevent adjacent small writes merging into one buffer. */ +static int append_fragment(int dest, const char *data, size_t length) +{ + int source[2], ret = 1; + + if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) + return error("source pipe"); + if (write(source[1], data, length) != (ssize_t)length) { + error("source write"); + goto out; + } + if (splice(source[0], NULL, dest, NULL, length, SPLICE_F_NONBLOCK) != + (ssize_t)length) { + error("pipe-to-pipe splice"); + goto out; + } + ret = 0; +out: + close(source[0]); + close(source[1]); + return ret; +} + +static int pipe_capacity(int fd, unsigned int slots, const char **skip_reason) +{ + long required = slots * sysconf(_SC_PAGESIZE); + int capacity = fcntl(fd, F_GETPIPE_SZ); + + if (capacity < 0) + return error("query pipe capacity"); + if (capacity >= required || + fcntl(fd, F_SETPIPE_SZ, required) >= required) + return 0; + ksft_print_msg("cannot reserve %u pipe slots: %s\n", slots, + strerror(errno)); + *skip_reason = "required pipe capacity unavailable"; + return KSFT_SKIP; +} + +/* Leave three bytes at a page's end, followed by five bytes on the next page. */ +static int append_page_tail(int dest, const char *body, + const char **skip_reason) +{ + long page = sysconf(_SC_PAGESIZE); + int source[2], ret = 1; + char *data; + + if (page < 8) { + ksft_print_msg("invalid page size %ld\n", page); + return 1; + } + data = malloc(page + 5); + if (!data) + return error("malloc"); + memset(data, 'x', page - 3); + memcpy(data + page - 3, body, 8); + if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) { + error("page-tail source pipe"); + goto free_data; + } + ret = pipe_capacity(source[1], 2, skip_reason); + if (ret) + goto out; + ret = 1; + if (write(source[1], data, page + 5) != page + 5) { + error("page-tail write"); + goto out; + } + if (read(source[0], data, page - 3) != page - 3) { + error("page-tail discard"); + goto out; + } + if (splice(source[0], NULL, dest, NULL, 8, SPLICE_F_NONBLOCK) != 8) { + error("page-tail pipe-to-pipe splice"); + goto out; + } + ret = 0; +out: + close(source[0]); + close(source[1]); +free_data: + free(data); + return ret; +} + +static int run_case(unsigned int family, bool connected, + const struct test_case *test, const char **skip_reason) +{ + size_t body_length = strlen(test->body), offset = 0; + int tx_family = family == 0 ? AF_INET : AF_INET6; + int rx_family = family == 1 ? AF_INET6 : AF_INET; + int rx = -1, tx = -1, pipefd[2] = { -1, -1 }; + size_t prefix_length = strlen(test->prefix); + struct sockaddr_storage address = { 0 }; + socklen_t address_length; + struct sockaddr_in6 *v6; + struct sockaddr_in *v4; + int ret = 1, zero = 0; + char expected[64]; + unsigned int i; + ssize_t count; + + v4 = (struct sockaddr_in *)&address; + v6 = (struct sockaddr_in6 *)&address; + *skip_reason = "address family unavailable"; + memcpy(expected, test->prefix, prefix_length); + memcpy(expected + prefix_length, test->body, body_length); + rx = socket(rx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (rx < 0) { + ret = unavailable() ? KSFT_SKIP : error("receive socket"); + goto out; + } + if (rx_family == AF_INET) { + v4->sin_family = AF_INET; + v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address_length = sizeof(*v4); + } else { + v6->sin6_family = AF_INET6; + v6->sin6_addr = in6addr_loopback; + address_length = sizeof(*v6); + } + if (bind(rx, (struct sockaddr *)&address, address_length)) { + ret = unavailable() ? KSFT_SKIP : error("bind loopback"); + goto out; + } + if (getsockname(rx, (struct sockaddr *)&address, &address_length)) { + error("getsockname"); + goto out; + } + if (family == 2) { + unsigned short port = v4->sin_port; + + memset(&address, 0, sizeof(address)); + v6->sin6_family = AF_INET6; + v6->sin6_port = port; + inet_pton(AF_INET6, "::ffff:127.0.0.1", &v6->sin6_addr); + address_length = sizeof(*v6); + } + tx = socket(tx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (tx < 0) { + ret = unavailable() ? KSFT_SKIP : error("send socket"); + goto out; + } + if (family == 2 && + setsockopt(tx, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero))) { + error("disable IPV6_V6ONLY"); + goto out; + } + if (connected && + connect(tx, (struct sockaddr *)&address, address_length)) { + error("connect"); + goto out; + } + if (test->ordinary) { + count = sendto(tx, expected, prefix_length + body_length, 0, + (struct sockaddr *)&address, address_length); + if (count != (ssize_t)(prefix_length + body_length)) { + error("ordinary sendto"); + goto out; + } + } else { + if (pipe2(pipefd, O_NONBLOCK | O_CLOEXEC)) { + error("assembled pipe"); + goto out; + } + ret = pipe_capacity(pipefd[1], test->fragments[2] ? 3 : 2, + skip_reason); + if (ret) + goto out; + ret = 1; + if (test->page_tail) { + ret = append_page_tail(pipefd[1], test->body, + skip_reason); + if (ret) + goto out; + ret = 1; + } else { + for (i = 0; i < ARRAY_SIZE(test->fragments); i++) { + if (!test->fragments[i]) + break; + if (append_fragment(pipefd[1], + test->body + offset, + test->fragments[i])) + goto out; + offset += test->fragments[i]; + } + } + /* Even an empty prefix establishes the destination and UDP cork. */ + count = sendto(tx, test->prefix, prefix_length, MSG_MORE, + (struct sockaddr *)&address, address_length); + if (count != (ssize_t)prefix_length) { + error("prime sendto(MSG_MORE)"); + goto out; + } + count = splice(pipefd[0], NULL, tx, NULL, body_length, + SPLICE_F_NONBLOCK | SPLICE_F_MORE); + if (count != (ssize_t)body_length) { + ksft_print_msg("socket splice: %zd, expected %zu: %s\n", + count, body_length, + count < 0 ? strerror(errno) : + "short transfer"); + goto out; + } + if (sendto(tx, "", 0, 0, (struct sockaddr *)&address, + address_length)) { + error("commit sendto"); + goto out; + } + } + ret = receive_one(rx, expected, prefix_length + body_length); +out: + if (pipefd[0] >= 0) { + close(pipefd[0]); + close(pipefd[1]); + } + if (tx >= 0) + close(tx); + if (rx >= 0) + close(rx); + return ret; +} + +int main(void) +{ + unsigned int family, connected, i; + const char *connection, *skip; + int ret; + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(families) * 2 * ARRAY_SIZE(cases)); + for (family = 0; family < ARRAY_SIZE(families); family++) { + for (connected = 0; connected < 2; connected++) { + connection = connected ? "connected" : "unconnected"; + for (i = 0; i < ARRAY_SIZE(cases); i++) { + ret = run_case(family, connected, &cases[i], + &skip); + if (ret == KSFT_SKIP) + ksft_test_result_skip("%s %s %s: %s\n", + families[family], + connection, + cases[i].name, + skip); + else + ksft_test_result(!ret, "%s %s %s\n", + families[family], + connection, + cases[i].name); + } + } + } + if (!ksft_get_pass_cnt() && !ksft_get_fail_cnt()) { + /* ksft_exit_skip() would add a result beyond the plan. */ + ksft_print_cnts(); + return KSFT_SKIP; + } + ksft_finished(); +} From 8b9993437429c66d11430ae61ffa5937a6abb4ae Mon Sep 17 00:00:00 2001 From: Yoshihisa Yamamoto Date: Thu, 24 Sep 2026 10:36:57 +0000 Subject: [PATCH 394/535] net: pcs: rzn1-miic: Clear MIIC protection state before unprotect sequence On RZ/T2H and RZ/N1D MIIC, writing 0x0000 to the MIIC protection register clears the protection state machine. The initial protection state cannot be assumed to be constant, as it may be influenced by previous activity before the driver takes ownership of the hardware. Clear the protection state before issuing the unprotect sequence so that register access always starts from a known state, regardless of any previous activity. Fixes: 7dc54d3b8d91 ("net: pcs: add Renesas MII converter driver") Signed-off-by: Yoshihisa Yamamoto Reviewed-by: Lad Prabhakar Link: https://patch.msgid.link/TYCPR01MB74815955A10817B64BD6FB93A4812@TYCPR01MB7481.jpnprd01.prod.outlook.com Signed-off-by: Jakub Kicinski --- drivers/net/pcs/pcs-rzn1-miic.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c index cb74861e823c..3aa513467060 100644 --- a/drivers/net/pcs/pcs-rzn1-miic.c +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -270,6 +270,9 @@ static struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs) static void miic_unlock_regs(struct miic *miic) { + /* Clear protection state */ + writel(0x0000, miic->base + MIIC_PRCMD); + /* Unprotect register writes */ writel(0x00A5, miic->base + MIIC_PRCMD); writel(0x0001, miic->base + MIIC_PRCMD); From 19419faf58dfe5bcc8a9e60e622976c5f8e3ddc1 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 28 Sep 2026 12:56:41 +0200 Subject: [PATCH 395/535] net: bcmgenet: fix NULL dereference in set_coalesce before first open The Rx ring priv and index are only set in bcmgenet_init_rx_ring(), which runs at open. Setting the Rx coalescing parameters on an interface that was never opened, e.g. "ethtool -C eth0 rx-usecs 50", dereferences a NULL ring->priv. The oops happens with RTNL held, so networking stays stuck until a reboot: Unable to handle kernel paging request at virtual address 0000000000002788 pc : bcmgenet_set_rx_coalesce.isra.0+0x10/0x88 lr : bcmgenet_set_coalesce+0xe8/0x180 Call trace: bcmgenet_set_rx_coalesce.isra.0+0x10/0x88 (P) __ethnl_set_coalesce.isra.0+0x490/0x570 ethnl_set_coalesce+0x48/0xc0 ethnl_default_set_doit+0xf4/0x230 genl_family_rcv_msg_doit+0xe8/0x160 genl_rcv_msg+0x220/0x2a0 netlink_rcv_skb+0x68/0x140 genl_rcv+0x40/0x60 netlink_unicast+0x338/0x3c0 netlink_sendmsg+0x19c/0x3f8 __sock_sendmsg+0x64/0xc0 __sys_sendto+0x124/0x198 __arm64_sys_sendto+0x30/0x48 Set priv and index at probe. Writing the registers while the interface is down is harmless because open resets the MAC and bcmgenet_init_rx_coalesce() reapplies the stored values. It was observed on a Raspberry Pi CM4, but should apply to all other genet devices. Fixes: 9f4ca05827a2 ("net: bcmgenet: Add support for adaptive RX coalescing") Signed-off-by: Nicolai Buchwitz Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260928105642.856210-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index f725d26e6020..e8908916558b 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -4151,8 +4151,11 @@ static int bcmgenet_probe(struct platform_device *pdev) netif_set_real_num_rx_queues(priv->dev, priv->hw_params->rx_queues + 1); /* Set default coalescing parameters */ - for (i = 0; i <= priv->hw_params->rx_queues; i++) + for (i = 0; i <= priv->hw_params->rx_queues; i++) { + priv->rx_rings[i].priv = priv; + priv->rx_rings[i].index = i; priv->rx_rings[i].rx_max_coalesced_frames = 1; + } /* Initialize u64 stats seq counter for 32bit machines */ for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { From ea4d4b5dddb5121e64f56fd8e0720bd8b447b63d Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Mon, 28 Sep 2026 08:46:16 -0400 Subject: [PATCH 396/535] net: cap skb->queue_mapping when the tx queue is picked skbedit can set skb->queue_mapping and raise the per-CPU skip_txqueue flag so __dev_queue_xmit() honours the mapping. __dev_queue_xmit() cleared the flag before sch_handle_egress() and only read it afterwards, so the flag was not confined to the xmit that set it: a nested xmit (mirred redirect or mirror, or a drop after skbedit) could set the flag and the outer xmit would consume it for an skb that never went through skbedit. A forwarded packet still carries the ingress NIC's rx_queue + 1 in skb->queue_mapping, so the outer device then indexes its tx queue state with that stale value. Taprio's child array q->qdiscs[] is sized to the device's queue count, so taprio_enqueue() indexes past its allocation and dereferences the result as a struct Qdisc *. We (ab)use the skb->nf_skip_egress which means "skip netfilter egress for this packet" to tag to "am I in tc egress?". Despite the overload I dont see it as a conflict since the marker is set only around the single sch_handle_egress() call and ingress path is guarded by tc_at_ingress. I will send a followup(net-next) patch once this hits net-next to rename the skb->nf_skip_egress bit/flag to skb->skip_egress Arm the flag only from the egress classifier that can use it: raise skip_txqueue from tcf_skbedit_act() only when it runs inside sch_handle_egress(), thanks to skb->nf_skip_egress. An egress qdisc classifier runs in q->enqueue(), after the tx queue has been picked, so a mapping it sets cannot affect the current packet; arming the flag there only pollutes it for a later xmit. Then own the flag for the xmit frame the egress hook runs in: save the incoming value and clear it just before sch_handle_egress(), and restore it after the hook - on the consumed (drop) path, or, in the same call that reads it, on the surviving path. The save and the restores stay inside the egress_needed_key static branch, so a packet pays for them only when egress hooks are active (2f1e85b1aee4). Store the value netdev_cap_txqueue() selected back into skb->queue_mapping in netdev_tx_queue_mapping(), as netdev_core_pick_tx() already does, so the skip_txqueue path never hands a later reader on the xmit path a mapping the device cannot serve. A store made still later in the same frame, by a tc BPF program attached to a transmit qdisc, is outside this path and is not re-capped; a separate followup will resolve that path. netdev_xmit_skip_txqueue() returns the previous flag value so the save-and-clear is one call, and a no-op stub is provided when CONFIG_NET_EGRESS is disabled. skb->nf_skip_egress is compiled under CONFIG_NET_EGRESS rather than CONFIG_NETFILTER_SKIP_EGRESS, so skb_at_tc_egress() is valid whenever the egress path is built. A local user in a network namespace can redirect a packet from a device with more TX queues to one with fewer after setting a mapping valid only on the larger device. That reaches these reads and, under KASAN, faults with "slab-out-of-bounds in taprio_enqueue". Conditions to recreate the bug: the report's own trigger is a local user with CAP_NET_ADMIN in a network namespace, so no eBPF program is needed. With CONFIG_NET_SCH_TAPRIO=y, CONFIG_NET_ACT_SKBEDIT=y, CONFIG_NET_ACT_MIRRED=y, CONFIG_NET_CLS_MATCHALL=y, CONFIG_NET_SCH_PRIO=y and KASAN enabled, create qa (3 queues), qb (2 queues) and qc (1 queue) as dummy devices; put a taprio root on qb (num_tc 1, queues 2@0) and clsact on all three; then add an egress matchall filter on every device. On qa: "action skbedit queue_mapping 2 pipe action mirred egress redirect dev qb". On qb: "action mirred egress mirror dev qc". On qc: "action skbedit queue_mapping 0 pipe". Send one packet out qa. qc's skbedit sets the flag while qb's outer xmit is in flight; without the fix qb consumes it and reads its two-entry taprio child array with the forwarded packet's stale mapping. A qc whose skbedit is instead installed in a transmit-qdisc classifier (a matchall filter on the qc root qdisc) reaches the same code path the same way without the fix. Testing: on a KASAN build with panic_on_warn=1 the unfixed kernel panics with "BUG: KASAN: slab-out-of-bounds in taprio_enqueue", a read 0 bytes past a 16-byte taprio_init() allocation, for the clsact-setter and the transmit-qdisc-classifier reproducers and for a clsact skbedit-then-tc-BPF store; the fixed kernel runs all three with no report, and the BPF store variant additionally shows the expected "selects TX queue" clamp notice from the write-back. Fixes: 2f1e85b1aee4 ("net: sched: use queue_mapping to pick tx queue") Reported-by: Zero Day Initiative Link: https://lore.kernel.org/netdev/CANn89iLwYx8nCVf0pCEk_MmEiyC6kQaMwCQT9WkQVeeNzNQHqQ@mail.gmail.com/ Link: https://lore.kernel.org/netdev/179008581937.2160803.7117814290574262942@kernel.org/ Link: https://lore.kernel.org/netdev/179033713973.2160803.4914570693994398206@kernel.org/ Link: https://lore.kernel.org/netdev/20260925180407.63647514@kernel.org/ Link: https://lore.kernel.org/netdev/CANn89i+k-mZKDQVtvws_MEXeuMTAdaCcOXFZE-RfhcGTu90sjA@mail.gmail.com/ Suggested-by: Eric Dumazet Suggested-by: Jakub Kicinski Tested-by: hybris Signed-off-by: Jamal Hadi Salim Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/QDISC-9R8V.v4.20260928081529@mojatatu.com Signed-off-by: Jakub Kicinski --- include/linux/netfilter_netdev.h | 2 +- include/linux/rtnetlink.h | 7 ++++++- include/linux/skbuff.h | 2 +- include/net/sch_generic.h | 9 +++++++++ net/core/dev.c | 31 ++++++++++++++++++++++++------- net/sched/act_skbedit.c | 5 ++--- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/include/linux/netfilter_netdev.h b/include/linux/netfilter_netdev.h index 3175073a66ba..2a854a0bbd4b 100644 --- a/include/linux/netfilter_netdev.h +++ b/include/linux/netfilter_netdev.h @@ -133,7 +133,7 @@ static inline struct sk_buff *nf_hook_egress(struct sk_buff *skb, int *rc, static inline void nf_skip_egress(struct sk_buff *skb, bool skip) { -#ifdef CONFIG_NETFILTER_SKIP_EGRESS +#ifdef CONFIG_NET_EGRESS skb->nf_skip_egress = skip; #endif } diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h index 95729339e7a5..a54ec40d095c 100644 --- a/include/linux/rtnetlink.h +++ b/include/linux/rtnetlink.h @@ -186,7 +186,12 @@ void net_dec_ingress_queue(void); #ifdef CONFIG_NET_EGRESS void net_inc_egress_queue(void); void net_dec_egress_queue(void); -void netdev_xmit_skip_txqueue(bool skip); +bool netdev_xmit_skip_txqueue(bool skip); +#else +static inline bool netdev_xmit_skip_txqueue(bool skip) +{ + return false; +} #endif void rtnetlink_init(void); diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 84308498a3a8..a3ff380d43f0 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1020,7 +1020,7 @@ struct sk_buff { #ifdef CONFIG_NET_REDIRECT __u8 from_ingress:1; #endif -#ifdef CONFIG_NETFILTER_SKIP_EGRESS +#ifdef CONFIG_NET_EGRESS __u8 nf_skip_egress:1; #endif #ifdef CONFIG_SKB_DECRYPTED diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index f35bd06a6bad..1acaadb3e2cf 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -810,6 +810,15 @@ static inline bool skb_at_tc_ingress(const struct sk_buff *skb) #endif } +static inline bool skb_at_tc_egress(const struct sk_buff *skb) +{ +#ifdef CONFIG_NET_EGRESS + return skb->nf_skip_egress && !skb_at_tc_ingress(skb); +#else + return false; +#endif +} + static inline bool skb_skip_tc_classify(struct sk_buff *skb) { #ifdef CONFIG_NET_CLS_ACT diff --git a/net/core/dev.c b/net/core/dev.c index f660fccfc0db..8638c994bbcb 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -4404,9 +4404,14 @@ EXPORT_SYMBOL(dev_loopback_xmit); static struct netdev_queue * netdev_tx_queue_mapping(struct net_device *dev, struct sk_buff *skb) { - int qm = skb_get_queue_mapping(skb); + int queue = skb_get_queue_mapping(skb); + int capped; - return netdev_get_tx_queue(dev, netdev_cap_txqueue(dev, qm)); + capped = netdev_cap_txqueue(dev, queue); + if (unlikely(capped != queue)) + skb_set_queue_mapping(skb, capped); + + return netdev_get_tx_queue(dev, capped); } #ifndef CONFIG_PREEMPT_RT @@ -4415,9 +4420,13 @@ static bool netdev_xmit_txqueue_skipped(void) return __this_cpu_read(softnet_data.xmit.skip_txqueue); } -void netdev_xmit_skip_txqueue(bool skip) +bool netdev_xmit_skip_txqueue(bool skip) { + bool prev = netdev_xmit_txqueue_skipped(); + __this_cpu_write(softnet_data.xmit.skip_txqueue, skip); + + return prev; } EXPORT_SYMBOL_GPL(netdev_xmit_skip_txqueue); @@ -4427,9 +4436,13 @@ static bool netdev_xmit_txqueue_skipped(void) return current->net_xmit.skip_txqueue; } -void netdev_xmit_skip_txqueue(bool skip) +bool netdev_xmit_skip_txqueue(bool skip) { + bool prev = netdev_xmit_txqueue_skipped(); + current->net_xmit.skip_txqueue = skip; + + return prev; } EXPORT_SYMBOL_GPL(netdev_xmit_skip_txqueue); #endif @@ -4848,21 +4861,25 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev) tcx_set_ingress(skb, false); #ifdef CONFIG_NET_EGRESS if (static_branch_unlikely(&egress_needed_key)) { + bool skip_txq; + if (nf_hook_egress_active()) { skb = nf_hook_egress(skb, &rc, dev); if (!skb) goto out; } - netdev_xmit_skip_txqueue(false); + skip_txq = netdev_xmit_skip_txqueue(false); nf_skip_egress(skb, true); skb = sch_handle_egress(skb, &rc, dev); - if (!skb) + if (!skb) { + netdev_xmit_skip_txqueue(skip_txq); goto out; + } nf_skip_egress(skb, false); - if (netdev_xmit_txqueue_skipped()) + if (netdev_xmit_skip_txqueue(skip_txq)) txq = netdev_tx_queue_mapping(dev, skb); } #endif diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c index bfec6b668410..26258b353cde 100644 --- a/net/sched/act_skbedit.c +++ b/net/sched/act_skbedit.c @@ -72,9 +72,8 @@ TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb, } if (params->flags & SKBEDIT_F_QUEUE_MAPPING && skb->dev->real_num_tx_queues > params->queue_mapping) { -#ifdef CONFIG_NET_EGRESS - netdev_xmit_skip_txqueue(true); -#endif + if (skb_at_tc_egress(skb)) + netdev_xmit_skip_txqueue(true); skb_set_queue_mapping(skb, tcf_skbedit_hash(params, skb)); } if (params->flags & SKBEDIT_F_MARK) { From 5f0764cb1c81a9712bce605d38efe6d844a70b29 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Sat, 26 Sep 2026 10:04:54 -0400 Subject: [PATCH 397/535] net: extend IPv6 exthdr detection of tunneled packets Commit c4336a07eb6b ("net: correctly handle tunneled traffic on IPV6_CSUM GSO fallback") split skb_gso_has_extension_hdr() into mutually exclusive branches on skb->encapsulation. This did not yet address all paths: 1. With skb->encapsulation set, the outer header is not checked. IPv6 tunnels such as ip6_gre and ip6_tunnel add an outer Destination Options header by default (encap_limit). Their GSO packets skip software GSO, then skb_csum_hwoffload_help() sees the outer extension header and calls skb_checksum_help() on the GSO skb, which warns and drops it. 2. Tunnels over IPv6 without an outer transport header, such as ip6_tunnel, leave skb->transport_header at the inner transport header. skb_network_header_len() then spans the outer IPv6, tunnel and inner IP headers, a false positive. 3. UDP tunnels without an inner network header, such as SCTP-in-UDP or PSP, have no inner IPv6 header to check. Decide on the outer header alone. 4. Directly dereferencing inner_ip_hdr(skb)->version without skb_header_pointer() is unsafe. Instead, check ipv6_ext_hdr(nexthdr) on the outer IPv6 header and, if set, on the inner IPv6 header. Read the headers with skb_header_pointer(). Keep the skb_network_header_len() check when !skb->encapsulation to also catch encapsulated packets without skb->encapsulation (e.g., virtio). Use the same helper in skb_csum_hwoffload_help(). Its open coded test has the false positive of (2) and ignores the inner header. Background: checksum offload of tunneled packets invariants: Non-GSO skb: - If the inner packet is CHECKSUM_PARTIAL, Local Checksum Offload computes the outer checksum in software and the device offloads only the inner L4 checksum. - If the inner packet is CHECKSUM_NONE (e.g., SCTP-in-UDP, ESP-in-UDP, Remote Checksum Offload), the device offloads the outer UDP or GRE checksum instead. GSO skb: - A device with NETIF_F_GSO_UDP_TUNNEL_CSUM or NETIF_F_GSO_GRE_CSUM computes both inner and outer checksums per segment. The outer checksum is seeded with only the pseudo-header checksum. A NETIF_F_IPV6_CSUM device must parse through the outer headers to reach the inner ones. Fixes: c4336a07eb6b ("net: correctly handle tunneled traffic on IPV6_CSUM GSO fallback") Cc: stable@vger.kernel.org Signed-off-by: Willem de Bruijn Link: https://patch.msgid.link/20260926140506.2335137-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/core/dev.c | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 8638c994bbcb..18dc88990510 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3818,20 +3818,36 @@ static netdev_features_t dflt_features_check(struct sk_buff *skb, return vlan_features_check(skb, features); } -static bool skb_gso_has_extension_hdr(const struct sk_buff *skb) +static bool __skb_has_ipv6_ext_hdr(const struct sk_buff *skb, int nhoff) { - if (!skb->encapsulation) - return ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 || - (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && - vlan_get_protocol(skb) == htons(ETH_P_IPV6))) && - skb_transport_header_was_set(skb) && - skb_network_header_len(skb) != sizeof(struct ipv6hdr)); - else - return (!skb_inner_network_header_was_set(skb) || - ((skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6 || - (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4 && - inner_ip_hdr(skb)->version == 6)) && - skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr))); + const struct ipv6hdr *ip6h; + struct ipv6hdr _ip6h; + + ip6h = skb_header_pointer(skb, nhoff, sizeof(_ip6h), &_ip6h); + return ip6h && ip6h->version == 6 && ipv6_ext_hdr(ip6h->nexthdr); +} + +static bool skb_has_ipv6_extension_hdr(const struct sk_buff *skb) +{ + if (vlan_get_protocol(skb) == htons(ETH_P_IPV6)) { + if (__skb_has_ipv6_ext_hdr(skb, skb_network_offset(skb))) + return true; + + /* Catch tunnels without skb->encapsulation (e.g., virtio). */ + if (!skb->encapsulation && + skb_transport_header_was_set(skb) && + skb_network_header_len(skb) != sizeof(struct ipv6hdr)) + return true; + } + + /* Tunnels without an inner network header, such as SCTP-in-UDP or + * PSP, have no inner IP header and thus no inner extension header. + */ + if (skb->encapsulation && skb_inner_network_header_was_set(skb) && + __skb_has_ipv6_ext_hdr(skb, skb_inner_network_offset(skb))) + return true; + + return false; } static netdev_features_t gso_features_check(const struct sk_buff *skb, @@ -3886,7 +3902,7 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb, * so neither does TSO that depends on it. */ if (features & NETIF_F_IPV6_CSUM && - skb_gso_has_extension_hdr(skb)) + skb_has_ipv6_extension_hdr(skb)) features &= ~(NETIF_F_IPV6_CSUM | NETIF_F_TSO6 | NETIF_F_GSO_UDP_L4); return features; @@ -3988,8 +4004,7 @@ int skb_csum_hwoffload_help(struct sk_buff *skb, return 0; if (features & (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM)) { - if (vlan_get_protocol(skb) == htons(ETH_P_IPV6) && - skb_network_header_len(skb) != sizeof(struct ipv6hdr)) + if (skb_has_ipv6_extension_hdr(skb)) goto sw_checksum; switch (skb->csum_offset) { From 6f0c2c4f5e7143eba75153ecc8af3a03f7b6a98b Mon Sep 17 00:00:00 2001 From: Sandeep Haemoon Date: Thu, 24 Sep 2026 23:51:37 +0300 Subject: [PATCH 398/535] net: ethernet: mtk_eth_soc: allocate dummy netdev sooner The RX rings and the shared NAPI are carried by an internal "dummy" netdev (eth->dummy_dev). mtk_probe() creates that dummy device, and adds the shared tx/rx NAPI to it, only after the MAC netdevs have been registered. That order has been there since the MT7623 support was added and leaves a window in which the netdevs are visible to the network core before the shared NAPI and the dummy device exist. If anything brings the first netdev up during that window (e.g. netifd opening the WAN link the instant the interface is registered), the first-open path runs without them. On NETSYS v2 and later SoCs mtk_rx_alloc() -> __xdp_rxq_info_reg() then dereferences eth->dummy_dev == NULL: WARNING: CPU: ... Missing net_device from driver in net/core/xdp.c and mtk_open() fails with -ENODEV. On NETSYS v1 SoCs (MT7621/MT7622/MT7623/MT7629) there is no page pool, so the same open instead calls napi_enable() on a napi_struct that netif_napi_add() has not initialised yet and dereferences a NULL napi->dev. Allocate the dummy netdev and add the shared tx/rx NAPI to it before the register_netdev() loop. Both the dummy-allocation failure and a register_netdev() failure now unwind through mtk_unreg_dev(), which unregisters the net_device notifiers and the netdevs registered so far, stopping a concurrently-opened netdev (ndo_stop) and its NAPI before the dummy carrier is freed. A netdev that opened during the window can also have queued the frame-engine reset worker (via the tx watchdog or phylink), so the probe error path now cancels eth->pending_work before freeing the shared state, as mtk_cleanup() already does on remove. Fixes: 656e705243fd ("net-next: mediatek: add support for MT7623 ethernet") Signed-off-by: Sandeep Haemoon Reviewed-by: Simon Horman Link: https://patch.msgid.link/patch-e52c13380b0768fec63af04c80e4b487@loljews.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mediatek/mtk_eth_soc.c | 38 ++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index 2ea5dfe85539..ad0fdc4133fb 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -5341,6 +5341,22 @@ static int mtk_probe(struct platform_device *pdev) } } + /* we run 2 devices on the same DMA ring so we need a dummy device + * for NAPI to work. Allocate it before registering the netdevs so a + * concurrent ndo_open (e.g. netifd bringing the first netdev up the + * instant it is registered) never observes eth->dummy_dev == NULL in + * mtk_dma_init() -> mtk_rx_alloc() -> __xdp_rxq_info_reg() (net/core/xdp.c + * "Missing net_device from driver") and fails the first open. + */ + eth->dummy_dev = alloc_netdev_dummy(0); + if (!eth->dummy_dev) { + err = -ENOMEM; + dev_err(eth->dev, "failed to allocated dummy device\n"); + goto err_unreg_netdev; + } + netif_napi_add(eth->dummy_dev, ð->tx_napi, mtk_napi_tx); + netif_napi_add(eth->dummy_dev, ð->rx_napi, mtk_napi_rx); + for (i = 0; i < MTK_MAX_DEVS; i++) { if (!eth->netdev[i]) continue; @@ -5355,18 +5371,6 @@ static int mtk_probe(struct platform_device *pdev) eth->netdev[i]->base_addr, eth->irq[MTK_FE_IRQ_SHARED]); } - /* we run 2 devices on the same DMA ring so we need a dummy device - * for NAPI to work - */ - eth->dummy_dev = alloc_netdev_dummy(0); - if (!eth->dummy_dev) { - err = -ENOMEM; - dev_err(eth->dev, "failed to allocated dummy device\n"); - goto err_unreg_netdev; - } - netif_napi_add(eth->dummy_dev, ð->tx_napi, mtk_napi_tx); - netif_napi_add(eth->dummy_dev, ð->rx_napi, mtk_napi_rx); - platform_set_drvdata(pdev, eth); schedule_delayed_work(ð->reset.monitor_work, MTK_DMA_MONITOR_TIMEOUT); @@ -5376,6 +5380,16 @@ static int mtk_probe(struct platform_device *pdev) err_unreg_netdev: mtk_unreg_dev(eth); err_deinit_ppe: + /* A netdev opened while probe was still registering can have queued + * the frame-engine reset worker, which takes rtnl and drives the + * hardware; cancel it before anything below is torn down. + */ + cancel_work_sync(ð->pending_work); + if (eth->dummy_dev) { + netif_napi_del(ð->tx_napi); + netif_napi_del(ð->rx_napi); + free_netdev(eth->dummy_dev); + } mtk_ppe_deinit(eth); mtk_mdio_cleanup(eth); err_free_dev: From 20f3599d85b416fd292a199364cb1248dcd9c780 Mon Sep 17 00:00:00 2001 From: Yongzhao Chen Date: Sun, 27 Sep 2026 17:51:36 +0200 Subject: [PATCH 399/535] net: phy: qcom: at803x: Fix IPQ5018 short-cable DAC values When "qcom,dac-preset-short-cable" is set, ipq5018_config_init() programs the MDAC (MMD1 0x8100) and EDAC (debug 0x4380) fields. Both fields occupy bits 15:8 (IPQ5018_PHY_DAC_MASK), but the value 0x10 is passed unshifted as the set argument of phy_modify_mmd() and at803x_debug_reg_mask(). Neither helper shifts or masks that argument, so both fields are cleared to 0x00 instead of being set to 0x10, and bit 4 of the low byte, which is outside the field, is set. Use FIELD_PREP() to place the value in the field. This matches the vendor SDK, which clears bits 15:8 and ORs in the value shifted left by 8. On a Redmi AX5400 board, where the IPQ5018 internal PHY connects to a QCA8337 switch PHY without a cable, MDAC and EDAC read 0x6868 and 0x7800 before the write. With this change they read back 0x1068 and 0x1000, with the low byte preserved. Without it, the same writes would leave 0x0078 and 0x0010. No in-tree DTS sets this property yet, but it is documented in qca,ar803x.yaml and used by several IPQ5018 boards in OpenWrt. Fixes: d46502279a11 ("net: phy: qcom: at803x: Add Qualcomm IPQ5018 Internal PHY support") Signed-off-by: Yongzhao Chen Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260927155136.2489-1-yongzhao.derek@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/qcom/at803x.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index 6872dbf77856..cacbadf1f484 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -1051,11 +1051,15 @@ static int ipq5018_config_init(struct phy_device *phydev) if (priv->set_short_cable_dac) { /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */ phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC, - IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_MMD1_MDAC_VAL); + IPQ5018_PHY_DAC_MASK, + FIELD_PREP(IPQ5018_PHY_DAC_MASK, + IPQ5018_PHY_MMD1_MDAC_VAL)); /* setting EDAC (Error-detection and Correction) in debug register */ at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC, - IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_DEBUG_EDAC_VAL); + IPQ5018_PHY_DAC_MASK, + FIELD_PREP(IPQ5018_PHY_DAC_MASK, + IPQ5018_PHY_DEBUG_EDAC_VAL)); } return 0; From 0d2c49915bc3aea4521cda5e4d42f24f63b94585 Mon Sep 17 00:00:00 2001 From: Quentin Freimanis Date: Sun, 27 Sep 2026 21:45:42 -0700 Subject: [PATCH 400/535] net: sparx5: skip ptp deinit if init was skipped sparx5_ptp_init() returns early on the base lan969x variants because they do not have the SPX5_FEATURE_PTP flag. It also returns early when no "ptp" interrupt is described. Unbinding the driver then causes a NULL pointer dereference when cleaning up uninitialized tx_skbs queues: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 Call trace: skb_queue_purge_reason+0x68/0x120 (P) sparx5_ptp_deinit+0x64/0xc0 mchp_sparx5_remove+0x38/0x70 platform_remove+0x20/0x30 device_remove+0x4c/0x80 Before per-port tx_skbs were added, a NULL dereference would have happened when calling ptp_clock_unregister() on the never-registered PTP clocks. Fix by returning early in sparx5_ptp_deinit() if PTP is not used. Fixes: 0933bd04047c ("net: sparx5: Add support for ptp clocks") Signed-off-by: Quentin Freimanis Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20260928044542.29287-1-quentin@q-lab.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index a16ec8136d6d..5953e04ef14e 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -673,6 +673,9 @@ void sparx5_ptp_deinit(struct sparx5 *sparx5) struct sparx5_port *port; int i; + if (!sparx5->ptp) + return; + if (sparx5->ptp_irq >= 0) { disable_irq(sparx5->ptp_irq); sparx5->ptp_irq = -ENXIO; From 1b92780ae4fc3be64f05d97ed8833c445e73b0cf Mon Sep 17 00:00:00 2001 From: Jeff Jo Date: Thu, 24 Sep 2026 15:44:58 -0700 Subject: [PATCH 401/535] tcp: refresh TS.Recent for accepted old ACKs A TCP packet can carry new data while acknowledging traffic in the opposite direction. With overlapping traffic in both directions, a delayed packet's acknowledgment can be older than one Linux has already accepted, even when that packet fills a gap in the received data. Linux accepts the data, but tcp_ack() takes the old_ack path and skips updating TS.Recent, the timestamp saved for outgoing acknowledgments. The reply therefore echoes an older timestamp. If the sender uses this echo to measure round-trip time after a long idle period, its estimate includes the idle time and can reduce its sending rate. Update TS.Recent in old_ack using tcp_replace_ts_recent(), before SACK processing can trigger a transmission. This reuses the existing timestamp and sequence checks, including PAWS protection against old duplicate packets. ACK validation already rejects old ACKs in SYN_RECV before this path, so no additional state check is needed. Echoing the timestamp of the packet that fills the receive gap follows RFC 7323 section 4.3. In a socket reproduction with 300 seconds idle, controlled reordering and retransmission to exercise timestamp-based RTT sampling, the sender's smoothed round-trip time was 37.5 seconds without the fix and 15.5 ms with it. Fixes: 12fb3dd9dc3c ("tcp: call tcp_replace_ts_recent() from tcp_ack()") Assisted-by: LLM sparse Signed-off-by: Jeff Jo Reviewed-by: Eric Dumazet Signed-off-by: David S. Miller --- net/ipv4/tcp_input.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 92bc60716f33..99baf14afdfd 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -4465,6 +4465,12 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) return 1; old_ack: + /* An old ACK can carry new data. Update TS.Recent before SACK + * processing can trigger a retransmission. + */ + if (flag & FLAG_UPDATE_TS_RECENT) + tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq); + /* If data was SACKed, tag it and see if we should send more data. * If data was DSACKed, see if we can undo a cwnd reduction. */ From 81fe707ae58e67e5a029a9778c4b0c1b478bcb97 Mon Sep 17 00:00:00 2001 From: Jeff Jo Date: Thu, 24 Sep 2026 15:44:59 -0700 Subject: [PATCH 402/535] selftests: net: check timestamp echo after an old ACK Add a regression test for a gap-filling packet whose acknowledgment has become old. Linux first sends data to the peer. Deliver two peer data packets out of order: the later packet acknowledges Linux's data, while the delayed packet still carries the earlier acknowledgment. Require the ACK that closes the receive gap to echo the delayed packet's timestamp, 301000. Without the fix, Linux accepts the data but still echoes the previously saved timestamp, 1000. Also check that the application can read all 34 bytes. Use a large jump in peer timestamps to represent the idle interval, with no real wait, loss or retransmission. The test directly checks the outgoing timestamp echo. It requires packetdrill's merged TSecr verification fix (linked below); older tools incorrectly pass on an unfixed kernel. Use the existing packetdrill selftest runner for IPv4, IPv6 and IPv4-mapped IPv6. Link: https://github.com/google/packetdrill/commit/83f72d3f9085d0e26eb4d206fe4d7cfab5b6d872 Assisted-by: LLM Signed-off-by: Jeff Jo Reviewed-by: Eric Dumazet Signed-off-by: David S. Miller --- .../net/packetdrill/tcp_old_ack_ts.pkt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt diff --git a/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt b/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt new file mode 100644 index 000000000000..f072e5e4c449 --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_old_ack_ts.pkt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +// An out-of-order segment advances SND.UNA. The gap filler still carries +// its original, older ACK. The reply must echo the gap filler's timestamp. +--tcp_ts_tick_usecs=1000 + +`./defaults.sh` + +0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 ++0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 ++0 bind(3, ..., ...) = 0 ++0 listen(3, 1) = 0 +0.100 < S 0:0(0) win 20000 ++0 > S. 0:0(0) ack 1 +0.200 < . 1:1(0) ack 1 win 20000 ++0 accept(3, ..., ...) = 4 +0.300 write(4, ..., 17) = 17 ++0 > P. 1:18(17) ack 1 +0.310 < P. 18:35(17) ack 18 win 20000 ++0 > . 18:18(0) ack 1 +0.320 < P. 1:18(17) ack 1 win 20000 ++0 > . 18:18(0) ack 35 ++0 read(4, ..., 34) = 34 From 747928b5d4ff8c8de55138a54c13980e4bb88578 Mon Sep 17 00:00:00 2001 From: Aohan Mei Date: Thu, 10 Sep 2026 16:33:26 +0800 Subject: [PATCH 403/535] netfilter: nft_flow_offload: drop flowtable reference on init error path nft_flow_offload_init() bumps the flowtable use count with nft_use_inc() before calling nf_ct_netns_get(). When the latter fails, the error is returned as-is and the reference is leaked. The upper layers do not balance it either: nf_tables_newexpr() clears expr->ops when the expression init callback fails, so the nft_expr_more() iteration in nft_rule_expr_deactivate() and nf_tables_rule_destroy() stops right before the failed expression and its ->destroy callback, which would drop the reference, never runs. Each failed rule addition therefore leaks one flowtable reference and the flowtable can no longer be removed: NFT_MSG_DELFLOWTABLE keeps reporting -EBUSY even though no rule references it. Save the nf_ct_netns_get() return value and undo the nft_use_inc() when it fails, restoring the inc/dec pairing within nft_flow_offload_init() itself. Fixes: a3c90f7a2323 ("netfilter: nf_tables: flow offload expression") Reported-by: TencentOS Corvus AI Cc: stable@vger.kernel.org Assisted-by: CodeBuddy:Kimi-K3 Signed-off-by: Aohan Mei Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_flow_offload.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c index 32b4281038dd..d3c5651dd699 100644 --- a/net/netfilter/nft_flow_offload.c +++ b/net/netfilter/nft_flow_offload.c @@ -160,6 +160,7 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, struct nft_flow_offload *priv = nft_expr_priv(expr); u8 genmask = nft_genmask_next(ctx->net); struct nft_flowtable *flowtable; + int err; if (!tb[NFTA_FLOW_TABLE_NAME]) return -EINVAL; @@ -174,7 +175,11 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, priv->flowtable = flowtable; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + nft_use_dec(&flowtable->use); + + return err; } static void nft_flow_offload_deactivate(const struct nft_ctx *ctx, From bdac17779f46817f60103baf09f0370546c3f023 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Thu, 10 Sep 2026 13:08:31 +0300 Subject: [PATCH 404/535] ipvs: fix missing counter decrement in lblc LBLC may delete cache entries for destinations that are removed or overloaded and replace them with available ones. But ip_vs_lblc_new() forgets to decrement the tbl->entries counter after calling ip_vs_lblc_del(). This can lead to increased shrinking of the cache with every new garbage collection. Fixes: 2f3d771a35fe ("ipvs: do not use dest after ip_vs_dest_put in LBLC") Link: https://sashiko.dev/#/patchset/0bdd5abe9968ded7ca2b9cb6844ba83d94cc8d53.1787318053.git.zhilinz%40nebusec.ai Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_lblc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index 693bcc82ccb7..a2b574904f13 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -203,6 +203,7 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr, if (en->dest == dest) return en; ip_vs_lblc_del(en); + atomic_dec(&tbl->entries); } en = kmalloc_obj(*en, GFP_ATOMIC); if (!en) From 2a3c3de660f96865f303fd25f1285f2ffbfd521d Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Thu, 10 Sep 2026 13:08:32 +0300 Subject: [PATCH 405/535] ipvs: bound LBLCR and LBLC cache growth ip_vs_lblcr_new() and ip_vs_lblc_new() create cache entries for every previously unseen destination address. The table max_size only tells the periodic collector to reclaim entries after the cache has already exceeded the limit. It does not reclaim entries that the attacker continues to use. Reject new cache entries once either table reaches max_size * 3 / 2. The extra headroom lets the periodic collector catch up while the existing scheduler fallback continues to use the selected destination when cache creation fails. New traffic therefore stays serviceable without growing the tables further. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Suggested-by: Julian Anastasov Signed-off-by: Zhiling Zou Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_lblc.c | 3 +++ net/netfilter/ipvs/ip_vs_lblcr.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index a2b574904f13..55af77a56929 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -205,6 +205,9 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr, ip_vs_lblc_del(en); atomic_dec(&tbl->entries); } + if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2) + return NULL; + en = kmalloc_obj(*en, GFP_ATOMIC); if (!en) return NULL; diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c index f53f05ceea36..858393b1d2d1 100644 --- a/net/netfilter/ipvs/ip_vs_lblcr.c +++ b/net/netfilter/ipvs/ip_vs_lblcr.c @@ -363,6 +363,9 @@ ip_vs_lblcr_new(struct ip_vs_lblcr_table *tbl, const union nf_inet_addr *daddr, en = ip_vs_lblcr_get(af, tbl, daddr); if (!en) { + if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2) + return NULL; + en = kmalloc_obj(*en, GFP_ATOMIC); if (!en) return NULL; From 616cf5c06934364ab1002b420ebe975850b4b208 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Sat, 19 Sep 2026 21:07:32 +0300 Subject: [PATCH 406/535] ipvs: do not create invisible templates The IP_VS_CONN_F_ONE_PACKET flag was implemented for normal connections. When conn template inherits this flag from dest->conn_flags it will not be hashed. As result, we will create new template for every new normal connection. Fix it to allow one template to be used by many normal connections. Fixes: 26ec037f9841 ("IPVS: one-packet scheduling") Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916231652.127456-1-pablo%40netfilter.org Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_conn.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 6fa3e1dc534c..cb009208826f 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1102,6 +1102,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) if (cp->protocol != IPPROTO_UDP) conn_flags &= ~IP_VS_CONN_F_ONE_PACKET; flags = cp->flags; + /* Only visible templates can control multiple connections */ + if (flags & IP_VS_CONN_F_TEMPLATE) + conn_flags &= ~IP_VS_CONN_F_ONE_PACKET; /* Bind with the destination and its corresponding transmitter */ if (flags & IP_VS_CONN_F_SYNC) { /* Synced conns are hashed, so they can not get this flag */ From f96e91748f6304668fe647e686d199d7207d36f1 Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Sat, 19 Sep 2026 21:07:33 +0300 Subject: [PATCH 407/535] ipvs: filter some flags received in the backup server While the IPVS SYNC protocol is not secure by design we can still protect the backup server from messages that can wreak havoc. This commit addresses problems from received connection flags or their combinations. We now drop messages as follows: 1. the NO_CPORT+TEMPLATE combination allows lookups for normal connections to hit template which can break in many ways. While the master does not sync connections with NO_CPORT flag, i.e. before they are established, we still accept NO_CPORT without TEMPLATE. 2. ONE_PACKET: it is not sent by master, so we do not expect it in backup. Before now it was ignored by IP_VS_CONN_F_BACKUP_MASK for protocol v1 while protocol v0 created connections that are not hashed and dropped immediately. Better to apply the IP_VS_CONN_F_BACKUP_MASK also to the flags from v0 messages for consistency with v1. Fixes: 87375ab47cd0 ("[IPVS]: ip_vs_ftp breaks connections using persistence") Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- net/netfilter/ipvs/ip_vs_sync.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 5383aeafb0ae..69dc28153ec1 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -949,6 +949,21 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa ip_vs_conn_put(cp); } +/* Check for incompatible flags */ +static bool ip_vs_sync_validate_flags(u32 flags) +{ + /* We do not expect NO_CPORT, especially to allow lookups + * to hit templates + */ + if (flags & IP_VS_CONN_F_NO_CPORT) { + if (flags & IP_VS_CONN_F_TEMPLATE) + return false; + } + if (flags & IP_VS_CONN_F_ONE_PACKET) + return false; + return true; +} + /* * Process received multicast message for Version 0 */ @@ -972,8 +987,7 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer return; } s = (struct ip_vs_sync_conn_v0 *) p; - flags = ntohs(s->flags) | IP_VS_CONN_F_SYNC; - flags &= ~IP_VS_CONN_F_HASHED; + flags = ntohs(s->flags); if (flags & IP_VS_CONN_F_SEQ_MASK) { opt = (struct ip_vs_sync_conn_options *)&s[1]; p += FULL_CONN_SIZE; @@ -986,6 +1000,13 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer p += SIMPLE_CONN_SIZE; } + if (!ip_vs_sync_validate_flags(flags)) { + IP_VS_DBG(2, "BACKUP v0, Invalid flags 0x%X\n", flags); + continue; + } + flags &= IP_VS_CONN_F_BACKUP_MASK; + flags |= IP_VS_CONN_F_SYNC; + state = ntohs(s->state); if (!(flags & IP_VS_CONN_F_TEMPLATE)) { pp = ip_vs_proto_get(s->protocol); @@ -1141,7 +1162,13 @@ static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *m } /* Get flags and Mask off unsupported */ - flags = ntohl(s->v4.flags) & IP_VS_CONN_F_BACKUP_MASK; + flags = ntohl(s->v4.flags); + if (!ip_vs_sync_validate_flags(flags)) { + IP_VS_DBG(3, "BACKUP, Invalid flags 0x%X\n", flags); + retc = 25; + goto out; + } + flags &= IP_VS_CONN_F_BACKUP_MASK; flags |= IP_VS_CONN_F_SYNC; state = ntohs(s->v4.state); From 86b6471e690c67d4c5a1892993f34e080f1eae9d Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Mon, 21 Sep 2026 10:08:22 +0800 Subject: [PATCH 408/535] netfilter: nft_set_rbtree: skip transaction elements during GC Since nft_set_commit_update() runs set commit callbacks before processing NEWSETELEM transactions, nft_rbtree_gc_scan() can observe elements added by the transaction being committed. The scan records an interval end in rbe_end without checking the element's transaction state. A later, unrelated expired start then moves both elements to the expired list. The synchronous GC queue can free the new end element before the transaction subsequently activates it, causing a use-after-free. Only consider elements that are fully active in both generations. This keeps transaction-state elements out of the GC scan and preserves interval pairing across skipped elements. KASAN reports: BUG: KASAN: slab-use-after-free in nft_setelem_activate nft_setelem_activate net/netfilter/nf_tables_api.c:7047 nf_tables_commit net/netfilter/nf_tables_api.c:11137 Allocated by task 130: nft_set_elem_init net/netfilter/nf_tables_api.c:6794 nft_add_set_elem net/netfilter/nf_tables_api.c:7523 Freed by task 130: nft_trans_gc_trans_free net/netfilter/nf_tables_api.c:10506 rcu_core kernel/rcu/tree.c:2919 Fixes: 1e3b9e1c77fe ("netfilter: nf_tables: call set ops .commit when building new ruleset blob") Reported-by: Assisted-by: LLM Signed-off-by: Weiming Shi Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nft_set_rbtree.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 9894832281c4..12431b55752f 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -900,6 +900,8 @@ static void nft_rbtree_gc_scan(struct nft_set *set) next = rb_next(node); rbe = rb_entry(node, struct nft_rbtree_elem, node); + if (!nft_set_elem_active(&rbe->ext, NFT_GENMASK_ANY)) + continue; /* elements are reversed in the rbtree for historical reasons, * from highest to lowest value, that is why end element is From 16d464013ec2267b00a83f4bfbb97b3ecc63bfa7 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Tue, 22 Sep 2026 21:18:52 +0200 Subject: [PATCH 409/535] netfilter: bpf: reject invalid NAT manipulation types As bpf_ct_set_nat_info() is not validating the NAT manipulation type a wrong value can be passed directly to nf_nat_setup_info(). This triggers the WARN_ON() at nf_nat_setup_info() and if panic_on_warn isn't set, then IPS_SRC_NAT_DONE is set without adding nat_bysource and conntrack cleanup tries to unlink an uninitialized hlist node. Fix this by checking that NAT manipulation type is correct before calling nf_nat_setup_info(). In addition, if the WARN_ON is hit, return NF_DROP instead of continuing with the processing to avoid similar situations in the future. Reported-by: VEGA Fixes: 0fabd2aa199f ("net: netfilter: add bpf_ct_set_nat_info kfunc helper") Signed-off-by: Fernando Fernandez Mancera Signed-off-by: Pablo Neira Ayuso --- net/netfilter/nf_nat_bpf.c | 3 +++ net/netfilter/nf_nat_core.c | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nf_nat_bpf.c b/net/netfilter/nf_nat_bpf.c index f9dd85ccea01..7572b58c448b 100644 --- a/net/netfilter/nf_nat_bpf.c +++ b/net/netfilter/nf_nat_bpf.c @@ -39,6 +39,9 @@ __bpf_kfunc int bpf_ct_set_nat_info(struct nf_conn___init *nfct, if (proto != NFPROTO_IPV4 && proto != NFPROTO_IPV6) return -EINVAL; + if (manip != NF_NAT_MANIP_SRC && manip != NF_NAT_MANIP_DST) + return -EINVAL; + memset(&range, 0, sizeof(struct nf_nat_range2)); range.flags = NF_NAT_RANGE_MAP_IPS; range.min_addr = *addr; diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index a4858c2b2d65..cc8e1e81006d 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -767,8 +767,9 @@ nf_nat_setup_info(struct nf_conn *ct, if (nf_ct_is_confirmed(ct)) return NF_ACCEPT; - WARN_ON(maniptype != NF_NAT_MANIP_SRC && - maniptype != NF_NAT_MANIP_DST); + if (WARN_ON(maniptype != NF_NAT_MANIP_SRC && + maniptype != NF_NAT_MANIP_DST)) + return NF_DROP; if (WARN_ON(nf_nat_initialized(ct, maniptype))) return NF_DROP; From 7c549fb7eecd01fdba7c0d12c01da876ef8a3214 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 23 Sep 2026 10:53:02 +0200 Subject: [PATCH 410/535] netfilter: flowtable: generalize pending status bit Rename NF_FLOW_HW_PENDING to NF_FLOW_PENDING and use it to inhibit the flowtable GC worker until pending hw offload work has been completed. Apparently, nf_flow_offload_stats() can schedule work to retrieve stats while the flow is being removed by GC. And this bit can also be used in a follow up patch to disable GC until the flow has been fully added in both directions. Revert the reordering done in commit d644b23afe1e ("netfilter: flowtable: publish HW_DEAD after worker is done") to prevent a race between GC and hw offload handler. Fixes: 2c8897953f3b ("netfilter: flowtable: Add pending bit for offload work") Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_flow_table.h | 2 +- net/netfilter/nf_flow_table_core.c | 7 ++++++- net/netfilter/nf_flow_table_offload.c | 14 +++++--------- net/sched/act_ct.c | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h index f2e2771f188f..5b611efaa3cd 100644 --- a/include/net/netfilter/nf_flow_table.h +++ b/include/net/netfilter/nf_flow_table.h @@ -183,10 +183,10 @@ enum nf_flow_flags { NF_FLOW_DNAT, NF_FLOW_CLOSING, NF_FLOW_TEARDOWN, + NF_FLOW_PENDING, NF_FLOW_HW, NF_FLOW_HW_DYING, NF_FLOW_HW_DEAD, - NF_FLOW_HW_PENDING, NF_FLOW_HW_BIDIRECTIONAL, NF_FLOW_HW_ESTABLISHED, }; diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c index 934c6151f558..36bbc7be2f74 100644 --- a/net/netfilter/nf_flow_table_core.c +++ b/net/netfilter/nf_flow_table_core.c @@ -575,7 +575,12 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct) static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table, struct flow_offload *flow, void *data) { - bool teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags); + bool teardown; + + if (test_bit(NF_FLOW_PENDING, &flow->flags)) + return; + + teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags); if (nf_flow_has_expired(flow) || nf_ct_is_dying(flow->ct) || diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 6757fd89c1f1..4365859220e6 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -995,6 +995,7 @@ static void flow_offload_work_del(struct flow_offload_work *offload) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL); if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags)) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY); + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); } static void flow_offload_tuple_stats(struct flow_offload_work *offload, @@ -1056,13 +1057,8 @@ static void flow_offload_work_handler(struct work_struct *work) default: WARN_ON_ONCE(1); } - - clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags); - if (offload->cmd == FLOW_CLS_DESTROY) { - /* Publish after the worker's last flow access. */ - smp_mb__before_atomic(); - set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); - } + smp_mb__before_atomic(); + clear_bit(NF_FLOW_PENDING, &offload->flow->flags); kfree(offload); } @@ -1089,12 +1085,12 @@ nf_flow_offload_work_alloc(struct nf_flowtable *flowtable, { struct flow_offload_work *offload; - if (test_and_set_bit(NF_FLOW_HW_PENDING, &flow->flags)) + if (test_and_set_bit(NF_FLOW_PENDING, &flow->flags)) return NULL; offload = kmalloc_obj(struct flow_offload_work, GFP_ATOMIC); if (!offload) { - clear_bit(NF_FLOW_HW_PENDING, &flow->flags); + clear_bit(NF_FLOW_PENDING, &flow->flags); return NULL; } diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 55f3521edb4c..626c9a5af0ef 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -289,7 +289,7 @@ static bool tcf_ct_flow_is_outdated(const struct flow_offload *flow) { return test_bit(IPS_SEEN_REPLY_BIT, &flow->ct->status) && test_bit(IPS_HW_OFFLOAD_BIT, &flow->ct->status) && - !test_bit(NF_FLOW_HW_PENDING, &flow->flags) && + !test_bit(NF_FLOW_PENDING, &flow->flags) && !test_bit(NF_FLOW_HW_ESTABLISHED, &flow->flags); } From 3ae37eafd36694bb2d3227f60ac98fbf602470a2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 23 Sep 2026 20:46:18 +0200 Subject: [PATCH 411/535] netfilter: flowtable: restore ieee80211 forward path Before commit 871df5007eda ("netfilter: flowtable: bail out if forward path cannot be discovered"), there was a fallback to set up a forward path in case .ndo_fill_forward_path fails or DEV_PATH_MTK_WDMA was used. Such fallback was used by commit d787a3e38f01 ("mac80211: add support for .ndo_fill_forward_path"). One possibility is to handle DEV_PATH_MTK_WDMA from the flowtable forward path discovery. However, this is only used internally by drivers to retrieve mtk_wdma information to set up hardware offload. Felix decided to use the .fill_forward_path interface for this purpose due to the lack of a better interface at that time. Add a new DEV_PATH_IEEE80211 path which is offered if the new ieee80211 flag is set on in the struct net_device_path_ctx to restore the flowtable with a ieee80211 netdevice. Handle this new DEV_PATH_IEEE80211 path just like DEV_PATH_ETHERNET and DEV_PATH_DSA, ie. this is the last netdevice in the stack. This new ieee80211 flag is implicitly unset for mtk_ppe and airoha which call dev_fill_forward_path() to retrieve a DEV_PATH_MTK_WDMA path. Fixes: 871df5007eda ("netfilter: flowtable: bail out if forward path cannot be discovered") Signed-off-by: Pablo Neira Ayuso --- include/linux/netdevice.h | 3 +++ net/mac80211/iface.c | 7 +++++++ net/netfilter/nf_flow_table_path.c | 3 +++ 3 files changed, 13 insertions(+) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 87cafc932e9e..3cff2174dc03 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -887,6 +887,7 @@ enum net_device_path_type { DEV_PATH_DSA, DEV_PATH_MTK_WDMA, DEV_PATH_TUN, + DEV_PATH_IEEE80211, }; struct net_device_path { @@ -953,6 +954,8 @@ struct net_device_path_ctx { u16 id; __be16 proto; } vlan[NET_DEVICE_PATH_VLAN_MAX]; + + bool ieee80211; }; enum tc_setup_type { diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 889c32fd8de1..c5584435fde9 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1023,6 +1023,13 @@ static int ieee80211_netdev_fill_forward_path(struct net_device_path_ctx *ctx, struct sta_info *sta; int ret = -ENOENT; + if (ctx->ieee80211) { + path->type = DEV_PATH_IEEE80211; + path->dev = ctx->dev; + ctx->dev = NULL; + return 0; + } + sdata = IEEE80211_DEV_TO_SUB_IF(ctx->dev); local = sdata->local; diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c index 1e55644f2edb..d90013685bf1 100644 --- a/net/netfilter/nf_flow_table_path.c +++ b/net/netfilter/nf_flow_table_path.c @@ -53,6 +53,7 @@ static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache, struct net_device_path_ctx ctx = { .dev = dev, .ether_type = ether_type, + .ieee80211 = true, }; struct neighbour *n; u8 nud_state; @@ -114,6 +115,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack, path = &stack->path[i]; switch (path->type) { case DEV_PATH_ETHERNET: + case DEV_PATH_IEEE80211: case DEV_PATH_DSA: case DEV_PATH_VLAN: case DEV_PATH_PPPOE: @@ -123,6 +125,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack, memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); if (path->type == DEV_PATH_ETHERNET || + path->type == DEV_PATH_IEEE80211 || path->type == DEV_PATH_DSA) break; From 99b43ede9e355ba35244cc9470bf1819774ce39d Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Thu, 24 Sep 2026 23:39:36 -0700 Subject: [PATCH 412/535] net: microchip: vcap: stop scanning after deleting key field The only call outside the KUnit tests is in sparx5_tc_add_rule_copy(), which passes a fixed key list with no duplicate entries. Therefore, the potential double-free is not currently reachable in tree. However, vcap_filter_rule_keys() is exported and does not require its key list entries to be unique. If a future or external caller supplies the same key at another position that the inner loop visits, it calls list_del() and kfree() a second time on the freed field, leading to a potential double-free. Break the inner loop after removing a matching field. The outer safe list traversal continues to process the remaining rule fields. Fixes: 465a38a269e9 ("net: microchip: sparx5: Support for copying and modifying rules in the API") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/ahs-lCAXXikkaHky@stanley.mountain/ Signed-off-by: Harshit Mogalapalli Reviewed-by: Daniel Machon Signed-off-by: David S. Miller --- drivers/net/ethernet/microchip/vcap/vcap_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/microchip/vcap/vcap_api.c b/drivers/net/ethernet/microchip/vcap/vcap_api.c index 788c0728d763..0f8d98f2094d 100644 --- a/drivers/net/ethernet/microchip/vcap/vcap_api.c +++ b/drivers/net/ethernet/microchip/vcap/vcap_api.c @@ -3527,8 +3527,8 @@ int vcap_filter_rule_keys(struct vcap_rule *rule, if (key == keylist[idx]) { list_del(&ckf->ctrl.list); kfree(ckf); - idx++; err = 0; + break; } } } From 4fa0c91c2c4c604e6080690a698d4c6405ba96ca Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 30 Sep 2026 20:58:24 +0900 Subject: [PATCH 413/535] ALSA: ctxfi: Fix silent playback on Titanium HD (SB1270) Commit 4b490e0d103c ("ALSA: ctxfi: Add ADC helper functions for GPIO") moved the GPIO_DATA read into hw_adc_stop(), but the Titanium HD (SB1270) setup in hw_adc_init() uses the local data variable holding the GPIO_CTRL value. Writing this value to GPIO_DATA sets the output mute bits. Playback runs without errors, but the outputs stay muted. Re-read GPIO_DATA before setting the speed mode bits. Also, add the delay after writing the mode bits, as in the original sequence. On affected kernels, switching the output selection was reported to restore clean playback. Fixes: 4b490e0d103c ("ALSA: ctxfi: Add ADC helper functions for GPIO") Cc: stable@vger.kernel.org Reported-by: Dany Patoine Closes: https://lore.kernel.org/linux-sound/DM6PR02MB5433A42E81FEFAC400DD1905EA8B2@DM6PR02MB5433.namprd02.prod.outlook.com/ Signed-off-by: Harin Lee Link: https://patch.msgid.link/20260930115824.739412-1-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/cthw20k2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/pci/ctxfi/cthw20k2.c b/sound/pci/ctxfi/cthw20k2.c index 36066ffe8784..26a6fa69f5f5 100644 --- a/sound/pci/ctxfi/cthw20k2.c +++ b/sound/pci/ctxfi/cthw20k2.c @@ -1895,6 +1895,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) if (hw->model == CTSB1270) { /* Set up the PCM4220 ADC on Titanium HD */ + data = hw_read_20kx(hw, GPIO_DATA); data &= ~0x0C; if (1 == info->msr) data |= 0x00; /* Single Speed Mode 32-50kHz */ @@ -1903,6 +1904,7 @@ static int hw_adc_init(struct hw *hw, const struct adc_conf *info) else data |= 0x04; /* Quad Speed Mode 108kHz-216kHz */ hw_write_20kx(hw, GPIO_DATA, data); + usleep_range(10000, 11000); } hw_adc_start(hw); From ece609abc95cc0f75e633d93f3d868b030d9a8fe Mon Sep 17 00:00:00 2001 From: renpanpan Date: Wed, 30 Sep 2026 14:55:04 +0800 Subject: [PATCH 414/535] ALSA: hda/realtek: Add mute LED quirk for HP Pavilion 14-dv1001TU The HP Pavilion 14-dv1001TU (subsystem ID 103c:8987) uses a Realtek ALC287 codec. The mute LED on this machine is controlled by GPIO bit 0x10, which was verified via hda-verb, but the subsystem ID is missing from the quirk table. As a result, the ALC287_FIXUP_HP_GPIO_LED fixup that drives GPIO 0x10 is never applied and the mute LED stays off even though the associated LED class device toggles correctly. Add the missing SND_PCI_QUIRK entry to map this subsystem ID to ALC287_FIXUP_HP_GPIO_LED so the mute LED works as expected. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221810 Signed-off-by: renpanpan Link: https://patch.msgid.link/20260930065504.24151-1-rppxj178543@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 333689f31775..566699482fb6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7528,6 +7528,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8975, "HP EliteBook x360 840 Aero G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x897d, "HP mt440 Mobile Thin Client U74", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8981, "HP Elite Dragonfly G3", ALC245_FIXUP_CS35L41_SPI_4), + SND_PCI_QUIRK(0x103c, 0x8987, "HP Pavilion 14-dv1001TU", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x898a, "HP Pavilion 15-eg100", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x898e, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x898f, "HP EliteBook 835 G9", ALC287_FIXUP_CS35L41_I2C_2), From 97384ef06c4fa79f7265ecd65c240bf40a8a9c41 Mon Sep 17 00:00:00 2001 From: Harrison Lucas Date: Tue, 29 Sep 2026 11:43:48 -0600 Subject: [PATCH 415/535] ALSA: hda/realtek: Fix mute and micmute LEDs on HP ENVY x360 15-ey0xxx The HP ENVY x360 2-in-1 Laptop 15-ey0xxx (SSID 103c:8a31, ALC245 with CS35L41 I2C amps) currently only gets ALC287_FIXUP_CS35L41_I2C_2, so the mute and micmute LEDs on the keyboard never light up. On this machine the mute LED is driven by COEF 0x0b bits 2-3 and the micmute LED by GPIO 0x04 (active low), which is exactly what ALC245_FIXUP_HP_X360_MUTE_LEDS sets up. Switch the quirk to ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, which keeps the CS35L41 setup and chains to that LED fixup. Verified by driving the COEF and GPIO values by hand with hda-verb, following Master Playback Switch and Mic ACP LED Capture Switch. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Harrison Lucas Link: https://patch.msgid.link/20260929174348.167537-1-tiiowsalt@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 566699482fb6..fd76d8b503a9 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7566,7 +7566,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8a31, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a36, "HP Pavilion Plus 14-eh0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8a3d, "HP Victus 15-fb0xxx (MB 8A3D)", ALC245_FIXUP_HP_MUTE_LED_V2_COEFBIT), From 819f6aacbb1d1621c6a62721e708835fe1fb75d8 Mon Sep 17 00:00:00 2001 From: Jakub Gawlik Date: Wed, 30 Sep 2026 16:58:39 +0200 Subject: [PATCH 416/535] ALSA: hda/realtek: Add micmute LED quirk for Acer Swift SFG16-72 The Acer Swift SFG16-72 (1025:173b) uses the ALC245 codec, but its mic-mute LED is not enabled by default. Apply the existing ALC245_FIXUP_ACER_MICMUTE_LED quirk to enable the mic-mute LED. Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Jakub Gawlik Link: https://patch.msgid.link/20260930145839.374762-1-jakubgawlik13@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index fd76d8b503a9..be5482530aa2 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7238,6 +7238,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x172c, "Acer Nitro ANV16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1731, "Acer Predator PHN16-72", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x173a, "Acer Swift SFG14-73", ALC245_FIXUP_ACER_MICMUTE_LED), + SND_PCI_QUIRK(0x1025, 0x173b, "Acer Swift SFG16-72", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1758, "Acer Nitro ANV15-41", ALC245_FIXUP_ACER_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x1826, "Acer Helios ZPC", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), SND_PCI_QUIRK(0x1025, 0x182c, "Acer Helios ZPD", ALC287_FIXUP_PREDATOR_SPK_CS35L41_I2C_2), From 572af6872e520c7102e58362ef7cd7c2ed4342be Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 29 Sep 2026 15:27:52 -0700 Subject: [PATCH 417/535] crypto: aes - Fix undesired override of some optimized AES modes The new library APIs for AES encryption modes were wired up to the traditional crypto API via crypto/aes.c. However, for now the kernel is still in a transitional state where various architectures still have architecture-optimized implementations of AES modes in arch/*/crypto/, wired up to the traditional crypto API only. Because of that, the crypto/aes.c algorithms were given a cra_priority of only 110 to prevent them from overriding arch/*/crypto/ in the traditional crypto API. However, because of how the traditional crypto API works, the cra_priority trick doesn't work in cases where the relevant algorithm isn't directly implemented by arch/*/crypto/ but rather is provided by a template instance using other code in arch/*/crypto/. For example, x86 doesn't have its own "ccm(aes)" but rather relies on the "ccm" template constructing it from the x86-optimized "ctr(aes)". The existence of the library-based "ccm(aes)" prevents that, even though its priority is lower than what the template would produce. Thus, "ccm(aes)" ends up using the slower single-block AES code. Therefore, skip wiring up the relevant library-based code to the traditional crypto API on architectures where this problem can occur, as determined by what exists in arch/*/crypto/ for each architecture. This is ugly, but it's also temporary: these conditions will go away as architecture-optimized implementations of AES modes are migrated into the library. But until then, we need to prevent performance regressions by ensuring that the optimized code continues to be used. Fixes: 20df21a482aa ("crypto: aes - Add CBC and CBC-CTS support using library") Fixes: 8ca62072faa1 ("crypto: aes - Add GCM support using library") Fixes: f70ad727d1d6 ("crypto: aes - Add CCM support using library") Fixes: 94efa0c9fb36 ("crypto: aes - Add XTS support using library") Closes: https://github.com/sparclinux/issues/issues/106 Acked-by: Ard Biesheuvel Tested-by: Stian Halseth Link: https://patch.msgid.link/20260929222752.36427-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- crypto/aes.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/crypto/aes.c b/crypto/aes.c index 94791f481e98..51eee78396ea 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -637,7 +637,18 @@ static struct skcipher_alg skcipher_algs[] = { .decrypt = crypto_aes_cbc_decrypt, }, #endif -#if IS_ENABLED(CONFIG_CRYPTO_CTS) + /* + * Don't register library-based "cts(cbc(aes))" on architectures where + * it might block a "better" implementation from being instantiated via + * the "cts" template. These exclusions are temporary and will go away + * as the arch-optimized AES code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_CTS) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC)) { .base.cra_name = "cts(cbc(aes))", .base.cra_driver_name = "cts-cbc-aes-lib", @@ -687,7 +698,13 @@ static struct skcipher_alg skcipher_algs[] = { .decrypt = crypto_aes_xctr_crypt, }, #endif -#if IS_ENABLED(CONFIG_CRYPTO_XTS) + /* + * Don't register library-based "xts(aes)" on architectures where it + * might block a "better" implementation from being instantiated via the + * "xts" template. This exclusion is temporary and will go away when + * the library AES-XTS is optimized for SPARC. + */ +#if IS_ENABLED(CONFIG_CRYPTO_XTS) && !IS_ENABLED(CONFIG_SPARC) { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", @@ -980,7 +997,20 @@ static __maybe_unused int crypto_aes_ccm_decrypt(struct aead_request *req) } static struct aead_alg aead_algs[] = { -#if IS_ENABLED(CONFIG_CRYPTO_GCM) + /* + * Don't register library-based "gcm(aes)" and "rfc4106(gcm(aes))" on + * architectures where they might block a "better" implementation from + * being instantiated via the "gcm" and "rfc4106" templates. These + * exclusions are temporary and will go away as the arch-optimized AES + * code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_GCM) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_RISCV) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC)) { .base.cra_name = "gcm(aes)", .base.cra_driver_name = "gcm-aes-lib", @@ -1012,7 +1042,20 @@ static struct aead_alg aead_algs[] = { .chunksize = AES_BLOCK_SIZE, }, #endif /* CONFIG_CRYPTO_GCM */ -#if IS_ENABLED(CONFIG_CRYPTO_CCM) + /* + * Don't register library-based "ccm(aes)" on architectures where it + * might block a "better" implementation from being instantiated via the + * "ccm" template. These exclusions are temporary and will go away as + * the arch-optimized AES code is migrated into the library. + */ +#if IS_ENABLED(CONFIG_CRYPTO_CCM) && \ + !(IS_ENABLED(CONFIG_ARM) || \ + IS_ENABLED(CONFIG_ARM64) || \ + IS_ENABLED(CONFIG_PPC) || \ + IS_ENABLED(CONFIG_RISCV) || \ + IS_ENABLED(CONFIG_S390) || \ + IS_ENABLED(CONFIG_SPARC) || \ + IS_ENABLED(CONFIG_X86)) { .base.cra_name = "ccm(aes)", .base.cra_driver_name = "ccm-aes-lib", From 69bf14300b50631e7c2271289d347bbd1b19a06c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 21 Sep 2026 11:17:26 -0300 Subject: [PATCH 418/535] netfs: clear post-EOF pagecache when extending a file via write Fix netfs to erase the contents of a hole created after the EOF by an ordinary write if dirty data has been previously left there by writes through an mmapped region. Neither the buffered nor the unbuffered/DIO write path clears that stale pagecache. Zero the tail of the folio straddling the EOF before an extending write. That is the only folio that can hold data written past the EOF through an mmap, as pages wholly beyond the EOF can't be faulted in. The folio is zeroed rather than dropped so a concurrent extending write can't lose data. Both write paths downgrade the i_rwsem to shared, so extending writes can run concurrently and the i_size read by the caller may be stale by the time the folio is locked. Re-read i_size under the folio lock and clamp the zeroed range up to it, so a racing write that already put data into the folio isn't clobbered. Wait for any writeback on the folio to finish before zeroing it so that the pagecache isn't modified while it may still be read by the transport during transmission. Honour IOCB_NOWAIT by returning -EAGAIN rather than blocking on the folio lock, on writeback, or in folio_mkclean()'s rmap walk when the folio is mapped. truncate_pagecache() can't be used here: it must be called with the i_rwsem held exclusively, but these write paths only hold it shared, and it would block unconditionally, breaking IOCB_NOWAIT. Callers that hold i_rwsem exclusively for the whole resize (truncate, setattr, fallocate, clone) exclude any genuine concurrent buffered writer, so staleness can instead be decided from the folio's dirty state, as pagecache_isize_extended() already does for filesystems that serialise writes against truncate/setattr via a single i_rwsem. Export netfs_clear_stale_post_isize() helper to handle such case. The helper is required by the CIFS client to fix generic/363. Closes: https://sashiko.dev/#/patchset/20260921230755.1133425-1-pc%40manguebit.org Fixes: 938e13a73b24 ("netfs: Implement buffered write API") Fixes: 153a9961b551 ("netfs: Implement unbuffered/DIO write support") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- Documentation/filesystems/netfs_library.rst | 26 ++++++ fs/netfs/buffered_write.c | 9 ++ fs/netfs/direct_write.c | 9 ++ fs/netfs/internal.h | 2 + fs/netfs/misc.c | 99 +++++++++++++++++++++ include/linux/netfs.h | 2 + 6 files changed, 147 insertions(+) diff --git a/Documentation/filesystems/netfs_library.rst b/Documentation/filesystems/netfs_library.rst index ddd799df6ce3..a9de281db8ee 100644 --- a/Documentation/filesystems/netfs_library.rst +++ b/Documentation/filesystems/netfs_library.rst @@ -451,6 +451,32 @@ one. The inode should be marked ``NETFS_ICTX_SINGLE_NO_UPLOAD`` if this API is to be used. The writeback function requires the buffer to be of ITER_FOLIOQ type. +Clearing Stale Post-EOF Pagecache +--------------------------------- + +When a file is extended, data left in the pagecache past the old EOF by a write +through an mmap must not be exposed as file content. Netfslib clears this on +its own write paths, and exports a helper so a filesystem can do the same from +a resize path (truncate, setattr, fallocate and the like) that holds the +inode's ``i_rwsem`` exclusively across the whole resize:: + + void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to); + +This zeroes any such data within the ``[from, to)`` hole to be made, where +@from is the old EOF and @to is the new one, and the caller must have already +updated ``i_size`` to @to before calling it. Only the folio straddling @from +can hold data written past the EOF through an mmap, as pages wholly beyond the +EOF can't be faulted in, so the zeroing is limited to that folio. The folio is +zeroed rather than dropped so that a concurrent extending write can't lose +data. + +This overlaps with ``pagecache_isize_extended()`` but can't reuse it: that +helper is keyed on a sub-page block size and is a no-op when the block size is +``>= PAGE_SIZE`` (as on network filesystems), and it doesn't wait for +writeback. As both address the same problem, a change to one should probably +be reflected in the other to keep them in sync. + High-Level VM API ================== diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index 2cdb68e6b16f..ecf119b4f916 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -469,6 +469,7 @@ ssize_t netfs_buffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *fr struct netfs_group *netfs_group) { struct file *file = iocb->ki_filp; + struct inode *inode = file_inode(file); ssize_t ret; trace_netfs_write_iter(iocb, from); @@ -481,6 +482,14 @@ ssize_t netfs_buffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *fr if (ret) return ret; + if (iocb->ki_pos > i_size_read(inode)) { + ret = netfs_clear_stale_pre_isize(inode, i_size_read(inode), + iocb->ki_pos, + iocb->ki_flags & IOCB_NOWAIT); + if (ret) + return ret; + } + return netfs_perform_write(iocb, from, netfs_group); } EXPORT_SYMBOL(netfs_buffered_write_iter_locked); diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 2361277416c7..8be74706d85e 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -359,6 +359,15 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from) ret = file_update_time(file); if (ret < 0) goto out; + + if (iocb->ki_pos > i_size_read(inode)) { + ret = netfs_clear_stale_pre_isize(inode, i_size_read(inode), + iocb->ki_pos, + iocb->ki_flags & IOCB_NOWAIT); + if (ret < 0) + goto out; + } + if (iocb->ki_flags & IOCB_NOWAIT) { /* We could block if there are any pages in the range. */ ret = -EAGAIN; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index c79c8e69d60c..786af76da98c 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -80,6 +80,8 @@ ssize_t netfs_wait_for_write(struct netfs_io_request *rreq); void netfs_wait_for_paused_read(struct netfs_io_request *rreq); void netfs_wait_for_paused_write(struct netfs_io_request *rreq); void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq); +int netfs_clear_stale_pre_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait); /* * objects.c diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index f5c1c463f4ff..523057390d6f 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -6,6 +6,7 @@ */ #include +#include #include "internal.h" /** @@ -582,3 +583,101 @@ void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq) trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs); finish_wait(&rreq->waitq, &myself); } + +/** + * netfs_clear_stale_isize - Clear stale pagecache in a to-be-created hole + * @inode: The inode to act upon. + * @from: The base of the hole to be made. + * @to: The top of the hole to be made. + * @nowait: True to return -EAGAIN rather than block. + * @exclusive: True if the caller holds i_rwsem exclusively for the resize. + * + * Zero any data left in the pagecache within the [@from, @to) hole by a + * write through an mmap so that it isn't exposed as file content once the + * file is extended. Only the uptodate folio straddling @from can hold such + * data as pages wholly beyond the EOF can't be faulted in, so the zeroing + * is limited to that folio. The folio is zeroed rather than dropped so + * that a concurrent extending write can't lose data. + * + * If @exclusive is false, @from is re-read from i_size and used to clamp + * the zeroed range, for callers that may race with another writer also + * extending the file (eg. multiple buffered writes extending the same file + * under a shared i_rwsem). If @exclusive is true, for callers that hold + * i_rwsem exclusively across the whole resize and have already updated + * i_size to @to, staleness is decided from the folio's dirty state instead: + * since no genuine concurrent buffered writer can be racing, a lockless + * stat() adopting a server-confirmed size mid-resize has no data behind it + * and never dirties the folio, so it can't fool this check into skipping + * the zeroing the way it could fool the @exclusive false clamp. + * + * pagecache_isize_extended() can't be reused here: it is keyed on a + * sub-page block size (a no-op when the block size is >= PAGE_SIZE, as on + * network filesystems), runs after i_size is updated, can't honour + * @nowait, and doesn't wait for writeback. Keep the two in sync if either + * is changed. + * + * Return: 0 on success, or -EAGAIN if @nowait is set and the folio is + * mapped or under writeback and so can't be cleaned without blocking. + */ +static int netfs_clear_stale_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait, bool exclusive) +{ + struct address_space *mapping = inode->i_mapping; + fgf_t fgp = FGP_LOCK; + struct folio *folio; + int ret; + + if (from >= to) + return 0; + + if (nowait) + fgp |= FGP_NOWAIT; + + folio = __filemap_get_folio(mapping, from >> PAGE_SHIFT, fgp, 0); + if (IS_ERR(folio)) + return PTR_ERR(folio) == -EAGAIN ? -EAGAIN : 0; + + ret = 0; + if (nowait && (folio_mapped(folio) || folio_test_writeback(folio))) { + ret = -EAGAIN; + goto out; + } + + folio_wait_writeback(folio); + + if (folio_mkclean(folio)) + folio_mark_dirty(folio); + + if (folio_test_uptodate(folio) && + (!exclusive || folio_test_dirty(folio))) { + uoff_t fpos = folio_pos(folio); + + if (!exclusive) + from = umax(from, i_size_read(inode)); + if (from < to && from < fpos + folio_size(folio)) { + size_t end = umin(to - fpos, folio_size(folio)); + size_t offset = from - fpos; + + folio_zero_segment(folio, offset, end); + } + } +out: + folio_unlock(folio); + folio_put(folio); + return ret; +} + +/* Clear stale pagecache before an extending buffered/DIO write. */ +int netfs_clear_stale_pre_isize(struct inode *inode, uoff_t from, + uoff_t to, bool nowait) +{ + return netfs_clear_stale_isize(inode, from, to, nowait, false); +} + +/* Clear stale pagecache when extending a file under an exclusive resize. */ +void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to) +{ + netfs_clear_stale_isize(inode, from, to, false, true); +} +EXPORT_SYMBOL(netfs_clear_stale_post_isize); diff --git a/include/linux/netfs.h b/include/linux/netfs.h index b4dd32863dd4..fe6275e55fc2 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -397,6 +397,8 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from); ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *iter, struct netfs_group *netfs_group); ssize_t netfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from); +void netfs_clear_stale_post_isize(struct inode *inode, uoff_t from, + uoff_t to); /* Single, monolithic object read/write API. */ void netfs_single_mark_inode_dirty(struct inode *inode); From 35024595f641e790e24609f3f046e1667e3b280a Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 20 Sep 2026 15:05:44 -0300 Subject: [PATCH 419/535] smb: client: clear post-EOF pagecache when extending a file via truncate cifs_setsize() relied on pagecache_isize_extended() to zero the tail of the folio straddling the old EOF, but that helper is a no-op on CIFS (i_blkbits is 14), so data dirtied past EOF through an mmap survived and became visible once the file was extended. Use netfs_clear_stale_post_isize() instead, called after i_size is updated since cifs_setsize() callers hold i_rwsem exclusively for the whole resize. truncate_pagecache() is then called as in truncate_setsize(): a no-op on extend, and it drops the pagecache beyond the new EOF on shrink. Also thread old_size as an explicit parameter through cifs_setsize() and cifs_resize_file_locked(), captured by each caller before its own resize RPC. This closes a race where a concurrent stat() could adopt the RPC's already-updated server size via is_size_safe_to_change() before cifs_setsize() reads old_size itself. Closes: https://sashiko.dev/#/patchset/20260921230755.1133425-1-pc%40manguebit.org Fixes: c510edb9734a ("cifs: call pagecache_isize_extended() in cifs_setsize() when extending") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsfs.h | 5 +++-- fs/smb/client/file.c | 3 ++- fs/smb/client/inode.c | 22 ++++++++++++++-------- fs/smb/client/smb2ops.c | 10 ++++++---- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 0c85daa8386e..e3d820c9778b 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -146,8 +146,9 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file, unsigned int flags); long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg); -void cifs_setsize(struct inode *inode, loff_t offset); -void cifs_resize_file_locked(struct inode *inode, loff_t offset); +void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset); +void cifs_resize_file_locked(struct inode *inode, loff_t old_size, + loff_t offset); struct fs_context; struct smb3_fs_context; diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 0d428517f454..4bcb87610897 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1017,6 +1017,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) if (!rc) { if (cfile) { struct netfs_inode *ictx = netfs_inode(inode); + loff_t old_size = i_size_read(inode); tcon = tlink_tcon(cfile->tlink); server = tcon->ses->server; @@ -1025,7 +1026,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) cfile, 0, false); if (!rc) { netfs_resize_file(&cinode->netfs, 0, true); - cifs_setsize(inode, 0); + cifs_setsize(inode, old_size, 0); cifs_invalidate_cache(inode, 0); } netfs_wb_end(ictx); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 1fe0ef0a95db..5062474991cf 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3053,15 +3053,12 @@ int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start, return -EOPNOTSUPP; } -void cifs_setsize(struct inode *inode, loff_t offset) +void cifs_setsize(struct inode *inode, loff_t old_size, loff_t offset) { - loff_t old_size; u64 blocks = CIFS_INO_BLOCKS(offset); spin_lock(&inode->i_lock); - old_size = i_size_read(inode); i_size_write(inode, offset); - /* * Extending EOF does not allocate the intervening range. Only clamp * i_blocks on shrink; allocation growth comes from writes or from the @@ -3071,20 +3068,28 @@ void cifs_setsize(struct inode *inode, loff_t offset) inode->i_blocks = blocks; spin_unlock(&inode->i_lock); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + + /* + * Zero the tail of the folio straddling the old EOF so data dirtied + * past EOF through an mmap isn't exposed. truncate_pagecache() then + * drops any pagecache beyond the new EOF, as in truncate_setsize(). + */ if (offset > old_size) - pagecache_isize_extended(inode, old_size, offset); + netfs_clear_stale_post_isize(inode, old_size, offset); + truncate_pagecache(inode, offset); netfs_wait_for_outstanding_io(inode); } -void cifs_resize_file_locked(struct inode *inode, loff_t offset) +void cifs_resize_file_locked(struct inode *inode, loff_t old_size, + loff_t offset) { struct fscache_cookie *cookie = cifs_inode_cookie(inode); lockdep_assert_held_write(&inode->i_rwsem); netfs_resize_file(netfs_inode(inode), offset, true); - cifs_setsize(inode, offset); + cifs_setsize(inode, old_size, offset); if (!cookie) return; @@ -3101,6 +3106,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, struct inode *inode = d_inode(dentry); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct cifsInodeInfo *cifsInode = CIFS_I(inode); + loff_t old_size = i_size_read(inode); struct tcon_link *tlink = NULL; struct cifs_tcon *tcon = NULL; struct TCP_Server_Info *server; @@ -3159,7 +3165,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry, set_size_out: if (rc == 0) - cifs_resize_file_locked(inode, size); + cifs_resize_file_locked(inode, old_size, size); return rc; } diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index aa142420dae2..1ada1c0a728d 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -2287,6 +2287,7 @@ smb2_duplicate_extents(const unsigned int xid, struct duplicate_extents_to_file dup_ext_buf; struct timespec64 ts; struct cifs_tcon *tcon = tlink_tcon(trgtfile->tlink); + loff_t old_size; u64 asize; /* server fileays advertise duplicate extent support with this flag */ @@ -2305,11 +2306,12 @@ smb2_duplicate_extents(const unsigned int xid, trgtfile->fid.volatile_fid, tcon->tid, tcon->ses->Suid, src_off, dest_off, len); inode = d_inode(trgtfile->dentry); - if (i_size_read(inode) < dest_off + len) { + old_size = i_size_read(inode); + if (old_size < dest_off + len) { rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false); if (rc) goto duplicate_extents_out; - cifs_resize_file_locked(inode, dest_off + len); + cifs_resize_file_locked(inode, old_size, dest_off + len); } rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, trgtfile->fid.volatile_fid, @@ -3883,7 +3885,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } new_eof = off + len; - cifs_resize_file_locked(inode, new_eof); + cifs_resize_file_locked(inode, old_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3931,7 +3933,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - cifs_resize_file_locked(inode, new_eof); + cifs_resize_file_locked(inode, old_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, From da4fe97fb6c730d9c94a115c7bfa6f98e0d578d0 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 20 Sep 2026 16:09:30 -0300 Subject: [PATCH 420/535] smb: client: discard post-EOF pagecache when extending a file via zero range smb3_zero_range() removed the pagecache only for the range being zeroed, so when it extends the file the folio straddling the old EOF - which may hold data dirtied past EOF through an mmap - was never dropped and became visible as file content once the file was extended. Lower the discard boundary to the smaller of the zero-range offset and the old EOF. Fixes: 72c419d9b073 ("cifs: fix smb3_zero_range so it can expand the file-size when required") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2ops.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1ada1c0a728d..849cfd2de705 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3560,8 +3560,11 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, /* * We zero the range through ioctl, so we need remove the page caches * first, otherwise the data may be inconsistent with the server. + * + * Start at the old EOF when extending so the folio straddling it, which + * may hold data written past EOF through an mmap, is dropped too. */ - truncate_pagecache_range(inode, offset, offset + len - 1); + truncate_pagecache_range(inode, min(offset, i_size), offset + len - 1); netfs_wait_for_outstanding_io(inode); /* if file not oplocked can't be sure whether asking to extend size */ From 7b473ac7cd6f7e087876d3c67b1bc6df662fb429 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 20 Sep 2026 16:37:41 -0300 Subject: [PATCH 421/535] smb: client: discard post-EOF pagecache when extending a file via copy range cifs_file_copychunk_range() invalidated the pagecache only for the destination region, so when a copy extends the file the folio straddling the old EOF - which may hold data dirtied past EOF through an mmap - was never dropped and became visible as file content once the file was extended. Start the invalidation at the smaller of the destination offset and the old EOF. filemap_invalidate_inode() writes back before invalidating while i_size is still old, so nothing past the old EOF is flushed to the server. Fixes: 8101d6e112e2 ("cifs: Fix copy offload to flush destination region") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsfs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 7ecd70efdfea..f1fb4b7b7b7c 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1581,8 +1581,13 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, /* Flush and invalidate all the folios in the destination region. If * the copy was successful, then some of the flush is extra overhead, * but we need to allow for the copy failing in some way (eg. ENOSPC). + * + * Start at the old EOF when extending so the folio straddling it, which + * may hold data written past EOF through an mmap, is dropped too. */ - rc = filemap_invalidate_inode(target_inode, true, destoff, destoff + len - 1); + rc = filemap_invalidate_inode(target_inode, true, + min(destoff, i_size_read(target_inode)), + destoff + len - 1); if (rc) goto unlock; From 4721587f96cb8de8164f3fd203093a0875560999 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 20 Sep 2026 16:38:03 -0300 Subject: [PATCH 422/535] smb: client: discard post-EOF pagecache when extending a file via clone range cifs_remap_file_range() discarded the pagecache only for the folios overlapping the destination region, so when a clone extends the file the folio straddling the old EOF - which may hold data dirtied past EOF through an mmap - was never dropped and became visible as file content once the file was extended. Start the discard at the smaller of fstart and the old EOF. i_size is still old here, so nothing past the old EOF is flushed to the server. Fixes: c54fc3a4f375 ("cifs: Fix flushing, invalidation and file size with FICLONE") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsfs.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index f1fb4b7b7b7c..6410249f529a 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1467,9 +1467,14 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, i_size = target_inode->i_size; spin_unlock(&target_inode->i_lock); - /* Discard all the folios that overlap the destination region. */ + /* + * Discard all the folios that overlap the destination region. Start at + * the old EOF when extending so the folio straddling it, which may hold + * data written past EOF through an mmap, is dropped too. + */ cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend); - truncate_inode_pages_range(&target_inode->i_data, fstart, fend); + truncate_inode_pages_range(&target_inode->i_data, + min(fstart, i_size), fend); fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size, 0); From aa994489bd835547c191827acc460192e147032d Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 25 Sep 2026 19:35:12 -0300 Subject: [PATCH 423/535] smb: client: flush and commit data before querying allocated ranges The mode 0 / FALLOC_FL_KEEP_SIZE fallocate emulation for small interior ranges in smb3_simple_fallocate_range() issues FSCTL_QUERY_ALLOCATED_RANGES and zero-fills any sub-range the server reports as an unallocated hole, to force block allocation without changing file contents. This is unsafe against Windows servers. Per the Win32 documentation, FSCTL_QUERY_ALLOCATED_RANGES only reports ranges that *may* contain nonzero data and is explicitly not coherent with recently written data: "a call to FSCTL_QUERY_ALLOCATED_RANGES [after writing to a network file] would not necessarily return a correct list of allocated regions. To ensure coherency [...] flush the data to the file" fsx (generic/363) reproduces the resulting corruption against Windows Server 2022: a range is written, an uncached read returns the data, yet the next FSCTL_QUERY_ALLOCATED_RANGES on that range reports it as a whole hole, so the emulation overwrites live data with zeroes. A network trace confirmed the WRITE, the READ returning data and the query returning an empty range list within microseconds. Samba does not exhibit this; its query is coherent with the written data. Write back the dirty pages, drain any outstanding I/O and issue an SMB2 FLUSH to force the server to commit the data before querying the allocated ranges, so the query reflects the data actually present. With this, generic/363 passes against both Windows Server 2022 and Samba over 100000 fsx operations. Fixes: 966a3cb7c7db ("cifs: improve fallocate emulation") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2ops.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 849cfd2de705..1c41d3b24c6d 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3750,6 +3750,24 @@ static int smb3_simple_fallocate_range(unsigned int xid, goto out; } + filemap_invalidate_lock(inode->i_mapping); + + /* + * Flush and commit the data to the server, otherwise + * FSCTL_QUERY_ALLOCATED_RANGES might report recently written data as + * unallocated holes on Windows Servers, and the loop below would + * then zero-fill them and corrupt the file. + */ + rc = filemap_write_and_wait_range(inode->i_mapping, off, + off + len - 1); + if (rc) + goto out_unlock; + netfs_wait_for_outstanding_io(inode); + rc = SMB2_flush(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid); + if (rc) + goto out_unlock; + in_data.file_offset = cpu_to_le64(off); in_data.length = cpu_to_le64(len); rc = SMB2_ioctl(xid, tcon, cfile->fid.persistent_fid, @@ -3759,7 +3777,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, 1024 * sizeof(struct file_allocated_range_buffer), (char **)&out_data, &out_data_len); if (rc) - goto out; + goto out_unlock; tmp_data = out_data; while (len) { @@ -3769,12 +3787,12 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (out_data_len == 0) { rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, len, buf); - goto out; + goto out_unlock; } if (out_data_len < sizeof(struct file_allocated_range_buffer)) { rc = -EINVAL; - goto out; + goto out_unlock; } range_start = le64_to_cpu(tmp_data->file_offset); @@ -3782,7 +3800,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, if (check_add_overflow(range_start, range_len, &range_end) || range_end > S64_MAX) { rc = -EINVAL; - goto out; + goto out_unlock; } if (off < range_start) { @@ -3797,11 +3815,11 @@ static int smb3_simple_fallocate_range(unsigned int xid, rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, l, buf); if (rc) - goto out; + goto out_unlock; off = off + l; len = len - l; if (len == 0) - goto out; + goto out_unlock; } /* * We are at a section of allocated data, just skip forward @@ -3820,6 +3838,8 @@ static int smb3_simple_fallocate_range(unsigned int xid, out_data_len -= sizeof(struct file_allocated_range_buffer); } + out_unlock: + filemap_invalidate_unlock(inode->i_mapping); out: kfree(out_data); kvfree(buf); From 3581f843defa0bb30fd7da1e7dba170465e13a95 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 25 Sep 2026 20:16:09 -0300 Subject: [PATCH 424/535] smb: client: drain outstanding I/O before truncating on O_TRUNC open cifs_do_truncate() flushes the pagecache with filemap_write_and_wait() before resetting the file size to zero, but that does not wait for direct/async I/O that was already in flight, which can complete after the truncate and reinstate stale data past the new EOF. Drain outstanding netfs I/O with netfs_wait_for_outstanding_io() before resizing the file. Reported-by: Frank Sorenson Fixes: a8603b52b39f ("smb: client: fix data corruption with concurrent writes and O_TRUNC") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 4bcb87610897..11355d6bf89d 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -1012,6 +1012,8 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry) } mapping_set_error(inode->i_mapping, rc); + netfs_wait_for_outstanding_io(inode); + cfile = find_writable_file(cinode, FIND_FSUID_ONLY); rc = cifs_file_flush(xid, inode, cfile); if (!rc) { From 8f8d32c1044f70e46e87550e039a15ff20a4b21c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 12:59:18 -0300 Subject: [PATCH 425/535] smb: client: flush dirty data before zeroing a range smb3_zero_range() emulates FALLOC_FL_ZERO_RANGE by invalidating the page cache over the target range with truncate_pagecache_range() and then issuing FSCTL_SET_ZERO_DATA to the server. Dirty data was only flushed conditionally, when the range reached or extended EOF. For a purely interior zero range that does not reach EOF, no flush happened, so a dirty folio overlapping the range could be written back to the server after the FSCTL and refill the range that was just zeroed with stale data. Fix this by unconditionally flushing and waiting for dirty data in the range before invalidating the page cache and issuing FSCTL_SET_ZERO_DATA, exactly as was done for smb3_punch_hole() in commit d7d2adcd022b ("smb/client: flush dirty data before punching a hole"); the two paths are structurally identical here. This is observed as generic/363 randomly reading stale data where a zeroed range is expected against Windows Server. Fixes: 91d1dfae4649 ("cifs: Fix FALLOC_FL_ZERO_RANGE to preflush buffered part of target region") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2ops.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 1c41d3b24c6d..a0fcb564e7a3 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3549,13 +3549,11 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, filemap_invalidate_lock(inode->i_mapping); netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); - if (offset + len >= remote_i_size && offset < i_size) { - unsigned long long top = umin(offset + len, i_size); - rc = filemap_write_and_wait_range(inode->i_mapping, offset, top - 1); - if (rc < 0) - goto zero_range_exit; - } + rc = filemap_write_and_wait_range(inode->i_mapping, offset, + offset + len - 1); + if (rc < 0) + goto zero_range_exit; /* * We zero the range through ioctl, so we need remove the page caches From c9b1194e8ec99d95db25db45edd2fbf690f043ab Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 13:04:44 -0300 Subject: [PATCH 426/535] smb: client: drain and invalidate before server-side copy/clone cifs_file_copychunk_range() and the clone (FICLONE) path of cifs_remap_file_range() do not serialise the destination page cache against the server-side copy the way the other server-side range operations (smb3_zero_range(), smb3_punch_hole(), smb3_collapse_range() and smb3_insert_range()) do. cifs_file_copychunk_range() invalidates the destination range with filemap_invalidate_inode(), which takes and drops the mapping's invalidate_lock internally, so the lock is no longer held when the copychunk ioctl is issued. It also never drains in-flight netfs I/O on the target. The clone path never takes the invalidate_lock at all (only i_rwsem), uses a bare truncate_inode_pages_range() and likewise does not drain outstanding I/O. As a result an asynchronous destination writeback can complete after the server-side copy/clone has run and reinstate stale data over the region just written by the server, corrupting the file. This is the same class of corruption as commit d7d2adcd022b ("smb/client: flush dirty data before punching a hole") and has been seen randomly in generic/363 against Windows Server. Fix both paths to follow the established ordering: hold the target mapping's invalidate_lock across the flush and invalidation of the destination and the server ioctl, and call netfs_wait_for_outstanding_io() on the target to drain in-flight writes before the ioctl is issued. Only the target inode's invalidate_lock is required, as the source is merely flushed and not invalidated; i_rwsem (already held via lock_two_nondirectories()) is acquired before the invalidate_lock, matching the VFS lock ordering. Since filemap_invalidate_inode() takes that same lock internally, replace it with its own unmap/flush/invalidate steps instead of calling it, and return early on a zero-length copy to avoid a range underflow. Fixes: 8101d6e112e2 ("cifs: Fix copy offload to flush destination region") Fixes: c54fc3a4f375 ("cifs: Fix flushing, invalidation and file size with FICLONE") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/cifsfs.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 6410249f529a..98b610b2a114 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1412,6 +1412,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, * server could even support copy of range where source = target */ lock_two_nondirectories(target_inode, src_inode); + filemap_invalidate_lock(target_inode->i_mapping); if (len == 0) { loff_t src_size = i_size_read(src_inode); @@ -1475,6 +1476,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, cifs_dbg(FYI, "about to discard pages %llx-%llx\n", fstart, fend); truncate_inode_pages_range(&target_inode->i_data, min(fstart, i_size), fend); + netfs_wait_for_outstanding_io(target_inode); fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size, 0); @@ -1513,6 +1515,7 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off, if (rc) CIFS_I(target_inode)->time = 0; unlock: + filemap_invalidate_unlock(target_inode->i_mapping); /* although unlocking in the reverse order from locking is not strictly necessary here it is a little cleaner to be consistent */ unlock_two_nondirectories(src_inode, target_inode); @@ -1536,6 +1539,9 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct cifs_tcon *target_tcon; ssize_t rc; + if (len == 0) + return 0; + cifs_dbg(FYI, "copychunk range\n"); if (!src_file->private_data || !dst_file->private_data) { @@ -1565,6 +1571,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, * server could even support copy of range where source = target */ lock_two_nondirectories(target_inode, src_inode); + filemap_invalidate_lock(target_inode->i_mapping); cifs_dbg(FYI, "about to flush pages\n"); @@ -1590,11 +1597,24 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, * Start at the old EOF when extending so the folio straddling it, which * may hold data written past EOF through an mmap, is dropped too. */ - rc = filemap_invalidate_inode(target_inode, true, - min(destoff, i_size_read(target_inode)), - destoff + len - 1); - if (rc) - goto unlock; + if (target_inode->i_mapping->nrpages) { + loff_t fstart = min(destoff, i_size_read(target_inode)); + loff_t fend = destoff + len - 1; + + unmap_mapping_pages(target_inode->i_mapping, + fstart >> PAGE_SHIFT, + (fend >> PAGE_SHIFT) - + (fstart >> PAGE_SHIFT) + 1, + false); + rc = filemap_write_and_wait_range(target_inode->i_mapping, + fstart, fend); + if (rc) + goto unlock; + invalidate_inode_pages2_range(target_inode->i_mapping, + fstart >> PAGE_SHIFT, + fend >> PAGE_SHIFT); + } + netfs_wait_for_outstanding_io(target_inode); fscache_invalidate(cifs_inode_cookie(target_inode), NULL, i_size_read(target_inode), 0); @@ -1626,6 +1646,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, CIFS_I(target_inode)->time = 0; unlock: + filemap_invalidate_unlock(target_inode->i_mapping); /* although unlocking in the reverse order from locking is not * strictly necessary here it is a little cleaner to be consistent */ From fafc68cccf8a00ed0a1f47c1413a3f16ce4dcdfa Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 17:15:47 -0300 Subject: [PATCH 427/535] smb: client: only require read lease for size-extending zero range smb3_zero_range() refuses any FALLOC_FL_ZERO_RANGE that clears FALLOC_FL_KEEP_SIZE with -EOPNOTSUPP whenever the inode is not read caching: /* if file not oplocked can't be sure whether asking to extend size */ rc = -EOPNOTSUPP; if (keep_size == false && !CIFS_CACHE_READ(cifsi)) goto zero_range_exit; The read lease is only needed to trust the cached i_size when deciding whether the range extends the file. When it is not held, the size can instead be fetched from the server, which is authoritative, rather than refusing the request outright: query the server's end of file and take the larger of it and the cached size for the interior-vs-extend decision. The larger of the two is used because the server's end of file reflects another client's growth while the cached size reflects this client's own writes that may not have reached the server yet; using the server size alone would wrongly shrink the file when the range flush above left an extending write unwritten. The query isn't lease-protected either, so its result is only trusted to confirm the range is interior, never to justify extending: if it still shows the range going past EOF, refuse with -EOPNOTSUPP instead of calling SMB2_set_eof(), which could otherwise shrink the file if another client extended it further between the query and the call. For the same reason, take the larger of the already-computed i_size and a fresh i_size_read(inode) right before that call, instead of relying on either alone: the local variable carries the query's result, which is never written back to the inode, while a concurrent write on this client can still extend the cached i_size during the flush, the query, or the zero-data round trip that happen in between. Rejecting interior ranges is observed as generic/363 randomly failing against Windows Server with do_zero_range: fallocate: Operation not supported fsx issues an interior, non-KEEP_SIZE zero range while the inode is transiently not read caching: the server had just downgraded the file's lease from RWH to RH after breaking the write caching, and the ensuing handle reopen/revalidation left CIFS_CACHE_READ momentarily clear. The range sat well within the server's end of file, so no extend was needed, yet the range was refused and fsx aborted. This keeps the emulation correct even when a genuine lease break from another client leaves the inode without read caching -- the case the -EOPNOTSUPP guard turned into a hard failure. The extra round trip only happens on the no-lease path; the common cached case is unchanged. Fixes: 30175628bf7f ("[SMB3] Enable fallocate -z support for SMB3 mounts") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2ops.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index a0fcb564e7a3..2d4cb54739ca 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3522,6 +3522,21 @@ static long smb3_zero_data(struct file *file, struct cifs_tcon *tcon, 0, NULL, NULL); } +static long query_server_eof(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, + unsigned long long *eof) +{ + struct smb2_file_all_info file_inf = {}; + long rc; + + rc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, + cfile->fid.volatile_fid, &file_inf); + if (!rc) + *eof = le64_to_cpu(file_inf.EndOfFile); + return rc; +} + static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, unsigned long long offset, unsigned long long len, bool keep_size) @@ -3565,10 +3580,16 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, truncate_pagecache_range(inode, min(offset, i_size), offset + len - 1); netfs_wait_for_outstanding_io(inode); - /* if file not oplocked can't be sure whether asking to extend size */ - rc = -EOPNOTSUPP; - if (keep_size == false && !CIFS_CACHE_READ(cifsi)) - goto zero_range_exit; + if (!keep_size && !CIFS_CACHE_READ(cifsi)) { + rc = query_server_eof(xid, tcon, cfile, &remote_i_size); + if (rc) + goto zero_range_exit; + i_size = max(i_size, remote_i_size); + if (i_size < new_size) { + rc = -EOPNOTSUPP; + goto zero_range_exit; + } + } fscache_invalidate(cifs_inode_cookie(inode), NULL, i_size_read(inode), 0); @@ -3580,7 +3601,7 @@ static long smb3_zero_range(struct file *file, struct cifs_tcon *tcon, /* * do we also need to change the size of the file? */ - if (keep_size == false && (unsigned long long)i_size_read(inode) < new_size) { + if (!keep_size && umax(i_size, i_size_read(inode)) < new_size) { rc = SMB2_set_eof(xid, tcon, cfile->fid.persistent_fid, cfile->fid.volatile_fid, cfile->pid, new_size); if (rc >= 0) { From 8db55c5cd831a786a1a2a2d391aa80354535a353 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 17:44:50 -0300 Subject: [PATCH 428/535] netfs: zero gaps in read-gaps folio to avoid writing back stale data netfs_read_gaps() leaves gaps around a streaming write's dirty region unread on the server side, so a short/EOF response there left stale folio content that later got written back. Zero it after the read completes, not before: netfs_wait_for_read() returns rreq->transferred once ret >= 0, so build an iterator over the same bvec array the read used, advance past ret, and zero the rest. The dirty region already maps to sink pages in that array rather than the real folio, so this can't touch it, and only the actual shortfall gets zeroed instead of the whole gap upfront. Fixes: ee4cdf7ba857 ("netfs: Speed up buffered reading") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/netfs/buffered_read.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 105194de6e13..e6506942aeda 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -541,6 +541,13 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) ret = netfs_wait_for_read(rreq); if (ret >= 0) { + if (ret < flen) { + struct iov_iter iter; + + iov_iter_bvec(&iter, ITER_DEST, bvec, i, flen); + iov_iter_advance(&iter, ret); + iov_iter_zero(flen - ret, &iter); + } if (group) folio_change_private(folio, group); else From b5d59e7270d078ab28a9c4b4d11dc3eb24671988 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 18:34:51 -0300 Subject: [PATCH 429/535] smb: client: only require read lease for size-extending preallocate smb3_simple_falloc() refuses any fallocate that clears FALLOC_FL_KEEP_SIZE with -EOPNOTSUPP whenever the inode is not read caching: /* if file not oplocked can't be sure whether asking to extend size */ if (!CIFS_CACHE_READ(cifsi)) if (!keep_size) { ... return rc; } As with smb3_zero_range(), the read lease is only needed to trust the cached size when deciding whether the request extends the file. When it is not held, the size can instead be fetched from the server, which is authoritative, rather than refusing the request outright: after flushing, query the server's end of file and take the larger of it and the cached size for the interior-vs-extend decision. The larger of the two is used because the server's end of file reflects another client's growth while the cached size reflects this client's own writes that may not have reached the server yet; using the server size alone would wrongly treat an interior request as extending when a range flush left an extending write unwritten. smb3_simple_fallocate_range(), which performs the actual interior emulation, decided whether the range already lies past EOF from its own i_size_read(inode) rather than the old_eof computed above. In the leaseless case, that is exactly the stale, undershooting size this patch works around: a genuinely interior range can read as past EOF by that stale count, which skips the FSCTL_QUERY_ALLOCATED_RANGES check entirely and overwrites already-allocated server data with zeroes instead of only filling the holes. Pass old_eof into smb3_simple_fallocate_range() and use it for that comparison instead of re-deriving a second, inconsistent one. Rejecting interior requests is observed as generic/363 randomly failing against Windows Server with do_preallocate: fallocate: Operation not supported fsx issues an interior, non-KEEP_SIZE preallocate while the inode is transiently not read caching: the server had just downgraded the file's lease from RWH to RH after breaking the write caching, and the ensuing handle reopen/revalidation left CIFS_CACHE_READ momentarily clear. The range sat within the server's end of file, so no extend was needed, yet it was refused and fsx aborted. This keeps the emulation correct even when a genuine lease break from another client leaves the inode without read caching -- the case the -EOPNOTSUPP guard turned into a hard failure. The extra flush and round trip only happen when both keep_size is false and no read lease is held; every other case is unchanged. Fixes: 9ccf3216238c ("Add support for original fallocate") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2ops.c | 46 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 2d4cb54739ca..583ab4c4d773 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -3747,7 +3747,8 @@ static int smb3_simple_fallocate_write_range(unsigned int xid, static int smb3_simple_fallocate_range(unsigned int xid, struct cifs_tcon *tcon, struct cifsFileInfo *cfile, - loff_t off, loff_t len) + loff_t off, loff_t len, + loff_t old_eof) { struct file_allocated_range_buffer in_data, *out_data = NULL, *tmp_data; struct inode *inode = d_inode(cfile->dentry); @@ -3763,7 +3764,7 @@ static int smb3_simple_fallocate_range(unsigned int xid, goto out; } - if (off >= i_size_read(inode)) { + if (off >= old_eof) { rc = smb3_simple_fallocate_write_range(xid, tcon, cfile, off, len, buf); goto out; @@ -3874,7 +3875,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, struct cifsFileInfo *cfile = file->private_data; long rc = -EOPNOTSUPP; unsigned int xid; - loff_t old_eof, new_eof; + loff_t old_eof, new_eof, local_eof; struct smb2_file_all_info file_inf; u64 asize; int qrc; @@ -3883,18 +3884,37 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, inode = d_inode(cfile->dentry); cifsi = CIFS_I(inode); - old_eof = i_size_read(inode); + old_eof = local_eof = i_size_read(inode); trace_smb3_falloc_enter(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len); - /* if file not oplocked can't be sure whether asking to extend size */ - if (!CIFS_CACHE_READ(cifsi)) - if (!keep_size) { + + if (!keep_size && !CIFS_CACHE_READ(cifsi)) { + unsigned long long server_eof; + + rc = filemap_write_and_wait(inode->i_mapping); + if (rc) { trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, tcon->tid, tcon->ses->Suid, off, len, rc); free_xid(xid); return rc; } + netfs_wait_for_outstanding_io(inode); + + rc = query_server_eof(xid, tcon, cfile, &server_eof); + if (rc) { + trace_smb3_falloc_err(xid, cfile->fid.persistent_fid, + tcon->tid, tcon->ses->Suid, off, len, rc); + free_xid(xid); + return rc; + } + /* + * Only use the larger EOF to decide whether we're extending. + * The pagecache zeroing below must still key off the local + * i_size, so keep local_eof for that. + */ + old_eof = max_t(loff_t, old_eof, server_eof); + } /* * Extending the file @@ -3918,7 +3938,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } rc = smb3_simple_fallocate_range(xid, tcon, cfile, - off, len); + off, len, old_eof); if (rc) { spin_lock(&inode->i_lock); cifsi->time = 0; @@ -3927,7 +3947,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } new_eof = off + len; - cifs_resize_file_locked(inode, old_eof, new_eof); + cifs_resize_file_locked(inode, local_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -3975,7 +3995,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, if (rc) goto out; - cifs_resize_file_locked(inode, old_eof, new_eof); + cifs_resize_file_locked(inode, local_eof, new_eof); qrc = SMB2_query_info(xid, tcon, cfile->fid.persistent_fid, @@ -4020,7 +4040,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, } } - if ((keep_size == true) || (i_size_read(inode) >= off + len)) { + if (keep_size || old_eof >= off + len) { /* * At this point, we are trying to fallocate an internal * regions of a sparse file. Since smb2 does not have a @@ -4037,7 +4057,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, */ if (len <= 1024 * 1024) { rc = smb3_simple_fallocate_range(xid, tcon, cfile, - off, len); + off, len, old_eof); goto out; } @@ -4049,7 +4069,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon, * ie potentially making a few extra pages at the beginning * or end of the file non-sparse via set_sparse is harmless. */ - if ((off > 8192) || (off + len + 8192 < i_size_read(inode))) { + if (off > 8192 || off + len + 8192 < old_eof) { rc = -EOPNOTSUPP; goto out; } From dd05add7b2b6004c5fa3a1f276f56d8668f86b65 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sat, 26 Sep 2026 19:20:30 -0300 Subject: [PATCH 430/535] netfs: zero the tail of a short DIO/unbuffered read The buffered read collector zero-fills the tail of a short read that stops below the inode's i_size (netfs_clear_unread()), so a read that races an extending write still returns the expected number of bytes. The non-buffered collector path does no such thing: it just records how much was transferred. Add the same zero-fill for the non-buffered case, gated on NETFS_SREQ_CLEAR_TAIL: a subreq's source sets that flag when a short result from it is known to be safe to treat as a hole, as opposed to NETFS_SREQ_HIT_EOF, which means the read genuinely ran off the end of the file and should be reported short as-is. Only CLEAR_TAIL should zero-fill here; a real EOF must stay a real short read. No source currently sets CLEAR_TAIL on an unbuffered/DIO subrequest, so this is inert on its own -- a following change teaches cifs to set it in the one case that needs it. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use one work item") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/netfs/read_collect.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index a94197ef0181..c5bfaf2c6f25 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -33,6 +33,22 @@ static void netfs_clear_unread(struct netfs_io_subrequest *subreq) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); } +static void netfs_clear_unread_dio(struct netfs_io_subrequest *subreq) +{ + uoff_t pos = subreq->start + subreq->transferred; + struct netfs_io_request *rreq = subreq->rreq; + size_t fill; + + if (pos >= rreq->i_size) + return; + + fill = min_t(uoff_t, rreq->i_size - pos, + subreq->len - subreq->transferred); + + netfs_reset_iter(subreq); + subreq->transferred += iov_iter_zero(fill, &subreq->io_iter); +} + /* * Cancel the copy-to-cache mark on a folio. */ @@ -311,6 +327,14 @@ static void netfs_collect_read_results(struct netfs_io_request *rreq) test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) netfs_read_unlock_folios(rreq, ¬es); } else { + if (!(notes & HIT_PENDING) && + front->error == 0 && + transferred < front->len && + test_bit(NETFS_SREQ_CLEAR_TAIL, &front->flags)) { + netfs_clear_unread_dio(front); + transferred = front->transferred; + trace_netfs_sreq(front, netfs_sreq_trace_clear); + } stream->collected_to = front->start + transferred; rreq->collected_to = stream->collected_to; } From 75aa4b4d557114276bb72e19a9bce5cb27b5e4b8 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 27 Sep 2026 18:56:55 -0300 Subject: [PATCH 431/535] smb: client: distinguish real EOF from a stale remote_i_size on read smb2_readv_callback() sets NETFS_SREQ_HIT_EOF whenever a short read lines up with netfs_read_remote_i_size(inode), the server's EOF as the client currently believes it. That belief can be stale: after a lease downgrade and handle reopen, the tracked remote_i_size can sit below the client's own i_size while an extending write hasn't reached the server yet. A read in that gap comes back short for a reason that has nothing to do with the file's real size, but was still marked HIT_EOF, and netfs reports a short read for it as-is. Only treat it as real EOF when the position is also at or past the client's own i_size; otherwise mark it NETFS_SREQ_CLEAR_TAIL instead, which tells netfs the shortfall is safe to zero-fill rather than report as a short read. This is what fsx (generic/363) sees as "short read: 0x0 bytes instead of 0x" against a Windows server. Fixes: 1da29f2c39b6 ("netfs, cifs: Fix handling of short DIO read") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/smb2pdu.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 3d7ead36d1a0..cfe3c4b4abe7 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4796,14 +4796,20 @@ smb2_readv_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid) rdata->got_bytes); if (rdata->result == -ENODATA) { - __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); rdata->result = 0; + if (rdata->subreq.start + rdata->subreq.transferred >= i_size_read(inode)) + __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); + else + __set_bit(NETFS_SREQ_CLEAR_TAIL, &rdata->subreq.flags); } else { size_t trans = rdata->subreq.transferred + rdata->got_bytes; if (trans < rdata->subreq.len && rdata->subreq.start + trans >= netfs_read_remote_i_size(inode)) { - __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); rdata->result = 0; + if (rdata->subreq.start + trans >= i_size_read(inode)) + __set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags); + else + __set_bit(NETFS_SREQ_CLEAR_TAIL, &rdata->subreq.flags); } if (rdata->got_bytes) __set_bit(NETFS_SREQ_MADE_PROGRESS, &rdata->subreq.flags); From 53c5c2c1095eb21e214811fec16cd75f89d72ee2 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 28 Sep 2026 10:23:00 -0300 Subject: [PATCH 432/535] smb: client: require stable pages for signed connections When signing, cifs computes the SMB signature over the pagecache folios in place and then hands those same folios to the socket. If a buffered write mutates a folio while a write subrequest is still in flight, the signature no longer matches the data that follows it, the server rejects the write with STATUS_ACCESS_DENIED (-EACCES), and the error is latched in the mapping, so the next fsync()/fallocate() returns -EIO. Mark the mapping for stable writes so netfs_perform_write() waits for writeback to complete before modifying an in-flight folio. This is only needed when the connection is signed. Fixes: 3ee1a1fc3981 ("cifs: Cut over to using netfslib") Reviewed-by: David Howells Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara Cc: Christian Brauner Cc: Matthew Wilcox Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 5062474991cf..f29555f3c7d0 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -88,6 +88,8 @@ static void cifs_set_ops(struct inode *inode) else inode->i_data.a_ops = &cifs_addr_ops; mapping_set_large_folios(inode->i_mapping); + if (tcon->ses->server->sign) + mapping_set_stable_writes(inode->i_mapping); break; case S_IFDIR: if (IS_AUTOMOUNT(inode)) { From c1774272c76e92fc3df10a8fedfc033716e55d1b Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 24 Aug 2026 11:55:30 +0800 Subject: [PATCH 433/535] iio: imu: inv_icm42607: propagate runtime suspend errors The runtime suspend callback returns success when updating PWR_MGMT0 fails. The PM core then marks the device suspended even though the shutdown outcome is unknown. If the write did not reach the sensor, the sensors remain running and draw power while the device is idle. The condition need not persist. If the bus error clears, a later sensor access either reads the current PWR_MGMT0 value from hardware or retries programming the requested enabled mode, allowing normal operation to resume. Return the underlying errno to the PM core. -EAGAIN and -EBUSY retain their transient-error semantics; other errors put runtime PM into an error state and cause later PM acquires to fail until the status is explicitly reset. In the normal idle case, a successful system suspend can perform that reset. This leaves the transient-versus-fatal classification to the PM core and matches the sibling ICM-42600 driver. Keep a void wrapper for the managed teardown action, where errors can only be logged. Fixes: 3007c1530f96 ("iio: imu: inv_icm42607: Add PM support for icm42607") Signed-off-by: Linmao Li Tested-by: Kanak Shilledar Tested-by: Chris Morgan Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm42607/inv_icm42607_core.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 190e998f7b8e..0da362967f63 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -537,9 +537,8 @@ static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st) return 0; } -static void inv_icm42607_sensors_off(void *_data) +static int inv_icm42607_sensors_off(struct inv_icm42607_state *st) { - struct inv_icm42607_state *st = _data; const struct device *dev = regmap_get_device(st->map); int ret; @@ -552,6 +551,13 @@ static void inv_icm42607_sensors_off(void *_data) st->conf.accel.mode); if (ret) dev_err(dev, "Unable to turn off sensors\n"); + + return ret; +} + +static void inv_icm42607_sensors_off_action(void *data) +{ + inv_icm42607_sensors_off(data); } static void inv_icm42607_disable_vddio_reg(void *_data) @@ -619,7 +625,7 @@ int inv_icm42607_core_probe(struct regmap *regmap, * Ensure if sensors get turned on at some point, they're turned off * as part of teardown. */ - ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off, st); + ret = devm_add_action_or_reset(dev, inv_icm42607_sensors_off_action, st); if (ret) return ret; @@ -688,8 +694,7 @@ static int inv_icm42607_runtime_suspend(struct device *dev) * however the tradeoff is that an unused sensor won't be * turned off until the entire chip is no longer in use. */ - inv_icm42607_sensors_off(st); - return 0; + return inv_icm42607_sensors_off(st); } EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42607_pm_ops, IIO_ICM42607) = { From 87775fff21b14cd37604530b1783f0a9db8f95a7 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 24 Aug 2026 11:55:31 +0800 Subject: [PATCH 434/535] iio: imu: inv_icm42607: restore runtime PM on system resume errors pm_runtime_force_suspend() leaves runtime PM disabled after it succeeds and expects pm_runtime_force_resume() to restore runtime PM management during system resume. The resume callback returns early if enabling the vddio regulator or synchronizing the register cache fails, skipping the matching pm_runtime_force_resume() call. Runtime PM consequently remains disabled after the system has resumed, so runtime autosuspend can no longer turn off sensors enabled afterward. Call pm_runtime_force_resume() on both error paths. Keep the first error as the return value and report a runtime PM restore failure separately. Fixes: 3007c1530f96 ("iio: imu: inv_icm42607: Add PM support for icm42607") Signed-off-by: Linmao Li Tested-by: Kanak Shilledar Tested-by: Chris Morgan Signed-off-by: Jonathan Cameron --- .../iio/imu/inv_icm42607/inv_icm42607_core.c | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 0da362967f63..f4ef75da22c7 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -664,9 +664,8 @@ static int inv_icm42607_suspend(struct device *dev) return 0; } -static int inv_icm42607_resume(struct device *dev) +static int inv_icm42607_resume_core(struct inv_icm42607_state *st) { - struct inv_icm42607_state *st = dev_get_drvdata(dev); int ret; ret = inv_icm42607_enable_vddio_reg(st); @@ -675,9 +674,25 @@ static int inv_icm42607_resume(struct device *dev) /* Sync the regcache again after regulator shutdown. */ regcache_mark_dirty(st->map); - ret = regcache_sync(st->map); - if (ret) + + return regcache_sync(st->map); +} + +static int inv_icm42607_resume(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + int ret; + + ret = inv_icm42607_resume_core(st); + if (ret) { + int rc; + + rc = pm_runtime_force_resume(dev); + if (rc) + dev_warn(dev, "Failed to restore runtime PM state: %d\n", rc); + return ret; + } return pm_runtime_force_resume(dev); } From e8dd273d199f5bbe295a8e3d8b777c5505b6b2e1 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Thu, 17 Sep 2026 14:00:52 +0100 Subject: [PATCH 435/535] iio: proximity: isl29501: Fix return type of isl29501_register_write isl29501_register_write() was returning a u32 instead of an int. Since the function returns negative error codes (such as -ERANGE or return values from i2c_smbus_write_byte_data()), returning an unsigned integer type prevents callers from correctly checking for negative error conditions. Fix this by changing the function return type from u32 to int. This was found through manual code review. Fixes: 1c28799257bc ("iio: light: isl29501: Add support for the ISL29501 ToF sensor.") Signed-off-by: Salah Triki Reviewed-by: Joshua Crofts Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/proximity/isl29501.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/proximity/isl29501.c b/drivers/iio/proximity/isl29501.c index 95fb7238f678..a98a9975323c 100644 --- a/drivers/iio/proximity/isl29501.c +++ b/drivers/iio/proximity/isl29501.c @@ -226,7 +226,7 @@ static int isl29501_register_read(struct isl29501_private *isl29501, return ret; } -static u32 isl29501_register_write(struct isl29501_private *isl29501, +static int isl29501_register_write(struct isl29501_private *isl29501, enum isl29501_register_name name, u32 value) { From 9db3deeb2de485a9d412f0825f9ab4c8e2bccf44 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 16 Sep 2026 19:18:38 +0200 Subject: [PATCH 436/535] iio: adc: stm32-adc: fix check on internal channel availability If an unsupported internal channel like vddgpu is requested, the driver prints a warning but falls through and assigns it a valid int_ch below. This causes a problem later during setup: stm32_adc_int_ch_enable() { ... case STM32_ADC_INT_CH_VDDGPU: stm32_adc_set_bits(adc, adc->cfg->regs->or_vddgpu.reg, adc->cfg->regs->or_vddgpu.mask); ... } Because the register offset is uninitialized (0), this performs a read-modify-write on offset 0, which corresponds to the ISR register. Fix this by returning before a valid int_ch is assigned. Choice is made to keep current driver behavior to warn about the channel name. Fixes: cf0fb80ae167 ("iio: adc: stm32-adc: add stm32mp13 support") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260911162602.D323F1F000FF@smtp.kernel.org/ Cc: stable@vger.kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Fabrice Gasnier Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 5c6c06b269be..b77f39f305d8 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -2263,33 +2263,37 @@ static int stm32_adc_populate_int_ch(struct iio_dev *indio_dev, const char *ch_n for (i = 0; i < STM32_ADC_INT_CH_NB; i++) { if (!strncmp(stm32_adc_ic[i].name, ch_name, STM32_ADC_CH_SZ)) { + bool na; + /* Check internal channel availability */ switch (i) { case STM32_ADC_INT_CH_VDDCORE: - if (!adc->cfg->regs->or_vddcore.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddcore.reg; break; case STM32_ADC_INT_CH_VDDCPU: - if (!adc->cfg->regs->or_vddcpu.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddcpu.reg; break; case STM32_ADC_INT_CH_VDDQ_DDR: - if (!adc->cfg->regs->or_vddq_ddr.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->or_vddq_ddr.reg; break; case STM32_ADC_INT_CH_VREFINT: - if (!adc->cfg->regs->ccr_vref.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->ccr_vref.reg; break; case STM32_ADC_INT_CH_VBAT: - if (!adc->cfg->regs->ccr_vbat.reg) - dev_warn(&indio_dev->dev, - "%s channel not available\n", ch_name); + na = !adc->cfg->regs->ccr_vbat.reg; break; + default: + return -EINVAL; + } + + if (na) { + /* + * Channel label matches an internal STM32 ADC channel. + * Warn about it, as there's normally no restriction on the + * name but that's not among available internal channels. + */ + dev_warn(&indio_dev->dev, "no %s internal channel\n", ch_name); + return 0; } if (stm32_adc_ic[i].idx != STM32_ADC_INT_CH_VREFINT) { From c5de6a6f855fdcf1e0ea549fb227a316db51c405 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Wed, 16 Sep 2026 19:15:04 +0200 Subject: [PATCH 437/535] iio: adc: stm32-adc: fix possible division by zero in processed channel In case the conversion has failed or returned zero, processing *val can lead to a division by zero. Need to check for errors, or converted value is zero, before processing the data. In case the converted value is zero, e.g. the Vrefint channel, this should be considered as invalid in all cases. Fixes: 0e346b2cfa85 ("iio: adc: stm32-adc: add vrefint calibration support") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260911161555.244F31F000FF@smtp.kernel.org/ Cc: stable@vger.kernel.org Signed-off-by: Fabrice Gasnier Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/stm32-adc.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index b77f39f305d8..90f0e257e6d1 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -1608,11 +1608,16 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev, ret = stm32_adc_single_conv(indio_dev, chan, val); else ret = -EINVAL; - - if (mask == IIO_CHAN_INFO_PROCESSED) - *val = STM32_ADC_VREFINT_VOLTAGE * adc->vrefint.vrefint_cal / *val; - iio_device_release_direct(indio_dev); + if (ret < 0) + return ret; + + if (mask == IIO_CHAN_INFO_PROCESSED) { + if (*val == 0) + return -EINVAL; + *val = STM32_ADC_VREFINT_VOLTAGE * adc->vrefint.vrefint_cal / *val; + } + return ret; case IIO_CHAN_INFO_SCALE: From 9ec6ecc95dda6d91fa6f671cd972dd4bfc05ba38 Mon Sep 17 00:00:00 2001 From: Marcelo Schmitt Date: Wed, 16 Sep 2026 14:01:34 -0300 Subject: [PATCH 438/535] iio: adc: ad7173: Fix digital filter configuration Filter enable and filter type selection masks were swapped on data preparation for filter configuration register write. Use the correct masks to set each property of post filter configuration. Fixes: ff06b39be1a1 ("iio: adc: ad7173: support changing filter type") Signed-off-by: Marcelo Schmitt Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7173.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index eb47175a0528..faebb7b34a15 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -775,9 +775,9 @@ static int ad7173_load_config(struct ad7173_state *st, return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | - FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, - post_filter_enable) | FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, post_filter_select) | FIELD_PREP(AD7173_FILTER_ORDER, 0) | FIELD_PREP(AD7173_FILTER_ODR_MASK, From d615210564205993e8f0e7ccb57cc2e66b7d5706 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Thu, 3 Sep 2026 12:43:09 +0100 Subject: [PATCH 439/535] iio: adc: ad4030: fix invalid oversampling_ratio validation In ad4030_set_avg_frame_len(), the logarithm is calculated before input validation. Passing zero or negative values leads to an undefined result from ilog2(). Validate that the input is strictly positive prior to computing its logarithm. Fixes: 949abd1ca5a4 ("iio: adc: ad4030: add averaging support") Assisted-by: LLM Signed-off-by: Salah Triki Reviewed-by: Andy Shevchenko Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4030.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c index e97400a1aa48..877b06007396 100644 --- a/drivers/iio/adc/ad4030.c +++ b/drivers/iio/adc/ad4030.c @@ -746,14 +746,21 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev, static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val) { struct ad4030_state *st = iio_priv(dev); - unsigned int avg_log2 = ilog2(avg_val); unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1; + unsigned int avg_log2; int freq_hz; int ret; - if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx]) + /* Reject unsupported modes */ + if (avg_val > ad4030_average_modes[last_avg_idx]) return -EINVAL; + /* Avoid invalid values for logarithm since it's undefined */ + if (avg_val < 1) + return -EINVAL; + + avg_log2 = ilog2(avg_val); + if (st->offload_trigger) { /* * The sample averaging and sampling frequency configurations From 49ee1c6a3ebda6b7de06b2961b0e09c1c3fe536a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Wed, 26 Aug 2026 16:31:56 +0800 Subject: [PATCH 440/535] iio: adc: ade9000: fix NULL pointer dereference in clkout registration ade9000_setup_clkout() passes NULL as the register address when registering a divider clock. During clock registration, the common clock framework calls clk_divider_recalc_rate(), which dereferences the address through readl(). As a result, probing an ADE9000 configured as a clock provider with an external input clock crashes. CLKOUT passes CLKIN through without changing its rate. Register it as a 1:1 fixed-factor clock, which does not require register access. This change does not affect the configuration using the internal clock, for which the driver does not register a clock provider. Fixes: 81de7b4619fc ("iio: adc: add ade9000 support") Cc: stable@vger.kernel.org Signed-off-by: Linmao Li Reviewed-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index da6caabfe2a4..4fc0eb7e70a8 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -1647,8 +1647,9 @@ static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) return 0; /* CLKOUT passes through CLKIN with divider of 1 */ - clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin), - CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL); + clkout_hw = devm_clk_hw_register_fixed_factor(dev, "clkout", + __clk_get_name(st->clkin), + CLK_SET_RATE_PARENT, 1, 1); if (IS_ERR(clkout_hw)) return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout"); From 52da294027f53e7084c18dd4b4f73354a40382f3 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 20 Sep 2026 11:33:03 +0800 Subject: [PATCH 441/535] iio: cdc: ad7150: fix OF matching and publish module aliases The OF match entries use positional initializers, which initialize the name field rather than compatible. Consequently the advertised AD7150, AD7151 and AD7156 compatible strings do not describe OF compatible matches. The table is also not exported for module alias generation. Use designated compatible initializers and publish the OF table. Keep the existing I2C ID table and its device-variant selection unchanged. The issue was found by our static-analysis tool. Fixes: 89f2d5b080bc ("staging:iio:cdc:ad7150: Add of_match_table") Assisted-by: LLM Cc: stable@vger.kernel.org Signed-off-by: Pengpeng Hou Signed-off-by: Jonathan Cameron --- drivers/iio/cdc/ad7150.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/iio/cdc/ad7150.c b/drivers/iio/cdc/ad7150.c index 2f35c6d2f9ce..b36ac4e2dae8 100644 --- a/drivers/iio/cdc/ad7150.c +++ b/drivers/iio/cdc/ad7150.c @@ -636,11 +636,13 @@ static const struct i2c_device_id ad7150_id[] = { MODULE_DEVICE_TABLE(i2c, ad7150_id); static const struct of_device_id ad7150_of_match[] = { - { "adi,ad7150" }, - { "adi,ad7151" }, - { "adi,ad7156" }, + { .compatible = "adi,ad7150" }, + { .compatible = "adi,ad7151" }, + { .compatible = "adi,ad7156" }, { } }; +MODULE_DEVICE_TABLE(of, ad7150_of_match); + static struct i2c_driver ad7150_driver = { .driver = { .name = "ad7150", From 84bb0fc46ccf6a545b8fdf57cda83770186ed8dd Mon Sep 17 00:00:00 2001 From: Jinseob Kim Date: Mon, 21 Sep 2026 15:24:21 +0900 Subject: [PATCH 442/535] iio: buffer: serialize buffer teardown with mode claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Buffer-mode claims hold mlock to guarantee that the device remains in buffer mode until the claim is released. Normal buffer updates take info_exist_lock followed by mlock in iio_update_buffers(). However, iio_device_unregister() disables and deactivates all buffers without taking mlock. This can invalidate buffer state, including active_scan_mask, while a buffer-mode claim is held. Take mlock in iio_disable_all_buffers() so that unregister honors the mode-claim lifetime guarantee. The info_exist_lock -> mlock ordering matches iio_update_buffers(). Fixes: 0a8565425afd ("iio: core: introduce iio_device_{claim|release}_buffer_mode() APIs") Suggested-by: Jonathan Cameron Signed-off-by: Jinseob Kim Reviewed-by: Joshua Crofts Reviewed-by: Nuno Sá Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 902401be0b67..4294101810fd 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -1377,6 +1377,10 @@ EXPORT_SYMBOL_GPL(iio_update_buffers); void iio_disable_all_buffers(struct iio_dev *indio_dev) { + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + + guard(mutex)(&iio_dev_opaque->mlock); + iio_disable_buffers(indio_dev); iio_buffer_deactivate_all(indio_dev); } From dd30c10ff60d2b30386c23bb7c61cc97f2385c97 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 22:09:43 +0800 Subject: [PATCH 443/535] iio: accel: kxcjk-1013: reject duplicate event disable The IIO core does not filter duplicate writes to the event enable attribute. kxcjk1013_write_event_config() already ignores repeated enable requests, but a repeated disable request still calls kxcjk1013_set_power_state(data, false), dropping a runtime PM reference that was not acquired for this request. This can underflow the runtime PM usage count and trigger a "Runtime PM usage count underflow" warning. Return early when the requested state already matches ev_enable_state. Fixes: b4b491c0832e ("iio: accel: kxcjk-1013: Support thresholds") Signed-off-by: Jiale Yao Cc: stable@vger.kernel.org Signed-off-by: Jonathan Cameron --- drivers/iio/accel/kxcjk-1013.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 166fb786425f..8994c9e8e048 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -1030,7 +1030,7 @@ static int kxcjk1013_write_event_config(struct iio_dev *indio_dev, struct kxcjk1013_data *data = iio_priv(indio_dev); int ret; - if (state && data->ev_enable_state) + if (state == data->ev_enable_state) return 0; mutex_lock(&data->mutex); From 9ee8306121495d2a25aa5d1bfd519f2748786b83 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 23 Sep 2026 09:48:07 +0000 Subject: [PATCH 444/535] iio: adc: ad_sigma_delta: fix use-after-free on unbind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ad_sd_buffer_postenable() allocates sigma_delta->samples_buf with devm_krealloc() at runtime, so its devres entry sits after all probe-time entries of the driver. devm resources are released in reverse allocation order, which means unbind frees samples_buf before iio_device_unregister() disables the buffers and detaches the trigger pollfunc. The data ready IRQ is still enabled at that point, so ad_sd_trigger_handler() can still run and memcpy() incoming samples into the freed samples_buf. Fix this by preallocating the buffer in devm_ad_sd_setup_buffer_and_trigger(), before the triggered buffer and the IRQ are set up, so it is freed only after iio_device_unregister() has drained the trigger handler via free_irq(). Size it for the worst case of all sequencer slots being active; ad_sd_validate_scan_mask() already caps the number of active channels at num_slots. This issue was found by an in-house static analysis tool. Fixes: 8bea9af887de ("iio: adc: ad_sigma_delta: Add sequencer support") Cc: stable@vger.kernel.org Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad_sigma_delta.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c index 1b410291da53..119ddc5a13e2 100644 --- a/drivers/iio/adc/ad_sigma_delta.c +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -498,7 +498,6 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) const struct iio_scan_type *scan_type = &indio_dev->channels[0].scan_type; struct spi_transfer *xfer = sigma_delta->sample_xfer; unsigned int i, slot, channel; - u8 *samples_buf; int ret; if (sigma_delta->num_slots == 1) { @@ -530,7 +529,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) xfer[1].bits_per_word = scan_type->realbits; xfer[1].len = spi_bpw_to_bytes(scan_type->realbits); } else { - unsigned int samples_buf_size, scan_size; + unsigned int scan_size; if (sigma_delta->active_slots > 1) { ret = ad_sigma_delta_append_status(sigma_delta, true); @@ -538,17 +537,6 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) return ret; } - samples_buf_size = - ALIGN(slot * BITS_TO_BYTES(scan_type->storagebits), - sizeof(s64)); - samples_buf_size += sizeof(s64); - samples_buf = devm_krealloc(&sigma_delta->spi->dev, - sigma_delta->samples_buf, - samples_buf_size, GFP_KERNEL); - if (!samples_buf) - return -ENOMEM; - - sigma_delta->samples_buf = samples_buf; scan_size = BITS_TO_BYTES(scan_type->realbits + scan_type->shift); /* For 24-bit data, there is an extra byte of padding. */ xfer[1].rx_buf = &sigma_delta->rx_buf[scan_size == 3 ? 1 : 0]; @@ -855,6 +843,23 @@ int devm_ad_sd_setup_buffer_and_trigger(struct device *dev, struct iio_dev *indi indio_dev->setup_ops = &ad_sd_buffer_setup_ops; } else { + const struct iio_scan_type *scan_type = + &indio_dev->channels[0].scan_type; + unsigned int samples_buf_size; + + /* + * Worst-case size: all sequencer slots can be active, capped + * at num_slots by ad_sd_validate_scan_mask(). + */ + samples_buf_size = + ALIGN(sigma_delta->num_slots * + BITS_TO_BYTES(scan_type->storagebits), + sizeof(s64)); + samples_buf_size += sizeof(s64); + sigma_delta->samples_buf = devm_kzalloc(dev, samples_buf_size, GFP_KERNEL); + if (!sigma_delta->samples_buf) + return -ENOMEM; + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, &ad_sd_trigger_handler, From 77f21a59e8cf7efcc1c4b3e9176e8b99993a7ae4 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Wed, 23 Sep 2026 21:13:50 -0400 Subject: [PATCH 445/535] of: Put coreboot node after compatibility check of_find_compatible_node() returns a referenced device node, but EXCLUDED_DEFAULT_CELLS_PLATFORMS used the result only as a boolean and discarded the reference. This leaked a reference each time the address-cells or size-cells warning condition was evaluated on a coreboot system. Use a helper that checks for the coreboot node with a scoped reference, so the reference is dropped once the check is done. Fixes: 8600058ba28a7 ("of: Add coreboot firmware to excluded default cells list") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260924011358.734722-1-oss.patchbox@gmail.com Signed-off-by: Rob Herring (Arm) --- drivers/of/base.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/of/base.c b/drivers/of/base.c index 378703dbc11f..57313055d9ed 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -95,9 +95,17 @@ static bool __of_node_is_type(const struct device_node *np, const char *type) return !strcmp(match, type); } +static bool of_coreboot_present(void) +{ + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, "coreboot"); + + return np; +} + #define EXCLUDED_DEFAULT_CELLS_PLATFORMS ( \ IS_ENABLED(CONFIG_SPARC) || \ - of_find_compatible_node(NULL, NULL, "coreboot") \ + of_coreboot_present() \ ) int of_bus_n_addr_cells(struct device_node *np) From 30724547b221e0e670ddfef140fc7d816b2aaec6 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Thu, 17 Sep 2026 12:42:47 +0000 Subject: [PATCH 446/535] of/irq: Fix remaining refcount leaks in of_irq_init() Successfully initialized controllers are moved to intc_parent_list, but the final cleanup only frees the descriptors. The device node references taken with of_node_get() and the interrupt parent references are therefore leaked on the normal return path. Drop both before kfree(), as the intc_desc_list cleanup already does. Fixes: 8363ccb917c6 ("of/irq: add missing of_node_put") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20260917124247.2151674-1-vulab@iscas.ac.cn Signed-off-by: Rob Herring (Arm) --- drivers/of/irq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/of/irq.c b/drivers/of/irq.c index ec035367c950..ef1ed9743907 100644 --- a/drivers/of/irq.c +++ b/drivers/of/irq.c @@ -754,6 +754,8 @@ void __init of_irq_init(const struct of_device_id *matches) list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); + of_node_put(desc->interrupt_parent); + of_node_put(desc->dev); kfree(desc); } err: From 5642b1648d5c4561d8ed7a410d0a7017f706604d Mon Sep 17 00:00:00 2001 From: Quentin Freimanis Date: Sun, 27 Sep 2026 21:50:12 -0700 Subject: [PATCH 447/535] net: sparx5: make ports inherit the switch base mac address type When the switch uses a random base MAC address it does not mark each port's address as random: $ dmesg | grep "MAC addr" sparx5-switch e00c0000.switch: MAC addr was not set, use random MAC $ cat /sys/class/net/eth0/addr_assign_type 0 0 is NET_ADDR_PERM, so userspace thinks this is a stable address. With a random MAC reported as NET_ADDR_PERM, systemd's MACAddressPolicy=persistent [1] leaves it unchanged, so a DHCP client gets a new IP address every boot and a static reservation can't be used. Reporting it as NET_ADDR_RANDOM makes systemd replace it with a stable address derived from the interface name and machine-id. Fixes: f3cad2611a77 ("net: sparx5: add hostmode with phylink support") Signed-off-by: Quentin Freimanis Reviewed-by: Simon Horman Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20260928045012.30178-1-quentin@q-lab.dev Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_main.c | 1 + drivers/net/ethernet/microchip/sparx5/sparx5_main.h | 1 + drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c index dad713e9ddd5..b530fb6d3d0c 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c @@ -898,6 +898,7 @@ static int mchp_sparx5_probe(struct platform_device *pdev) dev_info(sparx5->dev, "MAC addr was not set, use random MAC\n"); eth_random_addr(sparx5->base_mac); sparx5->base_mac[5] = 0; + sparx5->base_mac_assign_type = NET_ADDR_RANDOM; } sparx5->fdma_irq = platform_get_irq_byname(sparx5->pdev, "fdma"); diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h index eb57b86fbe22..0f7d8440f614 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h @@ -392,6 +392,7 @@ struct sparx5 { struct notifier_block switchdev_blocking_nb; /* Switch state */ u8 base_mac[ETH_ALEN]; + u8 base_mac_assign_type; /* Associated bridge device (when bridged) */ struct net_device *hw_bridge_dev; /* Bridged interfaces */ diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c index 4dedf5d92641..b3725cf2dd44 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_netdev.c @@ -294,6 +294,7 @@ struct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno) ndev->ethtool_ops = &sparx5_ethtool_ops; eth_hw_addr_gen(ndev, sparx5->base_mac, portno + 1); + ndev->addr_assign_type = sparx5->base_mac_assign_type; return ndev; } From 4f1da630d13de0370e2f6361f961f1c8b384af1c Mon Sep 17 00:00:00 2001 From: Jun Yang Date: Sat, 26 Sep 2026 17:55:15 +0800 Subject: [PATCH 448/535] sctp: check RCV_SHUTDOWN after the sendmsg connect wait sctp_wait_for_connect() drops the socket lock while it sleeps. An out-of-the-blue ABORT can then be processed from the socket backlog and unlink the association. If a concurrent shutdown(fd, SHUT_RD) sets RCV_SHUTDOWN, the waiter breaks with err == 0 before checking asoc->base.dead. Its final sctp_association_put() can then free the association, leaving sctp_sendmsg_to_asoc() to continue with a dangling pointer. Check RCV_SHUTDOWN along with the wait error in sctp_sendmsg_to_asoc() before using the association again. The check only accesses the socket, so it needs no additional association reference. Return the existing -ESRCH so that sctp_sendmsg() skips freeing a new association that may already have been destroyed. Keep sctp_wait_for_connect() unchanged to preserve its behavior for the connect() caller. Fixes: 668c9beb9020 ("sctp: implement assign_number for sctp_stream_interleave") Cc: stable@vger.kernel.org Reported-by: TencentOS Corvus AI Signed-off-by: Jun Yang Acked-by: Xin Long Link: https://patch.msgid.link/20260926095606.68601-1-juny24602@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index c7b9e325ec1c..4652fd90d9a6 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -1844,7 +1844,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc, if (asoc->ep->intl_enable) { timeo = sock_sndtimeo(sk, 0); err = sctp_wait_for_connect(asoc, &timeo); - if (err) { + if (err || sk->sk_shutdown & RCV_SHUTDOWN) { err = -ESRCH; goto err; } From 9e792901418d814295674d1f53d6b615fb5c15c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jari=20Sepp=C3=A4l=C3=A4?= Date: Mon, 28 Sep 2026 11:47:11 +0300 Subject: [PATCH 449/535] octeontx2-pf: Fix RSS indirection table size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conversion to the dedicated RSS context operations replaced the pointer to struct otx2_rss_ctx with an inline u32 ind_tbl[] array. However, otx2_rss_init() still uses sizeof(*rss->ind_tbl) to set rss_size. This now yields the size of one u32 (4), rather than the 256 entries in the indirection table. The RSS initialization and hardware programming loops use rss_size, so only four entries are initialized and programmed despite the NIX LF being allocated a 256-entry table. On an OCTEON CN102 with eight RX queues, the table contained 0, 1, 2, 3 followed by zeros. Traffic was concentrated on queue 0 and ethtool -X equal 8 did not correct the table. Use ARRAY_SIZE() to count the indirection table entries. On the same hardware, this restores the full table repeating queue numbers 0 through 7, and bidirectional multi-flow traffic increments multiple RX queues. Runtime testing on an Asterfusion ET2500 (OCTEON CN102 A0). Fixes: 62e01d8c4170 ("eth: otx2: migrate to the *_rxfh_context ops") Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/netdev/361df563-e29e-43bf-8806-24bdfefed33d@nocloud.org/ Signed-off-by: Jari Seppälä Reviewed-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260928084711.2462571-1-jase@nocloud.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 0f3f405d9f1b..b11389a841d1 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -394,7 +394,7 @@ int otx2_rss_init(struct otx2_nic *pfvf) struct otx2_rss_info *rss = &pfvf->hw.rss_info; int idx, ret = 0; - rss->rss_size = sizeof(*rss->ind_tbl); + rss->rss_size = ARRAY_SIZE(rss->ind_tbl); /* Init RSS key if it is not setup already */ if (!rss->enable) From 3cdeaef1754ab8155cdd828c958ada3d36e9ad9a Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Fri, 25 Sep 2026 09:10:40 +0200 Subject: [PATCH 450/535] r8169: disable EEE on RTL8168h/8111h Force EEE off on the RTL8168h/8111h (RTL_GIGA_MAC_VER_46) at PHY connect with phy_disable_eee(). Commit 202fef9bbbf5 ("net: phy: realtek: fix EEE advertisement write on the internal PHY MMD path") made EEE actually advertise on the generic Realtek PHY; the write had been a no-op before. On the RTL8168h that un-masks a latent defect: once EEE negotiates, RX silently stalls after ~7-20 minutes. The carrier stays up, no counter or dmesg moves, and only "ip link set down/up" recovers it; disabling EEE keeps the link stable. Root-causing the RTL8168h LPI/RX path needs hardware not available now, so disable EEE for this version. Use phy_disable_eee() rather than dropping the version from rtl_supports_eee(): the latter also skips rtl_enable_tx_lpi(), whose disable branch clears the MAC TX-LPI bits (ERI 0x1b0[1:0]) on link up. rtl_hw_start_8168h_1() does not clear them (the RTL8402/RTL8106e init does), so a warm reboot from an EEE-active state could otherwise leave TX-LPI asserted while the PHY no longer negotiates EEE. Keeping the version EEE-capable preserves that clear path. This also covers the RTL8168M, which shares RTL_GIGA_MAC_VER_46. Reported-by: Dennis Piecha Closes: https://lore.kernel.org/all/353419280.954808.1790290283530@mail.yahoo.com/ Fixes: 202fef9bbbf5 ("net: phy: realtek: fix EEE advertisement write on the internal PHY MMD path") Cc: stable@vger.kernel.org Signed-off-by: Oleksij Rempel Link: https://patch.msgid.link/20260925071040.2137178-1-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/realtek/r8169_main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c index ec4fc21fa21f..ee1bc9bd0447 100644 --- a/drivers/net/ethernet/realtek/r8169_main.c +++ b/drivers/net/ethernet/realtek/r8169_main.c @@ -5484,6 +5484,12 @@ static int r8169_mdio_register(struct rtl8169_private *tp) phy_disable_eee_mode(tp->phydev, ETHTOOL_LINK_MODE_2500baseT_Full_BIT); + /* Keep EEE off but the version stays EEE-capable so that link-up + * still clears the MAC TX-LPI bits. + */ + if (tp->mac_version == RTL_GIGA_MAC_VER_46) + phy_disable_eee(tp->phydev); + /* PHY will be woken up in rtl_open() */ phy_suspend(tp->phydev); From 7b97273cbebe9dd4fddc457f0c4f2a124d0e3996 Mon Sep 17 00:00:00 2001 From: Prathamesh Deshpande Date: Sat, 26 Sep 2026 17:53:57 +0100 Subject: [PATCH 451/535] net/mlx5e: Fix AF_XDP TX timestamp teardown NULL dereference During XSK TX queue teardown, outstanding descriptors are completed without a CQE. If one requested a TX timestamp, the completion path passes that NULL CQE to mlx5e_xsk_fill_timestamp(), which dereferences it. Return zero when no CQE is available, indicating that teardown did not produce a TX timestamp. Fixes: ec706a860eba ("net/mlx5e: Implement AF_XDP TX timestamp and checksum offload") Signed-off-by: Prathamesh Deshpande Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260926165402.5902-1-prathameshdeshpande7@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c index 77ea51bfbaae..2cca20e1b9b7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/xdp.c @@ -293,6 +293,9 @@ static u64 mlx5e_xsk_fill_timestamp(void *_priv) struct mlx5e_xsk_tx_complete *priv = _priv; u64 ts; + if (!priv->cqe) + return 0; + ts = get_cqe_ts(priv->cqe); if (mlx5_is_real_time_rq(priv->cq->mdev) || mlx5_is_real_time_sq(priv->cq->mdev)) From 8ec454dcd00ce3375119111c6394964455b36d0d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 28 Sep 2026 19:45:24 +0000 Subject: [PATCH 452/535] ipv6: sr: use skb_get_hash_net() in seg6_make_flowlabel() Since commit d58e468b1112 ("flow_dissector: implements flow dissector BPF hook") __skb_flow_dissect() needs a net pointer, either from skb->dev, skb->sk, or since commit 3cbf4ffba5ee ("net: plumb network namespace into __skb_flow_dissect") a caller provided pointer. syzbot was able to reach seg6_make_flowlabel() with an skb having neither skb->dev nor skb->sk set: a TIPC UDP bearer sends a discovery message through an IPv4 route using seg6 encap, while net.ipv6.seg6_flowlabel is set to 1. seg6_make_flowlabel() already has a net pointer, use skb_get_hash_net(). WARNING: net/core/flow_dissector.c:1131 at __skb_flow_dissect+0x910/0x5368 net/core/flow_dissector.c:1126, CPU#0: syz.0.17/4930 Call trace: __skb_flow_dissect+0x910/0x5368 net/core/flow_dissector.c:1126 (P) __skb_get_hash_net+0xe0/0x29c net/core/flow_dissector.c:1903 skb_get_hash include/linux/skbuff.h:1663 [inline] seg6_make_flowlabel+0xcc/0x1ec net/ipv6/seg6_iptunnel.c:132 __seg6_do_srh_encap+0x320/0xbf4 net/ipv6/seg6_iptunnel.c:161 seg6_do_srh+0x4b4/0xa44 net/ipv6/seg6_iptunnel.c:431 seg6_output_core+0x164/0x688 net/ipv6/seg6_iptunnel.c:681 seg6_output+0x44/0x1ac net/ipv6/seg6_iptunnel.c:744 lwtunnel_output+0x3d0/0x664 net/core/lwtunnel.c:356 dst_output include/net/dst.h:470 [inline] ip_local_out+0x110/0x148 net/ipv4/ip_output.c:131 iptunnel_xmit+0x50c/0xd38 net/ipv4/ip_tunnel_core.c:97 udp_tunnel_xmit_skb+0x220/0x348 net/ipv4/udp_tunnel_core.c:187 tipc_udp_xmit+0x75c/0x9c0 net/tipc/udp_media.c:202 tipc_udp_send_msg+0x214/0x374 net/tipc/udp_media.c:274 tipc_bearer_xmit_skb+0x260/0x3b0 net/tipc/bearer.c:576 tipc_enable_bearer net/tipc/bearer.c:366 [inline] __tipc_nl_bearer_enable+0xc90/0xfb0 net/tipc/bearer.c:1048 tipc_nl_bearer_enable+0x2c/0x48 net/tipc/bearer.c:1057 genl_family_rcv_msg_doit+0x1e4/0x2d4 net/netlink/genetlink.c:1114 Fixes: d58e468b1112 ("flow_dissector: implements flow dissector BPF hook") Reported-by: syzbot+9408fbe0e6452a12e9ab@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6abac2eb.3654fce1.1bec97.0000.GAE@google.com/ Signed-off-by: Eric Dumazet Reviewed-by: Xuanqiang Luo Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260928194524.3617299-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv6/seg6_iptunnel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 61c6a27bf202..7cbe0685f8bb 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -129,7 +129,7 @@ static __be32 seg6_make_flowlabel(struct net *net, struct sk_buff *skb, u32 hash; if (do_flowlabel > 0) { - hash = skb_get_hash(skb); + hash = skb_get_hash_net(net, skb); hash = rol32(hash, 16); flowlabel = (__force __be32)hash & IPV6_FLOWLABEL_MASK; } else if (!do_flowlabel && skb->protocol == htons(ETH_P_IPV6)) { From 8f1c2a10500a84a621ff13073f96deea0753ed42 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 29 Sep 2026 15:43:03 +0200 Subject: [PATCH 453/535] net: mvneta: clear XDP pfmemalloc flag between frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mvneta_swbm_add_rx_fragment() sets XDP_FLAGS_FRAGS_PF_MEMALLOC on the xdp_buff when a fragment page is a pfmemalloc one (page under memory pressure). The xdp_buff is reused for the next frame, but only the XDP_FLAGS_HAS_FRAGS bit was cleared at frame start, so the pfmemalloc bit leaked from one frame into the following ones. mvneta_swbm_build_skb() propagates the flag to skb->pfmemalloc through xdp_update_skb_frags_info(), so the skb of a subsequent fragmented frame could be wrongly marked as pfmemalloc even if none of its pages are under pressure. Clear all the xdp_buff flags in mvneta_swbm_rx_frame(), which is invoked for each new frame, instead of just the XDP_FLAGS_HAS_FRAGS bit. Fixes: ed7a58cb40bd ("net: marvell: rely on xdp_update_skb_shared_info utility routine") Reviewed-by: Simon Horman Signed-off-by: Lorenzo Bianconi Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260929-mvneta-xdp-clear-frag-fix-v4-1-1e63b25eeed8@oss.qualcomm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/mvneta.c | 2 +- include/net/xdp.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 543e566425c1..4f479b6f731e 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -2340,7 +2340,7 @@ mvneta_swbm_rx_frame(struct mvneta_port *pp, /* Prefetch header */ prefetch(data); - xdp_buff_clear_frags_flag(xdp); + xdp_reinit_buff(xdp); xdp_prepare_buff(xdp, data, pp->rx_offset_correction + MVNETA_MH_SIZE, data_len, true); } diff --git a/include/net/xdp.h b/include/net/xdp.h index aa742f413c35..07231adfb5f8 100644 --- a/include/net/xdp.h +++ b/include/net/xdp.h @@ -106,6 +106,11 @@ struct xdp_buff { }; }; +static __always_inline void xdp_reinit_buff(struct xdp_buff *xdp) +{ + xdp->flags = 0; +} + static __always_inline bool xdp_buff_has_frags(const struct xdp_buff *xdp) { return !!(xdp->flags & XDP_FLAGS_HAS_FRAGS); From 7375d38364a9aa66fb31716bcefef38aecad75d8 Mon Sep 17 00:00:00 2001 From: Reinhard Speyerer Date: Fri, 25 Sep 2026 15:14:32 +0200 Subject: [PATCH 454/535] net: usb: qmi_wwan: add Rolling Wireless RN947R Add Rolling Wireless RN947R 0x9300 composition: DIAG + ADB + NMEA + MODEM + RMNET + QDSS + ADPL T: Bus=01 Lev=01 Prnt=01 Port=02 Cnt=02 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=33f8 ProdID=9300 Rev= 0.00 S: Manufacturer=Rolling Wireless S: Product=RN947R S: SerialNumber=xxxxxxxxxxx C:* #Ifs= 7 Cfg#= 1 Atr=a0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=usbfs E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=84(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=86(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 8 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan E: Ad=88(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#=21 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=70 Driver=(none) E: Ad=89(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#=22 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=8a(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Signed-off-by: Reinhard Speyerer Link: https://patch.msgid.link/arZzuOOvLUZtdOlv@arcor.de Signed-off-by: Jakub Kicinski --- drivers/net/usb/qmi_wwan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index 0e2ab567dd40..a44d1b9756a1 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1455,6 +1455,7 @@ static const struct usb_device_id products[] = { {QMI_QUIRK_SET_DTR(0x1546, 0x1312, 4)}, /* u-blox LARA-R6 01B */ {QMI_QUIRK_SET_DTR(0x1546, 0x1342, 4)}, /* u-blox LARA-L6 */ {QMI_QUIRK_SET_DTR(0x33f8, 0x0104, 4)}, /* Rolling RW101 RMNET */ + {QMI_QUIRK_SET_DTR(0x33f8, 0x9300, 8)}, /* Rolling RN947R RMNET */ {QMI_FIXED_INTF(0x2dee, 0x4d22, 5)}, /* MeiG Smart SRM825L */ {QMI_QUIRK_SET_DTR(0x2dee, 0x4d63, 0)}, /* MeiG SRM813Q w/ Modem(PPP) */ {QMI_QUIRK_SET_DTR(0x2dee, 0x4d64, 0)}, /* MeiG SRM813Q w/ NMEA */ From 19465a9aeb664f1d710d0d22c07c31bfb7c85446 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 30 Sep 2026 15:47:15 -0500 Subject: [PATCH 455/535] smb: client: split cifsFileInfo bitfields to avoid shared-byte RMW races The invalidHandle, swapfile, oplock_break_cancelled, offload, and status_file_deleted fields are stored in the same bitfield byte in struct cifsFileInfo, but are updated in different code paths that may run simultaneously, and are protected by different locks. Since bitfield assignments generate byte-level read-modify-write operations, a modification to one flag can overwrite a concurrent modification to another flag. To avoid these races, convert these flags from a bitfield to separate bool fields. Closes: https://lore.kernel.org/r/7689764e-c0f6-4016-9557-b54cf4a3de4e@redhat.com Fixes: 3bc303c254335 ("cifs: convert oplock breaks to use slow_work facility (try #4)") Fixes: 4e8aea30f7751 ("smb3: enable swap on SMB3 mounts") Fixes: ffceb7640cbfe ("smb: client: do not defer close open handles to deleted files") Fixes: 173217bd73365 ("smb3: retrying on failed server close") Signed-off-by: Frank Sorenson Cc: stable@vger.kernel.org Reviewed-by: Namjae Jeon Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsglob.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 79e4e84f8985..5c5b76a9e9fe 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1450,11 +1450,11 @@ struct cifsFileInfo { struct dentry *dentry; struct tcon_link *tlink; unsigned int f_flags; - bool invalidHandle:1; /* file closed via session abend */ - bool swapfile:1; - bool oplock_break_cancelled:1; - bool status_file_deleted:1; /* file has been deleted */ - bool offload:1; /* offload final part of _put to a wq */ + bool invalidHandle; /* file closed via session abend */ + bool swapfile; + bool oplock_break_cancelled; + bool status_file_deleted; /* file has been deleted */ + bool offload; /* offload final part of _put to a wq */ __u16 oplock_epoch; /* epoch from the lease break */ __u32 oplock_level; /* oplock/lease level from the lease break */ int count; From 20388c8d1d74c203fec8194c45cd31afdfab934e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 8 Sep 2026 04:17:39 +0000 Subject: [PATCH 456/535] usb: gadget: aspeed-vhub: cancel wake work on device removal wake_work is armed from the gadget .wakeup callback to resume suspended downstream ports, and it is never cancelled in ast_vhub_remove(), so a work item queued before or during removal can run after devm has freed vhub and its ports, a use-after-free. Cancelling the work alone is not sufficient: usb_gadget_wakeup() takes no lock and the unbind path never clears wakeup_en, so a remote-wakeup request in flight on another CPU can re-arm the work after the cancel and before vhub is freed. ast_vhub_del_dev() already clears d->registered under vhub->lock before unregistering the gadget. Refuse the wakeup in ast_vhub_udc_wakeup() once d->registered is clear: the check and the schedule_work() are then atomic against the unbind, so a wakeup that passed before the flag was cleared is drained by the cancel_work_sync() after the del_dev loop, and one that arrives later returns without arming. Also move INIT_WORK() to the top of ast_vhub_probe(), since the probe error path reuses ast_vhub_remove() and would otherwise cancel a never-initialized work item. This issue was found by an in-house static analysis tool. Fixes: 7ecca2a4080c ("usb/gadget: Add driver for Aspeed SoC virtual hub") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Benjamin Herrenschmidt Link: https://patch.msgid.link/20260908041740.613713-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/core.c | 4 ++++ drivers/usb/gadget/udc/aspeed-vhub/dev.c | 2 +- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 5 +++-- drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 4a8b9ff8368f..069673f0d359 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) for (i = 0; i < vhub->max_ports; i++) ast_vhub_del_dev(&vhub->ports[i].dev); + /* Final drain; the worker takes vhub->lock, so stay outside of it */ + cancel_work_sync(&vhub->wake_work); + spin_lock_irqsave(&vhub->lock, flags); /* Mask & ack all interrupts */ @@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) return -ENOMEM; spin_lock_init(&vhub->lock); + INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); vhub->pdev = pdev; vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, VHUB_IRQ_DEV1_BIT); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 8b9449d16324..4b389de0d935 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) int rc = -EINVAL; spin_lock_irqsave(&d->vhub->lock, flags); - if (!d->wakeup_en) + if (!d->wakeup_en || !d->registered) goto err; DDBG(d, "Device initiated wakeup\n"); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 02fe1a08d575..d0345f31037a 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, ast_vhub_send_host_wakeup(vhub); } -static void ast_vhub_wake_work(struct work_struct *work) +void ast_vhub_wake_work(struct work_struct *work) { struct ast_vhub *vhub = container_of(work, struct ast_vhub, @@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) void ast_vhub_hub_wake_all(struct ast_vhub *vhub) { + lockdep_assert_held(&vhub->lock); + /* * A device is trying to wake the world, because this * can recurse into the device, we break the call chain @@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) int ast_vhub_init_hub(struct ast_vhub *vhub) { vhub->speed = USB_SPEED_UNKNOWN; - INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); return ast_vhub_init_desc(vhub); } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index aca2050e2db0..96f1c3709042 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); void ast_vhub_hub_resume(struct ast_vhub *vhub); void ast_vhub_hub_reset(struct ast_vhub *vhub); void ast_vhub_hub_wake_all(struct ast_vhub *vhub); +void ast_vhub_wake_work(struct work_struct *work); /* dev.c */ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); From 9cd072788c76595529eb38f42bf94d23852f8534 Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Fri, 18 Sep 2026 08:54:38 +0800 Subject: [PATCH 457/535] usb: gadget: f_fs: Fix NULL pointer dereference in FUNCTIONFS_ENDPOINT_DESC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When user space sets up FunctionFS with only full-speed descriptors (e.g., flags = FUNCTIONFS_HAS_FS_DESC) and the gadget operates at a higher speed (such as USB_SPEED_HIGH or USB_SPEED_SUPER), config_ep_by_speed() detects that the function lacks descriptors for the current speed, logs a warning, and falls back to full-speed descriptors. The endpoint is then successfully enabled via usb_ep_enable(), and epfile->ep becomes valid. However, when user space subsequently issues the FUNCTIONFS_ENDPOINT_DESC ioctl to query the endpoint descriptor, ffs_epfile_ioctl() selects the descriptor index solely based on gadget->speed: switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: desc_idx = 2; break; case USB_SPEED_HIGH: desc_idx = 1; break; default: desc_idx = 0; } desc = epfile->ep->descs[desc_idx]; memcpy(&desc1, desc, desc->bLength); Because user space only supplied full-speed descriptors, epfile->ep->descs[1] is NULL. Dereferencing desc->bLength triggers an immediate kernel NULL pointer dereference panic. Fix this by falling back through lower speed descriptors if the current speed descriptor was not supplied, matching the fallback logic in config_ep_by_speed() and historical f_fs behavior. If still missing, fall back to epfile->ep->ep->desc (the active descriptor), and return -EINVAL safely if no valid descriptor is found. In addition, mark desc as const to match struct usb_ep::desc and avoid discarding qualifiers. Fixes: c559a3534109 ("usb: gadget: f_fs: add ioctl returning ep descriptor") Cc: stable@kernel.org Signed-off-by: Yuchao Zhang Acked-by: Michał Nazarewicz Link: https://patch.msgid.link/20260918005438.81263-2-ndaugoing@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 43962e05eacf..c64a268e98a4 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, break; case FUNCTIONFS_ENDPOINT_DESC: { + const struct usb_endpoint_descriptor *desc; + struct usb_endpoint_descriptor desc1; int desc_idx; - struct usb_endpoint_descriptor desc1, *desc; switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: @@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, desc_idx = 0; } - desc = epfile->ep->descs[desc_idx]; + do { + desc = epfile->ep->descs[desc_idx]; + } while (!desc && --desc_idx >= 0); + + if (!desc) + desc = epfile->ep->ep->desc; + if (!desc) { + ret = -EINVAL; + break; + } + memcpy(&desc1, desc, desc->bLength); spin_unlock_irq(&epfile->ffs->eps_lock); From dc614d6bc991740d41d5a99ac7765c4b675dda88 Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Fri, 18 Sep 2026 19:43:09 +0800 Subject: [PATCH 458/535] USB: cdc-acm: skip URB restart in port_shutdown if disconnected acm_port_shutdown() is called from the tty-port machinery whenever the last user closes the port. acm_disconnect() sets acm->disconnected and calls tty_port_tty_vhangup(), which eventually triggers port_shutdown(). At that point acm_disconnect() has already called acm_poison_urbs() and is about to free the control and read URBs. However, acm_port_shutdown() unconditionally calls acm_unpoison_urbs() and, when the ALWAYS_POLL_CTRL quirk is set, immediately re-submits the ctrlurb and all read URBs. If the concurrent acm_disconnect() then frees those URBs and their DMA buffers while they are in-flight, the result is a use-after-free in acm_ctrl_irq() or acm_read_bulk_callback() accessing the freed DMA memory. Fix by checking acm->disconnected after draining the delayed anchor and returning early: if we are in a disconnect path the URBs must remain poisoned and must not be resubmitted. The early return is placed after the delayed-anchor drain loop because that loop only cancels write-side accounting (wb->use = false and autopm counter) and does not touch the hardware path. Fixes: f58752ebcb35 ("USB: cdc-acm: Add quirks for Yoga Book 9 14IAH10 INGENIC touchscreen") Cc: stable Signed-off-by: Yuchao Zhang Link: https://patch.msgid.link/20260918114309.3659635-2-ndaugoing@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 21417b4c5154..8ba4e4fa28f2 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -787,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) usb_autopm_put_interface_async(acm->control); } + if (acm->disconnected) + return; + acm_unpoison_urbs(acm); if (acm->quirks & ALWAYS_POLL_CTRL) { From 0e8baa78d872f2db440d8b9ded946ab8dceb89eb Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sun, 20 Sep 2026 23:17:02 +0000 Subject: [PATCH 459/535] usb: core: clear both ep_in and ep_out for non-ep0 control endpoints In usb_enable_endpoint(), non-zero control endpoints (where usb_endpoint_xfer_control(&ep->desc) is true) populate both dev->ep_in[epnum] and dev->ep_out[epnum] with the same struct usb_host_endpoint pointer. However, usb_disable_endpoint() only clears either dev->ep_out[epnum] or dev->ep_in[epnum] depending on the direction bit of epaddr when reset_hardware is set, leaving the opposite direction's array slot pointing to the disabled/freed endpoint. Clear both dev->ep_out[epnum] and dev->ep_in[epnum] when disabling a non-ep0 control endpoint with reset_hardware set, and update the function kerneldoc accordingly. Tested in QEMU against Linux 7.3.0-rc3 using dummy_hcd and raw-gadget to enumerate a USB device with a non-zero control endpoint (bEndpointAddress 0x01, bmAttributes USB_ENDPOINT_XFER_CONTROL), verifying that both dev->ep_in[1] and dev->ep_out[1] are cleared when the interface is disabled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Assisted-by: LLM Signed-off-by: Hui Peng Acked-by: Alan Stern Link: https://patch.msgid.link/20260920231702.110963-1-benquike@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 75e2bfd744a9..0cd6dd2b6334 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) * * Disables the endpoint for URB submission and nukes all pending URBs. * If @reset_hardware is set then also deallocates hcd/hardware state - * for the endpoint. + * for the endpoint (clearing both ep_in and ep_out pointers for + * bidirectional non-ep0 control endpoints). */ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, bool reset_hardware) @@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, dev->ep_in[epnum] = NULL; } if (ep) { + if (reset_hardware && epnum != 0 && + usb_endpoint_xfer_control(&ep->desc)) { + dev->ep_out[epnum] = NULL; + dev->ep_in[epnum] = NULL; + } ep->enabled = 0; usb_hcd_flush_endpoint(dev, ep); if (reset_hardware) From 9062d50e75c24dc7911be05c9b1719b3b256af15 Mon Sep 17 00:00:00 2001 From: Liu Chao Date: Mon, 21 Sep 2026 15:25:51 +0800 Subject: [PATCH 460/535] usb: gadget: f_uac1_legacy: validate bRequest index in generic_{set,get}_cmd generic_set_cmd() and generic_get_cmd() use the low nibble of ctrl->bRequest as an index into con->data[]: u8 cmd = (ctrl->bRequest & 0x0F); /* 0 .. 15 */ ... con->data[cmd] = value; /* OOB when cmd >= 5 */ struct usb_audio_control (include/linux/usb/audio.h) declares data as a 5-element array, so indices 5 through 15 write (or read) up to 44 bytes past the end of the array on the heap. A malicious USB host can craft a class-specific SET_CUR / GET_CUR request with an arbitrary bRequest value, triggering the out-of-bounds access from an IRQ completion handler with no further preconditions. Add an ARRAY_SIZE() guard to both functions. Fixes: c47d7b09891a ("USB: audio: add USB audio class definitions") Cc: stable Reviewed-by: Weibin Liu Signed-off-by: Liu Chao Reviewed-by: Ivy Lopez Link: https://patch.msgid.link/20260921072551.3708191-1-liuc63@xiaopeng.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uac1_legacy.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 3f52099a4fdd..7c8d60c7e87d 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + con->data[cmd] = value; return 0; @@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + return con->data[cmd]; } From 6c51f09c6db5868a5090e8dd4d7764660c87f1ad Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Wed, 16 Sep 2026 09:58:08 +0530 Subject: [PATCH 461/535] usb: typec: ucsi: Get the connector fwnode based on reg value ucsi_find_fwnode() currently maps UCSI connectors to Device tree connector nodes based on the order in which connector child nodes are described in DT. This can fail because the ordering of child nodes isn't guaranteed in Device-tree. For example, DTB may contain connector@1 before connector@0, causing connector numbers to be associated with the wrong fwnode. As a result, role switch and Type-C notifications can be delivered to the wrong remote endpoints. Fix this by using the "reg" property of each connector to match its corresponding fwnode. While at it, if the reg property isn't present, then fall back to the old method. Fixes: c1b0bc2dabfa ("usb: typec: Add support for UCSI interface") Cc: stable Signed-off-by: Prashanth K Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260916042808.2879079-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index bef3f9b71d71..c1450639c795 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) { struct fwnode_handle *fwnode; int i = 1; + int ret; + u32 port; - device_for_each_child_node(con->ucsi->dev, fwnode) - if (i++ == con->num) - return fwnode; + device_for_each_child_node(con->ucsi->dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + if (i == con->num) + return fwnode; + } else { + if (port == con->num - 1) + return fwnode; + } + + i++; + } return NULL; } From a107edec57659c5a1a2f016311b944af58c904c9 Mon Sep 17 00:00:00 2001 From: Jiazi Liu Date: Mon, 7 Sep 2026 15:37:56 +0800 Subject: [PATCH 462/535] usb: dwc3: gadget: fix IRQ storm on invalid event buffer count When dwc3_check_event_buf() reads a GEVNTCOUNT value exceeding the event buffer length, commit 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") returns IRQ_NONE without writing back GEVNTCOUNT. Since the DWC3 interrupt is level-triggered, the uncleared IRQ source keeps the line asserted, causing a tight IRQ storm that accumulates 99,900 unhandled interrupts and triggers spurious.c:184 BUG -> kernel panic. The resulting call stack: __report_bad_irq+0xac/0xc8 note_interrupt+0x340/0x468 handle_irq_event+0xac/0xc0 handle_fasteoi_irq+0x120/0x228 gic_handle_irq+0x68/0x108 ... kernel BUG at kernel/irq/spurious.c:184 To reproduce, write a bogus value exceeding the event buffer length directly to the GEVNTCOUNT register: devmem 4 0x1004 Write the bogus count back to GEVNTCOUNT to clear the IRQ source, consistent with the stale event clearing pattern in dwc3_event_buffers_setup(), and schedule error recovery to reinitialize the controller. Fixes: 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") Cc: stable Co-developed-by: Thinh Nguyen Signed-off-by: Thinh Nguyen Signed-off-by: Jiazi Liu Suggested-by: Thinh Nguyen Link: https://patch.msgid.link/20260907073756.22329-1-jiazi.liu1984@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 +++++++ drivers/usb/dwc3/ep0.c | 9 ++++ drivers/usb/dwc3/gadget.c | 105 +++++++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 608daeb7ef10..f3dabb37f806 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,6 +49,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 +#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -841,6 +842,12 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; +enum dwc3_err_state { + DWC3_ERR_NONE = 0, + DWC3_ERR_RECOVERY, + DWC3_ERR_UNRECOVERABLE, +}; + /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping + * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching + * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state + * @err_state: current error recovery state. + * @err_recovery_count: number of consecutive error recovery attempts until + * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; + struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1203,6 +1216,9 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; + /* serializes pull-up run/stop vs error recovery */ + struct mutex connect_mutex; + struct device *dev; struct device *sysdev; @@ -1332,6 +1348,9 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; + enum dwc3_err_state err_state; + + u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236a..46a34c4a046b 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + ret = -ESHUTDOWN; + goto out; + } + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f245e66cd13d..13096ee475d3 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); + /* Serialize against dwc3_err_recovery_work() */ + mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); + mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; + /* The controller is recovered. */ + if (dwc->err_state == DWC3_ERR_NONE) + dwc->err_recovery_count = 0; + /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } +static void dwc3_schedule_err_recovery(struct dwc3 *dwc) +{ + dwc->err_state = DWC3_ERR_RECOVERY; + schedule_work(&dwc->err_recovery_work); +} + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - return IRQ_NONE; + /* + * This is a fatal error - the driver and controller are out of + * sync on which event has been consumed. Reinitializing the + * controller is required to recover. Write the bogus count back + * to GEVNTCOUNT to clear the IRQ source, consistent with the + * stale event clearing in dwc3_event_buffers_setup(), then + * schedule error recovery. + */ + spin_lock(&dwc->lock); + dwc3_schedule_err_recovery(dwc); + spin_unlock(&dwc->lock); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + return IRQ_HANDLED; } evt->count = count; @@ -4735,6 +4784,54 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } +static void dwc3_err_recovery_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); + unsigned long flags; + int ret; + + dwc->err_recovery_count++; + + /* serializes against dwc3_gadget_pullup() */ + mutex_lock(&dwc->connect_mutex); + + ret = dwc3_gadget_soft_disconnect(dwc); + + dwc3_disconnect_gadget_sleepable(dwc); + + if (ret) + goto err_unrecoverable; + + if (dwc->err_recovery_count > DWC3_ERR_RECOVERY_MAX) + goto err_unrecoverable; + + if (dwc->softconnect) { + /* + * Wait irq to finish before soft_connect resets evt->lpos and + * the event buffer registers to avoid racing with + * dwc3_process_event_buf(). + */ + synchronize_irq(dwc->irq_gadget); + + ret = dwc3_gadget_soft_connect(dwc); + if (ret) + goto err_unrecoverable; + } + mutex_unlock(&dwc->connect_mutex); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); + return; + +err_unrecoverable: + dev_err(dwc->dev, "Unable to recover the controller\n"); + mutex_unlock(&dwc->connect_mutex); + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_UNRECOVERABLE; + spin_unlock_irqrestore(&dwc->lock, flags); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4778,6 +4875,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); + mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4874,6 +4973,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; + cancel_work_sync(&dwc->err_recovery_work); + mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); From e5052f2c5c73c1dcca42bafc0bc737af2b2af059 Mon Sep 17 00:00:00 2001 From: Jie Deng Date: Mon, 10 Aug 2026 10:36:26 +0800 Subject: [PATCH 463/535] usb: cdns3: Fix NULL pointer dereference in cdns3_pci_probe The Cadence USBSS controller is a two-function PCI device. The first probed function allocates the driver data and stores it with pci_set_drvdata(), while the second function reuses it via pci_get_drvdata() when pci_is_enabled() reports that the first function has already been probed. When the second function is probed while the first one has been enabled but has not yet set its driver data, pci_get_drvdata() returns NULL, and the subsequent wrap->devfn assignment dereferences a NULL pointer and crashes the kernel. logs: Call trace: cdns3_pci_probe+0xa4/0x300 local_pci_probe+0x44/0xa8 pci_call_probe+0x54/0x158 pci_device_probe+0x84/0x100 really_probe+0x184/0x3d0 __driver_probe_device+0x80/0x178 driver_probe_device+0x44/0xe8 __driver_attach+0xec/0x1f8 bus_for_each_dev+0x7c/0xe0 driver_attach+0x28/0x38 bus_add_driver+0x110/0x238 driver_register+0x64/0x128 __pci_register_driver+0x50/0x60 cdns3_pci_driver_init+0x28/0x38 do_one_initcall+0x5c/0x280 do_initcalls+0x104/0x1d8 kernel_init_freeable+0x140/0x218 kernel_init+0x28/0x1f8 ret_from_fork+0x10/0x20 Return -EPROBE_DEFER in this case so that probing is retried after the first function has completed its probe. Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver") Cc: stable Signed-off-by: Jie Deng Acked-by: Peter Chen Link: https://patch.msgid.link/20260810023626.70669-1-dengjie03@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-pci-wrap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c index eb5760f75b9d..fd06a3d8e638 100644 --- a/drivers/usb/cdns3/cdns3-pci-wrap.c +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, if (pci_is_enabled(func)) { wrap = pci_get_drvdata(func); + if (!wrap) { + dev_err(&pdev->dev, + "second function not initialized, retrying\n"); + return -EPROBE_DEFER; + } } else { wrap = kzalloc_obj(*wrap); if (!wrap) From aef5a7e207656ad6abdeeaa0bfc8ee9a1f9c02f6 Mon Sep 17 00:00:00 2001 From: Marek Maslanka Date: Fri, 25 Sep 2026 15:16:50 +0200 Subject: [PATCH 464/535] usb: typec: port-mapper: Only match USB4 port if host interface is available typec_port_match() adds a component match for the USB4 port whenever a USB 3.x port that shares the _PLD with the Type-C connector has the "usb4-host-interface" property, regardless of whether a USB4 port can ever be registered for it. The component framework binds the aggregate device only once every match has found its component, so if the USB4 port never shows up, the USB 2.0 and USB 3.x ports are not linked to the connector either: the "connector" symlinks are never created and USB devices enumerated on those ports are never linked with the Type-C partner. This happens in at least two cases: 1. CONFIG_USB4 is not reachable from the Type-C core, i.e. CONFIG_USB4=n, or CONFIG_USB4=m with CONFIG_TYPEC=y as in the x86_64 gki_defconfig. usb4_usb3_port_match() is then a stub that always returns false. 2. The firmware references a USB4 host interface that is disabled. For example, on Intel Alder Lake-N (ChromeOS Nissa) the TCSS xHCI USB3 ports (SS01-SS04) reference TDM0/TDM1, whose _STA returns 0 because the SoC has no integrated Thunderbolt/USB4 and the DMA controllers are disabled in TCSS DEVEN. No PCI device is enumerated for them. Only add the USB4 component match if CONFIG_USB4 is reachable and the referenced host interface is available and has been enumerated as a device. The latter mirrors the check in usb_acpi_add_usb4_devlink(), see commit 623dae3e7084 ("usb: acpi: fix boot hang due to early incorrect 'tunneled' USB3 device links"). Fixes: 4fd7a1f0f7f2 ("usb: typec: Connect Type-C port with associated USB4 port") Cc: stable Signed-off-by: Marek Maslanka Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260925131650.3777399-1-mmaslanka@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/port-mapper.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index cdbb7c11d714..3a97b084c3bd 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) return usb4_usb3_port_match(dev, fwnode); } +static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) +{ + if (!IS_REACHABLE(CONFIG_USB4)) + return false; + + struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = + fwnode_find_reference(fwnode, "usb4-host-interface", 0); + + /* + * The USB4 port can only appear if the host interface is enabled in + * the firmware and has been enumerated as a device. The latter is + * the same check as in usb_acpi_add_usb4_devlink(). + */ + return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && + nhi_fwnode->dev; +} + static int typec_port_compare(struct device *dev, void *fwnode) { return device_match_fwnode(dev, fwnode); @@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) adev_fwnode); /* - * If dev is USB 3.x port, it may have reference to the + * If dev is USB 3.x port, it may have reference to an available * USB4 host interface in which case we can also link the * Type-C port with the USB4 port. */ - if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) + if (typec_has_usb4_host_interface(adev_fwnode)) component_match_add(&arg->port->dev, &arg->match, usb4_port_compare, adev_fwnode); } From b263ff9b0fc5c1f371d65c4eb196b31263d039ff Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:37:34 -0400 Subject: [PATCH 465/535] USB: gadget: dummy-hcd: Fix wait for outstanding request completions The dummy-hcd driver emulates synchronize_irq() by waiting until its private callback_usage counter drops to 0 (with the private lock not held). This counter is incremented whenever a gadget driver callback occurs (which requires the private lock to be dropped), but not when a request completion handler is called. This is an oversight. Request completion is triggered by timer interrupts (emulating device IRQs in a real UDC), and the interrupt handlers are supposed to have completed when the synchronize_irq() emulation routine returns -- they aren't supposed to be in the middle of a completion callback. If this happens it can lead to a gadget driver's unbind routine running before all outstanding request completions have finished, maybe even allowing the gadget driver's module to be unloaded while a completion handler is still running. Fix the oversight by incrementing the callback_usage value across request completion callbacks. Link: https://lore.kernel.org/linux-usb/7a87e293-633c-4100-aa8d-91560ba5e5c5@rowland.harvard.edu/ Fixes: 7dbd8f4cabd9 ("USB: dummy-hcd: Fix erroneous synchronization change") Tested-by: Minseo Kim Signed-off-by: Alan Stern Cc: stable Link: https://patch.msgid.link/f23b9e1a-f110-441a-a1d8-95f442ec09d5@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index c0e40fa6dde5..0d30dd67b9ca 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, { bool fifo = req == &dum->fifo_req; + ++dum->callback_usage; spin_unlock(&dum->lock); usb_gadget_giveback_request(_ep, &req->req); spin_lock(&dum->lock); + --dum->callback_usage; if (fifo) dum->fifo_req_busy = 0; } @@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, req->req.complete = fifo_complete; list_add_tail(&req->queue, &ep->queue); + ++dum->callback_usage; spin_unlock(&dum->lock); _req->actual = _req->length; _req->status = 0; usb_gadget_giveback_request(_ep, _req); spin_lock(&dum->lock); + --dum->callback_usage; } else list_add_tail(&req->queue, &ep->queue); spin_unlock_irqrestore(&dum->lock, flags); From 9ee4266827cfb67b9fbb89136ea6a8f3d7809f5c Mon Sep 17 00:00:00 2001 From: Zeyu Li <13776528859@163.com> Date: Sat, 26 Sep 2026 22:03:36 +0800 Subject: [PATCH 466/535] ALSA: hda/realtek: Add ALC235 support for headset mode The ALC235 (0x10ec0235) is registered as the ALC255 codec variant in patch_alc269(), but the headset mode functions were missing a case for it, so the CTIA/OMTP jack type detection and routing never ran on this codec. As a result, a plugged-in headset is detected (jack presence works) but the microphone signal is never routed, leaving the capture stream permanently silent. Verified on an ASUS M5451GA laptop (HDA:10ec0235,10431814): with the ALC255 coef sequence applied, the headset microphone works correctly. Signed-off-by: Zeyu Li <13776528859@163.com> Link: https://patch.msgid.link/20260926140336.27896-1-13776528859@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/realtek.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index db365a746b1a..bc5226fad52b 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -1292,6 +1292,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) } switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1407,6 +1408,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_write_coef_idx(codec, 0x45, 0xc489); snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); @@ -1562,6 +1564,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) alc_process_coef_fw(codec, coef0225); alc_hp_enable_unmute(codec, 75); break; + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1663,6 +1666,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1780,6 +1784,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) }; switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); break; @@ -1881,6 +1886,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) } switch (codec->core.vendor_id) { + case 0x10ec0235: case 0x10ec0255: alc_process_coef_fw(codec, coef0255); msleep(300); From 6e0022b5ae3dc5b833af0fcc1578dc20a1d6bc71 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 23 Sep 2026 17:14:35 +0200 Subject: [PATCH 467/535] net: phy: aquantia: fix system interface type not updated in forced mode aqr_gen1_read_status() decodes the MDIO_PHYXS_VEND_IF_STATUS register to determine which SerDes interface the PHY is currently using on its system side and stores the result in phydev->interface. phylink relies on this value to configure the MAC. The autoneg == AUTONEG_DISABLE check is not correct: MDIO_PHYXS_VEND_IF_STATUS is set by the PHY firmware based on the negotiated link speed, not based on whether autoneg was used to reach it. When the link comes up at 1G in forced mode, the register correctly reads SGMII, but the early return prevents phydev->interface from being updated. It stays at whatever value it held before (typically 2500BASE-X from the initial autoneg run), so phylink configures the MAC for the wrong interface and the link cannot come up. Remove the autoneg guard so that the system interface type is always decoded when the link is up. Cc: stable@vger.kernel.org Fixes: 110a2432c520 ("net: phy: aquantia: add downshift support") Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260923-qcom-sa8255p-emac-v15-1-e82f33720737@oss.qualcomm.com Signed-off-by: Paolo Abeni --- drivers/net/phy/aquantia/aquantia_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/aquantia/aquantia_main.c b/drivers/net/phy/aquantia/aquantia_main.c index e95d391a6dc2..096407bebd52 100644 --- a/drivers/net/phy/aquantia/aquantia_main.c +++ b/drivers/net/phy/aquantia/aquantia_main.c @@ -544,7 +544,7 @@ static int aqr_gen1_read_status(struct phy_device *phydev) if (ret) return ret; - if (!phydev->link || phydev->autoneg == AUTONEG_DISABLE) + if (!phydev->link) return 0; /* The status register is not immediately correct on line side link up. @@ -594,6 +594,9 @@ static int aqr_gen1_read_status(struct phy_device *phydev) phydev->interface = aqr_translate_interface(phydev, interface); + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + /* Read rate from vendor register */ return aqr_gen1_read_rate(phydev); } From a3e981f42557d7bb4cb212462de463e0b6b8875b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 1 Oct 2026 10:16:44 +0200 Subject: [PATCH 468/535] Revert "usb: dwc3: gadget: fix IRQ storm on invalid event buffer count" This reverts commit a107edec57659c5a1a2f016311b944af58c904c9. It was incorrectly applied, and was 2 versions old. The "correct" one will be added instead afterward... Cc: stable Cc: Jiazi Liu Reported-by: Thinh Nguyen Fixes: a107edec5765 ("usb: dwc3: gadget: fix IRQ storm on invalid event buffer count") Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 ------- drivers/usb/dwc3/ep0.c | 9 ---- drivers/usb/dwc3/gadget.c | 105 +------------------------------------- 3 files changed, 2 insertions(+), 131 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index f3dabb37f806..608daeb7ef10 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,7 +49,6 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 -#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -842,12 +841,6 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; -enum dwc3_err_state { - DWC3_ERR_NONE = 0, - DWC3_ERR_RECOVERY, - DWC3_ERR_UNRECOVERABLE, -}; - /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1011,7 +1004,6 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping - * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1021,7 +1013,6 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching - * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1088,9 +1079,6 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state - * @err_state: current error recovery state. - * @err_recovery_count: number of consecutive error recovery attempts until - * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1201,7 +1189,6 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; - struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1216,9 +1203,6 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; - /* serializes pull-up run/stop vs error recovery */ - struct mutex connect_mutex; - struct device *dev; struct device *sysdev; @@ -1348,9 +1332,6 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; - enum dwc3_err_state err_state; - - u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 46a34c4a046b..310b5ffb236a 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,11 +200,6 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - ret = -ESHUTDOWN; - goto out; - } - if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -276,10 +271,6 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 13096ee475d3..f245e66cd13d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,10 +1149,6 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2064,10 +2060,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2300,10 +2292,6 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2318,10 +2306,6 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2453,10 +2437,6 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2484,10 +2464,6 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2857,13 +2833,10 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); - /* Serialize against dwc3_err_recovery_work() */ - mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); - mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4180,10 +4153,6 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; - /* The controller is recovered. */ - if (dwc->err_state == DWC3_ERR_NONE) - dwc->err_recovery_count = 0; - /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4671,12 +4640,6 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } -static void dwc3_schedule_err_recovery(struct dwc3 *dwc) -{ - dwc->err_state = DWC3_ERR_RECOVERY; - schedule_work(&dwc->err_recovery_work); -} - static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4710,21 +4673,9 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - /* - * This is a fatal error - the driver and controller are out of - * sync on which event has been consumed. Reinitializing the - * controller is required to recover. Write the bogus count back - * to GEVNTCOUNT to clear the IRQ source, consistent with the - * stale event clearing in dwc3_event_buffers_setup(), then - * schedule error recovery. - */ - spin_lock(&dwc->lock); - dwc3_schedule_err_recovery(dwc); - spin_unlock(&dwc->lock); - dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); - return IRQ_HANDLED; + return IRQ_NONE; } evt->count = count; @@ -4784,54 +4735,6 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } -static void dwc3_err_recovery_work(struct work_struct *work) -{ - struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); - unsigned long flags; - int ret; - - dwc->err_recovery_count++; - - /* serializes against dwc3_gadget_pullup() */ - mutex_lock(&dwc->connect_mutex); - - ret = dwc3_gadget_soft_disconnect(dwc); - - dwc3_disconnect_gadget_sleepable(dwc); - - if (ret) - goto err_unrecoverable; - - if (dwc->err_recovery_count > DWC3_ERR_RECOVERY_MAX) - goto err_unrecoverable; - - if (dwc->softconnect) { - /* - * Wait irq to finish before soft_connect resets evt->lpos and - * the event buffer registers to avoid racing with - * dwc3_process_event_buf(). - */ - synchronize_irq(dwc->irq_gadget); - - ret = dwc3_gadget_soft_connect(dwc); - if (ret) - goto err_unrecoverable; - } - mutex_unlock(&dwc->connect_mutex); - - spin_lock_irqsave(&dwc->lock, flags); - dwc->err_state = DWC3_ERR_NONE; - spin_unlock_irqrestore(&dwc->lock, flags); - return; - -err_unrecoverable: - dev_err(dwc->dev, "Unable to recover the controller\n"); - mutex_unlock(&dwc->connect_mutex); - spin_lock_irqsave(&dwc->lock, flags); - dwc->err_state = DWC3_ERR_UNRECOVERABLE; - spin_unlock_irqrestore(&dwc->lock, flags); -} - /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4875,8 +4778,6 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); - INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); - mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4973,8 +4874,6 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; - cancel_work_sync(&dwc->err_recovery_work); - mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); From 1ff77cbefe5a653ea12874c213ea4bd0d72c1067 Mon Sep 17 00:00:00 2001 From: Jiazi Liu Date: Tue, 15 Sep 2026 19:06:37 +0800 Subject: [PATCH 469/535] usb: dwc3: gadget: fix IRQ storm on invalid event buffer count When dwc3_check_event_buf() reads a GEVNTCOUNT value exceeding the event buffer length, commit 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") returns IRQ_NONE without writing back GEVNTCOUNT. Since the DWC3 interrupt is level-triggered, the uncleared IRQ source keeps the line asserted, causing a tight IRQ storm that accumulates 99,900 unhandled interrupts and triggers spurious.c:184 BUG -> kernel panic. The resulting call stack: __report_bad_irq+0xac/0xc8 note_interrupt+0x340/0x468 handle_irq_event+0xac/0xc0 handle_fasteoi_irq+0x120/0x228 gic_handle_irq+0x68/0x108 ... kernel BUG at kernel/irq/spurious.c:184 To reproduce, write a bogus value exceeding the event buffer length directly to the GEVNTCOUNT register: devmem 4 0x1004 Write the bogus count back to GEVNTCOUNT to clear the IRQ source, consistent with the stale event clearing pattern in dwc3_event_buffers_setup(), and schedule error recovery to reinitialize the controller. Fixes: 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") Cc: stable Suggested-by: Thinh Nguyen Signed-off-by: Jiazi Liu Link: https://patch.msgid.link/20260915110637.17658-1-jiazi.liu1984@gmail.com Acked-by: Thinh Nguyen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 +++++++ drivers/usb/dwc3/ep0.c | 9 ++++ drivers/usb/dwc3/gadget.c | 108 +++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 608daeb7ef10..f3dabb37f806 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,6 +49,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 +#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -841,6 +842,12 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; +enum dwc3_err_state { + DWC3_ERR_NONE = 0, + DWC3_ERR_RECOVERY, + DWC3_ERR_UNRECOVERABLE, +}; + /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping + * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching + * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state + * @err_state: current error recovery state. + * @err_recovery_count: number of consecutive error recovery attempts until + * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; + struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1203,6 +1216,9 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; + /* serializes pull-up run/stop vs error recovery */ + struct mutex connect_mutex; + struct device *dev; struct device *sysdev; @@ -1332,6 +1348,9 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; + enum dwc3_err_state err_state; + + u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236a..46a34c4a046b 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + ret = -ESHUTDOWN; + goto out; + } + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f245e66cd13d..ee837235630a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); + /* Serialize against dwc3_err_recovery_work() */ + mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); + mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; + /* The controller is recovered. */ + if (dwc->err_state == DWC3_ERR_NONE) + dwc->err_recovery_count = 0; + /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } +static void dwc3_schedule_err_recovery(struct dwc3 *dwc) +{ + dwc->err_state = DWC3_ERR_RECOVERY; + schedule_work(&dwc->err_recovery_work); +} + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - return IRQ_NONE; + /* + * This is a fatal error - the driver and controller are out of + * sync on which event has been consumed. Reinitializing the + * controller is required to recover. Write the bogus count back + * to GEVNTCOUNT to clear the IRQ source, consistent with the + * stale event clearing in dwc3_event_buffers_setup(), then + * schedule error recovery. + */ + spin_lock(&dwc->lock); + dwc3_schedule_err_recovery(dwc); + spin_unlock(&dwc->lock); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + return IRQ_HANDLED; } evt->count = count; @@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } +static void dwc3_err_recovery_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); + unsigned long flags; + int ret; + + /* serializes against dwc3_gadget_pullup() */ + mutex_lock(&dwc->connect_mutex); + + ret = dwc3_gadget_soft_disconnect(dwc); + + dwc3_disconnect_gadget_sleepable(dwc); + + if (ret) + goto err_unrecoverable; + + if (dwc->softconnect) { + u32 count; + /* + * Wait irq to finish before soft_connect resets evt->lpos and + * the event buffer registers to avoid racing with + * dwc3_process_event_buf(). + */ + synchronize_irq(dwc->irq_gadget); + + spin_lock_irqsave(&dwc->lock, flags); + count = ++dwc->err_recovery_count; + spin_unlock_irqrestore(&dwc->lock, flags); + + if (count > DWC3_ERR_RECOVERY_MAX) + goto err_unrecoverable; + + ret = dwc3_gadget_soft_connect(dwc); + if (ret) + goto err_unrecoverable; + } + mutex_unlock(&dwc->connect_mutex); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); + return; + +err_unrecoverable: + dev_err(dwc->dev, "Unable to recover the controller\n"); + mutex_unlock(&dwc->connect_mutex); + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_UNRECOVERABLE; + spin_unlock_irqrestore(&dwc->lock, flags); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); + mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; + cancel_work_sync(&dwc->err_recovery_work); + mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); From db0949bbd06db0a75b4b1076f1e25194be7d956a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 27 Sep 2026 05:59:57 +0000 Subject: [PATCH 470/535] tty: serial: max3100: shut down timer before freeing port max3100_shutdown() stops the polling timer but returns early during system suspend. If the SPI device is unbound before resume, the serial core does not call max3100_shutdown() again, so max3100_remove() frees the port while the timer remains armed. max3100_timeout() may then access the freed port and re-arm the timer. Add final timer teardown to max3100_remove() and use timer_shutdown_sync() to prevent a racing callback from re-arming it. Also free the IRQ and destroy the workqueue there before freeing the port. Keep timer_delete_sync() in max3100_shutdown() so that a subsequent open() can re-arm the timer. The workqueue is created before request_irq() and destroyed on both request_irq() failure and normal shutdown. Its presence at remove thus identifies the IRQ left registered when suspend bypasses shutdown. Free the IRQ before destroying the workqueue, in both max3100_shutdown() and max3100_remove(), and cancel the pending work in between. free_irq() waits for a running interrupt handler to finish and no new handler can start afterwards, so a racing handler cannot queue work on a workqueue that is being destroyed; the force_end_work check in max3100_dowork() only narrows that window, as a handler delayed between the check and queue_work() could still run after the teardown has cleared the workqueue pointer. Cancelling the work also keeps destroy_workqueue() from waiting on a queued work item in case remove() ever runs while the freezable workqueue is still frozen, e.g. device removal during the resume window, before workqueues are thawed. This issue was found by an in-house static analysis tool. Fixes: 7831d56b0a35 ("tty: MAX3100") Cc: stable@kernel.org # 6.2+ Assisted-by: Codex:gpt-5.6 Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260927055957.561030-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max3100.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c index a5a5cd68f696..f858a6669011 100644 --- a/drivers/tty/serial/max3100.c +++ b/drivers/tty/serial/max3100.c @@ -535,11 +535,11 @@ static void max3100_shutdown(struct uart_port *port) timer_delete_sync(&s->timer); if (s->workqueue) { + free_irq(port->irq, s); + cancel_work_sync(&s->work); destroy_workqueue(s->workqueue); s->workqueue = NULL; } - if (port->irq) - free_irq(port->irq, s); /* set shutdown mode to save power */ max3100_sr(s, MAX3100_WC | MAX3100_SHDN, &rx); @@ -753,6 +753,15 @@ static void max3100_remove(struct spi_device *spi) if (max3100s[i] == s) { dev_dbg(&spi->dev, "%s: removing port %d\n", __func__, i); uart_remove_one_port(&max3100_uart_driver, &max3100s[i]->port); + + s->force_end_work = 1; + timer_shutdown_sync(&s->timer); + if (s->workqueue) { + free_irq(s->port.irq, s); + cancel_work_sync(&s->work); + destroy_workqueue(s->workqueue); + s->workqueue = NULL; + } kfree(max3100s[i]); max3100s[i] = NULL; break; From 8167c1f071426706c233e93ecfd13aba7d5c06c8 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 27 Sep 2026 13:35:01 -0700 Subject: [PATCH 471/535] tty: serial: mpc52xx_uart: move static declarations up. Avoid a compilation error so that they're not used before being declared. Fixes: 4d105880666a ("tty: serial: mpc52xx_uart: add bounds check for psc_num array index") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609260356.tJWbd1WU-lkp@intel.com/ Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260927203501.14105-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mpc52xx_uart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/mpc52xx_uart.c b/drivers/tty/serial/mpc52xx_uart.c index b566206f42a2..472ec39caf27 100644 --- a/drivers/tty/serial/mpc52xx_uart.c +++ b/drivers/tty/serial/mpc52xx_uart.c @@ -634,6 +634,9 @@ static void __exit mpc512x_psc_fifoc_uninit(void) } } +static struct clk *psc_mclk_clk[MPC52xx_PSC_MAXNUM]; +static struct clk *psc_ipg_clk[MPC52xx_PSC_MAXNUM]; + /* 512x specific interrupt handler. The caller holds the port lock */ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port) { @@ -654,9 +657,6 @@ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port) return IRQ_NONE; } -static struct clk *psc_mclk_clk[MPC52xx_PSC_MAXNUM]; -static struct clk *psc_ipg_clk[MPC52xx_PSC_MAXNUM]; - /* called from within the .request_port() callback (allocation) */ static int mpc512x_psc_alloc_clock(struct uart_port *port) { From 8df07fe93573e9e548d6645273e9bcb6eb74059e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 30 Sep 2026 15:18:45 +0200 Subject: [PATCH 472/535] tty: fix saved termios reset race Resetting saved termios state on device registration is needed where a minor number can be reused for an entirely different device and where the old settings may prevent the port from even being opened (e.g. when CLOCAL is not set). Not all TTY drivers guarantee that the minor number is no longer in use when registering devices however, something which can lead to a use-after-free when closing a TTY (and saving its termios) races with re-registration. Add a new TTY_DRIVER_RESET_SAVED_TERMIOS flag to request that any saved termios state is reset on registration and only set it for drivers that make sure that the minor number is no longer in use. Fixes: 93857edd9829 ("tty: reset termios state on device registration") Reported-by: Chengfeng Ye Link: https://lore.kernel.org/20260926184154.3017929-1-nicoyip.dev@gmail.com Cc: stable@kernel.org # 4.12 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260930131845.1809256-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/uart.c | 3 ++- drivers/tty/tty_io.c | 18 ++++++++++-------- drivers/usb/class/cdc-acm.c | 2 +- drivers/usb/serial/usb-serial.c | 3 ++- include/linux/tty_driver.h | 6 ++++++ 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/drivers/staging/greybus/uart.c b/drivers/staging/greybus/uart.c index 24b4dab069c3..172cf900eaf0 100644 --- a/drivers/staging/greybus/uart.c +++ b/drivers/staging/greybus/uart.c @@ -948,7 +948,8 @@ static int gb_tty_init(void) int retval = 0; gb_tty_driver = tty_alloc_driver(GB_NUM_MINORS, TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(gb_tty_driver)) { pr_err("Can not allocate tty driver\n"); retval = PTR_ERR(gb_tty_driver); diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 1c30faae9ec1..1a6c8a1bc764 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3256,14 +3256,16 @@ struct device *tty_register_device_attr(struct tty_driver *driver, goto err_put; if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { - /* - * Free any saved termios data so that the termios state is - * reset when reusing a minor number. - */ - tp = driver->termios[index]; - if (tp) { - driver->termios[index] = NULL; - kfree(tp); + if (driver->flags & TTY_DRIVER_RESET_SAVED_TERMIOS) { + /* + * Free any saved termios data so that the termios + * state is reset when reusing a minor number. + */ + tp = driver->termios[index]; + if (tp) { + driver->termios[index] = NULL; + kfree(tp); + } } retval = tty_cdev_add(driver, devt, index, 1); diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7bc5329fa3ed..eeb401c0e5e1 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -2140,7 +2140,7 @@ static int __init acm_init(void) { int retval; acm_tty_driver = tty_alloc_driver(ACM_TTY_MINORS, TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(acm_tty_driver)) return PTR_ERR(acm_tty_driver); acm_tty_driver->driver_name = "acm", diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 17edc057a311..06e246426361 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1313,7 +1313,8 @@ static int __init usb_serial_init(void) int result; usb_serial_tty_driver = tty_alloc_driver(USB_SERIAL_TTY_MINORS, - TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_RESET_SAVED_TERMIOS); if (IS_ERR(usb_serial_tty_driver)) return PTR_ERR(usb_serial_tty_driver); diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h index 1f2896e56e77..62e459c5d3ae 100644 --- a/include/linux/tty_driver.h +++ b/include/linux/tty_driver.h @@ -73,6 +73,11 @@ struct serial_struct; * @TTY_DRIVER_NO_WORKQUEUE: * Do not create workqueue when tty_register_driver(). Whenever set, flip * buffer workqueue can be set by tty_port_link_wq() for every port. + * + * @TTY_DRIVER_RESET_SAVED_TERMIOS + * Reset any saved termios settings on device registration when reusing a + * minor number. Must only be set by drivers that guarantee that the minor + * number is no longer in use. */ enum tty_driver_flag { TTY_DRIVER_INSTALLED = BIT(0), @@ -84,6 +89,7 @@ enum tty_driver_flag { TTY_DRIVER_DYNAMIC_ALLOC = BIT(6), TTY_DRIVER_UNNUMBERED_NODE = BIT(7), TTY_DRIVER_NO_WORKQUEUE = BIT(8), + TTY_DRIVER_RESET_SAVED_TERMIOS = BIT(9), }; enum tty_driver_type { From abfafa6fc3c17b7dffa5dce3acc8dba70ed656b9 Mon Sep 17 00:00:00 2001 From: Paul Mbewe Date: Wed, 30 Sep 2026 16:32:07 +0200 Subject: [PATCH 473/535] serial: sc16is7xx: fix TX gap caused by kfifo circular buffer wrap-around kfifo_out_linear_ptr() returns only one contiguous linear segment of the xmit kfifo. When transmit data wraps around the end of the kfifo, only the first segment up to the buffer end is sent. The remaining data at the start of the kfifo is not sent until the next TX interrupt fires, resulting in a visible mid-frame TX gap on the wire. The resulting gap is unintended: data remains queued in the xmit kfifo, but the hardware TX FIFO drains empty before the remaining segment is sent. Such gaps can break timing-sensitive serial protocols such as Modbus RTU. Modbus RTU requires a message to be transmitted as a continuous stream. For baud rates above 19200, the Modbus Serial Line guide recommends a fixed 750 us inter-character timeout. On the tested 115200-baud system, oscilloscope measurements showed mid-frame gaps exceeding that value. Receivers using the recommended timeout may therefore discard the incomplete message. The incomplete transfer also causes unnecessary TX interrupts: instead of using all available hardware TX FIFO space in one go, the driver requires an extra interrupt to send the remaining segment after the wrap. After the tty xmit buffer was converted to a kfifo, the driver used uart_fifo_out() to copy data into a linear staging buffer, allowing a transfer to span the kfifo wrap-around boundary. Commit 133f4c00b8b2 ("serial: sc16is7xx: fix TX fifo corruption") replaced uart_fifo_out() with a single kfifo_out_linear_ptr() call to remove the shared TX/RX buffer. Since kfifo_out_linear_ptr() exposes only one contiguous segment, that change lost the wrap-around handling. Fix this by calling kfifo_out_linear_ptr() in a loop, advancing through all contiguous segments until the available hardware TX FIFO space is exhausted or the xmit kfifo is empty. This fixes the kfifo wrap-around gap independently of the stale-TXLVL refill issue. Tested on SC16IS752 over SPI driving RS-485 at 115200 baud 8N1 on an i.MX6ULL-based board. Oscilloscope measurements confirmed mid-frame breaks at the kfifo wrap-around boundary before the fix; no such breaks were observed afterward. Fixes: 133f4c00b8b2 ("serial: sc16is7xx: fix TX fifo corruption") Cc: stable@kernel.org Reported-by: Tobias Gannert Reviewed-by: Joachim Knorr Link: https://lore.kernel.org/linux-serial/20260623112225.82386-2-paultyson.mbewe@ziehl-abegg.de/ Signed-off-by: Paul Mbewe Link: https://patch.msgid.link/20260930143207.542930-1-paultyson.mbewe@ziehl-abegg.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 7107a0fb1e7b..fa7805d2cde2 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -652,7 +652,6 @@ static void sc16is7xx_handle_tx(struct uart_port *port) struct tty_port *tport = &port->state->port; unsigned long flags; unsigned int txlen; - unsigned char *tail; if (unlikely(port->x_char)) { sc16is7xx_port_write(port, SC16IS7XX_THR_REG, port->x_char); @@ -677,9 +676,19 @@ static void sc16is7xx_handle_tx(struct uart_port *port) txlen = 0; } - txlen = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); - sc16is7xx_fifo_write(port, tail, txlen); - uart_xmit_advance(port, txlen); + /* Handle circular buffer wrap-around by sending multiple segments */ + while (txlen > 0 && !kfifo_is_empty(&tport->xmit_fifo)) { + unsigned char *tail; + unsigned int to_send; + + to_send = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, txlen); + if (!to_send) + break; + + sc16is7xx_fifo_write(port, tail, to_send); + uart_xmit_advance(port, to_send); + txlen -= to_send; + } uart_port_lock_irqsave(port, &flags); if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) From 8aebfde6e84dceb7d47fe8001e50b754eb45d5df Mon Sep 17 00:00:00 2001 From: Simon Gassner Date: Thu, 1 Oct 2026 08:05:39 +0200 Subject: [PATCH 474/535] serial: tegra: don't clear the Tx FIFO on an Rx-only reset tegra_uart_fifo_reset() applies the Tegra30 workaround for "cannot clear the Tx FIFO while FIFO mode is enabled" unconditionally: it leaves FIFO mode, writes the requested FCR clear bits, and re-enters FIFO mode. Leaving FIFO mode empties both FIFOs, so an Rx-only reset discards queued Tx data as well. The break handler in tegra_uart_decode_rx_error() calls it with UART_FCR_CLEAR_RCVR only. On a half-duplex RS485 board whose Rx line is pulled low while the transceiver drives the bus, every transmission raises a spurious break, and all but the first character of the frame is lost. Only take the FIFO-mode path when the caller actually asked for CLEAR_XMIT. Likewise only wait for TEMT in that case: with the Tx FIFO deliberately left intact, that loop would otherwise spin for a full frame time in hard IRQ context with the port lock held. Tested on a Colibri T30 (Tegra30) with Rx DMA: - Break during transmission: the reset still fires from tegra_uart_decode_rx_error(), and the complete frame now reaches the peer. Before this change only the character in the shift register was sent. - Internal loopback with a generated break: the Rx FIFO is correctly cleared by the plain FCR write, and subsequent receive works with no frame, parity or overrun errors. Fixes: e9ea096dd225 ("serial: tegra: add serial driver") Cc: stable@kernel.org Signed-off-by: Simon Gassner Link: https://patch.msgid.link/20261001060539.32659-1-simon.gassner@noxsystems.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial-tegra.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial-tegra.c b/drivers/tty/serial/serial-tegra.c index 8004fc00fb9c..b0fc4ae3686f 100644 --- a/drivers/tty/serial/serial-tegra.c +++ b/drivers/tty/serial/serial-tegra.c @@ -306,11 +306,19 @@ static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits) { unsigned long fcr = tup->fcr_shadow; unsigned int lsr, tmout = 10000; + bool clear_tx = !!(fcr_bits & UART_FCR_CLEAR_XMIT); if (tup->rts_active) set_rts(tup, false); - if (tup->cdata->allow_txfifo_reset_fifo_mode) { + /* + * Leaving FIFO mode below is a workaround for a Tegra30 + * restriction on clearing the Tx FIFO while FIFO mode is + * enabled. It empties both FIFOs, so applying it to an + * Rx-only reset would destroy an in-flight transmission. + * Only take that path when the caller asked for CLEAR_XMIT. + */ + if (tup->cdata->allow_txfifo_reset_fifo_mode || !clear_tx) { fcr |= fcr_bits & (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); tegra_uart_write(tup, fcr, UART_FCR); } else { @@ -335,9 +343,15 @@ static void tegra_uart_fifo_reset(struct tegra_uart_port *tup, u8 fcr_bits) */ tegra_uart_wait_cycle_time(tup, 32); + /* + * Only wait for the transmitter to drain when the Tx FIFO was + * part of the reset. For an Rx-only reset it is left intact, + * and waiting for TEMT here would spin for a full frame time. + */ do { lsr = tegra_uart_read(tup, UART_LSR); - if ((lsr & UART_LSR_TEMT) && !(lsr & UART_LSR_DR)) + if ((!clear_tx || (lsr & UART_LSR_TEMT)) && + !(lsr & UART_LSR_DR)) break; udelay(1); } while (--tmout); From 527484911a40f6e84cf47e8bc490f7da61ab4151 Mon Sep 17 00:00:00 2001 From: Paul Mbewe Date: Wed, 30 Sep 2026 16:56:27 +0200 Subject: [PATCH 475/535] serial: sc16is7xx: refill TX FIFO below trigger using fresh TXLVL sc16is7xx_handle_tx() reads TXLVL once and sizes the hardware TX FIFO write from that value. TXLVL reports free space in the hardware TX FIFO. On this SPI-backed path, hardirq/softirq activity, RT scheduling, and waiting for synchronous SPI transfers can delay the refill while the UART continues draining. The TXLVL value can therefore become stale before the hardware TX FIFO write completes. One failing ftrace with the default 8-free-space trigger showed: tx_start txlvl=9 txlvl_read_us=580 tx_pre_write sent=9 pre_write_us=16 tx_segment sent=9 seg_us=364 tx_post_write txlvl_before=9 sent=9 txlvl_after=12 pending_after=29 post_gap_us=12 post_txlvl_us=130 The driver read 9 free spaces and wrote 9 bytes to the hardware TX FIFO, but the post-write TXLVL read still reported 12 free spaces while 29 bytes remained queued in the xmit kfifo. Even allowing for the post-write read window, the hardware TX FIFO had not been filled below the 8-free-space trigger, so no new threshold crossing was expected. The captured failing samples had the same pattern: data remained queued in the xmit kfifo while post-write TXLVL remained above the hardware trigger. The hardware TX FIFO then drained empty before another refill was requested, producing an unintended gap on the wire. Fix this by re-reading TXLVL after each hardware TX FIFO write while data remains queued in the xmit kfifo. If TXLVL is still at or above the trigger, top up the hardware TX FIFO again. Stop when the xmit kfifo is empty or a TXLVL read confirms that hardware TX FIFO free space is strictly below the trigger. Stopping when TXLVL was equal to the trigger still allowed TX gaps in the tested workload. Continuing until TXLVL was strictly below the trigger eliminated the observed gaps caused by stale-TXLVL under-fill. Program the hardware TX trigger explicitly through TLR using the same constant as the refill-loop threshold. This prevents the software refill condition from diverging from the programmed hardware trigger. Tested on SC16IS752 over 1 MHz SPI on an i.MX6ULL single-core PREEMPT_RT system, transmitting RS-485 at 115200 baud 8N1 under continuous Modbus RTU load. Fixes: dfeae619d781 ("serial: sc16is7xx") Cc: stable@kernel.org Reported-by: Tobias Gannert Link: https://lore.kernel.org/linux-serial/20260623112225.82386-3-paultyson.mbewe@ziehl-abegg.de/ Signed-off-by: Paul Mbewe Reviewed-by: David Laight Link: https://patch.msgid.link/20260930145628.566535-2-paultyson.mbewe@ziehl-abegg.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 38 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index fa7805d2cde2..1f7e98c2b570 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -216,6 +216,8 @@ #define SC16IS7XX_TLR_TX_TRIGGER(words) ((((words) / 4) & 0x0f) << 0) #define SC16IS7XX_TLR_RX_TRIGGER(words) ((((words) / 4) & 0x0f) << 4) +#define SC16IS7XX_TX_TRIGGER_LEVEL 8 + /* IOControl register bits (Only 75x/76x) */ #define SC16IS7XX_IOCONTROL_LATCH_BIT BIT(0) /* Enable input latching */ #define SC16IS7XX_IOCONTROL_MODEM_A_BIT BIT(1) /* Enable GPIO[7:4] as modem A pins */ @@ -647,6 +649,21 @@ static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, tty_flip_buffer_push(&port->state->port); } +static unsigned int sc16is7xx_txlvl(struct uart_port *port) +{ + unsigned int txlvl; + + txlvl = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); + if (txlvl > SC16IS7XX_FIFO_SIZE) { + dev_err_ratelimited(port->dev, + "chip reports %u free bytes in TX FIFO, but it only has %u\n", + txlvl, SC16IS7XX_FIFO_SIZE); + return 0; + } + + return txlvl; +} + static void sc16is7xx_handle_tx(struct uart_port *port) { struct tty_port *tport = &port->state->port; @@ -668,13 +685,7 @@ static void sc16is7xx_handle_tx(struct uart_port *port) } /* Limit to space available in TX FIFO */ - txlen = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG); - if (txlen > SC16IS7XX_FIFO_SIZE) { - dev_err_ratelimited(port->dev, - "chip reports %d free bytes in TX fifo, but it only has %d", - txlen, SC16IS7XX_FIFO_SIZE); - txlen = 0; - } + txlen = sc16is7xx_txlvl(port); /* Handle circular buffer wrap-around by sending multiple segments */ while (txlen > 0 && !kfifo_is_empty(&tport->xmit_fifo)) { @@ -687,7 +698,14 @@ static void sc16is7xx_handle_tx(struct uart_port *port) sc16is7xx_fifo_write(port, tail, to_send); uart_xmit_advance(port, to_send); - txlen -= to_send; + + if (kfifo_is_empty(&tport->xmit_fifo)) + break; + + /* Refill below the trigger to enable the next THRI crossing. */ + txlen = sc16is7xx_txlvl(port); + if (txlen < SC16IS7XX_TX_TRIGGER_LEVEL) + break; } uart_port_lock_irqsave(port, &flags); @@ -1139,6 +1157,10 @@ static int sc16is7xx_startup(struct uart_port *port) SC16IS7XX_TCR_RX_RESUME(24) | SC16IS7XX_TCR_RX_HALT(48)); + /* Sync hardware and software TX trigger levels */ + sc16is7xx_port_write(port, SC16IS7XX_TLR_REG, + SC16IS7XX_TLR_TX_TRIGGER(SC16IS7XX_TX_TRIGGER_LEVEL)); + /* Disable TCR/TLR access */ sc16is7xx_port_update(port, SC16IS7XX_MCR_REG, SC16IS7XX_MCR_TCRTLR_BIT, 0); From d9feaa93328a6f885afb8ac6374897fafc294222 Mon Sep 17 00:00:00 2001 From: Paul Mbewe Date: Wed, 30 Sep 2026 16:56:28 +0200 Subject: [PATCH 476/535] serial: sc16is7xx: reduce TX refill rate with half-FIFO trigger With the TX trigger set to 8 free spaces, THRI is generated roughly once per 8 transmitted bytes. At 115200 baud 8N1, this corresponds to approximately 0.7 ms between TX refill events. Set the TX trigger to 32 free spaces via TLR[3:0]. This makes each refill larger and reduces the refill rate by about 4x. At 115200 baud 8N1, the refill cadence becomes approximately 2.8 ms. The trade-off is that the time-to-empty after THRI asserts is reduced from 56 to 32 byte times. The fresh-TXLVL refill loop fills the hardware TX FIFO strictly below whichever trigger is selected. This patch changes only the refill frequency and the associated latency trade-off. With the two TX gap fixes applied in both configurations, changing the trigger from 8 to 32 free spaces produced the following median values from repeated top snapshots under the same continuous Modbus RTU load. Each transaction used an 8-byte RX request and a 255-byte TX response, so the workload was dominated by TX traffic: trigger=8 trigger=32 SPI IRQ thread CPU 15% 5% system CPU 44% 29% idle CPU 40% 52% one-minute load 2.02 0.99 The datasets contain 547 snapshots with trigger=8 and 535 snapshots with trigger=32. Only TLR[3:0] is changed. TLR[7:4] remains zero so the RX trigger retains its FCR setting. RX trigger tuning may also be useful, but generic RX/TX trigger configuration is left for follow-up work. SC16IS7XX_TX_TRIGGER_LEVEL is used for both the programmed TLR value and the TXLVL refill-loop threshold, keeping the hardware trigger and the software refill condition synchronized. TCR/TLR access requires EFR[4] and MCR[2], which are already enabled by the TCR setup immediately preceding the TLR write. Reviewed-by: Joachim Knorr Link: https://lore.kernel.org/linux-serial/20260623112225.82386-3-paultyson.mbewe@ziehl-abegg.de/ Signed-off-by: Paul Mbewe Link: https://patch.msgid.link/20260930145628.566535-3-paultyson.mbewe@ziehl-abegg.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 1f7e98c2b570..296079c16fdb 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -216,7 +216,7 @@ #define SC16IS7XX_TLR_TX_TRIGGER(words) ((((words) / 4) & 0x0f) << 0) #define SC16IS7XX_TLR_RX_TRIGGER(words) ((((words) / 4) & 0x0f) << 4) -#define SC16IS7XX_TX_TRIGGER_LEVEL 8 +#define SC16IS7XX_TX_TRIGGER_LEVEL 32 /* IOControl register bits (Only 75x/76x) */ #define SC16IS7XX_IOCONTROL_LATCH_BIT BIT(0) /* Enable input latching */ From 226bd5603371cd9f6642cbb9e9a677f445de3530 Mon Sep 17 00:00:00 2001 From: Yi Yang Date: Tue, 1 Sep 2026 11:31:31 +0000 Subject: [PATCH 477/535] vc_screen: reload vc pointer before if (ret) in vcs_write() to avoid UAF The reload of 'vc' added by commit 8fb9ea65c9d1 ("vc_screen: reload load of struct vc_data pointer in vcs_write() to avoid UAF") sits after the 'if (ret)' block, so the copy-failure break path exits the loop without reloading vc. If the vc was kfree()'d via vc_port_destruct during the unlocked copy_from_user() window, the post-loop 'if (written && vc) vcs_scr_updated(vc)' is reached with a stale non-NULL vc. The '&& vc' guard from commit a287620312dc ("vc_screen: fix null-ptr-deref in vcs_notifier() during concurrent vcs_write") only handles the NULL case, not this stale-non-NULL case; vcs_notifier() then reads param->vc->vc_num from freed memory: BUG: KASAN: slab-use-after-free in vcs_notifier+0x7c/0xd0 Read of size 2 at addr ffff888007149190 Call Trace: vcs_notifier+0x7c/0xd0 atomic_notifier_call_chain+0x70/0xa0 vcs_scr_updated+0x77/0xa0 vcs_write+0x71b/0x7e0 Allocated by task: vc_allocate -> con_install -> tty_open Freed by task: kfree <- vt_ioctl (VT_DISALLOCATE -> vc_port_destruct) Move the reload to immediately after console_lock(), before 'if (ret)', so every break path below passes a fresh vc to the post-loop vcs_scr_updated(). Fixes: a287620312dc ("vc_screen: fix null-ptr-deref in vcs_notifier() during concurrent vcs_write") Cc: stable@kernel.org Signed-off-by: Yi Yang Link: https://patch.msgid.link/20260901113131.2760010-1-yiyang13@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vc_screen.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c index bf1502fd5bd4..79453abcf4d9 100644 --- a/drivers/tty/vt/vc_screen.c +++ b/drivers/tty/vt/vc_screen.c @@ -631,6 +631,18 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) ret = copy_from_user(con_buf, buf, this_round); console_lock(); + /* The vc might have been freed or vcs_size might have changed + * while we slept to grab the user buffer; recheck here, before + * if (ret), so every break path below passes a fresh vc to the + * post-loop vcs_scr_updated(). Return data written so far. + */ + vc = vcs_vc(inode, &viewed); + if (!vc) { + if (written) + break; + return -ENXIO; + } + if (ret) { this_round -= ret; if (!this_round) { @@ -642,17 +654,6 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) return -EFAULT; } } - - /* The vc might have been freed or vcs_size might have changed - * while we slept to grab the user buffer, so recheck. - * Return data written up to now on failure. - */ - vc = vcs_vc(inode, &viewed); - if (!vc) { - if (written) - break; - return -ENXIO; - } size = vcs_size(vc, attr, false); if (size < 0) { if (written) From 0928ed9bd7946baee911efa401088697836e3b1e Mon Sep 17 00:00:00 2001 From: Zizhi Wo Date: Sat, 5 Sep 2026 14:43:37 +0800 Subject: [PATCH 478/535] vt: skip screen update for DEC alignment test on backgroup consoles [BUG] Recently, we encountered a KASAN warning as follows: BUG: KASAN: slab-out-of-bounds in fb_pad_aligned_buffer+0x11f/0x140 Read of size 1 at addr ff1100015fd9f6a4 by task tty_fbcon_oob/1239 CPU: 7 UID: 0 PID: 1239 Comm: tty_fbcon_oob Not tainted 7.3.0-rc1 #100 PREEMPT(full) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-4.fc41 04/01/2014 Call Trace: ... kasan_report+0xf0/0x120 fb_pad_aligned_buffer+0x11f/0x140 ccw_putcs+0x86c/0xa80 fbcon_putcs+0x338/0x410 do_update_region+0x21d/0x450 do_con_write+0x1e0e/0x4880 con_write+0x13/0x80 n_tty_write+0x374/0x1010 file_tty_write.isra.0+0x404/0x7a0 ... reproduce: 1) open /dev/tty0, set a KDFONTOP ioctl with op.op = KD_FONT_OP_SET, op.width = 8 and op.height = 16 (visible VC1) 2) open /dev/tty1, set a KDFONTOP ioctl with op.op = KD_FONT_OP_SET, op.width = 28 and op.height = 24 (invisible VC2) 3) echo 3 > /sys/devices/virtual/graphics/fbcon/rotate_all 4) write EShash8(esc hash8) to tty1 [CAUSE] All VCs render to the framebuffer. setfont only modifies the target VC's font without resizing the framebuffer backing buffer (par->rotated.buf); the buffer is only resized for the visible VC (fbcon_do_set_font -> ... -> vc_do_resize -> update_screen). This relies on the con_should_update() check performed before every update_region(). However, the ESC # 8 path (do_con_trol -> do_update_region) omits the con_should_update() check. After changing the font size of an invisible VC, do_update_region() fills using the new font size against a buffer that was never resized, causing an out-of-bounds access. [FIX] Only push the update when the console is visible and not blanked, add the con_should_update() check in the do_con_trol() like every other call site of do_update_region() (update_region(), invert_screen(), ...). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Signed-off-by: Zizhi Wo Link: https://patch.msgid.link/20260905064337.3083103-1-wozizhi@huaweicloud.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 51f8fd103dcf..49a140b49eff 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -2862,7 +2862,8 @@ static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, u8 c) csi_J(vc, CSI_J_VISIBLE); vc->vc_video_erase_char = (vc->vc_video_erase_char & 0xff00) | ' '; - do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2); + if (con_should_update(vc)) + do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2); } return; case ESsetG0: /* ESC ( */ From 39495ef5d6f8019e62d65807f217a7ca8232727a Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:00:41 +0000 Subject: [PATCH 479/535] vt: selection: Fix unsigned underflow and slab-out-of-bounds read in paste_selection() In paste_selection(), the loop copies min_t(unsigned int, vc_sel.buf_len - pasted,tty->receive_room) bytes per iteration into tty_ldisc_receive_buf() and increments pasted += count. Because the selection mutex (vc_sel.lock) is dropped inside the loop Whenever the line discipline buffer fills up and paste_selection() sleeps on tty->write_wait, a concurrent TIOCLINUX (TIOCL_SETSEL) ioctl can replace vc_sel.buffer with a shorter selection and reduce vc_sel.buf_len below pasted. When paste_selection() resumes, vc_sel.buf_len - pasted underflows as an unsigned integer to a large positive value, causing tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted, NULL, count) to read up to 4094 bytes out-of-bounds past the newly allocated vc_sel.buffer. Fix this by terminating the loop when pasted >= vc_sel.buf_len. Kernel stack trace (Linux 7.3.0-rc3): ================================================================== BUG: KASAN: slab-out-of-bounds in n_tty_receive_buf_common+0xa01/0x1650 Read of size 4094 at addr ffff888101c58010 by task kworker/u17:1/65 Workqueue: events_unbound flush_to_ldisc Call Trace: dump_stack_lvl+0x70/0xa0 print_report+0x153/0x4c6 kasan_report+0xf1/0x120 kasan_check_range+0x11c/0x200 __asan_memcpy+0x29/0x70 n_tty_receive_buf_common+0xa01/0x1650 tty_ldisc_receive_buf+0x66/0x110 tty_port_default_receive_buf+0x6b/0xb0 flush_to_ldisc+0x1b4/0x410 process_one_work+0x6ff/0x1110 worker_thread+0x4a8/0xb70 kthread+0x307/0x3e0 ret_from_fork+0x3ed/0x680 ================================================================== Fixes: e8c75a30a23c ("vt: selection, push sel_lock up") Cc: stable Assisted-by: LLM Signed-off-by: Hui Peng Link: https://patch.msgid.link/20260919110041.3763078-1-benquike@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/selection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 13f4e48b4142..f1a3bc5b5fad 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -434,8 +434,8 @@ int paste_selection(struct tty_struct *tty) bps = NULL; } - count = vc_sel.buf_len - pasted; - if (count) { + if (vc_sel.buf_len > pasted) { + count = vc_sel.buf_len - pasted; pasted += tty_ldisc_receive_buf(ld, vc_sel.buffer + pasted, NULL, count); if (vc_sel.buf_len > pasted) From c98bf3b86609a18ab16d067280257326e557c636 Mon Sep 17 00:00:00 2001 From: Hongjian Dai Date: Wed, 30 Sep 2026 00:43:40 +0800 Subject: [PATCH 480/535] i2c: at91: release DMA channels when probe defers at91_twi_probe_master() can return -EPROBE_DEFER from at91_init_twi_recovery_info() after at91_twi_configure_dma() has already claimed the tx/rx DMA channels. at91_twi_probe() then returns without releasing them, and since dma_request_chan() is not devres-managed the channels leak on every deferred probe attempt. Release the channels before deferring the probe. Fixes: f7eeb1af8537 ("i2c: at91: release DMA channels on remove and probe error") Assisted-by: LLM Signed-off-by: Hongjian Dai Signed-off-by: Andi Shyti Link: https://patch.msgid.link/7D85C6CB6BF0A82E+20260929164340.41628-1-daihongjian@kylinsec.com.cn --- drivers/i2c/busses/i2c-at91-master.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-at91-master.c b/drivers/i2c/busses/i2c-at91-master.c index 68238cc8aee0..e0c3de5e8203 100644 --- a/drivers/i2c/busses/i2c-at91-master.c +++ b/drivers/i2c/busses/i2c-at91-master.c @@ -921,8 +921,14 @@ int at91_twi_probe_master(struct platform_device *pdev, at91_calc_twi_clock(dev); rc = at91_init_twi_recovery_info(pdev, dev); - if (rc == -EPROBE_DEFER) + if (rc == -EPROBE_DEFER) { + /* + * The DMA channels were already set up above, release them + * before deferring the probe so they are not leaked. + */ + at91_twi_dma_release(dev); return rc; + } dev->adapter.algo = &at91_twi_algorithm; dev->adapter.quirks = &at91_twi_quirks; From ffb684f2aa141eeb0caa6308422e7e05d1ded7c4 Mon Sep 17 00:00:00 2001 From: Luo Gengkun Date: Sun, 20 Sep 2026 07:50:26 +0000 Subject: [PATCH 481/535] perf: Fix race between perf_event_exit_task() and perf_pending_task() A race condition exists between perf_event_exit_task() and perf_pending_task() during begin_new_exec(). During begin_new_exec(), perf_event_exit_task() may be called, and the PF_EXITING flag is not set on task. So perf_sigtrap() continues to execute and triggers WARN_ON_ONCE(event->ctx->task != current). Since both task exit and exec paths can call perf_event_exit_task() which sets ctx->task to TASK_TOMBSTONE, fix this by explicitly checking if event->ctx->task equals TASK_TOMBSTONE and dropping the redundant PF_EXITING check. Fixes: 97ba62b27867 ("perf: Add support for SIGTRAP on perf events") Signed-off-by: Luo Gengkun Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260920075026.990582-1-luogengkun2@huawei.com --- kernel/events/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 634d2ccbab82..c2882b5be7c3 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7626,9 +7626,11 @@ static void perf_sigtrap(struct perf_event *event) { /* * Both perf_pending_task() and perf_pending_irq() can race with the - * task exiting. + * task exiting or exec-ing. We can determine if such a race has + * occurred by checking if perf_event_exit_task(), which will set + * ctx->task to TASK_TOMBSTONE, has already been called. */ - if (current->flags & PF_EXITING) + if (event->ctx->task == TASK_TOMBSTONE) return; /* From b9d1fdc6f4ac1b6e49f9deafaf137da1407d9af1 Mon Sep 17 00:00:00 2001 From: Ian Rogers Date: Tue, 29 Sep 2026 15:23:32 -0700 Subject: [PATCH 482/535] perf: Replace perf_event_header__init_id with full header init perf_iterate_sb() invokes its callback for each matching perf_event on the CPU and task context, passing a shared caller-allocated event structure. perf_event_header__init_id() mutated header->size in place by adding event->id_header_size, requiring sideband output callbacks to save and restore header fields across iterations. Three sideband callbacks failed to save and restore header.size around perf_event_header__init_id(): - perf_event_ksymbol_output() - perf_event_bpf_output() - perf_event_text_poke_output() When multiple events with attr.ksymbol, attr.bpf_event, or attr.text_poke and sample_id_all are active on the same CPU, each subsequent event receives a record whose header.size is inflated by all preceding events' id_header_size values while only a single id_sample is written, leaving uninitialized ring-buffer bytes at the end of the record and causing userspace perf to fail with -EFAULT ("Bad address") when parsing the sample_id trailer. Similarly, perf_event_mmap_output() set PERF_RECORD_MISC_MMAP_BUILD_ID in mmap_event->event_id.header.misc when event->attr.build_id was enabled, but only saved and restored header.size and header.type. If an event with attr.build_id was followed by an event with attr.mmap2 and !attr.build_id, the second event received PERF_RECORD_MISC_MMAP_BUILD_ID in header.misc while its payload contained maj/min/ino/ino_generation instead of a build ID. Rather than splitting header initialization between callers and output callbacks and saving/restoring mutated header fields, replace perf_event_header__init_id() with perf_event_header__init(), which initializes header->type, header->misc, and header->size alongside the sample_id fields on each invocation. Fixes: 76193a94522f ("perf, bpf: Introduce PERF_RECORD_KSYMBOL") Fixes: 6ee52e2a3fe4 ("perf, bpf: Introduce PERF_RECORD_BPF_EVENT") Fixes: e17d43b93e54 ("perf: Add perf text poke event") Fixes: 88a16a130933 ("perf: Add build id data in mmap2 event") Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Ian Rogers Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260929222332.973435-1-irogers@google.com Cc: stable@vger.kernel.org --- arch/powerpc/perf/imc-pmu.c | 23 +-- include/linux/perf_event.h | 7 +- kernel/events/core.c | 303 +++++++++++++++--------------------- kernel/events/ring_buffer.c | 7 +- 4 files changed, 146 insertions(+), 194 deletions(-) diff --git a/arch/powerpc/perf/imc-pmu.c b/arch/powerpc/perf/imc-pmu.c index f401181d8669..a9f8464997a2 100644 --- a/arch/powerpc/perf/imc-pmu.c +++ b/arch/powerpc/perf/imc-pmu.c @@ -1275,6 +1275,8 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, struct perf_event_header *header, struct perf_event *event) { + u16 misc = 0; + /* Sanity checks for a valid record */ if (be64_to_cpu(READ_ONCE(mem->tb1)) > *prev_tb) *prev_tb = be64_to_cpu(READ_ONCE(mem->tb1)); @@ -1289,23 +1291,19 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, data->ip = be64_to_cpu(READ_ONCE(mem->ip)); data->period = event->hw.last_period; - header->type = PERF_RECORD_SAMPLE; - header->size = sizeof(*header) + event->header_size; - header->misc = 0; - if (cpu_has_feature(CPU_FTR_ARCH_31)) { switch (IMC_TRACE_RECORD_VAL_HVPR(be64_to_cpu(READ_ONCE(mem->val)))) { case 0:/* when MSR HV and PR not set in the trace-record */ - header->misc |= PERF_RECORD_MISC_GUEST_KERNEL; + misc |= PERF_RECORD_MISC_GUEST_KERNEL; break; case 1: /* MSR HV is 0 and PR is 1 */ - header->misc |= PERF_RECORD_MISC_GUEST_USER; + misc |= PERF_RECORD_MISC_GUEST_USER; break; case 2: /* MSR HV is 1 and PR is 0 */ - header->misc |= PERF_RECORD_MISC_KERNEL; + misc |= PERF_RECORD_MISC_KERNEL; break; case 3: /* MSR HV is 1 and PR is 1 */ - header->misc |= PERF_RECORD_MISC_USER; + misc |= PERF_RECORD_MISC_USER; break; default: pr_info("IMC: Unable to set the flag based on MSR bits\n"); @@ -1313,11 +1311,14 @@ static int trace_imc_prepare_sample(struct trace_imc_data *mem, } } else { if (is_kernel_addr(data->ip)) - header->misc |= PERF_RECORD_MISC_KERNEL; + misc |= PERF_RECORD_MISC_KERNEL; else - header->misc |= PERF_RECORD_MISC_USER; + misc |= PERF_RECORD_MISC_USER; } - perf_event_header__init_id(header, data, event); + perf_event_header__init(header, data, + PERF_RECORD_SAMPLE, misc, + sizeof(*header) + event->header_size, + event); return 0; } diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 915c6fd3f084..2d5e7687875b 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1506,9 +1506,10 @@ is_default_overflow_handler(struct perf_event *event) } extern void -perf_event_header__init_id(struct perf_event_header *header, - struct perf_sample_data *data, - struct perf_event *event); +perf_event_header__init(struct perf_event_header *header, + struct perf_sample_data *data, + u32 type, u16 misc, u16 size, + struct perf_event *event); extern void perf_event__output_id_sample(struct perf_event *event, struct perf_output_handle *handle, diff --git a/kernel/events/core.c b/kernel/events/core.c index c2882b5be7c3..3aa223595e7d 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -8131,10 +8131,15 @@ static void __perf_event_header__init_id(struct perf_sample_data *data, } } -void perf_event_header__init_id(struct perf_event_header *header, - struct perf_sample_data *data, - struct perf_event *event) +void perf_event_header__init(struct perf_event_header *header, + struct perf_sample_data *data, + u32 type, u16 misc, u16 size, + struct perf_event *event) { + header->type = type; + header->misc = misc; + header->size = size; + if (event->attr.sample_id_all) { header->size += event->id_header_size; __perf_event_header__init_id(data, event, event->attr.sample_type); @@ -8971,17 +8976,16 @@ perf_event_read_event(struct perf_event *event, struct perf_output_handle handle; struct perf_sample_data sample; struct perf_read_event read_event = { - .header = { - .type = PERF_RECORD_READ, - .misc = 0, - .size = sizeof(read_event) + event->read_size, - }, .pid = perf_event_pid(event, task), .tid = perf_event_tid(event, task), }; int ret; - perf_event_header__init_id(&read_event.header, &sample, event); + perf_event_header__init(&read_event.header, &sample, + PERF_RECORD_READ, + /* misc= */ 0, + sizeof(read_event) + event->read_size, + event); ret = perf_output_begin(&handle, &sample, event, read_event.header.size); if (ret) return; @@ -9222,6 +9226,7 @@ struct perf_task_event { u32 ptid; u64 time; } event_id; + int new; }; static int perf_event_task_match(struct perf_event *event) @@ -9238,17 +9243,21 @@ static void perf_event_task_output(struct perf_event *event, struct perf_output_handle handle; struct perf_sample_data sample; struct task_struct *task = task_event->task; - int ret, size = task_event->event_id.header.size; + int ret; if (!perf_event_task_match(event)) return; - perf_event_header__init_id(&task_event->event_id.header, &sample, event); + perf_event_header__init(&task_event->event_id.header, &sample, + task_event->new ? PERF_RECORD_FORK : PERF_RECORD_EXIT, + /* misc= */ 0, + sizeof(task_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, task_event->event_id.header.size); if (ret) - goto out; + return; task_event->event_id.pid = perf_event_pid(event, task); task_event->event_id.tid = perf_event_tid(event, task); @@ -9270,8 +9279,6 @@ static void perf_event_task_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - task_event->event_id.header.size = size; } static void perf_event_task(struct task_struct *task, @@ -9288,18 +9295,7 @@ static void perf_event_task(struct task_struct *task, task_event = (struct perf_task_event){ .task = task, .task_ctx = task_ctx, - .event_id = { - .header = { - .type = new ? PERF_RECORD_FORK : PERF_RECORD_EXIT, - .misc = 0, - .size = sizeof(task_event.event_id), - }, - /* .pid */ - /* .ppid */ - /* .tid */ - /* .ptid */ - /* .time */ - }, + .new = new, }; perf_iterate_sb(perf_event_task_output, @@ -9376,6 +9372,7 @@ struct perf_comm_event { u32 pid; u32 tid; } event_id; + bool exec; }; static int perf_event_comm_match(struct perf_event *event) @@ -9389,18 +9386,21 @@ static void perf_event_comm_output(struct perf_event *event, struct perf_comm_event *comm_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int size = comm_event->event_id.header.size; int ret; if (!perf_event_comm_match(event)) return; - perf_event_header__init_id(&comm_event->event_id.header, &sample, event); + perf_event_header__init(&comm_event->event_id.header, &sample, + PERF_RECORD_COMM, + comm_event->exec ? PERF_RECORD_MISC_COMM_EXEC : 0, + sizeof(comm_event->event_id) + comm_event->comm_size, + event); ret = perf_output_begin(&handle, &sample, event, comm_event->event_id.header.size); if (ret) - goto out; + return; comm_event->event_id.pid = perf_event_pid(event, comm_event->task); comm_event->event_id.tid = perf_event_tid(event, comm_event->task); @@ -9412,8 +9412,6 @@ static void perf_event_comm_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - comm_event->event_id.header.size = size; } static void perf_event_comm_event(struct perf_comm_event *comm_event) @@ -9428,8 +9426,6 @@ static void perf_event_comm_event(struct perf_comm_event *comm_event) comm_event->comm = comm; comm_event->comm_size = size; - comm_event->event_id.header.size = sizeof(comm_event->event_id) + size; - perf_iterate_sb(perf_event_comm_output, comm_event, NULL); @@ -9446,15 +9442,8 @@ void perf_event_comm(struct task_struct *task, bool exec) .task = task, /* .comm */ /* .comm_size */ - .event_id = { - .header = { - .type = PERF_RECORD_COMM, - .misc = exec ? PERF_RECORD_MISC_COMM_EXEC : 0, - /* .size */ - }, - /* .pid */ - /* .tid */ - }, + /* .event_id */ + .exec = exec, }; perf_event_comm_event(&comm_event); @@ -9488,18 +9477,20 @@ static void perf_event_namespaces_output(struct perf_event *event, struct perf_namespaces_event *namespaces_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - u16 header_size = namespaces_event->event_id.header.size; int ret; if (!perf_event_namespaces_match(event)) return; - perf_event_header__init_id(&namespaces_event->event_id.header, - &sample, event); + perf_event_header__init(&namespaces_event->event_id.header, &sample, + PERF_RECORD_NAMESPACES, + /* misc= */ 0, + sizeof(namespaces_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, namespaces_event->event_id.header.size); if (ret) - goto out; + return; namespaces_event->event_id.pid = perf_event_pid(event, namespaces_event->task); @@ -9511,8 +9502,6 @@ static void perf_event_namespaces_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - namespaces_event->event_id.header.size = header_size; } static void perf_fill_ns_link_info(struct perf_ns_link_info *ns_link_info, @@ -9543,11 +9532,7 @@ void perf_event_namespaces(struct task_struct *task) namespaces_event = (struct perf_namespaces_event){ .task = task, .event_id = { - .header = { - .type = PERF_RECORD_NAMESPACES, - .misc = 0, - .size = sizeof(namespaces_event.event_id), - }, + /* .header */ /* .pid */ /* .tid */ .nr_namespaces = NR_NAMESPACES, @@ -9615,18 +9600,19 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data) struct perf_cgroup_event *cgroup_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - u16 header_size = cgroup_event->event_id.header.size; int ret; + u16 size = sizeof(cgroup_event->event_id) + cgroup_event->path_size; if (!perf_event_cgroup_match(event)) return; - perf_event_header__init_id(&cgroup_event->event_id.header, - &sample, event); + perf_event_header__init(&cgroup_event->event_id.header, &sample, + PERF_RECORD_CGROUP, /* misc= */ 0, size, + event); ret = perf_output_begin(&handle, &sample, event, cgroup_event->event_id.header.size); if (ret) - goto out; + return; perf_output_put(&handle, cgroup_event->event_id); __output_copy(&handle, cgroup_event->path, cgroup_event->path_size); @@ -9634,8 +9620,6 @@ static void perf_event_cgroup_output(struct perf_event *event, void *data) perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - cgroup_event->event_id.header.size = header_size; } static void perf_event_cgroup(struct cgroup *cgrp) @@ -9650,11 +9634,6 @@ static void perf_event_cgroup(struct cgroup *cgrp) cgroup_event = (struct perf_cgroup_event){ .event_id = { - .header = { - .type = PERF_RECORD_CGROUP, - .misc = 0, - .size = sizeof(cgroup_event.event_id), - }, .id = cgroup_id(cgrp), }, }; @@ -9677,7 +9656,6 @@ static void perf_event_cgroup(struct cgroup *cgrp) while (!IS_ALIGNED(size, sizeof(u64))) cgroup_event.path[size++] = '\0'; - cgroup_event.event_id.header.size += size; cgroup_event.path_size = size; perf_iterate_sb(perf_event_cgroup_output, @@ -9733,38 +9711,40 @@ static void perf_event_mmap_output(struct perf_event *event, struct perf_mmap_event *mmap_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int size = mmap_event->event_id.header.size; - u32 type = mmap_event->event_id.header.type; - bool use_build_id; + int size = sizeof(mmap_event->event_id) + mmap_event->file_size; + u32 type = PERF_RECORD_MMAP; + u16 misc = PERF_RECORD_MISC_USER; + bool use_build_id = false; int ret; if (!perf_event_mmap_match(event, data)) return; if (event->attr.mmap2) { - mmap_event->event_id.header.type = PERF_RECORD_MMAP2; - mmap_event->event_id.header.size += sizeof(mmap_event->maj); - mmap_event->event_id.header.size += sizeof(mmap_event->min); - mmap_event->event_id.header.size += sizeof(mmap_event->ino); - mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation); - mmap_event->event_id.header.size += sizeof(mmap_event->prot); - mmap_event->event_id.header.size += sizeof(mmap_event->flags); + type = PERF_RECORD_MMAP2; + size += sizeof(mmap_event->maj); + size += sizeof(mmap_event->min); + size += sizeof(mmap_event->ino); + size += sizeof(mmap_event->ino_generation); + size += sizeof(mmap_event->prot); + size += sizeof(mmap_event->flags); + use_build_id = event->attr.build_id && mmap_event->build_id_size; + if (use_build_id) + misc |= PERF_RECORD_MISC_MMAP_BUILD_ID; } + if (!(mmap_event->vma->vm_flags & VM_EXEC)) + misc |= PERF_RECORD_MISC_MMAP_DATA; - perf_event_header__init_id(&mmap_event->event_id.header, &sample, event); + perf_event_header__init(&mmap_event->event_id.header, &sample, + type, misc, size, event); ret = perf_output_begin(&handle, &sample, event, mmap_event->event_id.header.size); if (ret) - goto out; + return; mmap_event->event_id.pid = perf_event_pid(event, current); mmap_event->event_id.tid = perf_event_tid(event, current); - use_build_id = event->attr.build_id && mmap_event->build_id_size; - - if (event->attr.mmap2 && use_build_id) - mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_BUILD_ID; - perf_output_put(&handle, mmap_event->event_id); if (event->attr.mmap2) { @@ -9789,9 +9769,6 @@ static void perf_event_mmap_output(struct perf_event *event, perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - mmap_event->event_id.header.size = size; - mmap_event->event_id.header.type = type; } static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) @@ -9887,11 +9864,6 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) mmap_event->prot = prot; mmap_event->flags = flags; - if (!(vma->vm_flags & VM_EXEC)) - mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA; - - mmap_event->event_id.header.size = sizeof(mmap_event->event_id) + size; - if (atomic_read(&nr_build_id_events)) build_id_parse_nofault(vma, mmap_event->build_id, &mmap_event->build_id_size); @@ -10011,11 +9983,7 @@ void perf_event_mmap(struct vm_area_struct *vma) /* .file_name */ /* .file_size */ .event_id = { - .header = { - .type = PERF_RECORD_MMAP, - .misc = PERF_RECORD_MISC_USER, - /* .size */ - }, + /* .header */ /* .pid */ /* .tid */ .start = vma->vm_start, @@ -10045,18 +10013,15 @@ void perf_event_aux_event(struct perf_event *event, unsigned long head, u64 size; u64 flags; } rec = { - .header = { - .type = PERF_RECORD_AUX, - .misc = 0, - .size = sizeof(rec), - }, .offset = head, .size = size, .flags = flags, }; int ret; - perf_event_header__init_id(&rec.header, &sample, event); + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_AUX, /* misc= */ 0, sizeof(rec), + event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) @@ -10081,15 +10046,14 @@ void perf_log_lost_samples(struct perf_event *event, u64 lost) struct perf_event_header header; u64 lost; } lost_samples_event = { - .header = { - .type = PERF_RECORD_LOST_SAMPLES, - .misc = 0, - .size = sizeof(lost_samples_event), - }, .lost = lost, }; - perf_event_header__init_id(&lost_samples_event.header, &sample, event); + perf_event_header__init(&lost_samples_event.header, &sample, + PERF_RECORD_LOST_SAMPLES, + /* misc= */ 0, + sizeof(lost_samples_event), + event); ret = perf_output_begin(&handle, &sample, event, lost_samples_event.header.size); @@ -10114,6 +10078,8 @@ struct perf_switch_event { u32 next_prev_pid; u32 next_prev_tid; } event_id; + bool sched_in; + bool preempt; }; static int perf_event_switch_match(struct perf_event *event) @@ -10126,6 +10092,9 @@ static void perf_event_switch_output(struct perf_event *event, void *data) struct perf_switch_event *se = data; struct perf_output_handle handle; struct perf_sample_data sample; + u32 type; + u16 misc; + u16 size; int ret; if (!perf_event_switch_match(event)) @@ -10133,18 +10102,22 @@ static void perf_event_switch_output(struct perf_event *event, void *data) /* Only CPU-wide events are allowed to see next/prev pid/tid */ if (event->ctx->task) { - se->event_id.header.type = PERF_RECORD_SWITCH; - se->event_id.header.size = sizeof(se->event_id.header); + type = PERF_RECORD_SWITCH; + size = sizeof(se->event_id.header); } else { - se->event_id.header.type = PERF_RECORD_SWITCH_CPU_WIDE; - se->event_id.header.size = sizeof(se->event_id); + type = PERF_RECORD_SWITCH_CPU_WIDE; + size = sizeof(se->event_id); se->event_id.next_prev_pid = perf_event_pid(event, se->next_prev); se->event_id.next_prev_tid = perf_event_tid(event, se->next_prev); } + misc = se->sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT; + if (se->preempt) + misc |= PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; - perf_event_header__init_id(&se->event_id.header, &sample, event); + perf_event_header__init(&se->event_id.header, &sample, + type, misc, size, event); ret = perf_output_begin(&handle, &sample, event, se->event_id.header.size); if (ret) @@ -10170,22 +10143,11 @@ static void perf_event_switch(struct task_struct *task, switch_event = (struct perf_switch_event){ .task = task, .next_prev = next_prev, - .event_id = { - .header = { - /* .type */ - .misc = sched_in ? 0 : PERF_RECORD_MISC_SWITCH_OUT, - /* .size */ - }, - /* .next_prev_pid */ - /* .next_prev_tid */ - }, + /* .event_id */ + .sched_in = sched_in, + .preempt = !sched_in && task_is_runnable(task), }; - if (!sched_in && task_is_runnable(task)) { - switch_event.event_id.header.misc |= - PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; - } - perf_iterate_sb(perf_event_switch_output, &switch_event, NULL); } @@ -10205,20 +10167,17 @@ static void perf_log_throttle(struct perf_event *event, int enable) u64 id; u64 stream_id; } throttle_event = { - .header = { - .type = PERF_RECORD_THROTTLE, - .misc = 0, - .size = sizeof(throttle_event), - }, .time = perf_event_clock(event), .id = primary_event_id(event), .stream_id = event->id, }; - if (enable) - throttle_event.header.type = PERF_RECORD_UNTHROTTLE; - - perf_event_header__init_id(&throttle_event.header, &sample, event); + perf_event_header__init(&throttle_event.header, &sample, + enable ? PERF_RECORD_UNTHROTTLE + : PERF_RECORD_THROTTLE, + /* misc= */ 0, + sizeof(throttle_event), + event); ret = perf_output_begin(&handle, &sample, event, throttle_event.header.size); @@ -10257,12 +10216,14 @@ static void perf_event_ksymbol_output(struct perf_event *event, void *data) struct perf_output_handle handle; struct perf_sample_data sample; int ret; + u16 size = sizeof(ksymbol_event->event_id) + ksymbol_event->name_len; if (!perf_event_ksymbol_match(event)) return; - perf_event_header__init_id(&ksymbol_event->event_id.header, - &sample, event); + perf_event_header__init(&ksymbol_event->event_id.header, &sample, + PERF_RECORD_KSYMBOL, /* misc= */ 0, size, + event); ret = perf_output_begin(&handle, &sample, event, ksymbol_event->event_id.header.size); if (ret) @@ -10303,11 +10264,6 @@ void perf_event_ksymbol(u16 ksym_type, u64 addr, u32 len, bool unregister, .name = name, .name_len = name_len, .event_id = { - .header = { - .type = PERF_RECORD_KSYMBOL, - .size = sizeof(ksymbol_event.event_id) + - name_len, - }, .addr = addr, .len = len, .ksym_type = ksym_type, @@ -10351,8 +10307,11 @@ static void perf_event_bpf_output(struct perf_event *event, void *data) if (!perf_event_bpf_match(event)) return; - perf_event_header__init_id(&bpf_event->event_id.header, - &sample, event); + perf_event_header__init(&bpf_event->event_id.header, &sample, + PERF_RECORD_BPF_EVENT, + /* misc= */ 0, + sizeof(bpf_event->event_id), + event); ret = perf_output_begin(&handle, &sample, event, bpf_event->event_id.header.size); if (ret) @@ -10408,10 +10367,6 @@ void perf_event_bpf_event(struct bpf_prog *prog, bpf_event = (struct perf_bpf_event){ .prog = prog, .event_id = { - .header = { - .type = PERF_RECORD_BPF_EVENT, - .size = sizeof(bpf_event.event_id), - }, .type = type, .flags = flags, .id = prog->aux->id, @@ -10439,18 +10394,23 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data) struct perf_callchain_deferred_event *deferred_event = data; struct perf_output_handle handle; struct perf_sample_data sample; - int ret, size = deferred_event->event.header.size; + int ret; + u16 size = sizeof(deferred_event->event) + (deferred_event->trace->nr * sizeof(u64)); if (!event->attr.defer_output) return; /* XXX do we really need sample_id_all for this ??? */ - perf_event_header__init_id(&deferred_event->event.header, &sample, event); + perf_event_header__init(&deferred_event->event.header, &sample, + PERF_RECORD_CALLCHAIN_DEFERRED, + PERF_RECORD_MISC_USER, + size, + event); ret = perf_output_begin(&handle, &sample, event, deferred_event->event.header.size); if (ret) - goto out; + return; perf_output_put(&handle, deferred_event->event); for (int i = 0; i < deferred_event->trace->nr; i++) { @@ -10460,8 +10420,6 @@ static void perf_callchain_deferred_output(struct perf_event *event, void *data) perf_event__output_id_sample(event, &handle, &sample); perf_output_end(&handle); -out: - deferred_event->event.header.size = size; } static void perf_unwind_deferred_callback(struct unwind_work *work, @@ -10470,12 +10428,6 @@ static void perf_unwind_deferred_callback(struct unwind_work *work, struct perf_callchain_deferred_event deferred_event = { .trace = trace, .event = { - .header = { - .type = PERF_RECORD_CALLCHAIN_DEFERRED, - .misc = PERF_RECORD_MISC_USER, - .size = sizeof(deferred_event.event) + - (trace->nr * sizeof(u64)), - }, .cookie = cookie, .nr = trace->nr, }, @@ -10487,7 +10439,8 @@ static void perf_unwind_deferred_callback(struct unwind_work *work, struct perf_text_poke_event { const void *old_bytes; const void *new_bytes; - size_t pad; + u16 tot; + u16 pad; u16 old_len; u16 new_len; @@ -10508,13 +10461,18 @@ static void perf_event_text_poke_output(struct perf_event *event, void *data) struct perf_text_poke_event *text_poke_event = data; struct perf_output_handle handle; struct perf_sample_data sample; + u16 size = sizeof(text_poke_event->event_id) + text_poke_event->tot + text_poke_event->pad; u64 padding = 0; int ret; if (!perf_event_text_poke_match(event)) return; - perf_event_header__init_id(&text_poke_event->event_id.header, &sample, event); + perf_event_header__init(&text_poke_event->event_id.header, &sample, + PERF_RECORD_TEXT_POKE, + PERF_RECORD_MISC_KERNEL, + size, + event); ret = perf_output_begin(&handle, &sample, event, text_poke_event->event_id.header.size); @@ -10552,15 +10510,11 @@ void perf_event_text_poke(const void *addr, const void *old_bytes, text_poke_event = (struct perf_text_poke_event){ .old_bytes = old_bytes, .new_bytes = new_bytes, + .tot = tot, .pad = pad, .old_len = old_len, .new_len = new_len, .event_id = { - .header = { - .type = PERF_RECORD_TEXT_POKE, - .misc = PERF_RECORD_MISC_KERNEL, - .size = sizeof(text_poke_event.event_id) + tot + pad, - }, .addr = (unsigned long)addr, }, }; @@ -10591,13 +10545,12 @@ static void perf_log_itrace_start(struct perf_event *event) event->attach_state & PERF_ATTACH_ITRACE) return; - rec.header.type = PERF_RECORD_ITRACE_START; - rec.header.misc = 0; - rec.header.size = sizeof(rec); rec.pid = perf_event_pid(event, current); rec.tid = perf_event_tid(event, current); - perf_event_header__init_id(&rec.header, &sample, event); + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_ITRACE_START, /* misc= */ 0, sizeof(rec), + event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) @@ -10622,12 +10575,10 @@ void perf_report_aux_output_id(struct perf_event *event, u64 hw_id) if (event->parent) event = event->parent; - rec.header.type = PERF_RECORD_AUX_OUTPUT_HW_ID; - rec.header.misc = 0; - rec.header.size = sizeof(rec); - rec.hw_id = hw_id; - - perf_event_header__init_id(&rec.header, &sample, event); + rec.hw_id = hw_id; + perf_event_header__init(&rec.header, &sample, + PERF_RECORD_AUX_OUTPUT_HW_ID, /* misc= */ 0, + sizeof(rec), event); ret = perf_output_begin(&handle, &sample, event, rec.header.size); if (ret) diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 1b1ffe0533e5..13bd42e5493d 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -246,14 +246,13 @@ __perf_output_begin(struct perf_output_handle *handle, handle->size = (1UL << page_shift) - offset; if (unlikely(have_lost)) { - lost_event.header.size = sizeof(lost_event); - lost_event.header.type = PERF_RECORD_LOST; - lost_event.header.misc = 0; lost_event.id = event->id; lost_event.lost = local_xchg(&rb->lost, 0); /* XXX mostly redundant; @data is already fully initializes */ - perf_event_header__init_id(&lost_event.header, data, event); + perf_event_header__init(&lost_event.header, data, + PERF_RECORD_LOST, /* misc= */ 0, + sizeof(lost_event), event); perf_output_put(handle, lost_event); perf_event__output_id_sample(event, handle, data); } From 357e8a77a501d96c9517f01f4a211eed5e9c9184 Mon Sep 17 00:00:00 2001 From: Zhengchuan Liang Date: Mon, 28 Sep 2026 10:59:35 -0700 Subject: [PATCH 483/535] perf: Require kernel access for text poke events Perf events with exclude_kernel=1 can be opened without kernel perf access. However, exclude_kernel does not suppress text-poke sideband records. Every PERF_RECORD_TEXT_POKE is marked PERF_RECORD_MISC_KERNEL and contains a raw kernel instruction address. An unprivileged task can therefore open and mmap a task-local software event with text_poke=1. Both opening a count-only tracepoint event and configuring UDP GRO for ESP-in-UDP cause updates to inline static calls; the observer receives the relocated addresses of the modified instructions. For a known kernel image, any such address reveals the runtime kernel text base despite KASLR. Call perf_allow_kernel() whenever attr.text_poke is set, regardless of exclude_kernel. Events that neither monitor kernel execution nor request text-poke records retain their existing permissions. Fixes: e17d43b93e54 ("perf: Add perf text poke event") Assisted-by: LLM Signed-off-by: Zhengchuan Liang Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/99131354c41e23188f778b92f90363775b482395.1790573390.git.zcliangcn@gmail.com --- kernel/events/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/events/core.c b/kernel/events/core.c index 3aa223595e7d..7846d70be57f 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -13906,7 +13906,7 @@ SYSCALL_DEFINE5(perf_event_open, if (err) return err; - if (!attr.exclude_kernel || + if (!attr.exclude_kernel || attr.text_poke || ((attr.sample_type & PERF_SAMPLE_CALLCHAIN) && !attr.exclude_callchain_kernel)) { err = perf_allow_kernel(); From 573371caf7aea8582212da81dcead2c3f48fac4b Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Thu, 1 Oct 2026 11:00:31 +0200 Subject: [PATCH 484/535] arm64: errata: Factor out broken AMU const counter cap Move the workaround from the erratum 2457168-specific cpucap to a generic broken AMU constant-counter one. This keeps the existing Cortex-A510 handling unchanged while allowing other errata with similar AMU constant counter issue to share the capability bit and call sites. Signed-off-by: Beata Michalska Reviewed-by: Vladimir Murzin Signed-off-by: Will Deacon --- arch/arm64/Kconfig | 8 ++++++++ arch/arm64/kernel/cpu_errata.c | 20 ++++++++++++++------ arch/arm64/kernel/cpufeature.c | 2 +- arch/arm64/kernel/topology.c | 7 ++++--- arch/arm64/tools/cpucaps | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef944..3b6c052d46a1 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1040,9 +1040,17 @@ config ARM64_ERRATUM_1902691 If unsure, say Y. +config ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT + bool + # Targeting errata affecting AMEVCNTR01, the AMU constant counter. + # On affected CPUs the counter may not increment at the expected rate, + # making it unreliable. As a result, AMU users should treat it as + # unavailable rather than relying on misleading counter values. + config ARM64_ERRATUM_2457168 bool "Cortex-A510: 2457168: workaround for AMEVCNTR01 incrementing incorrectly" depends on ARM64_AMU_EXTN + select ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT default y help This option adds the workaround for ARM Cortex-A510 erratum 2457168. diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index 8ec47d89b45b..dbd70728a9bd 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -384,6 +384,16 @@ static const struct arm64_cpu_capabilities arm64_repeat_tlbi_list[] = { }; #endif +#ifdef CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT +static const struct midr_range workaround_amu_constcnt_list[] = { +#ifdef CONFIG_ARM64_ERRATUM_2457168 + /* Cortex-A510 r0p0-r1p1 */ + MIDR_RANGE(MIDR_CORTEX_A510, 0, 0, 1, 1), +#endif + {} +}; +#endif /* CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT */ + #ifdef CONFIG_CAVIUM_ERRATUM_23154 static const struct midr_range cavium_erratum_23154_cpus[] = { MIDR_ALL_VERSIONS(MIDR_THUNDERX), @@ -919,14 +929,12 @@ const struct arm64_cpu_capabilities arm64_errata[] = { ERRATA_MIDR_REV_RANGE(MIDR_CORTEX_A510, 0, 0, 2) }, #endif -#ifdef CONFIG_ARM64_ERRATUM_2457168 +#ifdef CONFIG_ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT { - .desc = "ARM erratum 2457168", - .capability = ARM64_WORKAROUND_2457168, + .desc = "Broken AMU AMEVCNTR01 (const counter)", + .capability = ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT, .type = ARM64_CPUCAP_WEAK_LOCAL_CPU_FEATURE, - - /* Cortex-A510 r0p0-r1p1 */ - CAP_MIDR_RANGE(MIDR_CORTEX_A510, 0, 0, 1, 1) + CAP_MIDR_RANGE_LIST(workaround_amu_constcnt_list) }, #endif #ifdef CONFIG_ARM64_ERRATUM_2038923 diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa..3ae3ad1a1cdf 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -2089,7 +2089,7 @@ static void cpu_amu_enable(struct arm64_cpu_capabilities const *cap) cpumask_set_cpu(smp_processor_id(), &amu_cpus); /* 0 reference values signal broken/disabled counters */ - if (!this_cpu_has_cap(ARM64_WORKAROUND_2457168)) + if (!this_cpu_has_cap(ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT)) update_freq_counters_refs(); } } diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 1773dac6fdf8..214d153cba6e 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -400,7 +400,7 @@ static void cpu_read_constcnt(void *val) * the counter is disabled. A return value of 0 at counter read is * properly handled as an error case by the users of the counter. */ - *(u64 *)val = this_cpu_has_cap(ARM64_WORKAROUND_2457168) ? + *(u64 *)val = this_cpu_has_cap(ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT) ? 0UL : read_constcnt(); } @@ -461,8 +461,9 @@ static void amu_read_core_const_ctrs(void *val) /* * cpu_read_constcnt() incurs slight latency due to the - * ARM64_WORKAROUND_2457168 check. Read it first to minimize - * the sampling skew between the const and core counters. + * ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT check. + * Read it first to minimize the sampling skew between the const + * and core counters. */ cpu_read_constcnt(&ctrs->constcnt); cpu_read_corecnt(&ctrs->corecnt); diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index 2775ba3359cf..58b6c084f9f2 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -105,7 +105,6 @@ WORKAROUND_1902691 WORKAROUND_2038923 WORKAROUND_2064142 WORKAROUND_2077057 -WORKAROUND_2457168 WORKAROUND_2645198 WORKAROUND_2658417 WORKAROUND_4193714 @@ -132,3 +131,4 @@ WORKAROUND_REPEAT_TLBI_SYNC WORKAROUND_SPECULATIVE_AT WORKAROUND_SPECULATIVE_SSBS WORKAROUND_SPECULATIVE_UNPRIV_LOAD +WORKAROUND_BROKEN_AMU_CONSTCNT From 45f7304679875eb000d67e194090ee7abd05c89c Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Thu, 1 Oct 2026 11:00:32 +0200 Subject: [PATCH 485/535] arm64: errata: Add Cortex-A725 erratum 3821522 workaround Cortex-A725 erratum 3821522 affects the CNT_CYCLES event, which can incur a significant increment error when a CPU enters and subsequently exits WFE or WFI, and may no longer track the system counter frequency. The AMEVCNTR01_EL0 counter is used as the AMU constant counter for frequency invariance and CPPC FFH feedback counters. Wire the affected Cortex-A725 range into the shared broken AMU constant-counter capability so the affected counter is treated as unavailable by returning zero in the AMU counter paths. This prevents the broken counter from being used as a reference source. The erratum can also affect PMUv3 users of the CNT_CYCLES event, but this workaround intentionally does not change PMU event handling. Hiding or rejecting the PMU event from the erratum code would change the perf-visible PMU event interface, including raw event selection, and would need a separate PMU-specific approach rather than being folded into the AMU reference-counter workaround. Cc: stable@vger.kernel.org Signed-off-by: Beata Michalska Reviewed-by: Vladimir Murzin Signed-off-by: Will Deacon --- Documentation/arch/arm64/silicon-errata.rst | 2 ++ arch/arm64/Kconfig | 22 +++++++++++++++++++++ arch/arm64/kernel/cpu_errata.c | 4 ++++ arch/arm64/kernel/topology.c | 9 +++++---- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index ac3248b9f2f3..99a1eb4b833d 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -174,6 +174,8 @@ stable kernels. +----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-A725 | #3456106 | ARM64_ERRATUM_3194386 | +----------------+-----------------+-----------------+-----------------------------+ +| ARM | Cortex-A725 | #3821522 | ARM64_ERRATUM_3821522 | ++----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-X1 | #1502854 | N/A | +----------------+-----------------+-----------------+-----------------------------+ | ARM | Cortex-X1 | #3324344 | ARM64_ERRATUM_3194386 | diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 3b6c052d46a1..cab741a695f5 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1081,6 +1081,28 @@ config ARM64_ERRATUM_2645198 If unsure, say Y. +config ARM64_ERRATUM_3821522 + bool "Cortex-A725: 3821522: workaround for possible CNT_CYCLES increment error due to WFE/WFI" + depends on ARM64_AMU_EXTN + select ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT + default y + help + This option adds the workaround for ARM Cortex-A725 erratum 3821522. + + On affected A725 cores, the CNT_CYCLES event may incur a significant + increment error when entering and subsequently exiting WFx. + As a result, the CNT_CYCLES may diverge from the system counter + frequency at which it is expected to increment. + This renders the AMU counter AMEVCNTR01, that implements CNT_CYCLES, + being unreliable and unsuitable for use. + + Since there is no hardware workaround, reads of the affected CNT_CYCLES + counter return 0 in the relevant paths. This causes users of the counter + to treat it as unavailable and is functionally equivalent to firmware + disabling the affected counter. + + If unsure, say Y. + config ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD bool diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index dbd70728a9bd..e0c09402540c 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -389,6 +389,10 @@ static const struct midr_range workaround_amu_constcnt_list[] = { #ifdef CONFIG_ARM64_ERRATUM_2457168 /* Cortex-A510 r0p0-r1p1 */ MIDR_RANGE(MIDR_CORTEX_A510, 0, 0, 1, 1), +#endif +#ifdef CONFIG_ARM64_ERRATUM_3821522 + /* Cortex-A725 r0p0 - r0p2 */ + MIDR_RANGE(MIDR_CORTEX_A725, 0, 0, 0, 2), #endif {} }; diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 214d153cba6e..806373aac3db 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -395,10 +395,11 @@ static void cpu_read_corecnt(void *val) static void cpu_read_constcnt(void *val) { /* - * Return 0 if the current CPU is affected by erratum 2457168. A value - * of 0 is also returned if the current CPU does not support AMUs or if - * the counter is disabled. A return value of 0 at counter read is - * properly handled as an error case by the users of the counter. + * Return 0 if the current CPU is affected by a HW erratum. + * A value of 0 is also returned if the current CPU does not + * support AMUs or if the counter is disabled. A return + * value of 0 at counter read is properly handled as an error + * case by the users of the counter. */ *(u64 *)val = this_cpu_has_cap(ARM64_WORKAROUND_BROKEN_AMU_CONSTCNT) ? 0UL : read_constcnt(); From f1d565dc92955f05383794e0fa27e61ee0555027 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 1 Oct 2026 17:10:35 +0200 Subject: [PATCH 486/535] ALSA: core: Define auto-cleanup for snd_card_free() For the errors at the probe time, we'd need to call rather snd_card_free() instead of the snd_card_unref() -- the former calls explicitly snd_card_disconnect() that cleans up the registered devices, etc, while the latter may leak in corner cases. For convenience, define a new auto-clean with snd_card_free. Link: https://patch.msgid.link/20261001151039.592033-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/core.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/sound/core.h b/include/sound/core.h index 2ca24ac7e37f..15be947a730e 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -340,6 +340,15 @@ static inline void snd_card_unref(struct snd_card *card) DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) +/* + * For automatic error handling at probe time, assign the card like: + * struct snd_card *card __free(snd_card_free) = NULL; + * then create and process as usual. + * But, don't forget to clear to NULL at successful return for avoiding + * the unexpected cleanup! + */ +DEFINE_FREE(snd_card_free, struct snd_card *, if (_T) snd_card_free(_T)) + #define snd_card_set_dev(card, devptr) ((card)->dev = (devptr)) /* device.c */ From e6229c0402ea4863ec74802d79d5b52b9398d66c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 1 Oct 2026 17:10:36 +0200 Subject: [PATCH 487/535] ALSA: ice1712: Fix the error handling via auto-cleanup at probe We used the auto-cleanup via snd_card_unref() for errors at probe of ice1712 driver, but this may be problematic in a subtle way when an error happens at snd_card_register(); since snd_card_unref() skips the snd_card_disconnect() call, it may miss some resource clearance. For fixing the issue, use the new __free(snd_card_free) instead, which does call snd_card_free() explicitly at errors for avoiding such a pitfall. Fixes: d736eba9c453 ("ALSA: ice1712: Fix the card leak at probe error with the auto-cleanup") Link: https://patch.msgid.link/20261001151039.592033-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/pci/ice1712/ice1712.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index d6bfd8fb20e6..1fad8c418132 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2523,7 +2523,7 @@ static int snd_ice1712_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card __free(snd_card_unref) = NULL; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info * const *tbl, *c; From cad16d850e3759dd82cd869f739b56e9844a1fee Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Thu, 1 Oct 2026 09:58:30 +0100 Subject: [PATCH 488/535] spi: cs42l43: Workaround for wrong speaker ID on Dell XPS 13 DX13260 On Dell XPS 13 DX13260 create an acpi_gpio_mapping with exactly two GPIO entries to point at the two pins in the GpioIo(). Use this to read the speaker ID GPIOs. This fixes problems on Dell XPS 13 DX13260: - No speaker audio - The wrong firmware was loaded so the speaker protection did not match the speaker characteristics. The Dell XPS 13 DX13260 has two speaker ID GPIOs, to form a 2-bit ID. The ACPI GpioIo() has both pins but the Linux-specific spk-id-gpios property only has a mapping to the first pin. This meant that the speaker ID was wrong in most cases, and that would lead to the codec driver loading the wrong amp firmware, or not finding a firmware (as 0 is not a valid ID on this laptop). Assisted-by: Codex:gpt-6-sol Reported-by: Wiza Jalakasi Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221956 Tested-by: Wiza Jalakasi Fixes: f3c605147741e ("spi: cs42l43: Add GPIO speaker id support to the bridge configuration") Signed-off-by: Richard Fitzgerald Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20261001085830.4014291-1-rf@opensource.cirrus.com Signed-off-by: Mark Brown --- drivers/spi/spi-cs42l43.c | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/drivers/spi/spi-cs42l43.c b/drivers/spi/spi-cs42l43.c index 7106a8a4f280..6772abb483dd 100644 --- a/drivers/spi/spi-cs42l43.c +++ b/drivers/spi/spi-cs42l43.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -215,12 +216,108 @@ static size_t cs42l43_spi_max_length(struct spi_device *spi) return CS42L43_SPI_MAX_LENGTH; } +/* + * Workaround needed for two speaker ID pins in one ACPI GpioIo() but + * the Linux-specific _DSD property only contains one pin. + * Create a temporary acpi_gpio_mapping pointing at both pins. + */ +static const struct acpi_gpio_params cs42l43_2bit_speaker_id_from_one_gpioio_params[] = { + [0] = { + .crs_entry_index = 0, + .line_index = 0, + }, + [1] = { + .crs_entry_index = 0, + .line_index = 1, + }, +}; + +static const struct acpi_gpio_mapping cs42l43_2bit_speaker_id_from_one_gpioio_mapping[] = { + { + .name = "spk-id-quirk-gpios", + .data = cs42l43_2bit_speaker_id_from_one_gpioio_params, + .size = ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params), + }, + { } +}; + +static int cs42l43_get_2bit_speaker_id_from_one_gpioio(struct cs42l43_spi *priv, int *result) +{ + struct fwnode_reference_args args; + struct acpi_device *adev; + struct gpio_desc *desc; + u32 spkid = 0; + int i, ret; + + /* Use the _DSD property to get the node containing the GpioIo() */ + ret = fwnode_property_get_reference_args(dev_fwnode(priv->dev), "spk-id-gpios", + NULL, 3, 0, &args); + if (ret) + return ret; + + struct fwnode_handle *fwnode __free(fwnode_handle) = args.fwnode; + + /* An acpi_gpio_mapping must be added to the node that contains the GpioIo() */ + adev = to_acpi_device_node(fwnode); + if (!adev) + return -EINVAL; + + ret = acpi_dev_add_driver_gpios(adev, cs42l43_2bit_speaker_id_from_one_gpioio_mapping); + if (ret) + return ret; + + /* gpiod_get_array() can't read from a mapping in a child node */ + for (i = 0; i < ARRAY_SIZE(cs42l43_2bit_speaker_id_from_one_gpioio_params); i++) { + desc = fwnode_gpiod_get_index(fwnode, "spk-id-quirk", i, GPIOD_IN, + dev_name(priv->dev)); + if (IS_ERR(desc)) { + ret = PTR_ERR(desc); + goto out; + } + + ret = gpiod_get_value_cansleep(desc); + gpiod_put(desc); + if (ret < 0) + goto out; + + spkid |= (u32)ret << i; + } + + dev_dbg(priv->dev, "spk-id-gpios = %u\n", spkid); + *result = spkid; + ret = 0; +out: + acpi_dev_remove_driver_gpios(adev); + + return ret; +} + +static const struct dmi_system_id cs42l43_spk_id_quirks[] = { + { + .ident = "Dell XPS 13 DX13260", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0E53"), + }, + .driver_data = cs42l43_get_2bit_speaker_id_from_one_gpioio, + }, + { } +}; + static int cs42l43_get_speaker_id_gpios(struct cs42l43_spi *priv, int *result) { + const struct dmi_system_id *dmi_id; struct gpio_descs *descs; u32 spkid; int i, ret; + dmi_id = dmi_first_match(cs42l43_spk_id_quirks); + if (dmi_id) { + int (*get_speaker_id)(struct cs42l43_spi *priv, int *result) = dmi_id->driver_data; + + return get_speaker_id(priv, result); + } + descs = gpiod_get_array_optional(priv->dev, "spk-id", GPIOD_IN); if (!descs) return 0; From e060d9069b49fe55f38057dfe592068014652671 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 1 Oct 2026 14:08:36 +0100 Subject: [PATCH 489/535] arm64/fpsimd: signal: Forbid non-streaming SVE payload on SME-only systems When system_supports_sme() is true but system_supports_sve() is false, restoring a specifically crafted SVE signal context can result in the task erroneously having non-streaming SVE state. Subsequent attempts to manipulate the task's FPSIMD/SVE/SME state can result in a variety of problems, including fatal EL1 UNDEFs. In such configurations, the kernel always creates an SVE signal context when delivering a signal, and this can only be in one of two states: (1) SVE_SIG_FLAG_SM is set, and an SVE payload is present containing streaming mode SVE state. The recorded VL is the task's live streaming VL. (2) SVE_SIG_FLAG_SM is clear, and an SVE payload is not present. The FPSIMD context contains the non-streaming mode FPSIMD state. The recorded VL is 0. Currently restore_sve_fpsimd_context() correctly rejects cases where SVE_SIG_FLAG_SM is set and an SVE payload is not present, but fails to reject cases where SVE_SIG_FLAG_SM is clear and an SVE payload is present. Consequently, restore_sve_fpsimd_context() can place the task in a state where it has non-streaming SVE state even when this is not supported by HW. For example, this can cause a later EL1 UNDEF when the kernel attempts to restore the task's ZCR_EL1 value: | # ./sme-sigcontext-to-sve | Internal error: Oops - Undefined instruction: 0000000002000000 [#1] SMP | Modules linked in: | CPU: 0 UID: 0 PID: 131 Comm: sme-sigcontext- Not tainted 7.3.0-rc1 #1 PREEMPT | Hardware name: linux,dummy-virt (DT) | pstate: 61402009 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) | pc : fpsimd_restore_current_state+0x258/0x458 | lr : exit_to_user_mode_loop+0xb8/0x188 | sp : ffff80008056be40 | x29: ffff80008056be40 x28: fff00000c1670000 x27: 0000000000000000 | x26: 0000000000000000 x25: 0000000000000000 x24: 0000000000000000 | x23: ffff80008056bec0 x22: 0000000000000008 x21: 0000000000000040 | x20: 0000000000000081 x19: 0000000008800010 x18: 0000000000000000 | x17: 0000fffffd412570 x16: 0000000000001000 x15: 0000fffffd4123b0 | x14: 0000fffffd412780 x13: 0000fffffd412be8 x12: 0000000047435300 | x11: 0000fffffd412570 x10: 0000000000000000 x9 : 0000000045585401 | x8 : fff00000c18a6c44 x7 : 0000000000000000 x6 : 0000000000000002 | x5 : 0000000000000002 x4 : ffff800080568000 x3 : 0000000000000001 | x2 : 0000000008800010 x1 : fff00000c1670000 x0 : 0000000008800000 | Call trace: | fpsimd_restore_current_state+0x258/0x458 (P) | exit_to_user_mode_loop+0xb8/0x188 | el0_svc+0x1cc/0x1d0 | el0t_64_sync_handler+0xa0/0xe4 | el0t_64_sync+0x198/0x19c | Code: d5384101 f9400020 53175c03 36b80de0 (d5381202) | ---[ end trace 0000000000000000 ]--- | Kernel panic - not syncing: Oops - Undefined instruction: Fatal exception in interrupt | Kernel Offset: 0x291fb1000000 from 0xffff800080000000 | PHYS_OFFSET: 0x40000000 | CPU features: 0x0,00000000,0052802f,ffb88f43,3afcf73f | Memory Limit: none Rework restore_sve_fpsimd_context() to reject cases where SVE_SIG_FLAG_SM is clear and an SVE payload is not present. As parse_user_sigframe() rejects SVE signal frames when neither SVE nor SME are supported, it isn't necessary for restore_sve_fpsimd_context() to handle the case where neither are supported. Fixes: 7dde62f0687c ("arm64/signal: Always accept SVE signal frames on SME only systems") Signed-off-by: Mark Rutland Reviewed-by: Mark Brown Cc: Catalin Marinas Cc: Will Deacon Cc: stable@vger.kernel.org Signed-off-by: Will Deacon --- arch/arm64/kernel/signal.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 38e6fa204c17..6a06a4e352b1 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -433,6 +433,7 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) unsigned int vl, vq; struct user_fpsimd_state fpsimd; u16 user_vl, flags; + bool fpsimd_only; bool sm; if (user->sve_size < sizeof(*user->sve)) @@ -443,19 +444,33 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (err) return err; + fpsimd_only = (user->sve_size == sizeof(*user->sve)); sm = flags & SVE_SIG_FLAG_SM; + if (sm) { if (!system_supports_sme()) return -EINVAL; + /* + * Streaming SVE state is always preserved with an SVE payload. + * Only accept streaming state which has an SVE payload. + */ + if (fpsimd_only) + return -EINVAL; + vl = task_get_sme_vl(current); } else { /* - * A SME only system use SVE for streaming mode so can - * have a SVE formatted context with a zero VL and no - * payload data. + * Non-streaming SVE state may be preserved without an SVE + * payload, in which case all state is saved in the FPSIMD + * context. + * + * On SME-only systems, non-streaming (FPSIMD-only) state is + * always preserved without an SVE payload, and with VL==0. On + * such systems, only accept non-streaming state without an SVE + * payload. */ - if (!system_supports_sve() && !system_supports_sme()) + if (!system_supports_sve() && !fpsimd_only) return -EINVAL; vl = task_get_sve_vl(current); @@ -464,16 +479,7 @@ static int restore_sve_fpsimd_context(struct user_ctxs *user) if (user_vl != vl) return -EINVAL; - /* - * Non-streaming SVE state may be preserved without an SVE payload, in - * which case the SVE context only has a header with VL==0, and all - * state can be restored from the FPSIMD context. - * - * Streaming SVE state is always preserved with an SVE payload. For - * consistency and robustness, reject restoring streaming SVE state - * without an SVE payload. - */ - if (!sm && user->sve_size == sizeof(*user->sve)) + if (fpsimd_only) return restore_fpsimd_context(user); vq = sve_vq_from_vl(vl); From 71cc2c67fb8f8d5aa8154eb482e9846d2214f11b Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Tue, 29 Sep 2026 20:02:09 +0000 Subject: [PATCH 490/535] KVM: arm64: Use stage-1 leaf size for VM_PFNMAP When commit 2aa53d68cee6 ("KVM: arm64: Try stage2 block mapping for host device MMIO") added VM_PFNMAP support to get_vma_page_shift(), stage-1 page tables did not support huge PFNMAP (as in VFIO-PCI vfio_pci_mmap_huge_fault()) and transparent_hugepage_adjust() was unsafe for MMIO as it dereferenced struct page. Since commit 6011cf68c885 ("KVM: arm64: Walk userspace page tables to compute the THP mapping size"), transparent_hugepage_adjust() instead walks the host stage-1 page tables via get_user_mapping_size() without touching struct page. Meanwhile, commit 3e509c9b03f9 ("mm/arm64: support large pfn mappings") enabled stage-1 huge PFNMAP. So. we can drop the VMA-based VM_PFNMAP size calculation in get_vma_page_shift() and let transparent_hugepage_adjust() derive the stage-2 mapping size directly from the populated stage-1 leaf for non-cacheable mappings as well. Assisted-by: LLM Cc: stable@vger.kernel.org Fixes: 2aa53d68cee6 ("KVM: arm64: Try stage2 block mapping for host device MMIO") Reviewed-by: Marc Zyngier Signed-off-by: Mostafa Saleh Signed-off-by: Oliver Upton --- arch/arm64/kvm/mmu.c | 47 ++++++-------------------------------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 2d44cd6a5aed..bf6d6526639f 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1468,32 +1468,11 @@ transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot, return PAGE_SIZE; } -static int get_vma_page_shift(struct vm_area_struct *vma, unsigned long hva) +static int get_vma_page_shift(struct vm_area_struct *vma) { - unsigned long pa; - - if (is_vm_hugetlb_page(vma) && !(vma->vm_flags & VM_PFNMAP)) + if (is_vm_hugetlb_page(vma)) return huge_page_shift(hstate_vma(vma)); - if (!(vma->vm_flags & VM_PFNMAP)) - return PAGE_SHIFT; - - VM_BUG_ON(is_vm_hugetlb_page(vma)); - - pa = (vma->vm_pgoff << PAGE_SHIFT) + (hva - vma->vm_start); - -#ifndef __PAGETABLE_PMD_FOLDED - if ((hva & (PUD_SIZE - 1)) == (pa & (PUD_SIZE - 1)) && - ALIGN_DOWN(hva, PUD_SIZE) >= vma->vm_start && - ALIGN(hva, PUD_SIZE) <= vma->vm_end) - return PUD_SHIFT; -#endif - - if ((hva & (PMD_SIZE - 1)) == (pa & (PMD_SIZE - 1)) && - ALIGN_DOWN(hva, PMD_SIZE) >= vma->vm_start && - ALIGN(hva, PMD_SIZE) <= vma->vm_end) - return PMD_SHIFT; - return PAGE_SHIFT; } @@ -1794,7 +1773,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd, vma_shift = PAGE_SHIFT; } else { s2vi->max_map_size = PUD_SIZE; - vma_shift = get_vma_page_shift(vma, s2fd->hva); + vma_shift = get_vma_page_shift(vma); } switch (vma_shift) { @@ -1952,16 +1931,6 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd, return -EFAULT; } } else { - /* - * If the page was identified as device early by looking at - * the VMA flags, vma_pagesize is already representing the - * largest quantity we can map. If instead it was mapped - * via __kvm_faultin_pfn(), vma_pagesize is set to PAGE_SIZE - * and must not be upgraded. - * - * In both cases, we don't let transparent_hugepage_adjust() - * change things at the last minute. - */ s2vi->map_non_cacheable = true; } @@ -2051,10 +2020,10 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd, /* * If we are not forced to use page mapping, check if we are - * backed by a THP and thus use block mapping if possible. + * backed by a huge stage-1 mapping and thus use block mapping if + * possible. */ - if (mapping_size == PAGE_SIZE && - !(s2vi->max_map_size == PAGE_SIZE || s2vi->map_non_cacheable)) { + if (mapping_size == PAGE_SIZE && s2vi->max_map_size != PAGE_SIZE) { if (perm_fault_granule > PAGE_SIZE) { mapping_size = perm_fault_granule; } else { @@ -2135,10 +2104,6 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd) return ret; } - /* - * Let's check if we will get back a huge page backed by hugetlbfs, or - * get block mapping for device MMIO region. - */ ret = kvm_s2_fault_pin_pfn(s2fd, &s2vi); if (ret != 1) return ret; From b88822d2584f78cbdad1d8256510dcac108b5630 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Thu, 1 Oct 2026 14:52:54 +0000 Subject: [PATCH 491/535] bpf: Fix packet range of pointers sharing an id Since commit 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers"), find_good_pkt_pointers() sets the range of all packet pointers sharing an id from the umax of the compared pointer, and check_packet_access() requires umax + off + size <= range. That assumes the umax of two such pointers differ by exactly their constant distance. reg_bounds_sync() breaks it when var_off tightens one umax and not the other: r4 &= 0x38 if r4 > 50 goto exit ; umax 50, var_off (0x0; 0x38) r5 = pkt + r4 ; umax 50 r6 = r5 r6 += 8 ; umax 56, not 58 Comparing r6 with pkt_end sets the range to 56, and the valid 8-byte load at r5 is rejected (50 + 8 > 56). Comparing r5 sets it to 50, and the out-of-bounds 1-byte load at r6 - 7, i.e. r5 + 1, is accepted (56 - 7 + 1 <= 50). Don't call reg_bounds_sync() on a packet pointer that keeps its id (a constant was added or subtracted) or its range (an unknown non-negative value was subtracted), so that var_off cannot tighten its umax. Only update the 32-bit bounds from var_off: reg_bounds_sanity_check() wants them constant when the lower half of var_off is, e.g. for pkt + 8. This relies on nothing else changing the 64-bit bounds of a packet pointer, which holds today. var_off of such a pointer is no longer narrowed by its bounds. Adjust three verifier_align expectations; the low bits, which the alignment checks use, don't change. veristat on the selftests shows no verdict changes and +0.8% insns in test_cls_redirect_subprogs. Fixes: 022ac0750883 ("bpf: use reg->var_off instead of reg->off for pointers") Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20261001145255.855630-1-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/verifier.c | 10 +++++++++- tools/testing/selftests/bpf/progs/verifier_align.c | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 41b49c56e123..5f874979b8d7 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14826,7 +14826,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range."); return -EINVAL; } - reg_bounds_sync(dst_reg); + /* + * A packet pointer that keeps its id or range is checked against a + * range set from the checked pointer's umax, so var_off must not tighten + * its umax. r32 must still match var_off for reg_bounds_sanity_check(). + */ + if (reg_is_pkt_pointer(dst_reg) && (known || dst_reg->range > 0)) + __update_reg32_bounds(dst_reg); + else + reg_bounds_sync(dst_reg); bounds_ret = sanitize_check_bounds(env, insn, dst_reg); if (bounds_ret == -EACCES) return bounds_ret; diff --git a/tools/testing/selftests/bpf/progs/verifier_align.c b/tools/testing/selftests/bpf/progs/verifier_align.c index 3e52686515ca..0083ef8b8ba7 100644 --- a/tools/testing/selftests/bpf/progs/verifier_align.c +++ b/tools/testing/selftests/bpf/progs/verifier_align.c @@ -284,7 +284,7 @@ __msg("26: {{.*}} R5=pkt(r=8,imm=14)") */ __msg("28: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc){{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)") /* Constant is added to R5 again, setting reg->off to 18. */ -__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0x7fc)") +__msg("29: {{.*}} R5=pkt(id=3,{{[^)]*}}var_off=(0x2; 0xffc)") /* And once more we add a variable; resulting {{[^)]*}}var_off * is still (4n), fixed offset is not changed. * Also, we create a new reg->id. @@ -359,7 +359,7 @@ __msg("7: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)") __msg("8: {{.*}} R6={{[^)]*}}var_off=(0x2; 0x7fc)") /* Packet pointer has (4n+2) offset */ __msg("11: {{.*}} R5={{[^)]*}}var_off=(0x2; 0x7fc)") -__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x7fc)") +__msg("12: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)") /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) * which is 2. Then the variable offset is (4n+2), so @@ -375,7 +375,7 @@ __msg("17: {{.*}} R6={{[^)]*}}var_off=(0x0; 0x3fc)") * another (4n+2). */ __msg("19: {{.*}} R5={{[^)]*}}var_off=(0x2; 0xffc)") -__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0xffc)") +__msg("20: {{.*}} R4={{[^)]*}}var_off=(0x2; 0x1ffc)") /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) * which is 2. Then the variable offset is (4n+2), so From 33a154a96e71a34a1bcca9f40da343dbbf7b38b4 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Thu, 1 Oct 2026 14:52:55 +0000 Subject: [PATCH 492/535] selftests/bpf: Test packet range of pointers sharing an id Add tests where two packet pointers share an id and tightening one pointer's umax from its var_off would put it less than their constant distance from the other's umax: with an index & 0x38 capped at 50, the base pointer keeps umax 50, so the pointer 8 bytes further on must keep umax 58, even though its known bits allow at most 56. These refused a valid program or accepted an out-of-bounds access before the fix: - check the advanced copy, load through the base: valid, was refused; - check the base, load the byte at base + 1 through a copy advanced by 8: was accepted; - check base + 4, load 4 bytes at base + 2 through base + 8: reads two bytes past the checked range, was accepted; - the same as the second with data_meta pointers checked against data: was accepted. These pass with and without the fix and cover nearby paths: - subtract an unknown scalar from a checked pointer and load below it (the range is kept across a new id); - reach a load through two paths whose checks cover 8 and 7 bytes after the loaded pointer; the second path must not be pruned by the first; - spill a copy of a pointer, check the pointer, fill the copy and load one byte past the checked range: the load is refused, and the copy has the range of the check. Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20261001145255.855630-2-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../bpf/progs/verifier_direct_packet_access.c | 178 ++++++++++++++++++ .../bpf/progs/verifier_meta_access.c | 30 +++ 2 files changed, 208 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c index 915a9707298b..139ff019d87d 100644 --- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c @@ -920,4 +920,182 @@ l1_%=: r0 = *(u8*)(r9 + 0); \ : __clobber_all); } +SEC("tc") +__description("direct packet access: 8-aligned offset, check p + 8, load 8 bytes at p") +__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT) +__naked void pkt_same_id_check_copy_load_base(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r6 > r3 goto l0_%=; \ + /* [r5, r5 + 8) is in the packet */ \ + r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: 8-aligned offset, check p, load past it via p + 8") +__failure __msg("invalid access to packet, off=51 size=1") +__naked void pkt_same_id_check_base_load_via_copy(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r5 > r3 goto l0_%=; \ + /* r6 - 7 is r5 + 1 */ \ + r0 = *(u8*)(r6 - 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: 8-aligned offset, check p + 4, 4-byte load at p + 2") +__failure __msg("invalid access to packet, off=52 size=4") +__naked void pkt_same_id_check_base_4_load_via_copy(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + r7 = r5; \ + r7 += 4; \ + if r7 > r3 goto l0_%=; \ + /* [r5, r5 + 4) is in the packet, [r5 + 2, r5 + 6) may not be */ \ + r0 = *(u32*)(r6 - 6); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: checked pointer minus non-negative unknown keeps range") +__success __retval(0) __flag(BPF_F_ANY_ALIGNMENT) +__naked void pkt_sub_unknown_keeps_range(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0x1f; \ + r4 += 8; \ + r5 = r2; \ + r5 += 40; \ + if r5 > r3 goto l0_%=; \ + /* r5 is 8 to 39 bytes below the checked pointer */ \ + r5 -= r4; \ + r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: no pruning of a path whose checks prove fewer bytes") +__failure __msg("invalid access to packet, off=255 size=8") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_same_id_pruning(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r0 = *(u32*)(r1 + %[__sk_buff_priority]); \ + r4 &= 0xff; \ + r5 = r2; \ + r5 += r4; \ + if r0 != 0 goto l1_%=; \ + /* this path proves [r5, r5 + 8) */ \ + r6 = r5; \ + r6 += 8; \ + if r6 > r3 goto l0_%=; \ + goto l2_%=; \ +l1_%=: /* this path proves [r5, r5 + 7) */ \ + if r5 > r3 goto l0_%=; \ + r6 = r5; \ + r6 += 7; \ + if r6 > r3 goto l0_%=; \ +l2_%=: r0 = *(u64*)(r5 + 0); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)), + __imm_const(__sk_buff_priority, offsetof(struct __sk_buff, priority)) + : __clobber_all); +} + +SEC("tc") +__description("direct packet access: spilled copy of checked pointer, load past range") +__failure __msg("invalid access to packet, off=262 size=1, R7(id={{[0-9]+}},off=262,r=262)") +__naked void pkt_spilled_copy_gets_range(void) +{ + asm volatile (" \ + r2 = *(u32*)(r1 + %[__sk_buff_data]); \ + r3 = *(u32*)(r1 + %[__sk_buff_data_end]); \ + r4 = *(u32*)(r1 + %[__sk_buff_mark]); \ + r4 &= 0xff; \ + r5 = r2; \ + r5 += r4; \ + *(u64*)(r10 - 8) = r5; \ + /* proves only bytes before r5 */ \ + if r5 > r3 goto l0_%=; \ + r6 = r5; \ + r6 += 7; \ + if r6 > r3 goto l0_%=; \ + /* [r5, r5 + 7) is in the packet */ \ + r7 = *(u64*)(r10 - 8); \ + r0 = *(u8*)(r7 + 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)), + __imm_const(__sk_buff_mark, offsetof(struct __sk_buff, mark)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_meta_access.c b/tools/testing/selftests/bpf/progs/verifier_meta_access.c index 62235f032ffe..c87e0be4b2ff 100644 --- a/tools/testing/selftests/bpf/progs/verifier_meta_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_meta_access.c @@ -281,4 +281,34 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("meta access, 8-aligned offset, check p, load a byte at p + 1 via p + 8") +__failure __msg("invalid access to packet, off=51 size=1") +__naked void meta_access_check_base_load_via_copy(void) +{ + asm volatile (" \ + r9 = r1; \ + call %[bpf_get_prandom_u32]; \ + r4 = r0; \ + r4 &= 0x38; \ + if r4 > 50 goto l0_%=; \ + /* r4 is a multiple of 8, at most 48 */ \ + r2 = *(u32*)(r9 + %[xdp_md_data_meta]); \ + r3 = *(u32*)(r9 + %[xdp_md_data]); \ + r5 = r2; \ + r5 += r4; \ + r6 = r5; \ + r6 += 8; \ + if r5 > r3 goto l0_%=; \ + /* r6 - 7 is r5 + 1 */ \ + r0 = *(u8*)(r6 - 7); \ +l0_%=: r0 = 0; \ + exit; \ +" : + : __imm(bpf_get_prandom_u32), + __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_meta, offsetof(struct xdp_md, data_meta)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; From 47f4f695cec1eb82d961fdb466cd7c99d2865b7c Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Wed, 30 Sep 2026 09:59:18 +0000 Subject: [PATCH 493/535] bpf: Factor out __do_call_rcu_ttrace() Move the part of do_call_rcu_ttrace() that runs after call_rcu_ttrace_in_progress is set into __do_call_rcu_ttrace(). The next patch will call it from __free_rcu(). No functional change. Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260930095920.601738-2-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/memalloc.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index 8a8f088e83e6..08e4dde66cd5 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -298,19 +298,10 @@ static void enque_to_free(struct bpf_mem_cache *c, void *obj) llist_add(llnode, &c->free_by_rcu_ttrace); } -static void do_call_rcu_ttrace(struct bpf_mem_cache *c) +static void __do_call_rcu_ttrace(struct bpf_mem_cache *c) { struct llist_node *llnode, *t; - if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { - if (unlikely(READ_ONCE(c->draining))) { - scoped_guard(raw_spinlock_irqsave, &c->lock) - llnode = llist_del_all(&c->free_by_rcu_ttrace); - free_all(c, llnode, !!c->percpu_size); - } - return; - } - WARN_ON_ONCE(!llist_empty(&c->waiting_for_gp_ttrace)); llist_for_each_safe(llnode, t, llist_del_all(&c->free_by_rcu_ttrace)) llist_add(llnode, &c->waiting_for_gp_ttrace); @@ -328,6 +319,22 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) call_rcu_tasks_trace(&c->rcu_ttrace, __free_rcu); } +static void do_call_rcu_ttrace(struct bpf_mem_cache *c) +{ + struct llist_node *llnode; + + if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { + if (unlikely(READ_ONCE(c->draining))) { + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); + free_all(c, llnode, !!c->percpu_size); + } + return; + } + + __do_call_rcu_ttrace(c); +} + static void free_bulk(struct bpf_mem_cache *c) { struct bpf_mem_cache *tgt = c->tgt; From fbd97dd1d3ce32d32fb7be86acc3fdb7c04fa043 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Wed, 30 Sep 2026 09:59:19 +0000 Subject: [PATCH 494/535] bpf: Fix objects stuck in free_by_rcu_ttrace do_call_rcu_ttrace() returns early when call_rcu_ttrace_in_progress is set and leaves the objects in free_by_rcu_ttrace. __free_rcu() frees waiting_for_gp_ttrace only and clears the flag. Hence the objects that free_bulk() or __free_by_rcu() added while RCU tasks trace GP was in flight stay in free_by_rcu_ttrace until free_bulk() or alloc_bulk() is called for the same bpf_mem_cache again, which may never happen. The number of such objects is not bounded. Turn call_rcu_ttrace_in_progress into three states: 0 - idle 1 - __free_rcu() is queued 2 - __free_rcu() is queued and free_by_rcu_ttrace got more objects since do_call_rcu_ttrace() sets 2. __free_rcu() does cmpxchg(1 -> 0) and starts the next GP when it fails. It cannot clear the flag first and check free_by_rcu_ttrace later, since bpf_mem_alloc_destroy() frees bpf_mem_cache without waiting for RCU callbacks when the flag is zero. Now __free_rcu() queues itself, so the one that didn't see 'draining' may do call_rcu_tasks_trace() after rcu_barrier_tasks_trace() in free_mem_alloc(). Queue it under rcu_read_lock() and do synchronize_rcu() before the barriers. Calling rcu_barrier_tasks_trace() twice works too, but creating and destroying hash maps in a loop on many cpus slows down to one free_mem_alloc() per GP and kworkers pile up. Fixes: 8d5a8011b35d ("bpf: Batch call_rcu callbacks instead of SLAB_TYPESAFE_BY_RCU.") Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260930095920.601738-3-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- kernel/bpf/memalloc.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index 08e4dde66cd5..15684d0fc883 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -118,6 +118,11 @@ struct bpf_mem_cache { struct llist_head free_by_rcu_ttrace; struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; + /* + * 0 - idle + * 1 - __free_rcu() is queued + * 2 - __free_rcu() is queued and free_by_rcu_ttrace got more objects since + */ atomic_t call_rcu_ttrace_in_progress; raw_spinlock_t lock; }; @@ -276,6 +281,8 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per return cnt; } +static void __do_call_rcu_ttrace(struct bpf_mem_cache *c); + static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); @@ -285,7 +292,19 @@ static void __free_rcu(struct rcu_head *head) llnode = llist_del_all(&c->waiting_for_gp_ttrace); free_all(c, llnode, !!c->percpu_size); - atomic_set(&c->call_rcu_ttrace_in_progress, 0); + + /* + * do_call_rcu_ttrace() that ran while GP was in flight left its objects + * in free_by_rcu_ttrace. This cache may never free or alloc in bulk + * again, so start the next GP from here. + * 'c' can be freed as soon as call_rcu_ttrace_in_progress is zero. + */ + if (atomic_cmpxchg(&c->call_rcu_ttrace_in_progress, 1, 0) == 1) + return; + + /* Pairs with synchronize_rcu() in free_mem_alloc() */ + guard(rcu)(); + __do_call_rcu_ttrace(c); } static void enque_to_free(struct bpf_mem_cache *c, void *obj) @@ -302,6 +321,12 @@ static void __do_call_rcu_ttrace(struct bpf_mem_cache *c) { struct llist_node *llnode, *t; + /* + * Must be done before llist_del_all(). Objects that it misses were + * added by do_call_rcu_ttrace() that will set 2 after this store. + */ + atomic_set(&c->call_rcu_ttrace_in_progress, 1); + WARN_ON_ONCE(!llist_empty(&c->waiting_for_gp_ttrace)); llist_for_each_safe(llnode, t, llist_del_all(&c->free_by_rcu_ttrace)) llist_add(llnode, &c->waiting_for_gp_ttrace); @@ -323,7 +348,7 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) { struct llist_node *llnode; - if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { + if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 2)) { if (unlikely(READ_ONCE(c->draining))) { scoped_guard(raw_spinlock_irqsave, &c->lock) llnode = llist_del_all(&c->free_by_rcu_ttrace); @@ -707,7 +732,12 @@ static void free_mem_alloc(struct bpf_mem_alloc *ma) * to wait for the pending __free_by_rcu(), and __free_rcu(). RCU Tasks * Trace grace period implies RCU grace period, so all __free_rcu don't * need extra call_rcu() (and thus extra rcu_barrier() here). + * + * __free_rcu() queues itself again unless it sees 'draining'. After + * synchronize_rcu() it either did that already or will not do it, so + * rcu_barrier_tasks_trace() cannot miss it. */ + synchronize_rcu(); rcu_barrier(); /* wait for __free_by_rcu */ rcu_barrier_tasks_trace(); /* wait for __free_rcu */ free_mem_alloc_no_barrier(ma); From aeb709c767aeca996e6011133e4f7bdb2a4af652 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Wed, 30 Sep 2026 09:59:20 +0000 Subject: [PATCH 495/535] selftests/bpf: Add a test for objects stuck in free_by_rcu_ttrace Delete all elements of BPF_F_NO_PREALLOC hash map in one batch. The first free_bulk() starts RCU tasks trace GP and the rest of the elements are freed while it's in flight. Wait for call_rcu_ttrace_in_progress to clear in bpf_mem_cache of every cpu and check that free_by_rcu_ttrace and waiting_for_gp_ttrace lists are empty. Signed-off-by: Alexei Starovoitov Link: https://lore.kernel.org/bpf/20260930095920.601738-4-alexei.starovoitov@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- .../selftests/bpf/prog_tests/bpf_ma_ttrace.c | 60 +++++++++++++++++++ .../selftests/bpf/progs/bpf_ma_ttrace.c | 50 ++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c create mode 100644 tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c new file mode 100644 index 000000000000..a1d41b109940 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/bpf_ma_ttrace.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "bpf_ma_ttrace.skel.h" + +#define NR_ELEMS 4096 + +/* + * The first free_bulk() starts RCU tasks trace GP. The rest of the elements are + * deleted while it's in flight. They should be freed without further alloc or + * free from this map. + */ +void test_bpf_ma_ttrace(void) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct bpf_ma_ttrace *skel; + __u32 cnt = NR_ELEMS; + long *vals = NULL; + int *keys = NULL; + int i, err, fd, nr_cpus; + + skel = bpf_ma_ttrace__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "nr_cpus")) + goto out; + skel->bss->nr_cpus = nr_cpus; + + keys = calloc(NR_ELEMS, sizeof(*keys)); + vals = calloc(NR_ELEMS, sizeof(*vals)); + if (!ASSERT_OK_PTR(keys, "keys") || !ASSERT_OK_PTR(vals, "vals")) + goto out; + for (i = 0; i < NR_ELEMS; i++) + keys[i] = i; + + fd = bpf_map__fd(skel->maps.htab); + err = bpf_map_update_batch(fd, keys, vals, &cnt, NULL); + if (!ASSERT_OK(err, "update_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "update_cnt")) + goto out; + err = bpf_map_delete_batch(fd, keys, &cnt, NULL); + if (!ASSERT_OK(err, "delete_batch") || !ASSERT_EQ(cnt, NR_ELEMS, "delete_cnt")) + goto out; + + /* Wait for all __free_rcu() callbacks to finish */ + for (i = 0; i < 300; i++) { + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.check_ttrace), &opts); + if (!ASSERT_OK(err, "test_run") || !ASSERT_OK(opts.retval, "retval")) + goto out; + if (!skel->bss->in_progress) + break; + usleep(100000); + } + ASSERT_EQ(skel->bss->nr_caches, nr_cpus, "nr_caches"); + ASSERT_EQ(skel->bss->in_progress, 0, "in_progress"); + ASSERT_EQ(skel->bss->not_freed, 0, "not_freed"); +out: + free(keys); + free(vals); + bpf_ma_ttrace__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c new file mode 100644 index 000000000000..31b2a962e339 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/bpf_ma_ttrace.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include "bpf_kfuncs.h" + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(map_flags, BPF_F_NO_PREALLOC); + __uint(max_entries, 4096); + __type(key, int); + __type(value, long); +} htab SEC(".maps"); + +extern const void __per_cpu_offset __ksym; + +int nr_cpus; +int nr_caches; +int in_progress; +int not_freed; + +/* Look at bpf_mem_cache of every cpu that htab allocates its elements from */ +SEC("syscall") +int check_ttrace(void *ctx) +{ + struct bpf_htab *h = bpf_core_cast(&htab, struct bpf_htab); + unsigned long cache = (unsigned long)BPF_CORE_READ(h, ma.cache); + const unsigned long *offsets = &__per_cpu_offset; + struct bpf_mem_cache *c; + unsigned long off; + int cpu; + + nr_caches = 0; + in_progress = 0; + not_freed = 0; + bpf_for(cpu, 0, nr_cpus) { + if (bpf_probe_read_kernel(&off, sizeof(off), offsets + cpu)) + return -1; + c = bpf_core_cast((void *)(cache + off), struct bpf_mem_cache); + if (c->unit_size) + nr_caches++; + if (c->call_rcu_ttrace_in_progress.counter) + in_progress++; + if (c->free_by_rcu_ttrace.first || c->waiting_for_gp_ttrace.first) + not_freed++; + } + return 0; +} + +char _license[] SEC("license") = "GPL"; From 015e72cde567d2450e282c2790773463156b2d0a Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Fri, 4 Sep 2026 17:18:17 +0800 Subject: [PATCH 496/535] drm/amd/display: check dc_state_create_copy() for NULL in dm_suspend dc_state_create_copy() can return NULL on allocation failure. dm_suspend() only conditionally skips dm_gpureset_toggle_interrupts() and continues execution, returning success. dm_resume() then dereferences the NULL cached_dc_state in link_enc_cfg_copy() and the following dc_state->stream_count loop, crashing during GPU reset recovery. Return -ENOMEM immediately if the copy fails, so the caller aborts suspend instead of leaving a NULL cached state for resume. Fixes: 8092aa3ab8f7 ("drm/amd/display: Add null checker before passing variables") Reviewed-by: George Zhang Signed-off-by: Jiangshan Yi Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 84b6d7932fdd2857795a257f9f258562a3cddcab) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 91fdf3de7202..e0485d76dc59 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -1582,9 +1582,14 @@ static int dm_suspend(struct amdgpu_ip_block *ip_block) dc_allow_idle_optimizations(adev->dm.dc, false); dm->cached_dc_state = dc_state_create_copy(dm->dc->current_state); + if (!dm->cached_dc_state) { + drm_err(adev_to_drm(adev), + "Failed to allocate cached DC state during suspend\n"); + mutex_unlock(&dm->dc_lock); + return -ENOMEM; + } - if (dm->cached_dc_state) - dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false); + dm_gpureset_toggle_interrupts(adev, dm->cached_dc_state, false); res = amdgpu_dm_commit_zero_streams(dm->dc); if (res != DC_OK) { From eb5650d1f367722d0136de18998749008fe7d2a7 Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Thu, 10 Sep 2026 20:56:48 +0200 Subject: [PATCH 497/535] drm/amd/display: Default HDMI RGB output to limited range on CTA modes With the Broadcast RGB property at Automatic, amdgpu_dm_get_output_color_space() selects COLOR_SPACE_SRGB for RGB output, so DC neither compresses the pixels nor, without a QS-capable sink, signals full range in the AVI InfoFrame. A sink that follows CTA-861 treats the default quantization of a CTA video format as limited and expands 16-235 to 0-255: everything below 16 is crushed to black, everything above 235 clips. Follow the CTA-861 default for Automatic instead, as the DRM HDMI state helper (hdmi_is_limited_range()) and i915 do: limited range on an HDMI sink for CTA modes other than 640x480, full range elsewhere. Full and Limited keep their explicit meaning. The same rule applies to the BT.2020 RGB branch. With commit 892659399f64 ("drm/amd/display: Propagate HDMI RGB quantization selectability") the AVI InfoFrame then carries the matching Q value on sinks that support selection. Measured on a Radeon RX 7600 (DCN 3.2.1) driving a JVC DLA-RS4100 at 1920x1080p24 RGB 12 bpc: the sink reports the signal as limited range while the picture shows crushed shadows; rendering limited range in the client makes it match a reference source. The KUnit fixtures that reach amdgpu_dm_get_output_color_space() now carry a connector, since Automatic reads its display_info, and five cases cover the new rule. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5796 Reviewed-by: George Zhang Signed-off-by: Adrian Betschart Assisted-by: Claude Code:claude-fable-5-1 Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d23f735d0eac52c673d91e21d146b09588537efc) --- .../display/amdgpu_dm/amdgpu_dm_connector.c | 22 +++- .../tests/amdgpu_dm_connector_test.c | 117 ++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index 154e1f35dcb1..dbd1e8429a4d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -732,6 +732,24 @@ get_aspect_ratio(const struct drm_display_mode *mode_in) } EXPORT_IF_KUNIT(get_aspect_ratio); +/* + * CTA-861 5.1: RGB video formats default to limited range except 640x480 (VIC 1). + * With Broadcast RGB left at Automatic follow that default, as i915 does. + */ +static bool rgb_output_is_limited_range(const struct dc_crtc_timing *dc_crtc_timing, + const struct drm_connector_state *connector_state) +{ + switch (connector_state->hdmi.broadcast_rgb) { + case DRM_HDMI_BROADCAST_RGB_FULL: + return false; + case DRM_HDMI_BROADCAST_RGB_LIMITED: + return true; + default: + return connector_state->connector->display_info.is_hdmi && + dc_crtc_timing->vic > 1; + } +} + enum dc_color_space amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, const struct drm_connector_state *connector_state) @@ -757,7 +775,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, case DRM_MODE_COLORIMETRY_BT2020_RGB: case DRM_MODE_COLORIMETRY_BT2020_YCC: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_2020_RGB_LIMITEDRANGE; else color_space = COLOR_SPACE_2020_RGB_FULLRANGE; @@ -769,7 +787,7 @@ amdgpu_dm_get_output_color_space(const struct dc_crtc_timing *dc_crtc_timing, default: if (dc_crtc_timing->pixel_encoding == PIXEL_ENCODING_RGB) { color_space = COLOR_SPACE_SRGB; - if (connector_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + if (rgb_output_is_limited_range(dc_crtc_timing, connector_state)) color_space = COLOR_SPACE_SRGB_LIMITED; /* * 27030khz is the separation point between HDTV and SDTV diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c index 212a7536e65b..59ce682f491b 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/tests/amdgpu_dm_connector_test.c @@ -409,7 +409,9 @@ static void dm_test_output_color_space_default_rgb_full(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; @@ -559,7 +561,9 @@ static void dm_test_output_color_space_bt2020_rgb(struct kunit *test) { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; @@ -584,6 +588,110 @@ static void dm_test_output_color_space_bt2020_rgb_limited(struct kunit *test) (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); } +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_cta - Automatic follows the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB_LIMITED); +} + +/** + * dm_test_output_color_space_default_rgb_auto_hdmi_vga - 640x480 (VIC 1) stays full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_hdmi_vga(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 1; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_auto_dp_cta - Automatic on a non-HDMI sink is full range + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_auto_dp_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_default_rgb_full_hdmi_cta - Full overrides the CTA-861 default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_default_rgb_full_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_DEFAULT; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_FULL; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_SRGB); +} + +/** + * dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta - BT.2020 RGB follows the same default + * @test: The KUnit test context + */ +static void dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta(struct kunit *test) +{ + struct dc_crtc_timing timing = {}; + struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + + connector->display_info.is_hdmi = true; + timing.pixel_encoding = PIXEL_ENCODING_RGB; + timing.vic = 32; + state.connector = connector; + state.colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; + state.hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; + + KUNIT_EXPECT_EQ(test, (int)amdgpu_dm_get_output_color_space(&timing, &state), + (int)COLOR_SPACE_2020_RGB_LIMITEDRANGE); +} + /** * dm_test_output_color_space_bt2020_ycc - Test Output color space bt2020 ycc * @test: The KUnit test context @@ -647,7 +755,9 @@ static void dm_test_output_color_space_bt2020_ycc_rgb_encoding(struct kunit *tes { struct dc_crtc_timing timing = {}; struct drm_connector_state state = {}; + struct drm_connector *connector = kunit_kzalloc(test, sizeof(*connector), GFP_KERNEL); + state.connector = connector; timing.pixel_encoding = PIXEL_ENCODING_RGB; state.colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; @@ -3425,6 +3535,7 @@ static struct dm_test_fill_ctx *dm_test_fill_ctx_alloc(struct kunit *test) ctx->conn_state = drmm_kzalloc(ctx->drm, sizeof(*ctx->conn_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->conn_state); + ctx->conn_state->connector = &ctx->aconnector->base; ctx->stream = kunit_kzalloc(test, sizeof(*ctx->stream), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->stream); ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -3818,6 +3929,7 @@ static struct dm_test_stream_ctx *dm_test_stream_ctx_alloc(struct kunit *test) ctx->dm_state = kunit_kzalloc(test, sizeof(*ctx->dm_state), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, ctx->dm_state); + ctx->dm_state->base.connector = &ctx->aconnector->base; ctx->dm_state->scaling = RMX_OFF; ctx->mode = kunit_kzalloc(test, sizeof(*ctx->mode), GFP_KERNEL); @@ -5458,6 +5570,11 @@ static struct kunit_case amdgpu_dm_connector_tests[] = { KUNIT_CASE(dm_test_output_color_space_oprgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb), KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_limited), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_hdmi_vga), + KUNIT_CASE(dm_test_output_color_space_default_rgb_auto_dp_cta), + KUNIT_CASE(dm_test_output_color_space_default_rgb_full_hdmi_cta), + KUNIT_CASE(dm_test_output_color_space_bt2020_rgb_auto_hdmi_cta), KUNIT_CASE(dm_test_output_color_space_bt2020_ycc), KUNIT_CASE(dm_test_output_color_space_default_ycbcr709_y_only), KUNIT_CASE(dm_test_output_color_space_default_ycbcr601), From 5e08102d1d336593160d1434261d50b0c0f6cbe6 Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Sat, 12 Sep 2026 17:11:37 +0200 Subject: [PATCH 498/535] drm/amd/display: Preserve eDP mode on initial ASSR failure Commit 56d8ce9d8c17 ("drm/amd/display: Apply correct panel mode when reinitializing hardware") made ASSR failure fall back to the default panel mode unless eDP mode had previously been applied. However, dc_link is zero-initialized and DP_PANEL_MODE_DEFAULT is zero. Before the first call to dp_set_panel_mode(), panel_mode therefore looks like a previously applied default mode. If ASSR setup fails during the first link training attempt, the driver incorrectly trains the eDP link using the default scrambling mode. On a Google Vilboz Chromebook running self-built coreboot firmware and PSP verstage, this leaves the panel mostly black with corrupted output along the top edge. Track whether panel_mode has actually been initialized, and only consult the saved mode after it has been applied. This retains the recovery behavior while preserving eDP mode during initial link training. Fixes: 56d8ce9d8c17 ("drm/amd/display: Apply correct panel mode when reinitializing hardware") Assisted-by: Pi:gpt-5.6-sol Reviewed-by: George Zhang Reviewed-by: Alex Hung Signed-off-by: Arthur Heymans Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit ccb6c2702033f1002b45807fb3cc938760f26f78) Cc: stable@vger.kernel.org # 6.4.x --- drivers/gpu/drm/amd/display/dc/dc.h | 1 + .../amd/display/dc/link/protocols/link_edp_panel_control.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 705c500ad711..871063f809dc 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -1905,6 +1905,7 @@ struct dc_scratch_space { struct ddc_service *ddc; enum dp_panel_mode panel_mode; + bool panel_mode_initialized; bool aux_mode; /* Private to DC core */ diff --git a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c index 16951a9550f2..21dd91fc8010 100644 --- a/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c +++ b/drivers/gpu/drm/amd/display/dc/link/protocols/link_edp_panel_control.c @@ -93,6 +93,7 @@ void dp_set_panel_mode(struct dc_link *link, enum dp_panel_mode panel_mode) } link->panel_mode = panel_mode; + link->panel_mode_initialized = true; DC_LOG_DETECTION_DP_CAPS("%d eDP panel mode supported: %d, enabled: %d\n", link->link_index, link->dpcd_caps.panel_mode_edp, @@ -1297,7 +1298,8 @@ void edp_set_panel_assr(struct dc_link *link, struct pipe_ctx *pipe_ctx, result = cp_psp->funcs.enable_assr(cp_psp->handle, link); - if (!result && link->panel_mode != DP_PANEL_MODE_EDP) + if (!result && link->panel_mode_initialized && + link->panel_mode != DP_PANEL_MODE_EDP) *panel_mode = DP_PANEL_MODE_DEFAULT; } } From 88427119e1f039c8cdffd63726c136a986ef519f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 23 Sep 2026 14:20:31 +0200 Subject: [PATCH 499/535] drm/amd/display: Fix fast updates on DCE 6.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DCE6 should use legacy fast updates, just like all DCE8 - DCN2. Set that flag for DCE 6.0, 6.1 and 6.4 (all DCE6 versions). Technically DCE6 should have been included when the fast update code was refactored, but it just happened to work until now. A recent commit "Attach only plane updates that actually changed" has exposed this issue because with that commit, DC accidentally took the new fast update code path for DCE6 as well, which caused it to boot into a black screen with a flip done timeout. This fix should be backported to all kernel versions that have "Refactor fast update to use new HWSS build sequence", but this patch won't apply cleanly to older kernels due to another refactor in "Remove dc param from check_update". Fixes: 0baae6246307 ("drm/amd/display: Refactor fast update to use new HWSS build sequence") Fixes: 9ec11bb842b6 ("drm/amd/display: Remove dc param from check_update") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260923122033.1046651-1-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 698fda6e360625850413a2d1a90b72ae5f03073d) Cc: stable@vger.kernel.org --- .../amd/display/dc/resource/dce60/dce60_resource.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c index d2d56a1c4b8b..8612cc66a6ba 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c @@ -448,6 +448,12 @@ static const struct dce_abm_mask abm_mask = { ABM_MASK_SH_LIST_DCE110(_MASK) }; +static const struct dc_debug_options debug_defaults = { 0 }; + +static const struct dc_check_config config_defaults = { + .enable_legacy_fast_update = true, +}; + #define CTX ctx #define REG(reg) mm ## reg @@ -937,6 +943,8 @@ static bool dce60_construct( dc->caps.max_cursor_size = 64; dc->caps.dual_link_dvi = true; dc->caps.extended_aux_timeout_support = false; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * @@ -1134,6 +1142,8 @@ static bool dce61_construct( dc->caps.i2c_speed_in_khz = 40; dc->caps.max_cursor_size = 64; dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * @@ -1332,6 +1342,8 @@ static bool dce64_construct( dc->caps.i2c_speed_in_khz = 40; dc->caps.max_cursor_size = 64; dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * From 687d31ea9ac76b299817060fa1e5582d6c752ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 23 Sep 2026 14:20:32 +0200 Subject: [PATCH 500/535] drm/amd/display: Fix fast updates on DCE 8.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DCE8 should use legacy fast updates. This was already set for DCE 8.0 and 8.3, but it seems that DCE 8.1 (found in Kaveri chips) was forgotten. This fix should be backported to all kernel versions that have "Refactor fast update to use new HWSS build sequence", but this patch won't apply cleanly to older kernels due to another refactor in "Remove dc param from check_update". Fixes: 0baae6246307 ("drm/amd/display: Refactor fast update to use new HWSS build sequence") Fixes: 9ec11bb842b6 ("drm/amd/display: Remove dc param from check_update") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260923122033.1046651-2-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 57605379559e3b5044c8274a1010ec658e5e73af) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c index 26820d35478a..2f4de697ec4b 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce80/dce80_resource.c @@ -1148,6 +1148,8 @@ static bool dce81_construct( dc->caps.max_cursor_size = 128; dc->caps.min_horizontal_blanking_period = 80; dc->caps.is_apu = true; + dc->debug = debug_defaults; + dc->check_config = config_defaults; /************************************************* * Create resources * From 3a52d587d1fc8e8bf94ba1b07374f7ec91aaffb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 23 Sep 2026 14:20:33 +0200 Subject: [PATCH 501/535] drm/amd/display: Mark DCE 6.4 as not APU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DCE 6.4 is found in Oland chips, which were advertised as low-end gaming GPUs in 2013~2015 and then have been sold as low-end workstation GPUs until 2017~2019. These are not APUs. Fixes: 7c15fd86aaec ("drm/amd/display: dc/dce: add initial DCE6 support (v10)") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260923122033.1046651-3-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 1072aa811ea1e61d5f763e4ec1bc6d24bb61f14a) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c index 8612cc66a6ba..65c7dae3ff66 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dce60/dce60_resource.c @@ -1341,7 +1341,6 @@ static bool dce64_construct( dc->caps.max_downscale_ratio = 200; dc->caps.i2c_speed_in_khz = 40; dc->caps.max_cursor_size = 64; - dc->caps.is_apu = true; dc->debug = debug_defaults; dc->check_config = config_defaults; From cd883d0a3937df82a5ea03709ba8ba623b868419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 23 Sep 2026 14:03:53 +0200 Subject: [PATCH 502/535] drm/amd/pm/si: Check AMD_IS_MOBILITY flag for PPSMC_SYSTEMFLAG_GPIO_DC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unfortunately, some desktop boards eg. the FirePro D500 have the HARDWAREDC platform flag set and also have a battery power state configured in their VBIOS. This makes no sense for a desktop GPU and results in incorrect behaviour: the clocks are stuck at lowest. We observed that the kernel driver can work around the issue in two possible ways: 1. Set PPSMC_SWSTATE_FLAG_DC on all power states 2. Clear PPSMC_SYSTEMFLAG_GPIO_DC We think that the PPSMC_SYSTEMFLAG_GPIO_DC flag makes the SMC assume it's running on battery even though this is a desktop machine with no battery, and that's why it doesn't do DPM on power states without PPSMC_SYSTEMFLAG_GPIO_DC. Issue was uncovered by "Fix updating clock limits from power states" because previously the limits for the battery state were not tracked separately. However, battery power state has lower frequencies and voltages, so the kernel doesn't set the DC flag on the current power state anymore. That causes the SMC to be stuck on the lowest clocks. Let's clear PPSMC_SYSTEMFLAG_GPIO_DC on desktop GPUs. We can use the AMD_IS_MOBILITY flag to determine that. Fixes: e6c5d36756e7 ("drm/amd/pm/si: Fix updating clock limits from power states") Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/16352 Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260923120354.1027996-1-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit 58473c7c49ce9f5f5a4a1c24ffb9aa1d3c8771f4) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 012227c70600..064f7615759c 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -5263,7 +5263,8 @@ static int si_init_smc_table(struct amdgpu_device *adev) break; } - if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC) + if ((adev->flags & AMD_IS_MOBILITY) && + (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_HARDWAREDC)) table->systemFlags |= PPSMC_SYSTEMFLAG_GPIO_DC; if (adev->pm.dpm.platform_caps & ATOM_PP_PLATFORM_CAP_REGULATOR_HOT) { From 6b6c4fab8815bd1da5643c19278d364026b8665b Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Thu, 17 Sep 2026 15:31:04 +0800 Subject: [PATCH 503/535] drm/amdgpu: support non-guilty job save on ring backup amdgpu_ring_backup_unprocessed_commands() is also used to preserve a ring that carries no guilty command - for example a collateral kernel gfx ring that shares the faulting pipe during a gfx user-queue pipe reset. Such a ring is backed up with a NULL guilty_fence and all of its unprocessed commands must be saved so they can be replayed after the reset rebuilds the ring's MQD. The early-return that skips an already-seen guilty fence compared "ring->guilty_fence == guilty_fence", which is also true when both are NULL. A non-guilty save therefore hit the early return and left ring_backup_entries_to_copy at 0, so the pending commands were dropped when the ring was rebuilt. Guard the early return with a non-NULL guilty_fence so a NULL fence never takes it and every unprocessed command is backed up. Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit b3f73daa1c2498273981b68b7534e540614069e3) --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 3043ad041bb4..6537e8845fb6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -733,7 +733,9 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, * we are skipping it on purpose. */ if (!ring->ring_backup_entries_to_copy) { - amdgpu_fence_driver_force_completion(ring, &guilty_fence->base); + if (guilty_fence) + amdgpu_fence_driver_force_completion(ring, + &guilty_fence->base); return; } ring->reemit = true; @@ -804,12 +806,11 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; ring->ring_backup_entries_to_copy = 0; - /* if we've already seen this fence, return early. - * ring->ring_backup_entries_to_copy is set to 0 so - * the reemit helper will return early as well to - * avoid getting stuck in a reemit loop. + /* + * Avoid replay loops for a repeated guilty fence. NULL identifies a + * collateral ring, whose pending commands still need backup. */ - if (ring->guilty_fence == guilty_fence) { + if (guilty_fence && ring->guilty_fence == guilty_fence) { ring->guilty_fence = NULL; return; } From 56b2b97e7893f495b2e2a91cd3e405b6358c61f0 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Sun, 20 Sep 2026 17:20:21 +0800 Subject: [PATCH 504/535] drm/amdgpu: fix empty fence backup and replay intervals amdgpu_ring_set_fence_errors_and_reemit() and amdgpu_ring_backup_unprocessed_commands() masked last_seq and sync_seq to fence-array indices before the loop, then walked a do/while until the two indices met. When hardware completed every command before the driver signalled the fences, last_seq == sync_seq, but the masked-index do/while could not tell that empty interval from a full table and walked the whole fence array, backing up and replaying already-completed commands. Read the raw sequence numbers, return early through amdgpu_fence_process() when they are equal, and mask only at the array index. Guard the force-completion against a NULL guilty_fence so a non-guilty backup does not dereference it. Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 78fbe156637a11456fcd29615a56e098ec8a6990) --- drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c index 6537e8845fb6..3017ddcefbe2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_fence.c @@ -724,8 +724,14 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, bool is_guilty_fence; bool is_guilty_context; - last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; - seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + last_seq = amdgpu_fence_read(ring); + seq = ring->fence_drv.sync_seq; + + /* Hardware completion can precede the interrupt which signals fences. */ + if (last_seq == seq) { + amdgpu_fence_process(ring); + return; + } /* If there is nothing to reemit, return early and set an error on the fence * if applicable. If all of the fences are siganlled, this will be a nop. @@ -743,9 +749,8 @@ void amdgpu_ring_set_fence_errors_and_reemit(struct amdgpu_ring *ring, spin_lock_irqsave(&ring->fence_drv.lock, flags); do { last_seq++; - last_seq &= ring->fence_drv.num_fences_mask; - ptr = &ring->fence_drv.fences[last_seq]; + ptr = &ring->fence_drv.fences[last_seq & ring->fence_drv.num_fences_mask]; rcu_read_lock(); unprocessed = rcu_dereference(*ptr); @@ -802,8 +807,8 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, struct amdgpu_fence *fence; u32 seq, last_seq; - last_seq = amdgpu_fence_read(ring) & ring->fence_drv.num_fences_mask; - seq = ring->fence_drv.sync_seq & ring->fence_drv.num_fences_mask; + last_seq = amdgpu_fence_read(ring); + seq = ring->fence_drv.sync_seq; ring->ring_backup_entries_to_copy = 0; /* @@ -816,11 +821,10 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, } ring->guilty_fence = guilty_fence; - do { + while (last_seq != seq) { last_seq++; - last_seq &= ring->fence_drv.num_fences_mask; - ptr = &ring->fence_drv.fences[last_seq]; + ptr = &ring->fence_drv.fences[last_seq & ring->fence_drv.num_fences_mask]; rcu_read_lock(); unprocessed = rcu_dereference(*ptr); @@ -830,7 +834,7 @@ void amdgpu_ring_backup_unprocessed_commands(struct amdgpu_ring *ring, amdgpu_ring_backup_unprocessed_command(ring, fence); } rcu_read_unlock(); - } while (last_seq != seq); + } } struct amdgpu_fence * From 9fe5745f9d8eca38d31d9b672a1998edf856f651 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 16 Sep 2026 13:44:27 +0800 Subject: [PATCH 505/535] drm/amdgpu/gfx12: set KMD_QUEUE for kernel gfx queues Set KMD_QUEUE in the GFX MQD and kernel ring control registers so CP preserves kernel IB VMIDs instead of replacing them with the queue VMID. Use prop->kernel_queue to distinguish kernel and user MQDs. Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 14741c01680a803050e949744089b57fa53e02f7) --- drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c index e2a81a55c63b..d69c43f1e194 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v12_0.c @@ -2767,6 +2767,7 @@ static int gfx_v12_0_cp_gfx_resume(struct amdgpu_device *adev) rb_bufsz = order_base_2(ring->ring_size / 8); tmp = REG_SET_FIELD(0, CP_RB0_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, KMD_QUEUE, 1); WREG32_SOC15(GC, 0, regCP_RB0_CNTL, tmp); /* Initialize the ring buffer's write pointers */ @@ -3059,6 +3060,7 @@ static int gfx_v12_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, tmp = regCP_GFX_HQD_CNTL_DEFAULT; tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, KMD_QUEUE, prop->kernel_queue); #ifdef __BIG_ENDIAN tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, BUF_SWAP, 1); #endif From 8228688eb40cc116127e80394cb8157417854c70 Mon Sep 17 00:00:00 2001 From: Jesse Zhang Date: Wed, 16 Sep 2026 13:44:41 +0800 Subject: [PATCH 506/535] drm/amdgpu/gfx11: set KMD_QUEUE for kernel gfx queues Set KMD_QUEUE in the GFX MQD and kernel ring control registers so CP preserves kernel IB VMIDs instead of replacing them with the queue VMID. Use prop->kernel_queue to distinguish kernel and user MQDs. Signed-off-by: Jesse Zhang Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 893642e0e3b2f5aa818e0ee4a49e2d401d541e3a) --- drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c index 0ff5a80aa918..7a006b22e4be 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v11_0.c @@ -3808,6 +3808,7 @@ static int gfx_v11_0_cp_gfx_resume(struct amdgpu_device *adev) rb_bufsz = order_base_2(ring->ring_size / 8); tmp = REG_SET_FIELD(0, CP_RB0_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_RB0_CNTL, KMD_QUEUE, 1); WREG32_SOC15(GC, 0, regCP_RB0_CNTL, tmp); /* Initialize the ring buffer's write pointers */ @@ -4216,6 +4217,7 @@ static int gfx_v11_0_gfx_mqd_init(struct amdgpu_device *adev, void *m, tmp = regCP_GFX_HQD_CNTL_DEFAULT; tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BUFSZ, rb_bufsz); tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, RB_BLKSZ, rb_bufsz - 2); + tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, KMD_QUEUE, prop->kernel_queue); #ifdef __BIG_ENDIAN tmp = REG_SET_FIELD(tmp, CP_GFX_HQD_CNTL, BUF_SWAP, 1); #endif From 0e4101c11aed803e6e69a83f8cc48160a676c19d Mon Sep 17 00:00:00 2001 From: Andrew Martin Date: Mon, 14 Sep 2026 12:19:10 +0000 Subject: [PATCH 507/535] drm/amdkfd: Fix always mapped range mapping to all ACCESS GPUs CWSR ranges use the always mapped flag and only need a mapping on the GPU running the queue. However, svm_range_validate_and_map now updates the mapping on every GPU with the ACCESS attribute, which is all supported GPUs when XNACK is on. After the range is prefetched to VRAM, those GPUs may span different XGMI hives, so the page owner mismatches and is set to NULL. hmm_range_fault then tries to migrate the VRAM pages back to system memory, but svm_migrate_to_ram skips it because migrating pages back to system memory is not allowed while the GPU mapping is being updated, so the CPU page fault is never resolved. Only map to the ACCESS GPUs if the range is not mapped to any GPU and is not in VRAM. Fixes: b96f26104946 ("drm/amdkfd: Unmap svm range from GPU set to no-access") Signed-off-by: Andrew Martin Assisted-by: Claude:claude-opus-5 Acked-by: Alex Deucher Reviewed-by: Philip Yang Signed-off-by: Alex Deucher (cherry picked from commit 093cf8842ace35ce212858d5059fee007ec8e778) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdkfd/kfd_svm.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c index fa4054d51f60..d7f2ff5c8f02 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_svm.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_svm.c @@ -1752,12 +1752,15 @@ static int svm_range_validate_and_map(struct mm_struct *mm, } /* - * If prange with always mapped flag, update mapping on GPUs with - * ACCESS attribute + * If prange with always mapped flag is not mapped or migrated + * yet, update mapping on GPUs with ACCESS attribute. Don't add + * them otherwise, ACCESS GPUs may be on different XGMI hives, + * that would force hmm_range_fault to migrate VRAM back to ram. */ - if (prange->flags & KFD_IOCTL_SVM_FLAG_GPU_ALWAYS_MAPPED) - bitmap_or(ctx->bitmap, ctx->bitmap, prange->bitmap_access, - MAX_GPU_INSTANCE); + if (bitmap_empty(ctx->bitmap, MAX_GPU_INSTANCE) && + prange->flags & KFD_IOCTL_SVM_FLAG_GPU_ALWAYS_MAPPED) + bitmap_copy(ctx->bitmap, prange->bitmap_access, + MAX_GPU_INSTANCE); } else { bitmap_or(ctx->bitmap, prange->bitmap_access, prange->bitmap_aip, MAX_GPU_INSTANCE); From eaeac1498b289b342449b678a567a9012360f91a Mon Sep 17 00:00:00 2001 From: Pierre-Eric Pelloux-Prayer Date: Thu, 24 Sep 2026 09:37:28 +0200 Subject: [PATCH 508/535] drm/amdgpu: implement workaround for sdma dcc corruption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For unknown reasons, on gfx12 using multiple entities can causes random corruption of BOs with DCC. This workaround seems to prevent the issue until the root cause is understood and fixed. Link: https://gitlab.freedesktop.org/drm/amd/-/work_items/5663 Fixes: 3a6f6eeb3db5 ("drm/amdgpu: give ttm entities access to all the sdma scheds") Signed-off-by: Pierre-Eric Pelloux-Prayer Reviewed-by: Alex Deucher Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit cd647796ac1334acec0468fae49a0dacbba2a34c) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 016957cac1f2..3838e464703c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -2342,7 +2342,7 @@ void amdgpu_ttm_fini(struct amdgpu_device *adev) void amdgpu_ttm_enable_buffer_funcs(struct amdgpu_device *adev) { struct ttm_resource_manager *man = ttm_manager_type(&adev->mman.bdev, TTM_PL_VRAM); - u32 num_clear_entities, num_move_entities; + u32 num_clear_entities, num_move_entities, sdma_ip_version; int r, i, j; if (!adev->mman.initialized || amdgpu_in_reset(adev) || @@ -2367,6 +2367,12 @@ void amdgpu_ttm_enable_buffer_funcs(struct amdgpu_device *adev) num_clear_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); num_move_entities = MIN(adev->mman.num_buffer_funcs_scheds, TTM_NUM_MOVE_FENCES); + /* TODO: workaround for DCC corruption when moving BOs from multiple queues at + * the same time: use a single queue until the root cause is identified and fixed. + */ + sdma_ip_version = amdgpu_ip_version(adev, SDMA0_HWIP, 0); + if (sdma_ip_version == IP_VERSION(7, 0, 0) || sdma_ip_version == IP_VERSION(7, 0, 1)) + num_move_entities = 1; adev->mman.clear_entities = kzalloc_objs(struct amdgpu_ttm_buffer_entity, num_clear_entities); From 51cc916648d1421e0271090fb4fa86550a7a96d9 Mon Sep 17 00:00:00 2001 From: Ray Wu Date: Fri, 18 Sep 2026 11:05:46 +0800 Subject: [PATCH 509/535] drm/amd/display: map PWM brightness through custom backlight curve [Why/How] On the PWM path, convert the userspace brightness to an input signal and derive the target luminance from the custom backlight curve, then pass the resulting millipercent to the power module. This keeps PWM programming and backlight curve mapping in the power module, aligning the Linux path with the Windows behavior. Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/5723 Fixes: 3c108046e1d6 ("drm/amd/display: Add power module on Linux") Assisted-by: Cursor:Claude-Opus-4.8 Signed-off-by: Ray Wu Signed-off-by: Fangzhi Zuo Reviewed-by: ChiaHsuan (Tom) Chung Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit bc4e099caa85e8d4c3555851a0feb07058c4a1fe) Cc: stable@vger.kernel.org --- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 + .../display/amdgpu_dm/amdgpu_dm_backlight.c | 92 ++++++++++++++++--- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index e0485d76dc59..1b42c4606002 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -426,6 +426,8 @@ static int amdgpu_dm_init_power_module(struct amdgpu_display_manager *dm) init_data[i].use_custom_backlight_caps = false; init_data[i].custom_backlight_caps_config_no = 0; init_data[i].use_linear_backlight_curve = false; + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + init_data[i].use_linear_backlight_curve = true; init_data[i].def_varibright_enable = 0; init_data[i].varibright_level = 0; /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c index b9e90ea449ca..703d0617df6d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_backlight.c @@ -39,6 +39,7 @@ #include "modules/inc/mod_power.h" #include +#include #include #include #include @@ -118,6 +119,68 @@ static inline u32 scale_fw_to_input(int min, int max, u64 input) return min + DIV_ROUND_CLOSEST_ULL(input * (max - min), AMDGPU_MAX_BL_LEVEL); } +static u32 backlight_signal_to_luminance(const struct amdgpu_dm_backlight_caps *caps, + u32 signal) +{ + u32 lower_signal, upper_signal; + u32 lower_lum, upper_lum; + int i; + + if (!caps->data_points) + return DIV_ROUND_CLOSEST(signal * 100, AMDGPU_MAX_BL_LEVEL); + + if (signal < caps->luminance_data[0].input_signal) { + lower_signal = 0; + lower_lum = 0; + upper_signal = caps->luminance_data[0].input_signal; + upper_lum = caps->luminance_data[0].luminance; + } else if (signal >= + caps->luminance_data[caps->data_points - 1].input_signal) { + lower_signal = caps->luminance_data[caps->data_points - 1].input_signal; + lower_lum = caps->luminance_data[caps->data_points - 1].luminance; + upper_signal = caps->max_input_signal; + upper_lum = 100; + } else { + for (i = 1; + i < caps->data_points && + signal > caps->luminance_data[i].input_signal; + i++) + ; + + lower_signal = caps->luminance_data[i - 1].input_signal; + upper_signal = caps->luminance_data[i].input_signal; + lower_lum = caps->luminance_data[i - 1].luminance; + upper_lum = caps->luminance_data[i].luminance; + } + + if (upper_signal == lower_signal) + return upper_lum; + + return lower_lum + DIV_ROUND_CLOSEST((upper_lum - lower_lum) * + (signal - lower_signal), + upper_signal - lower_signal); +} + +static u32 convert_brightness_to_millipercent(const struct amdgpu_dm_backlight_caps *caps, + u32 user_brightness) +{ + unsigned int min, max; + u32 signal, luminance; + + if (!get_brightness_range(caps, &min, &max)) + return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, + MAX_BACKLIGHT_LEVEL); + + user_brightness = clamp(user_brightness, min, max); + if (amdgpu_dc_debug_mask & DC_DISABLE_CUSTOM_BRIGHTNESS_CURVE) + return DIV_ROUND_CLOSEST_ULL((u64)user_brightness * 100000, max); + + signal = scale_input_to_fw(max, user_brightness); + luminance = backlight_signal_to_luminance(caps, signal); + + return min_t(u32, luminance * 1000, 100000); +} + STATIC_IFN_KUNIT void convert_custom_brightness(const struct amdgpu_dm_backlight_caps *caps, unsigned int min, unsigned int max, @@ -260,10 +323,10 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, struct amdgpu_dm_backlight_caps *caps; struct dc_link *link; u32 brightness = 0; + u32 millipercent = 0; bool rc = false, reallow_idle = false; struct drm_connector *connector; struct dc_stream_state *stream; - unsigned int min, max; list_for_each_entry(connector, &dm->ddev->mode_config.connector_list, head) { struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); @@ -286,12 +349,21 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, /* update scratch register */ if (bl_idx == 0) amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); - brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); - link = (struct dc_link *)dm->backlight_link[bl_idx]; - /* Apply brightness quirk */ - if (caps->brightness_mask) - brightness |= caps->brightness_mask; + link = (struct dc_link *)dm->backlight_link[bl_idx]; + stream = dm_find_stream_with_link(dm, link); + if (!stream) + return; + + if (caps->aux_support) { + brightness = convert_brightness_from_user(caps, dm->brightness[bl_idx]); + /* Apply brightness quirk */ + if (caps->brightness_mask) + brightness |= caps->brightness_mask; + } else { + millipercent = convert_brightness_to_millipercent(caps, + user_brightness); + } if (trace_amdgpu_dm_brightness_enabled()) { trace_amdgpu_dm_brightness(__builtin_return_address(0), @@ -301,10 +373,6 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, power_supply_is_system_supplied() > 0); } - stream = dm_find_stream_with_link(dm, link); - if (!stream) - return; - mutex_lock(&dm->dc_lock); if (dm->dc->caps.ips_support && dm->dc->ctx->dmub_srv->idle_allowed) { dc_allow_idle_optimizations(dm->dc, false); @@ -316,10 +384,8 @@ void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, AUX_BL_DEFAULT_TRANSITION_TIME_MS, false, true); } else { /* power module uses millipercent */ - get_brightness_range(caps, &min, &max); - brightness = DIV_ROUND_CLOSEST(brightness * 100, (max - min)) * 1000; rc = mod_power_set_backlight_percent(dm->power_module, stream, - brightness, 0, false); + millipercent, 0, false); } /* From 0bfb1bfc82e8fa386d4fad1caa5812b6afafa626 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 25 Sep 2026 13:46:19 -0400 Subject: [PATCH 510/535] drm/amdgpu/gmc12: properly pass flush_type to gmc_v12_0_flush_vm_hub() In gmc_v12_0_flush_gpu_tlb(), flush_type was not properly passed to gmc_v12_0_flush_vm_hub(). This only affected the MMIO path. In most cases the flush would go through MES. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher (cherry picked from commit 6498e33f22a12fdc759a0c78bfe0547819277a36) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c index b05465dc30b4..ba5b1a17af61 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_0.c @@ -327,7 +327,7 @@ static void gmc_v12_0_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, return; } - gmc_v12_0_flush_vm_hub(adev, vmid, vmhub, 0); + gmc_v12_0_flush_vm_hub(adev, vmid, vmhub, flush_type); return; } From eadb85cca111152bd334b861fb53747ab851d13a Mon Sep 17 00:00:00 2001 From: Theo Andersen Carton Date: Thu, 17 Sep 2026 21:15:12 +0200 Subject: [PATCH 511/535] drm/amdgpu: skip the noirq suspend reset for a switcheroo-parked GPU amdgpu_pmops_suspend_noirq() resets the ASIC unconditionally. When the GPU has been parked by vga_switcheroo it has neither power nor a PCIe link, so the reset cannot reach it: pci_set_power_state() reports the device as inaccessible and amdgpu_asic_reset() returns -EINVAL. A failure there aborts the entire noirq suspend phase, and with it the system suspend, so the machine cannot sleep at all while the GPU is switched off. amdgpu_device_prepare(), amdgpu_device_suspend() and amdgpu_device_resume() all bail out early on DRM_SWITCH_POWER_OFF. This callback was added later, for an unrelated reason, and did not inherit the check. nouveau guards every one of its PM callbacks the same way. Bail out the same way here. On a single-GPU system switch_power_state is never DRM_SWITCH_POWER_OFF, so this is a no-op there. Found on a MacBookPro11,5, where the Radeon is powered down through apple-gmux so that the internal panel can be driven by the iGPU instead. Every suspend failed in amdgpu_pmops_suspend_noirq() while the card was off; with this check a full S3 cycle completes. Fixes: 9e051720f9d3 ("drm/amdgpu: Ensure HDA function is suspended before ASIC reset") Signed-off-by: Theo Andersen Carton Signed-off-by: Alex Deucher (cherry picked from commit 539d0558638a6dcc066f1cf592b6b9a52adc8ea4) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index 5c33c19fd9bc..d9d794a7db96 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2664,6 +2664,14 @@ static int amdgpu_pmops_suspend_noirq(struct device *dev) struct amdgpu_device *adev = drm_to_adev(drm_dev); int r; + /* + * A GPU parked by vga_switcheroo has no power and no PCIe link, so the + * ASIC reset below would fail and abort the whole noirq suspend phase. + * Bail out like amdgpu_device_prepare/suspend/resume() already do. + */ + if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) + return 0; + if (amdgpu_acpi_should_gpu_reset(adev)) { amdgpu_device_lock_reset_domain(adev->reset_domain); r = amdgpu_asic_reset(adev); From f5a5add3605935163dd0f75749b6a7869e9765af Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 20 Sep 2026 00:55:11 +0800 Subject: [PATCH 512/535] drm/amdgpu: fix IP instance memory leak on kobject add failure amdgpu_discovery_sysfs_ips() allocates ip_hw_instance with kzalloc_flex() and initializes its embedded kobject before calling kobject_add(). If kobject_add() fails, the return value is ignored and execution continues without dropping the initial kobject reference. The failed kobject is not retained in the kset list, so the normal sysfs teardown path cannot find it. As a result, ip_hw_instance_release() is never called and the ip_hw_instance allocation is leaked. Call kobject_put() when kobject_add() fails so the initial reference is dropped and ip_hw_instance_release() can free the allocation. Keep the existing best-effort sysfs behavior by continuing with the remaining IP entries after the failed registration. The issue was identified by a static analysis tool I developed and confirmed by manual review. Fixes: a6c40b178092 ("drm/amdgpu: Show IP discovery in sysfs") Reviewed-by: Lijo Lazar Signed-off-by: Guangshuo Li Signed-off-by: Alex Deucher (cherry picked from commit 5c75fbcca8af1096d8d9415b91d8ea6212c22ee1) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 62f5c5cbd7f9..7ab2ad2c5ba9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -1325,6 +1325,8 @@ static int amdgpu_discovery_sysfs_ips(struct amdgpu_device *adev, ip_hw_instance->kobj.kset = &ip_hw_id->hw_id_kset; res = kobject_add(&ip_hw_instance->kobj, NULL, "%d", ip_hw_instance->num_instance); + if (res) + kobject_put(&ip_hw_instance->kobj); next_ip: if (reg_base_64) ip_offset += struct_size(ip, base_address_64, From f0013046151b7467e62c471c1c83d63ea255799c Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 18:33:18 +0800 Subject: [PATCH 513/535] drm/amdgpu: fix ACP MFD device leak on init failure acp_hw_init() registers ACP child devices with mfd_add_devices() before attaching them to the ACP power domain and initializing the hardware. If attaching a child to the power domain fails, or if the ACP reset or clock enable operation times out, the failure path frees only the source cell, resource, and platform-data allocations. The child platform devices already registered by mfd_add_devices() remain registered and are never released. Remove the children from the power domain and unregister the MFD devices on failures that occur after mfd_add_devices() succeeds. Keep mfd_add_devices() failures on the existing cleanup path since the MFD core already rolls back partially registered children itself. The issue was identified by a static analysis tool I developed and confirmed by manual review. Fixes: 25030321ba28 ("drm/amd: add pm domain for ACP IP sub blocks") Signed-off-by: Guangshuo Li Signed-off-by: Alex Deucher (cherry picked from commit 63da68d6a2279ec945581c990c5a59a5ae8b5560) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c index 9014678d75ab..be4d00ac96b3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -318,7 +318,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) r = device_for_each_child(adev->acp.parent, &adev->acp.acp_genpd->gpd, acp_genpd_add_device); if (r) - goto failure; + goto failure_remove_mfd; break; } default: @@ -443,7 +443,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) r = device_for_each_child(adev->acp.parent, &adev->acp.acp_genpd->gpd, acp_genpd_add_device); if (r) - goto failure; + goto failure_remove_mfd; } /* Assert Soft reset of ACP */ @@ -461,7 +461,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); r = -ETIMEDOUT; - goto failure; + goto failure_remove_mfd; } udelay(100); } @@ -479,7 +479,7 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) if (--count == 0) { dev_err(&adev->pdev->dev, "Failed to reset ACP\n"); r = -ETIMEDOUT; - goto failure; + goto failure_remove_mfd; } udelay(100); } @@ -489,6 +489,11 @@ static int acp_hw_init(struct amdgpu_ip_block *ip_block) cgs_write_register(adev->acp.cgs_device, mmACP_SOFT_RESET, val); return 0; +failure_remove_mfd: + device_for_each_child(adev->acp.parent, NULL, + acp_genpd_remove_device); + mfd_remove_devices(adev->acp.parent); + failure: kfree(adev->acp.i2s_pdata); kfree(adev->acp.acp_res); From cc172e177795c5f87bc2d71768d077e1c10633e8 Mon Sep 17 00:00:00 2001 From: Willian Oliveira Date: Mon, 21 Sep 2026 22:54:10 -0300 Subject: [PATCH 514/535] drm/amdgpu/gfx6: fix firmware leak on teardown The firmware requested in gfx_v6_0_init_microcode() is not released when the GFX block is torn down, leaking the firmware resources. Add gfx_v6_0_free_microcode() and call it from gfx_v6_0_sw_fini() to release the PFP, ME, CE and RLC firmware. Signed-off-by: Willian Oliveira Signed-off-by: Alex Deucher (cherry picked from commit 386e81346f95ab0be5a82c5535de932e7a456924) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c index 563abcf718b7..493ccfa26655 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c @@ -322,6 +322,14 @@ static const u32 verde_rlc_save_restore_register_list[] = 0x00000000 }; +static void gfx_v6_0_free_microcode(struct amdgpu_device *adev) +{ + amdgpu_ucode_release(&adev->gfx.pfp_fw); + amdgpu_ucode_release(&adev->gfx.me_fw); + amdgpu_ucode_release(&adev->gfx.ce_fw); + amdgpu_ucode_release(&adev->gfx.rlc_fw); +} + static int gfx_v6_0_init_microcode(struct amdgpu_device *adev) { const char *chip_name; @@ -3249,6 +3257,7 @@ static int gfx_v6_0_sw_fini(struct amdgpu_ip_block *ip_block) amdgpu_ring_fini(&adev->gfx.compute_ring[i]); amdgpu_gfx_rlc_fini(adev); + gfx_v6_0_free_microcode(adev); return 0; } From 1d1f1acecddf742eb1f305122209bc2034fd90b1 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Tue, 22 Sep 2026 12:10:40 -0400 Subject: [PATCH 515/535] drm/amdgpu: drop userq callback assignment for sdma 7.1 Not enabled in 7.1 so drop the assignment. Cc: Horatio Zhang Cc: Hawking.Zhang@amd.com Fixes: 4ed5116aacf6 ("drm/amdgpu: Add sdma v7_1_0 support") Reviewed-by: Hawking Zhang Signed-off-by: Alex Deucher (cherry picked from commit 106e19f69a066fb6354dac04e5f5ed43a06727f7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c index 18366e16ef3f..1e4a3dcde1a2 100644 --- a/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c +++ b/drivers/gpu/drm/amd/amdgpu/sdma_v7_1.c @@ -1339,10 +1339,6 @@ static int sdma_v7_1_sw_init(struct amdgpu_ip_block *ip_block) else DRM_ERROR("Failed to allocated memory for SDMA IP Dump\n"); -#ifdef CONFIG_DRM_AMDGPU_NAVI3X_USERQ - adev->userq_funcs[AMDGPU_HW_IP_DMA] = &userq_mes_funcs; -#endif - return r; } From e3e7fdece7069d09ddd818564853174865d4796a Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 25 Sep 2026 16:11:38 -0400 Subject: [PATCH 516/535] drm/amdgpu/gmc12.1: properly pass flush_type to gmc_v12_1_flush_vm_hub() In gmc_v12_1_flush_gpu_tlb(), flush_type was not properly passed to gmc_v12_1_flush_vm_hub(). This only affected the MMIO path. In most cases the flush would go through MES. Reviewed-by: Mukul Joshi Signed-off-by: Alex Deucher (cherry picked from commit 1f0f66fab18dfa8f14db82b8a6b82dcc21926248) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c index 5fe43f7eab29..5d0d316fa94e 100644 --- a/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c +++ b/drivers/gpu/drm/amd/amdgpu/gmc_v12_1.c @@ -389,7 +389,7 @@ static void gmc_v12_1_flush_gpu_tlb(struct amdgpu_device *adev, uint32_t vmid, return; } - gmc_v12_1_flush_vm_hub(adev, vmid, vmhub, 0); + gmc_v12_1_flush_vm_hub(adev, vmid, vmhub, flush_type); return; } From fbc988f236a60cf50a89da78fc6b7213daa9999c Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 27 Sep 2026 12:16:01 -0400 Subject: [PATCH 517/535] drm/amd/display: Fix sanitizer check for the DML frame size limit Building x86_64 allmodconfig with clang fails: .../dml2_0/dml21/src/dml2_core/dml2_core_dcn4_calcs.c:10491:13: error: stack frame size (3128) exceeds limit (3072) in 'dml_core_mode_programming' [-Werror,-Wframe-larger-than] That config enables both KASAN and UBSAN, and should get the 4096 byte limit meant for clang COMPILE_TEST sanitizer builds. The check in the dml and dml2_0 Makefiles concatenates the three symbols with no separator: ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) KASAN and KCSAN cannot be enabled together, so before UBSAN was added the string was at most "y". UBSAN can be enabled alongside either of them, and with both set the string becomes "yy", $(filter y,yy) is empty, and the build falls through to the non-sanitizer limit of 3072. Adding UBSAN to the check therefore lowered the limit for the most heavily instrumented builds instead of raising it. Separate the symbols with spaces so that $(filter) sees individual words, and test for a non-empty result. Only the configs with two sanitizers enabled change behaviour. Found by KernelCI builds of the linus-next tree. Fixes: ebf8b0fd8508 ("drm/amd/display: Relax DML frame limit with UBSAN") Assisted-by: LLM Signed-off-by: Sasha Levin Signed-off-by: Alex Deucher (cherry picked from commit 0400d53fd03e7d4beb09fdabda42a7b2c4ca465d) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/dml/Makefile | 2 +- drivers/gpu/drm/amd/display/dc/dml2_0/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 91465ac05c97..eb4ce5b1ecfb 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -29,7 +29,7 @@ dml_ccflags := $(CC_FLAGS_FPU) dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) + ifneq ($(filter y,$(CONFIG_KASAN) $(CONFIG_KCSAN) $(CONFIG_UBSAN)),) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 39ee2d1999dd..289bac08c82c 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -28,7 +28,7 @@ dml2_ccflags := $(CC_FLAGS_FPU) dml2_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) + ifneq ($(filter y,$(CONFIG_KASAN) $(CONFIG_KCSAN) $(CONFIG_UBSAN)),) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else From 106b4a2a78b2a2f14885604891957b84bba502db Mon Sep 17 00:00:00 2001 From: Adrian Betschart Date: Fri, 11 Sep 2026 17:13:14 +0200 Subject: [PATCH 518/535] drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validation amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and uses the first encoding that validates. YCbCr 4:4:4 is listed before RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the "color format" property is left at AUTO, even though RGB fits the same link. That contradicts the documented AUTO behaviour for HDMI in enum drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when the bandwidth is not available or the mode is 4:2:0-only), which the amdgpu implementation of the property also describes. It also leaves the "Broadcast RGB" property without effect on such sinks, since the quantization range it selects only applies to RGB output. Try RGB first. The mask still holds every encoding the sink supports, so a mode that cannot carry RGB falls back exactly as before. For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI sink that advertised it. Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation") Suggested-by: Adolfo Rodrigues Assisted-by: Claude Code:claude-fable-5-1 Signed-off-by: Adrian Betschart Reviewed-by: Fangzhi Zuo Tested-by: Adolfo Rodrigues Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7) --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c index dbd1e8429a4d..d386bb1475bb 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_connector.c @@ -2274,8 +2274,8 @@ amdgpu_dm_create_validate_stream_for_sink(struct drm_connector *connector, * below gate which of these entries are actually attempted. */ static const enum dc_pixel_encoding encoding_order[] = { - PIXEL_ENCODING_YCBCR444, PIXEL_ENCODING_RGB, + PIXEL_ENCODING_YCBCR444, PIXEL_ENCODING_YCBCR422, PIXEL_ENCODING_YCBCR420, }; From 217f64f348c90cba62164e7ff38e0ccf78a5ca09 Mon Sep 17 00:00:00 2001 From: Hari Mishal Date: Wed, 16 Sep 2026 16:23:32 +0200 Subject: [PATCH 519/535] drm/amd/display: guard dc_sink dereferences in MST mode validation dm_dp_mst_is_port_support_mode() reads aconnector->dc_sink->dsc_caps... for the DSC branch-throughput check, and get_conv_frl_bw()'s HDMI-PCON FRL-bandwidth path reads aconnector->dc_sink->edid_caps.max_frl_rate, both without a NULL check. dc_sink is cleared asynchronously on MST unplug, and both functions run from paths that the driver's own comments document as racing that teardown: the connector probe worker's ->mode_valid callback and a compositor's atomic check, neither of which holds the MST manager lock that the teardown path uses. The former does have an existing dsc_aux NULL check, but dsc_aux isn't reliably cleared in every path that clears dc_sink, so it doesn't cover this. Fail the port-support check and skip the FRL conversion path when the sink is already gone. Fixes: f04d275d94e1 ("drm/amd/display: add mst port output bw check") Fixes: 5c9b8b27a883 ("drm/amd/display: Tie FRL support into amdgpu_dm") Assisted-by: gkh_clanker_t1000 Signed-off-by: Hari Mishal Reviewed-by: Fangzhi Zuo Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 7c3db8da4e039698ae198c870712428644e965c7) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 045a7f88b754..7261317d4c0f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -1231,7 +1231,8 @@ static bool get_conv_frl_bw(struct amdgpu_dm_connector *aconnector, unsigned int max_sink_bw_in_kbps = 0; unsigned int dsc_max_sink_bw_in_kbps = 0; - if (aconnector->dc_link->dc->caps.dp_hdmi21_pcon_support && + if (aconnector->dc_sink && + aconnector->dc_link->dc->caps.dp_hdmi21_pcon_support && aconnector->mst_downstream_port_caps.bytes.byte0.bits.DWN_STRM_PORTX_TYPE == DOWN_STREAM_DETAILED_HDMI) { max_conv_bw_in_kbps = dc_link_bw_kbps_from_raw_frl_link_rate_data( aconnector->dc_link->dc, @@ -1996,6 +1997,9 @@ enum dc_status dm_dp_mst_is_port_support_mode( struct dc_dsc_config_options dsc_options = {0}; uint32_t stream_kbps; + if (!aconnector->dc_sink) + return DC_FAIL_BANDWIDTH_VALIDATE; + /* DSC unnecessary case * Check if timing could be supported within end-to-end BW */ From de1eab13efef83fd2b76f4688de5bf672644596f Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Sun, 27 Sep 2026 16:55:04 +0200 Subject: [PATCH 520/535] drm/radeon: Read the VRAM VBIOS signature with readb() igp_read_bios_from_vram() checked bios[0]/bios[1] with a plain __iomem load, which faults on sparc64 before the copy runs at all. radeon_read_bios() already reads its two signature bytes with readb() ahead of its own copy; use the same accessor here, keeping the check before the allocation. Fixes: b442962a9e82 ("drm/radeon/kms: add support for "Surround View"") Signed-off-by: Imre Kaloz Signed-off-by: Alex Deucher (cherry picked from commit 09155b8932e5013dbce0f5cdff7d75264a279ee5) Cc: stable@vger.kernel.org --- drivers/gpu/drm/radeon/radeon_bios.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/radeon/radeon_bios.c b/drivers/gpu/drm/radeon/radeon_bios.c index 8595f4c6e2e5..1da6bf1c17ff 100644 --- a/drivers/gpu/drm/radeon/radeon_bios.c +++ b/drivers/gpu/drm/radeon/radeon_bios.c @@ -49,6 +49,7 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) { uint8_t __iomem *bios; + uint8_t val1, val2; resource_size_t vram_base; resource_size_t size = 256 * 1024; /* ??? */ @@ -63,7 +64,10 @@ static bool igp_read_bios_from_vram(struct radeon_device *rdev) return false; } - if (size == 0 || bios[0] != 0x55 || bios[1] != 0xaa) { + val1 = readb(&bios[0]); + val2 = readb(&bios[1]); + + if (size == 0 || val1 != 0x55 || val2 != 0xaa) { iounmap(bios); return false; } From b76a5b7bee22556914f7cde210f695ef162d81bf Mon Sep 17 00:00:00 2001 From: Simon Polack Date: Thu, 17 Sep 2026 12:42:34 +0200 Subject: [PATCH 521/535] drm/amd/display: Fix stale replay_events after mod_power stream removal [Why] mod_power_remove_stream() shifts the remaining power_entity slots down but does not move replay_events, and mod_power_add_stream() does not initialize it. replay_events therefore stay bound to the map slot instead of the stream. When several streams are disabled in one atomic commit, amdgpu_dm_mod_power_update_streams() removes them one after another. The eDP stream can then be looked up in a slot whose stale replay_events already have replay_event_hw_programming set, so amdgpu_dm_replay_set_event() returns early ("already in desired state") without calling mod_power_set_replay_event(). Replay is not disabled before the eDP panel is powered off. After DPMS on, the sink reports neither replay state nor frame lock (DPCD 0x378 = 0x00, no error bits), so the HPD IRQ recovery does not trigger and the panel stays black until a full modeset. Seen with an eDP panel using FreeSync Replay plus two DP-MST displays: DPMS off/on of all outputs leaves eDP black, while DPMS of eDP alone works. Doing an eDP-only DPMS first makes the next all-output DPMS fail reliably. [How] Shift replay_events together with the PSR cached fields in mod_power_remove_stream() and initialize it to replay_event_vsync in mod_power_add_stream(), matching the psr_event_vsync initial value used for PSR (both vsync events are driven together by amdgpu_dm_crtc_set_static_screen_optimze()). Tested on 7.3.0-rc3 (238650ef6c7c): the reproducer above now recovers reliably, and Replay still engages when the screen is idle. The issue was debugged with help from an AI assistant (Claude), which analysed ftrace/kprobe traces and the driver source, pointed to the missing replay_events handling and suggested this change. I collected the traces and built and tested the fix on the affected hardware. Fixes: 4cef2ac4c795 ("drm/amd/display: Introduce power module on Linux") Assisted-by: Claude:claude-opus-5 Signed-off-by: Simon Polack Reviewed-by: Ray Wu Tested-by: Daniel Wheeler Signed-off-by: Alex Deucher (cherry picked from commit 3d47e38e271195435e125527b224d11cfdb777b1) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/modules/power/power.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/amd/display/modules/power/power.c b/drivers/gpu/drm/amd/display/modules/power/power.c index 2f9690e65ca9..d900be8cb5fc 100644 --- a/drivers/gpu/drm/amd/display/modules/power/power.c +++ b/drivers/gpu/drm/amd/display/modules/power/power.c @@ -328,6 +328,7 @@ bool mod_power_add_stream(struct mod_power *mod_power, core_power->map[core_power->num_entities].psr_enabled = 0; core_power->map[core_power->num_entities].psr_events = psr_event_vsync; core_power->map[core_power->num_entities].psr_power_opt = 0; + core_power->map[core_power->num_entities].replay_events = replay_event_vsync; core_power->num_entities++; return true; } @@ -387,6 +388,7 @@ bool mod_power_remove_stream(struct mod_power *mod_power, core_power->map[i].psr_enabled = core_power->map[i + 1].psr_enabled; core_power->map[i].psr_events = core_power->map[i + 1].psr_events; core_power->map[i].psr_power_opt = core_power->map[i + 1].psr_power_opt; + core_power->map[i].replay_events = core_power->map[i + 1].replay_events; memcpy(core_power->map[i].psr_context, core_power->map[i + 1].psr_context, sizeof(struct mod_power_psr_context)); memset(core_power->map[i + 1].psr_context, 0, sizeof(struct mod_power_psr_context)); From 08c9100f65299c64439e22e6057d750ae0e6c660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 23 Sep 2026 14:03:54 +0200 Subject: [PATCH 522/535] drm/amd/pm/si: Fix updating clock limits on AC/DC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assume that the AC limits are the maximum of all power states, and the DC limits are the maximum of battery power states. This shouldn't make any difference in practice, but is cleaner and more robust against bogus information in the VBIOS. Fixes: e6c5d36756e7 ("drm/amd/pm/si: Fix updating clock limits from power states") Signed-off-by: Timur Kristóf Reviewed-by: Mario Limonciello Link: https://patch.msgid.link/20260923120354.1027996-2-timur.kristof@gmail.com Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher (cherry picked from commit ef8cb9dcc7db7b7abcbdbe870a080cc81e4f0bf3) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c index 064f7615759c..d26be73637e3 100644 --- a/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c +++ b/drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c @@ -7238,6 +7238,19 @@ static void si_parse_pplib_non_clock_info(struct amdgpu_device *adev, adev->pm.dpm.uvd_ps = rps; } +static void si_update_limits(struct amdgpu_clock_and_voltage_limits *limits, + struct rv7xx_pl *pl) +{ + if (pl->sclk > limits->sclk) + limits->sclk = pl->sclk; + if (pl->mclk > limits->mclk) + limits->mclk = pl->mclk; + if (pl->vddc > limits->vddc) + limits->vddc = pl->vddc; + if (pl->vddci > limits->vddci) + limits->vddci = pl->vddci; +} + static void si_parse_pplib_clock_info(struct amdgpu_device *adev, struct amdgpu_ps *rps, int index, union pplib_clock_info *clock_info) @@ -7246,7 +7259,6 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, struct evergreen_power_info *eg_pi = evergreen_get_pi(adev); struct si_power_info *si_pi = si_get_pi(adev); struct si_ps *ps = si_get_ps(rps); - struct amdgpu_clock_and_voltage_limits *limits; u16 leakage_voltage; struct rv7xx_pl *pl = &ps->performance_levels[index]; int ret; @@ -7311,26 +7323,14 @@ static void si_parse_pplib_clock_info(struct amdgpu_device *adev, * VBIOS can contain conflicting values between: * - the maximum allowed clocks and voltages on AC or DC * - the clocks and voltages in power states on AC or DC + * + * Assume that the AC limits are the maximum of all power states, + * and the DC limits are the maximum of battery power states. */ + si_update_limits(&adev->pm.dpm.dyn_state.max_clock_voltage_on_ac, pl); if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == - ATOM_PPLIB_CLASSIFICATION_UI_PERFORMANCE) - limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_ac; - else if ((rps->class & ATOM_PPLIB_CLASSIFICATION_UI_MASK) == ATOM_PPLIB_CLASSIFICATION_UI_BATTERY) - limits = &adev->pm.dpm.dyn_state.max_clock_voltage_on_dc; - else - limits = NULL; - - if (limits) { - if (pl->sclk > limits->sclk) - limits->sclk = pl->sclk; - if (pl->mclk > limits->mclk) - limits->mclk = pl->mclk; - if (pl->vddc > limits->vddc) - limits->vddc = pl->vddc; - if (pl->vddci > limits->vddci) - limits->vddci = pl->vddci; - } + si_update_limits(&adev->pm.dpm.dyn_state.max_clock_voltage_on_dc, pl); } union pplib_power_state { From a22e5e4f2ebbdbb962cc3233094d189c4dfaa051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Beltr=C3=A1n=20Millal=C3=A9n?= Date: Thu, 24 Sep 2026 18:05:23 -0300 Subject: [PATCH 523/535] drm/amdgpu: reset VI ASIC on MacBookPro14,3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a MacBookPro14,3 with a Radeon Pro 555 (Polaris11), the framebuffer is at MC address 0 when amdgpu loads after a cold boot, as the firmware leaves it (MC_VM_FB_LOCATION = 0x007f0000), while the VBIOS ASIC_Init table places it at 0xF4_0000_0000 (0xf47ff400). amdgpu reads the location once, at init, so after a re-POST (S3 resume or GPU reset) the framebuffer has moved and the driver keeps programming the old one: the SMU is handed a table that was never written and the GPU does not come back, which leaves the internal panel black. Resetting the ASIC on load makes ASIC_Init run before the driver reads the location, so the driver uses the VBIOS placement from the start and every later re-POST puts the framebuffer back where it already is. Add the Radeon Pro 555 used in this machine to the existing VI reset quirk table. Tested on a MacBookPro14,3 on 6.18.49 with the quirk table backported (the kernel also carries unrelated local PCI and ACPI patches for this machine). The framebuffer is at 0x000000F400000000 after both cold and warm boot, and the GPU survived 9 S3 cycles (lid close and rtcwake, one of them with the lid closed for about 7.5 minutes and a USB-C disk attached), each followed by a few minutes of 3D load; no ring timeouts or VM faults were reported. The reset adds about 0.23 s to amdgpu init. Suggested-by: Christian König Suggested-by: Alex Deucher Link: https://lore.kernel.org/all/20260924132952.25054-1-fbeltranmillalen@gmail.com/ Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Francisco Beltrán Millalén Signed-off-by: Alex Deucher (cherry picked from commit b6b1d97218518003107d4446fac278fa3e0a19a0) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/vi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index 352c27103305..e926d09128e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -1397,6 +1397,7 @@ struct vi_reset_quirk { static const struct vi_reset_quirk vi_reset_quirks[] = { { 0x67ef, PCI_VENDOR_ID_APPLE, 0x0190, 0xe3 }, /* Radeon Pro 555X */ { 0x67ef, PCI_VENDOR_ID_APPLE, 0x018f, 0xc2 }, /* Radeon Pro 560X */ + { 0x67ef, PCI_VENDOR_ID_APPLE, 0x017a, 0xc7 }, /* Radeon Pro 555 */ }; static bool vi_need_reset_on_init(struct amdgpu_device *adev) From 3753e34b73fcbe2a2edbe984614a008a261697ef Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:40:58 +0000 Subject: [PATCH 524/535] drm/mediatek: Fix pdev reference leak in mtk_drm_bind() When the device is not the mmsys master, mtk_drm_bind() returns early after taking a reference on the disp-mutex device via of_find_device_by_node(), without ever dropping it: mtk_drm_unbind() only puts mutex_dev for the master. Drop the reference before returning from the non-master path. Fixes: 1ef7ed48356c ("drm/mediatek: Modify mediatek-drm for mt8195 multi mmsys support") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260916174058.2088709-1-vulab@iscas.ac.cn/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_drm_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index b281482ab8c8..8484be0fe224 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -638,8 +638,10 @@ static int mtk_drm_bind(struct device *dev) private->mtk_drm_bound = true; private->dev = dev; - if (!mtk_drm_get_all_drm_priv(dev)) + if (!mtk_drm_get_all_drm_priv(dev)) { + put_device(private->mutex_dev); return 0; + } drm = drm_dev_alloc(&mtk_drm_driver, dev); if (IS_ERR(drm)) { From 2af45da333bdd7be2666688a0f861e0750e40e49 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:42:29 +0000 Subject: [PATCH 525/535] drm/mediatek: Fix runtime PM leak in mtk_hdmi_ddc_v2_probe() pm_runtime_get_sync() unconditionally bumps the usage counter, but nothing puts it back when devm_i2c_add_adapter() fails, leaving the DDC runtime-resumed after a failed probe. Drop the reference on that error path. Fixes: 8d0f79886273 ("drm/mediatek: Introduce HDMI/DDC v2 for MT8195/MT8188") Signed-off-by: Wentao Liang Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260916174229.2088824-1-vulab@iscas.ac.cn/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c index 31e81a6de6d8..1ec973d3f52c 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c @@ -376,8 +376,10 @@ static int mtk_hdmi_ddc_v2_probe(struct platform_device *pdev) pm_runtime_get_sync(dev); ret = devm_i2c_add_adapter(dev, &ddc->adap); - if (ret < 0) + if (ret < 0) { + pm_runtime_put_noidle(dev); return dev_err_probe(dev, ret, "Cannot add DDC I2C adapter\n"); + } platform_set_drvdata(pdev, ddc); return 0; From 048739be3e9cacae5301b68245668002fe6f0ce5 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 21:11:56 +0800 Subject: [PATCH 526/535] drm/mediatek: Fix ovl adaptor platform device leak mtk_drm_probe() creates an OVL adaptor platform device with platform_device_register_data() when the display pipeline requires the OVL adaptor. If a later initialization step fails, the probe error path releases the DRM resources without unregistering the already registered OVL adaptor device. The normal remove path likewise leaves the device registered after the DRM driver is unbound. Keep track of whether the OVL adaptor was successfully registered and unregister it on probe failure. Also recover the platform device from the stored DDP component device and unregister it during normal removal. The issue was identified by a static analysis tool I developed and confirmed by manual review. Fixes: 0d9eee9118b7 ("drm/mediatek: Add drm ovl_adaptor sub driver for MT8195") Cc: stable@vger.kernel.org Signed-off-by: Guangshuo Li Link: https://patchwork.kernel.org/project/linux-mediatek/patch/20260921131156.403652-1-lgs201920130244@gmail.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_drm_drv.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/mediatek/mtk_drm_drv.c b/drivers/gpu/drm/mediatek/mtk_drm_drv.c index 8484be0fe224..dc8e3e917dd7 100644 --- a/drivers/gpu/drm/mediatek/mtk_drm_drv.c +++ b/drivers/gpu/drm/mediatek/mtk_drm_drv.c @@ -1062,7 +1062,7 @@ static int mtk_drm_probe(struct platform_device *pdev) struct mtk_mmsys_driver_data *mtk_drm_data; struct device_node *node; struct component_match *match = NULL; - struct platform_device *ovl_adaptor; + struct platform_device *ovl_adaptor = NULL; int ret; int i; @@ -1210,6 +1210,8 @@ static int mtk_drm_probe(struct platform_device *pdev) err_pm: pm_runtime_disable(dev); err_node: + if (ovl_adaptor) + platform_device_unregister(ovl_adaptor); of_node_put(private->mutex_node); for (i = 0; i < DDP_COMPONENT_DRM_ID_MAX; i++) of_node_put(private->comp_node[i]); @@ -1219,9 +1221,15 @@ static int mtk_drm_probe(struct platform_device *pdev) static void mtk_drm_remove(struct platform_device *pdev) { struct mtk_drm_private *private = platform_get_drvdata(pdev); + struct device *ovl_adaptor_dev; int i; + ovl_adaptor_dev = + private->ddp_comp[DDP_COMPONENT_DRM_OVL_ADAPTOR].dev; + component_master_del(&pdev->dev, &mtk_drm_ops); + if (ovl_adaptor_dev) + platform_device_unregister(to_platform_device(ovl_adaptor_dev)); pm_runtime_disable(&pdev->dev); of_node_put(private->mutex_node); for (i = 0; i < DDP_COMPONENT_DRM_ID_MAX; i++) From 7a39c732a22ec4a369af9e19fbe3d666ec83eec4 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Wed, 30 Sep 2026 13:36:18 +0800 Subject: [PATCH 527/535] kprobes: Skip disarmed probes when checking optkprobe overlap On x86, an optkprobe at A replaces five bytes with a jump. If a disabled probe B is at A+2, get_optimized_kprobe() stops at B when arming a new probe C at A+4. It leaves A optimized: A A+1 A+2 A+3 A+4 A's jump | e9 | d0 | d1 | d2 | d3 | after C | e9 | d0 | d1 | d2 | cc | The INT3 for C overwrites the last byte of A's jump displacement, so execution can jump to the wrong address. B can have prepared optinsns while disarmed, but has no jump to unoptimize. Continue past disarmed and unprepared probes to find the active optimized probe before arming a probe in its jump. Link: https://lore.kernel.org/all/20260930053618.104498-1-leon.hwang@linux.dev/ Fixes: afd66255b9a4 ("kprobes: Introduce kprobes jump optimization") Cc: stable@vger.kernel.org Signed-off-by: Leon Hwang Signed-off-by: Masami Hiramatsu (Google) --- kernel/kprobes.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 4edd8ca5c657..e787e4948c8b 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -496,14 +496,16 @@ static bool kprobe_queued(struct kprobe *p) static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr) { int i; - struct kprobe *p = NULL; + struct kprobe *p; struct optimized_kprobe *op; /* Don't check i == 0, since that is a breakpoint case. */ - for (i = 1; !p && i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++) + for (i = 1; i < MAX_OPTIMIZED_LENGTH / sizeof(kprobe_opcode_t); i++) { p = get_kprobe(addr - i); + /* A disabled probe can have prepared, but inactive, optinsns. */ + if (!p || !kprobe_optready(p) || kprobe_disarmed(p)) + continue; - if (p && kprobe_optready(p)) { op = container_of(p, struct optimized_kprobe, kp); if (arch_within_optimized_kprobe(op, addr)) return p; From 6c95ca52f27855dd2fb74131c8d4e8325d2af7de Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 2 Oct 2026 09:27:36 +0200 Subject: [PATCH 528/535] tty: add missing driver flag kernel-doc colon A recent change adding a new TTY driver flag left out a colon required for well-formed kernel-doc. Fixes: 8df07fe93573 ("tty: fix saved termios reset race") Reported-by: Randy Dunlap Link: https://lore.kernel.org/ec3a09d6-4ba4-41b8-abb1-b59e265f4532@infradead.org Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20261002072736.2063004-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/tty_driver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h index 62e459c5d3ae..40ae3bb13e11 100644 --- a/include/linux/tty_driver.h +++ b/include/linux/tty_driver.h @@ -74,7 +74,7 @@ struct serial_struct; * Do not create workqueue when tty_register_driver(). Whenever set, flip * buffer workqueue can be set by tty_port_link_wq() for every port. * - * @TTY_DRIVER_RESET_SAVED_TERMIOS + * @TTY_DRIVER_RESET_SAVED_TERMIOS: * Reset any saved termios settings on device registration when reusing a * minor number. Must only be set by drivers that guarantee that the minor * number is no longer in use. From 26f6b6357b1b06e6aaa9b8d796989cca785d8e1d Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Thu, 1 Oct 2026 10:25:15 +0200 Subject: [PATCH 529/535] irq: Make refcount_interrupt kunit test selectable Currently, refcount_interrupt_test is built unconditionally when CONFIG_KUNIT is enabled, causing it to run unexpectedly during boot. Fix this by introducing CONFIG_REFCOUNT_INTERRUPT_KUNIT_TEST so the test can be configured independently, following standard kunit practices. Fixes: 07a88e2bcd5b ("irq: Add KUnit test for refcounted interrupt enable/disable") Signed-off-by: Kuan-Wei Chiu Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Lyude Paul Reviewed-by: Radu Rendec Reviewed-by: Boqun Feng Link: https://patch.msgid.link/20261001082519.16195-2-boqun@kernel.org --- kernel/irq/Kconfig | 12 ++++++++++++ kernel/irq/Makefile | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/kernel/irq/Kconfig b/kernel/irq/Kconfig index 05cba4e16dad..6923f37eaab4 100644 --- a/kernel/irq/Kconfig +++ b/kernel/irq/Kconfig @@ -150,6 +150,18 @@ config IRQ_KUNIT_TEST If unsure, say N. +config REFCOUNT_INTERRUPT_KUNIT_TEST + tristate "Test refcounted interrupt enable/disable" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the kunit tests for the refcounted interrupt + infrastructure. It verifies the correctness of single, nested, + and multiple interrupt enable/disable state changes and ensures + that the underlying reference counting mechanisms work as expected. + + If unsure, say N. + endmenu config GENERIC_IRQ_MULTI_HANDLER diff --git a/kernel/irq/Makefile b/kernel/irq/Makefile index 44c4d6fc502a..0e5df962a149 100644 --- a/kernel/irq/Makefile +++ b/kernel/irq/Makefile @@ -16,4 +16,4 @@ obj-$(CONFIG_SMP) += affinity.o obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o obj-$(CONFIG_IRQ_KUNIT_TEST) += irq_test.o -obj-$(CONFIG_KUNIT) += refcount_interrupt_test.o +obj-$(CONFIG_REFCOUNT_INTERRUPT_KUNIT_TEST) += refcount_interrupt_test.o From f35e3b5784221654f9cdbd6222275a7fa203f6c3 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Thu, 1 Oct 2026 13:50:22 +0000 Subject: [PATCH 530/535] futex: Fix private hash use-after-free on resize poll_state_synchronize_rcu(mm->futex.phash.batches) is used by futex_ref_drop() to check that a grace period has passed since the current hash was published. This relies on batches referencing a grace period which started after the hash pointer was assigned. __futex_pivot_hash() sets mmph->batches before it replaces mmph->hash: scoped_guard(rcu) { mmph->batches = get_state_synchronize_rcu(); rcu_assign_pointer(mmph->hash, new); } The scoped_guard(rcu) doesn't stop new grace periods from starting, and if one starts between those two assignments, futex_ref_drop() can move forward while a reader still holds a pointer to the old hash. Fix things by setting mmph->batches after assigning mmph->hash. The scoped_guard(rcu) isn't needed, so let's drop that as well. Fixes: 56180dd20c19 ("futex: Use RCU-based per-CPU reference counting instead of rcuref_t") Assisted-by: kres Signed-off-by: Chris Mason Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Paul E. McKenney Link: https://patch.msgid.link/20261001135022.2220288-1-mason@kernel.org --- kernel/futex/core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kernel/futex/core.c b/kernel/futex/core.c index a061f54b606d..095f9fe440e3 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -213,10 +213,12 @@ static bool __futex_pivot_hash(struct mm_struct *mm, struct futex_private_hash * futex_rehash_private(fph, new); } new->state = FR_PERCPU; - scoped_guard(rcu) { - mmph->batches = get_state_synchronize_rcu(); - rcu_assign_pointer(mmph->hash, new); - } + rcu_assign_pointer(mmph->hash, new); + /* + * mmph->batches must reference a grace period which started after + * mmph->hash was assigned. See futex_ref_drop(). + */ + mmph->batches = get_state_synchronize_rcu(); kvfree_rcu(fph, rcu); return true; } From eb13a1ff271b0d180687eebb30611b0b276d5193 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 25 Sep 2026 22:46:32 +0100 Subject: [PATCH 531/535] random: vDSO: avoid call to memset() when zeroing reserved parameter After a recent change in LLVM [1], builds with the random vDSO implementation, such as PowerPC and RISC-V, fail when checking the vDSO: arch/powerpc/kernel/vdso/vdso32.so.dbg: dynamic relocations are not supported arch/riscv/kernel/vdso/vdso.so.dbg: dynamic relocations are not supported memset() is now generated when zeroing params->reserved for some builds because LLVM has an optimization (now run in more instances) that can recognize at compile time when it is assigning a static value to a contiguous area of memory and turn that into a call to memset(). Both clang and GCC assume memset() is always available [2]. Clang has an internal fiddly hook, -max-store-memset, which we can set to a high number, to disable generating out of line memset calls [3]. Similarly, GCC has -finline-stringops=memset to do the same [4], should this issue ever hit future version of GCC. While these options wouldn't make sense for normal kernel code, it is fine for the extremely limited and intentionally compact vDSO code. Link: https://github.com/llvm/llvm-project/commit/90cebef1411617fc3eedd359bdf00cb44b1c2439 [1] Link: https://gcc.gnu.org/onlinedocs/gcc-16.2.0/gcc/Standards.html#index-ffreestanding [2] Link: https://github.com/llvm/llvm-project/commit/b28eeb28bea39148738dc375e8a97072a1907e64 [3] Link: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-finline-stringops [4] Closes: https://github.com/ClangBuiltLinux/linux/issues/2183 Cc: stable@vger.kernel.org # v6.12+ Signed-off-by: Nathan Chancellor Signed-off-by: Jason A. Donenfeld --- arch/arm64/kernel/vdso/Makefile | 2 +- arch/loongarch/vdso/Makefile | 1 + arch/powerpc/kernel/vdso/Makefile | 1 + arch/riscv/kernel/vdso/Makefile | 1 + arch/s390/kernel/vdso/Makefile | 1 + arch/x86/entry/vdso/vdso64/Makefile | 2 +- init/Kconfig | 5 +++++ 7 files changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile index 7dec05dd33b7..f6619e1cb2ce 100644 --- a/arch/arm64/kernel/vdso/Makefile +++ b/arch/arm64/kernel/vdso/Makefile @@ -41,7 +41,7 @@ CC_FLAGS_REMOVE_VDSO := $(CC_FLAGS_FTRACE) -Os $(CC_FLAGS_SCS) \ $(CC_FLAGS_LTO) $(CC_FLAGS_CFI) \ -Wmissing-prototypes -Wmissing-declarations -CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables +CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables $(CONFIG_CC_OPT_INLINE_MEMSET) CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO) CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO) diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile index 9c9181bb4071..0b84892d084b 100644 --- a/arch/loongarch/vdso/Makefile +++ b/arch/loongarch/vdso/Makefile @@ -16,6 +16,7 @@ ccflags-vdso := \ $(filter -m64,$(KBUILD_CFLAGS)) \ $(filter -march=%,$(KBUILD_CFLAGS)) \ $(filter -m%-float,$(KBUILD_CFLAGS)) \ + $(CONFIG_CC_OPT_INLINE_MEMSET) \ $(CLANG_FLAGS) \ -D__VDSO__ diff --git a/arch/powerpc/kernel/vdso/Makefile b/arch/powerpc/kernel/vdso/Makefile index 368759f81708..0d1a49529885 100644 --- a/arch/powerpc/kernel/vdso/Makefile +++ b/arch/powerpc/kernel/vdso/Makefile @@ -43,6 +43,7 @@ ccflags-y := -fno-common -fno-builtin -DBUILD_VDSO ccflags-y += $(DISABLE_LATENT_ENTROPY_PLUGIN) ccflags-y += $(call cc-option, -fno-stack-protector) ccflags-y += -DDISABLE_BRANCH_PROFILING +ccflags-y += $(CONFIG_CC_OPT_INLINE_MEMSET) ccflags-y += -ffreestanding -fasynchronous-unwind-tables ccflags-remove-y := $(CC_FLAGS_FTRACE) ldflags-y := -Wl,--hash-style=both -nostdlib -shared -z noexecstack $(CLANG_FLAGS) diff --git a/arch/riscv/kernel/vdso/Makefile b/arch/riscv/kernel/vdso/Makefile index 8dbf2532a573..c023046a3fd7 100644 --- a/arch/riscv/kernel/vdso/Makefile +++ b/arch/riscv/kernel/vdso/Makefile @@ -36,6 +36,7 @@ endif ccflags-y := -fno-stack-protector ccflags-y += -DDISABLE_BRANCH_PROFILING ccflags-y += -fno-builtin +ccflags-y += $(CONFIG_CC_OPT_INLINE_MEMSET) ccflags-y += $(KBUILD_BASE_ISA)$(CFI_MARCH) ccflags-y += $(CFI_FULL) asflags-y += $(KBUILD_BASE_ISA)$(CFI_MARCH) diff --git a/arch/s390/kernel/vdso/Makefile b/arch/s390/kernel/vdso/Makefile index 35c834b895ec..54bcf2984ca1 100644 --- a/arch/s390/kernel/vdso/Makefile +++ b/arch/s390/kernel/vdso/Makefile @@ -29,6 +29,7 @@ KBUILD_CFLAGS_VDSO := $(filter-out -munaligned-symbols,$(KBUILD_CFLAGS_VDSO)) KBUILD_CFLAGS_VDSO := $(filter-out -fno-asynchronous-unwind-tables,$(KBUILD_CFLAGS_VDSO)) KBUILD_CFLAGS_VDSO += -fPIC -fno-common -fno-builtin -fasynchronous-unwind-tables KBUILD_CFLAGS_VDSO += -fno-stack-protector $(DISABLE_KSTACK_ERASE) +KBUILD_CFLAGS_VDSO += $(CONFIG_CC_OPT_INLINE_MEMSET) ldflags-y := -shared -soname=linux-vdso.so.1 \ --hash-style=both --build-id=sha1 \ $(call ld-option, --eh-frame-hdr) -T diff --git a/arch/x86/entry/vdso/vdso64/Makefile b/arch/x86/entry/vdso/vdso64/Makefile index 7c0790065b5e..c2353a065279 100644 --- a/arch/x86/entry/vdso/vdso64/Makefile +++ b/arch/x86/entry/vdso/vdso64/Makefile @@ -14,7 +14,7 @@ vobjs-$(CONFIG_X86_SGX) += vsgx.o vobjs-$(CONFIG_FUTEX_ROBUST_UNLOCK) += vfutex.o # Compilation flags -flags-y := -DBUILD_VDSO64 -m64 -mcmodel=small +flags-y := -DBUILD_VDSO64 -m64 -mcmodel=small $(CONFIG_CC_OPT_INLINE_MEMSET) # The location of this include matters! include $(src)/../common/Makefile.include diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c52..ad592fdf29af 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -173,6 +173,11 @@ config CC_HAS_ALLOC_TOKEN config CC_HAS_MULTIDIMENSIONAL_NONSTRING def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) +config CC_OPT_INLINE_MEMSET + string + default "-finline-stringops=memset" if $(cc-option,-finline-stringops=memset) + default "-mllvm -max-store-memset=4294967294" if $(cc-option,-mllvm -max-store-memset=4294967294) + config LD_CAN_USE_KEEP_IN_OVERLAY # ld.lld prior to 21.0.0 did not support KEEP within an overlay description # https://github.com/llvm/llvm-project/pull/130661 From de020dc8049bfb2b22e3b6d99c031feb2e22d112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Mete=20Kaya?= Date: Tue, 29 Sep 2026 11:10:34 +0300 Subject: [PATCH 532/535] bpf: Fix missing migration protection in __rhtab_map_lookup_and_delete_batch() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bpf_mem_cache_free_rcu() uses this_cpu_ptr() which requires migration to be disabled. All callers of rhtab_delete_elem() disable migration except __rhtab_map_lookup_and_delete_batch(), which calls it under rcu_read_lock() only. On CONFIG_PREEMPT_RCU, rcu_read_lock() does not disable preemption or migration, so the task can migrate between CPUs during the delete loop, causing this_cpu_ptr() to trigger: BUG: using smp_processor_id() in preemptible [00000000] code Fix by wrapping the delete loop in migrate_disable()/migrate_enable() in __rhtab_map_lookup_and_delete_batch(), matching the migration protection that the other callers already provide. Fixes: 818e00848227 ("bpf: Implement iteration ops for resizable hashtab") Reported-by: syzbot+fd7e415d891073b83e1f@syzkaller.appspotmail.com Signed-off-by: Ömer Mete Kaya Signed-off-by: Alexei Starovoitov Acked-by: Mykyta Yatsenko Link: https://patch.msgid.link/20260929081609.557899-1-omermetekaya0@gmail.com Closes: https://syzkaller.appspot.com/bug?extid=fd7e415d891073b83e1f --- kernel/bpf/hashtab.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 548eb1cc7c45..13a2356c84cf 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -3374,8 +3374,10 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map, } if (do_delete) { + migrate_disable(); for (i = 0; i < total; i++) rhtab_delete_elem(rhtab, del_elems[i], NULL, 0); + migrate_enable(); } rcu_read_unlock(); From 40cce45b921f7930886bcba43069edc579725457 Mon Sep 17 00:00:00 2001 From: Nikola Prica Date: Mon, 21 Sep 2026 13:19:03 +0200 Subject: [PATCH 533/535] PCI: Accept AtomicOps already enabled by the hypervisor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_enable_atomic_ops_to_root() currently fails when no Root Port is visible. That is common in passthrough guests (ESXi, Hyper-V): the Endpoint is assigned to the VM, but the Root Port above it is not visible in the guest topology. In those setups the hypervisor may already have enabled AtomicOp Requester Enable on the device. If PCI_EXP_DEVCTL2_ATOMIC_REQ is set, treat AtomicOps as already enabled and return success instead of failing the Root Port walk. After 1ae8c4ce1570 ("PCI: Enable AtomicOps only if Root Port supports them"), pci_enable_atomic_ops_to_root() always returns failure if the Root Port is not visible, so drivers don't use atomics when they could. On systems where the Root Port is not visible but *does* support AtomicOps, this is a regression: prior to 1ae8c4ce1570, it enabled AtomicOps in the endpoint and returned success. Fixes: 1ae8c4ce1570 ("PCI: Enable AtomicOps only if Root Port supports them") Signed-off-by: Nikola Prica [bhelgaas: commit log, code comment] Signed-off-by: Bjorn Helgaas Tested-by: Gerd Bayer Reviewed-by: Christian König Reviewed-by: Gerd Bayer Link: https://patch.msgid.link/20260921111903.978687-1-nikprica@amd.com --- drivers/pci/pci.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index b2879a6be5f8..8cc6a89130b5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3769,8 +3769,21 @@ int pci_enable_atomic_ops_to_root(struct pci_dev *dev, u32 cap_mask) } root = pcie_find_root_port(dev); - if (!root) + if (!root) { + + /* + * A hypervisor may expose a topology with the root port + * not visible to the guest. If the hypervisor has already + * set AtomicOp Requester Enable in the endpoint, we assume + * it has verified support in the root port, so it is safe + * for the driver to use AtomicOps. + */ + pcie_capability_read_dword(dev, PCI_EXP_DEVCTL2, &ctl2); + if (ctl2 & PCI_EXP_DEVCTL2_ATOMIC_REQ) + return 0; + return -EINVAL; + } pcie_capability_read_dword(root, PCI_EXP_DEVCAP2, &cap); if ((cap & cap_mask) != cap_mask) From 3bf8d603fd45649c106cae6290a10f20b1bbb569 Mon Sep 17 00:00:00 2001 From: Martino Papero Date: Wed, 30 Sep 2026 22:10:41 +0200 Subject: [PATCH 534/535] Input: atkbd - skip deactivate for Lenovo IdeaPad Slim 3 15IWC11 The internal keyboard on the Lenovo IdeaPad Slim 3 15IWC11 (83RR) does not work correctly with the default i8042 settings. Using i8042.nopnp=1 and i8042.dumbkbd=1 restores keyboard input, but prevents the Caps Lock LED from working. Using i8042.nopnp=1 alone does not fix the keyboard. The laptop works correctly when atkbd_deactivate_fixup is used instead. Add a DMI quirk for the 83RR to enable it. This was tested without any i8042 command-line parameters. Keyboard input and the Caps Lock LED work correctly, including after suspend and resume and after a cold boot. Link: https://lore.kernel.org/all/4f43465f-98ba-4722-8e29-03df20315369@kernel.org/ Signed-off-by: Martino Papero Reviewed-by: Hans de Goede Link: https://patch.msgid.link/c0b597b8-e7ad-4fba-a2bb-5d252e3dfbd3@lcb.to.it Cc: stable@vger.kernel.org Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/atkbd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index 2627434099ff..15ae5352e14b 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -1938,6 +1938,14 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = { }, .callback = atkbd_deactivate_fixup, }, + { + /* Lenovo IdeaPad Slim 3 15IWC11 (83RR) */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83RR"), + }, + .callback = atkbd_deactivate_fixup, + }, { /* Xiaomi Book Pro 14 (TM2424) */ .matches = { From a90ee4305c4a5df72c11b31dacfdc76e00fcf78a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 4 Oct 2026 13:45:25 -0700 Subject: [PATCH 535/535] Linux 7.3-rc6 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6b8b812f23b9..7c855e6fe544 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc5 +EXTRAVERSION = -rc6 NAME = Baby Opossum Posse # *DOCUMENTATION*