From 93847823b6761e1791a7db10f55bbd4f79fa0dbc Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Fri, 21 Aug 2026 05:03:23 +0000 Subject: [PATCH 001/590] tee: qcomtee: fix kernel-doc warnings Fix kernel-doc issues present since the driver was added in commit d6e290837e50 ("tee: add Qualcomm TEE driver"): - async_release() documents its second parameter as @msg, but the parameter is named async_msg (msg is a local variable); - struct qcomtee_msg_object_invoke documents member @ctx, but the member is named cxt; - enum qcomtee_arg_type leaves QCOMTEE_ARG_TYPE_NR undescribed. The cxt spelling is part of the QTEE message format and is used throughout the driver; struct qcomtee_msg_callback in the same header already documents it as @cxt, so the comment is corrected rather than the member. No functional changes. Fixes: d6e290837e50 ("tee: add Qualcomm TEE driver") Reviewed-by: Amirreza Zarrabi Assisted-by: Claude:claude-opus-5 [kernel-doc] Signed-off-by: Babanpreet Singh Signed-off-by: Jens Wiklander --- drivers/tee/qcomtee/async.c | 4 ++-- drivers/tee/qcomtee/qcomtee_msg.h | 2 +- drivers/tee/qcomtee/qcomtee_object.h | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tee/qcomtee/async.c b/drivers/tee/qcomtee/async.c index 31bff4309e67..5849e5161203 100644 --- a/drivers/tee/qcomtee/async.c +++ b/drivers/tee/qcomtee/async.c @@ -97,10 +97,10 @@ static void qcomtee_get_async_buffer(struct qcomtee_object_invoke_ctx *oic, /** * async_release() - Process QTEE async release requests. * @oic: context used for the current invocation. - * @msg: async message for object release. + * @async_msg: async message for object release. * @size: size of the async buffer available. * - * Return: Size of the outbound buffer used when processing @msg. + * Return: Size of the outbound buffer used when processing @async_msg. */ static size_t async_release(struct qcomtee_object_invoke_ctx *oic, struct qcomtee_async_msg_hdr *async_msg, diff --git a/drivers/tee/qcomtee/qcomtee_msg.h b/drivers/tee/qcomtee/qcomtee_msg.h index 878f70178a5b..5d7b21fdd368 100644 --- a/drivers/tee/qcomtee/qcomtee_msg.h +++ b/drivers/tee/qcomtee/qcomtee_msg.h @@ -112,7 +112,7 @@ union qcomtee_msg_arg { /** * struct qcomtee_msg_object_invoke - Direct object invocation message. - * @ctx: object ID hosted in QTEE. + * @cxt: object ID hosted in QTEE. * @op: operation for the object. * @counts: number of different types of arguments in @args. * @args: array of arguments. diff --git a/drivers/tee/qcomtee/qcomtee_object.h b/drivers/tee/qcomtee/qcomtee_object.h index 8b4401ecad48..d5de02dcef3b 100644 --- a/drivers/tee/qcomtee/qcomtee_object.h +++ b/drivers/tee/qcomtee/qcomtee_object.h @@ -74,6 +74,7 @@ enum qcomtee_object_type { * @QCOMTEE_ARG_TYPE_OO: output object (OO). * @QCOMTEE_ARG_TYPE_IB: input buffer (IB). * @QCOMTEE_ARG_TYPE_IO: input object (IO). + * @QCOMTEE_ARG_TYPE_NR: number of argument types. * * Use the invalid type to specify the end of the argument array. */ From f0ea4824deebf21dacfbe397c4b0a5fd3e745a8d Mon Sep 17 00:00:00 2001 From: Marco Chen Date: Sat, 8 Aug 2026 15:54:50 -0400 Subject: [PATCH 002/590] 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 003/590] 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 004/590] 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 005/590] 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 006/590] 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 007/590] 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 008/590] 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 009/590] 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 010/590] 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 011/590] 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 012/590] 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 013/590] 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 014/590] 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 015/590] 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 016/590] 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 017/590] 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 018/590] 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 019/590] 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 020/590] 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 021/590] 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 022/590] 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 023/590] 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 024/590] 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 025/590] 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 026/590] 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 027/590] 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 028/590] 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 029/590] 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 030/590] 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 031/590] 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 032/590] 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 033/590] 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 034/590] 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 035/590] 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 35ce3c571297aee13c6da604d022929fd1aa9efb Mon Sep 17 00:00:00 2001 From: Dale Whinham Date: Sun, 30 Aug 2026 18:40:07 +0100 Subject: [PATCH 036/590] arm64: dts: qcom: x1-denali: Fix microphone distortion The microphones (2x) on Surface Pro 11 are currently ususable due to a misconfigured sample rate, which results in a very distorted recording with the voice barely audible. Under Windows, the DMIC0_CTL register is set to 0x00000005 while recording: bit 0 = 1 --> DMIC0 enabled bits 3:1 = 2 --> DMIC clock divider = /4 The current dmic-sample-rate of 4800000 corresponds to the default DMIC clock divider of /2. Dropping dmic-sample-rate to 2400000 sets the divider to /4, matching the working Windows configuration and resulting in perfect audio. While we're at it, remove the reference to dmic23_default, as we only have two microphones and don't need to configure the pins for a 3rd/4th microphone. Signed-off-by: Dale Whinham Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260830-surface-pro-11-mic-fix-v1-1-36a53140373b@gmail.com Signed-off-by: Bjorn Andersson --- arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi index ce7d958ca687..e3a53b8930d9 100644 --- a/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi +++ b/arch/arm64/boot/dts/qcom/x1-microsoft-denali.dtsi @@ -863,11 +863,11 @@ spkr_01_sd_n_active: spkr-01-sd-n-active-state { }; &lpass_vamacro { - pinctrl-0 = <&dmic01_default>, <&dmic23_default>; + pinctrl-0 = <&dmic01_default>; pinctrl-names = "default"; vdd-micb-supply = <&vreg_l1b_1p8>; - qcom,dmic-sample-rate = <4800000>; + qcom,dmic-sample-rate = <2400000>; }; &mdss { From 5ffd02d16f4e2f0566b8f31d5610538987e1944c Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:04 +0200 Subject: [PATCH 037/590] clk: qcom: gcc-eliza: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: 3d356ab4a1ec ("clk: qcom: Add support for Global clock controller on Eliza") Signed-off-by: Luca Weiss Tested-by: Abel Vesa Reviewed-by: Abel Vesa Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-1-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-eliza.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-eliza.c b/drivers/clk/qcom/gcc-eliza.c index 3e26c7a1e5b5..fe18942a59ac 100644 --- a/drivers/clk/qcom/gcc-eliza.c +++ b/drivers/clk/qcom/gcc-eliza.c @@ -3010,8 +3010,6 @@ static const u32 gcc_eliza_critical_cbcrs[] = { 0x26034, /* GCC_CAMERA_XO_CLK */ 0x27004, /* GCC_DISP_AHB_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32038, /* GCC_VIDEO_XO_CLK */ }; @@ -3045,6 +3043,10 @@ static const struct regmap_config gcc_eliza_regmap_config = { static void clk_eliza_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core and gcc ufs phy axi clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_axi_clk, true); From 62bbf7ed9ad97c17377377aae50b7ea7feb9464e Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:05 +0200 Subject: [PATCH 038/590] clk: qcom: gcc-hawi: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: 67121dad6cba ("clk: qcom: Add support for global clock controller on Hawi") Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-2-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-hawi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-hawi.c b/drivers/clk/qcom/gcc-hawi.c index 6fb5a3db9586..f1b95753c3a3 100644 --- a/drivers/clk/qcom/gcc-hawi.c +++ b/drivers/clk/qcom/gcc-hawi.c @@ -3692,8 +3692,6 @@ static const u32 gcc_hawi_critical_cbcrs[] = { 0x67084, /* GCC_PCIE_1_RSC_CORE_CLK */ 0x43014, /* GCC_PCIE_LINK_XO_CLK */ 0x6b088, /* GCC_PCIE_RSC_CORE_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32028, /* GCC_VIDEO_XO_CLK */ }; @@ -3763,6 +3761,10 @@ static const struct regmap_config gcc_hawi_regmap_config = { static void clk_hawi_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } From 0bfb542fc3368371cefb4cf45ef21c3ba2d37c3f Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 7 Aug 2026 09:46:06 +0200 Subject: [PATCH 039/590] clk: qcom: gcc-kaanapali: Fix always-enabling PCIE_RSCC clocks The critical_cbrcrs array will get qcom_branch_set_clk_en() called on each entry which will set BIT(0) to enable the clock. This is however wrong for these two PCIE_RSCC clocks which need BIT(20) and BIT(21) set respectively to enable them. Fixes: d1919c375f21 ("clk: qcom: Add support for Global clock controller on Kaanapali") Signed-off-by: Luca Weiss Link: https://lore.kernel.org/r/20260807-various-pcie-clk-fix-v1-3-5caf78a599d7@fairphone.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gcc-kaanapali.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/clk/qcom/gcc-kaanapali.c b/drivers/clk/qcom/gcc-kaanapali.c index 842c1a70c691..5bcbcd178657 100644 --- a/drivers/clk/qcom/gcc-kaanapali.c +++ b/drivers/clk/qcom/gcc-kaanapali.c @@ -3464,8 +3464,6 @@ static const u32 gcc_kaanapali_critical_cbcrs[] = { 0x9f004, /* GCC_EVA_AHB_CLK */ 0x9f024, /* GCC_EVA_XO_CLK */ 0x71004, /* GCC_GPU_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_CFG_AHB_CLK */ - 0x52010, /* GCC_PCIE_RSCC_XO_CLK */ 0x32004, /* GCC_VIDEO_AHB_CLK */ 0x32040, /* GCC_VIDEO_XO_CLK */ }; @@ -3480,6 +3478,10 @@ static const struct regmap_config gcc_kaanapali_regmap_config = { static void clk_kaanapali_regs_configure(struct device *dev, struct regmap *regmap) { + /* Keep clocks always enabled */ + regmap_update_bits(regmap, 0x52010, BIT(20), BIT(20)); /* GCC_PCIE_RSCC_CFG_AHB_CLK */ + regmap_update_bits(regmap, 0x52010, BIT(21), BIT(21)); /* GCC_PCIE_RSCC_XO_CLK */ + /* FORCE_MEM_CORE_ON for ufs phy ice core clocks */ qcom_branch_set_force_mem_core(regmap, gcc_ufs_phy_ice_core_clk, true); } From 8f3f88309fa4e975ac27368ee945c2c202251a7c Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Sat, 1 Aug 2026 23:01:17 +0530 Subject: [PATCH 040/590] clk: qcom: gpucc-glymur: Mark the GPU CX GDSC as votable The GPU CX GDSC on Glymur is a votable power domain and hence should not be polled for power-off status during GDSC disable. Also, its power-on status should be read from the GDS hardware control register. Accommodate this by removing POLL_CFG_GDSCR and adding the VOTABLE flag for the GPU CX GDSC, similar to other platforms (e.g. SM8750 and Milos). Fixes: 67e645285dd0 ("clk: qcom: Add support for GPUCC and GXCLK for Glymur") Signed-off-by: Taniya Das Reviewed-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260801-kaanapali-gpu-voteable-v1-1-fe804d24d2e9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-glymur.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gpucc-glymur.c b/drivers/clk/qcom/gpucc-glymur.c index 001b2454786a..cac51de20b29 100644 --- a/drivers/clk/qcom/gpucc-glymur.c +++ b/drivers/clk/qcom/gpucc-glymur.c @@ -510,7 +510,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_glymur_clocks[] = { From 55981579b27b6541aeeab81c768f248dba0491ac Mon Sep 17 00:00:00 2001 From: Taniya Das Date: Sat, 1 Aug 2026 23:01:18 +0530 Subject: [PATCH 041/590] clk: qcom: gpucc-kaanapali: Mark the GPU CX GDSC as votable The GPU CX GDSC on Kaanapali is a votable power domain and hence should not be polled for power-off status during GDSC disable. Also, its power-on status should be read from the GDS hardware control register. Accommodate this by removing POLL_CFG_GDSCR and adding the VOTABLE flag for the GPU CX GDSC, similar to other platforms (e.g. SM8750 and Milos). Fixes: 685ec348339b ("clk: qcom: Add support for GPUCC and GXCLK for Kaanapali") Signed-off-by: Taniya Das Reviewed-by: Jagadeesh Kona Link: https://lore.kernel.org/r/20260801-kaanapali-gpu-voteable-v1-2-fe804d24d2e9@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/clk/qcom/gpucc-kaanapali.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/clk/qcom/gpucc-kaanapali.c b/drivers/clk/qcom/gpucc-kaanapali.c index ae5563e516f6..f75f2caa2205 100644 --- a/drivers/clk/qcom/gpucc-kaanapali.c +++ b/drivers/clk/qcom/gpucc-kaanapali.c @@ -380,7 +380,7 @@ static struct gdsc gpu_cc_cx_gdsc = { .name = "gpu_cc_cx_gdsc", }, .pwrsts = PWRSTS_OFF_ON, - .flags = POLL_CFG_GDSCR | RETAIN_FF_ENABLE, + .flags = RETAIN_FF_ENABLE | VOTABLE, }; static struct clk_regmap *gpu_cc_kaanapali_clocks[] = { From df82be40ae0868be98c8febaf7928cf3e44f7ab9 Mon Sep 17 00:00:00 2001 From: Vladislav Leonov Date: Sun, 23 Aug 2026 15:20:33 +0300 Subject: [PATCH 042/590] 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 043/590] 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 044/590] 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 045/590] 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 046/590] 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 047/590] 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 048/590] 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 049/590] 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 050/590] 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 8188a59dd6782786556b18f515090b79cb701c45 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:29 +0200 Subject: [PATCH 051/590] arm64: dts: lx2160a: fix incorrect pinmux For gpio1 value set for pinctrl is outside the mask and so will do nothing. Add the missing offset for value field to be the same as the mask as expected. Fixes: 284ad7064aaa ("arm64: dts: lx2160a: complete pinmux for rcwsr12 configuration word") Suggested-by: Thibaut Collet Reviewed-by: Josua Mayer Signed-off-by: Vincent Jardin Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index ae6e37870c5e..d4522a19405a 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1622,7 +1622,7 @@ fspi_data74_pins: xspi1-data74-pins { }; gpio1_31_28_pins: xspi1-data74-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 15)>; + pinctrl-single,bits = <0x0 (0x1 << 15) (0x7 << 15)>; }; fspi_data30_pins: xspi1-data30-pins { @@ -1630,7 +1630,7 @@ fspi_data30_pins: xspi1-data30-pins { }; gpio1_27_24_pins: xspi1-data30-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 18)>; + pinctrl-single,bits = <0x0 (0x1 << 18) (0x7 << 18)>; }; fspi_dqs_sck_cs10_pins: xspi1-base-pins { @@ -1638,7 +1638,7 @@ fspi_dqs_sck_cs10_pins: xspi1-base-pins { }; gpio1_23_20_pins: xspi1-base-gpio-pins { - pinctrl-single,bits = <0x0 0x1 (0x7 << 21)>; + pinctrl-single,bits = <0x0 (0x1 << 21) (0x7 << 21)>; }; esdhc0_cmd_data30_clk_vsel_pins: sdhc1-base-sdhc-vsel-pins { From 6affdda171ca8f2e082e8c4fba56233a2f8c9910 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:30 +0200 Subject: [PATCH 052/590] arm64: dts: lx2160a: fix IIC1 pinmux submask rejected by pinctrl-single IIC1_PMUX is a single bit field at RCWSR14 bit 10, so the mask for the two iic1 pinmux nodes was narrowed from 0x7 to 0x1 to avoid writing the two reserved bits next to it. With pinctrl-single,bit-per-mux the driver does not take the supplied mask as the bits to modify. It derives the field from the node's pinctrl-single,function-mask, which is 0x7 here, and then requires the supplied mask to match it exactly: bit_pos = __ffs(mask); /* 10 */ mask_pos = ((pcs->fmask) << bit_pos); /* 0x7 << 10 = 0x1c00 */ submask = mask & mask_pos; /* 0x400 */ ... if (submask != mask_pos) { dev_warn(pcs->dev, "Invalid submask 0x%x for %pOFn at 0x%x\n", submask, np, offset); continue; } A one bit mask gives submask 0x400 against mask_pos 0x1c00, so both nodes are rejected and the pins are never muxed with this message: pinctrl-single 70010012c.pinmux: Invalid submask 0x400 for iic1-i2c-pins pinctrl-single 70010012c.pinmux: Invalid submask 0x400 for iic1-gpio-pins The values are not changed: - 0x0 selects IIC1 - 0x1 << 10 selects GPIO_1[3:2]. Fixes: 7a3cc49ad1fc ("arm64: dts: lx2160a: change i2c0 (iic1) pinmux mask to one bit") Signed-off-by: Vincent Jardin Reviewed-by: Josua Mayer Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index d4522a19405a..d9d4500a4051 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1687,12 +1687,19 @@ i2c6_i2c7_pins: iic8-iic7-i2c-pins { }; /* RCWSR14 */ + /* + * IIC1_PMUX is a single bit at RCWSR14[10], but bit-per-mux + * uses the mask for the field to clear before applying the + * value. + * So bits 11 and 12, which are reserved, are written as 0 + * on every mux switch, it is harmless. + */ i2c0_pins: iic1-i2c-pins { - pinctrl-single,bits = <0x8 0x0 (0x1 << 10)>; + pinctrl-single,bits = <0x8 0x0 (0x7 << 10)>; }; gpio0_3_2_pins: iic1-gpio-pins { - pinctrl-single,bits = <0x8 (0x1 << 10) (0x1 << 10)>; + pinctrl-single,bits = <0x8 (0x1 << 10) (0x7 << 10)>; }; }; From f14f4dc8ea3810f8ed4f0f3ea13f8bba3e2b0f04 Mon Sep 17 00:00:00 2001 From: Vincent Jardin Date: Mon, 24 Aug 2026 23:25:31 +0200 Subject: [PATCH 053/590] arm64: dts: lx2160a: fix the iic5 spi3 pinmux offset and value The iic5-spi3-pins node has both of its first two cells wrong. The offset selects the register within the node's reg range, where: 0x0 is RCWSR12, 0x4 is RCWSR13, 0x8 is RCWSR14. An offset of 0x3 is not one of them and is not even aligned for the declared 32 bit register width. It is the only node in this pinmux that does not use 0x0, 0x4 neither 0x8. The value is wrong too: IIC5_PMUX is RCWSR12[11:9] and it encodes 0b000 for IIC5, 0b001 for GPIO_1[25:24], 0b010 for SDHC1_CLK_SYNC_OUT/IN, 0b011 for {IIC5_SCL,IIC5_SDA} = {SPI3_SOUT,SPI3_SIN}. This node selects the last of those, so it needs 0x3, not the 0x2, which is the value of the neighbouring sdhc1 clock sync node it was copied from. Fixes: 284ad7064aaa ("arm64: dts: lx2160a: complete pinmux for rcwsr12 configuration word") Reviewed-by: Josua Mayer Signed-off-by: Vincent Jardin Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi index d9d4500a4051..bccf2d673023 100644 --- a/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi +++ b/arch/arm64/boot/dts/freescale/fsl-lx216x.dtsi @@ -1602,7 +1602,7 @@ esdhc0_clksync_pins: iic5-sdhc-clk-pins { }; dspi2_miso_mosi_pins: iic5-spi3-pins { - pinctrl-single,bits = <0x3 (0x2 << 9) (0x7 << 9)>; + pinctrl-single,bits = <0x0 (0x3 << 9) (0x7 << 9)>; }; i2c5_pins: iic6-i2c-pins { From cd243401994880b8b6810c53177bce56eebaece5 Mon Sep 17 00:00:00 2001 From: Carlo Szelinsky Date: Wed, 12 Aug 2026 13:42:28 +0300 Subject: [PATCH 054/590] 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 055/590] 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 056/590] 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 057/590] 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 058/590] 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 059/590] 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 060/590] 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 0f300db7b7315b2c7ea0e2d4437b88de162d191f Mon Sep 17 00:00:00 2001 From: Vladyslav Ivashchenko Date: Fri, 4 Sep 2026 15:31:55 +0200 Subject: [PATCH 061/590] 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 062/590] 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 063/590] 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 064/590] 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 065/590] 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 066/590] 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 067/590] 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 068/590] 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 069/590] 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 070/590] 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 071/590] 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 072/590] 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 073/590] 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 074/590] 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 9bc184a2eda8b773c88ecbff934001ad649b9cc1 Mon Sep 17 00:00:00 2001 From: Yuxiao Wang Date: Wed, 9 Sep 2026 12:44:00 +0000 Subject: [PATCH 075/590] 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 076/590] 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 077/590] 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 078/590] 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 079/590] 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 080/590] 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 081/590] 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 082/590] 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 083/590] 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 084/590] 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 085/590] 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 086/590] 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 087/590] 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 088/590] 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 089/590] 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 090/590] 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 091/590] 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 092/590] 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 093/590] 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 094/590] 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 7ba5290db6bf365e46fb70bf13dc2a47048165ea Mon Sep 17 00:00:00 2001 From: Stefano Radaelli Date: Wed, 2 Sep 2026 18:43:17 +0200 Subject: [PATCH 095/590] arm64: dts: imx8mp-var-dart-sonata: Fix Sonata SD I/O supply LDO5 powers the USDHC2 I/O rail on DART-MX8M-PLUS. Without a regulator consumer, the kernel disables LDO5 during late init, which removes the SD card and causes filesystem I/O errors. Reference LDO5 as the Sonata USDHC2 VQMMC supply, so the regulator remains enabled while the SD interface is active. Fixes: 41cd3d91ca859 ("arm64: dts: imx8mp-var-dart: Add support for Variscite Sonata board") Co-developed-by: Luan Rafael Carneiro Signed-off-by: Luan Rafael Carneiro Signed-off-by: Stefano Radaelli Signed-off-by: Frank Li --- arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts index 983d67a0c1de..7f78026e70ff 100644 --- a/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts +++ b/arch/arm64/boot/dts/freescale/imx8mp-var-dart-sonata.dts @@ -511,6 +511,7 @@ &usdhc2 { pinctrl-2 = <&pinctrl_usdhc2_200mhz>, <&pinctrl_usdhc2_gpio>; cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>; vmmc-supply = <®_usdhc2_vmmc>; + vqmmc-supply = <&ldo5>; bus-width = <4>; status = "okay"; }; From 59e0cc31cc5c43586c6eaada62c2c863bfbcbfb5 Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Wed, 9 Sep 2026 14:16:36 +0200 Subject: [PATCH 096/590] 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 097/590] 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 d558af8ee0bb1b2e88e696be84adb93cf2b7be6f Mon Sep 17 00:00:00 2001 From: Troy Mitchell Date: Mon, 7 Sep 2026 09:54:36 +0800 Subject: [PATCH 098/590] 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 099/590] 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 100/590] 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 101/590] 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 102/590] 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 103/590] 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 104/590] 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 105/590] 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 106/590] 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 107/590] 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 108/590] 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 109/590] 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 110/590] 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 111/590] 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 112/590] 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 113/590] 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 114/590] 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 115/590] 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 116/590] 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 117/590] 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 118/590] 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 119/590] 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 120/590] 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 121/590] 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 122/590] 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 123/590] 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 124/590] 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 125/590] 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 126/590] 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 127/590] 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 128/590] 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 129/590] 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 130/590] 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 131/590] 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 132/590] 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 133/590] 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 134/590] 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 135/590] 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 136/590] 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 137/590] 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 138/590] 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 139/590] 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 764b683a6180fd72c931c13fea98d383506a3475 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 15 Sep 2026 00:37:35 +0000 Subject: [PATCH 140/590] dm-integrity: fix buffer overflow in inline mode with large tag size In inline mode, dm_integrity_check and dm_integrity_inline_recheck compute the checksum of each block into an on-stack buffer of HASH_MAX_DIGESTSIZE bytes. integrity_sector_checksum pads its result with zeroes up to the tag size, so if the tag size is larger than HASH_MAX_DIGESTSIZE, the padding runs past the end of the buffer. With CONFIG_STACKPROTECTOR the kernel panics in dm_integrity_check on the first read. Enlarge both buffers to hold MAX_TAG_SIZE bytes, as commit b93b6643e9b5 ("dm integrity: fix a crash with unusually large tag size") did for integrity_metadata. Note that this is not a security bug because the buffer overflow can only be triggered by root. Fixes: fb0987682c62 ("dm-integrity: introduce the Inline mode") Cc: stable@vger.kernel.org Signed-off-by: Ben Cressey Assisted-by: LLM Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 92970e12267a..0862f31b8498 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -2721,7 +2721,7 @@ static void dm_integrity_inline_recheck(struct work_struct *w) outgoing_data = dio->integrity_payload + PAGE_SIZE; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; int r; struct bio_integrity_payload *bip; struct bio_vec bv; @@ -2788,7 +2788,7 @@ static inline bool dm_integrity_check(struct dm_integrity_c *ic, struct dm_integ unsigned pos = 0; while (dio->bio_details.bi_iter.bi_size) { - char digest[HASH_MAX_DIGESTSIZE]; + char digest[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; struct bio_vec bv = bio_iter_iovec(bio, dio->bio_details.bi_iter); char *mem = integrity_kmap(ic, bv.bv_page); integrity_sector_checksum(ic, &dio->ahash_req, dio->bio_details.bi_iter.bi_sector, mem, bv.bv_offset, digest); From 3195e0daf590683992edb4eed32269e569e04924 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Tue, 15 Sep 2026 00:39:10 +0000 Subject: [PATCH 141/590] dm-crypt: reject the lmk IV mode with AEAD ciphers A "capi:" table line that pairs an AEAD cipher with the lmk IV mode, for example "capi:gcm(aes)-lmk" with the integrity:28:aead option, causes an oops when it is loaded. lmk sets tfms_count to 64, but crypt_alloc_tfms_aead only allocates one transform, so crypt_setkey reads past the end of the tfms_aead array and calls crypto_aead_setkey on the values it finds there. lmk exists for compatibility with Loop-AES, which uses CBC, so refuse it with an AEAD cipher rather than allocate 64 AEAD transforms. The check cannot go in crypt_iv_lmk_ctr with the other lmk checks, because the IV constructor runs after crypt_set_key. Note that this is not a security bug because the memory corruption can only be triggered by root. Fixes: 33d2f09fcb35 ("dm crypt: introduce new format of cipher with "capi:" prefix") Cc: stable@vger.kernel.org Signed-off-by: Ben Cressey Assisted-by: LLM Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 9e170de50ad3..0bd22bda3e2d 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -2894,8 +2894,13 @@ static int crypt_ctr_cipher_new(struct dm_target *ti, char *cipher_in, char *key } } - if (*ivmode && !strcmp(*ivmode, "lmk")) + if (*ivmode && !strcmp(*ivmode, "lmk")) { + if (crypt_integrity_aead(cc)) { + ti->error = "AEAD transforms not supported for LMK"; + return -EINVAL; + } cc->tfms_count = 64; + } if (*ivmode && !strcmp(*ivmode, "essiv")) { if (!*ivopts) { From 75fc8a3ee5aee72f2fea3b6436170fae175f7f19 Mon Sep 17 00:00:00 2001 From: Sheetal Date: Mon, 21 Sep 2026 08:57:02 +0000 Subject: [PATCH 142/590] 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 9b3fcf519d370df94a56f3b272d9eddffffa5be8 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Sat, 19 Sep 2026 00:43:39 +0100 Subject: [PATCH 143/590] dm ioctl: don't let data_start run past the buffer struct dm_ioctl has seven bytes of trailing padding, so the smallest buffer userspace is allowed to ask for, offsetof(struct dm_ioctl, data), is seven bytes shorter than sizeof(struct dm_ioctl). get_result_buffer() sets data_start to sizeof(struct dm_ioctl) without looking at how big the buffer actually is. Ask for the minimum and data_start ends up seven bytes past the end. Callers add it to data_size, and ctl_ioctl() copies that much back to userspace: BUG: KASAN: slab-out-of-bounds in _copy_to_user+0xad/0xd0 Read of size 312 at addr ffff8880169e4c00 by task repro-dm/140 ctl_ioctl+0x5e3/0xcb0 dm_ctl_ioctl+0x25/0x40 allocated 305-byte region [ffff8880169e4c00, ffff8880169e4d31) A DM_LIST_DEVICES with data_size set to 305 is enough to hit it, and the seven bytes that leak are whatever sat next to the allocation. Clamp data_start to the buffer size. Note that this is not a security bug because the out-of-bounds read can only be triggered by root. Reported-by: syzbot+48d935cf48a4a76be346@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=48d935cf48a4a76be346 Fixes: 76c072b48e39 ("dm ioctl: move compat code") Cc: stable@vger.kernel.org Signed-off-by: Palla Raghunath Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a6b8e97755cd..a005f124d706 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -656,10 +656,16 @@ static void *get_result_buffer(struct dm_ioctl *param, size_t param_size, { param->data_start = align_ptr(param + 1) - (void *) param; - if (param->data_start < param_size) + if (param->data_start < param_size) { *len = param_size - param->data_start; - else + } else { + /* + * The buffer can be as small as offsetof(*param, data), which + * is less than sizeof(*param), so don't run off the end of it. + */ *len = 0; + param->data_start = param_size; + } return ((void *) param) + param->data_start; } From 50b617ba788e5c27dfc6efc90556cdcc9509028d Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sat, 19 Sep 2026 11:25:23 +0000 Subject: [PATCH 144/590] 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 145/590] 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 146/590] 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 147/590] 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 37aba16e7943634eb3e5e2e7e850ebb38500d935 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Mon, 21 Sep 2026 16:45:36 +0206 Subject: [PATCH 148/590] 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 58a00330248154461c52a6f3bd5c01e5b67f9560 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 18 Sep 2026 11:38:38 +0200 Subject: [PATCH 149/590] power: sequencing: pcie-m2: Add Lenovo ThinkPad T14s gen6 WCN7850 subsystem PCI ids Commit a39ac4651e3b ("power: sequencing: pcie-m2: Match WCN6855 and WCN7851 UART BT variants by subdevice ID") has caused the serdev for the WCN7850 on the Lenovo ThinkPad T14s gen6 WCN7850 no longer getting created. Add a new match using the PCI subdev ids from the T14s gen6 to fix this. Fixes: a39ac4651e3b ("power: sequencing: pcie-m2: Match WCN6855 and WCN7851 UART BT variants by subdevice ID") Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260918093838.6645-1-johannes.goede@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 471ffe914a4e..0b72e3a6992b 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -197,6 +197,8 @@ static const struct pci_device_id pwrseq_m2_pci_ids[] = { .driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" }, { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_QCOM, 0x337c), .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x1107, PCI_VENDOR_ID_LENOVO, 0xe0e9), + .driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" }, { } /* Sentinel */ }; From d30fba93ebfc11e4604a7fcc73fdf28684191dde Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Fri, 18 Sep 2026 13:17:44 +0200 Subject: [PATCH 150/590] 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 151/590] 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 152/590] 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 153/590] 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 154/590] 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 155/590] 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 156/590] 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 157/590] 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 158/590] 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 159/590] 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 160/590] 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 161/590] 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 162/590] 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 163/590] 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 164/590] 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 165/590] 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 166/590] 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 167/590] 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 168/590] 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 169/590] 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 170/590] 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 171/590] 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 172/590] 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 173/590] 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 174/590] 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 175/590] 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 176/590] 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 177/590] 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 178/590] 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 179/590] 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 180/590] 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 181/590] 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 182/590] 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 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 183/590] 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 184/590] 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 185/590] 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 186/590] 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 187/590] 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 188/590] 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 189/590] 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 190/590] 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 191/590] 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 192/590] 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 ad139384ecddb6f3e7e3c3c12765aafae0e39670 Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Thu, 24 Sep 2026 18:23:53 +0000 Subject: [PATCH 193/590] 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 3afefbfe55c2a8a0c4bdf6f4cc1f773da120027c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 15 Sep 2026 17:46:57 +0200 Subject: [PATCH 194/590] 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 195/590] 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 196/590] 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 7fc61cdab13004a5e6dff5b279918b19b62eb200 Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Mon, 14 Sep 2026 16:48:52 +0900 Subject: [PATCH 197/590] 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 198/590] 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 199/590] 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 200/590] 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 201/590] 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 202/590] 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 203/590] 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 204/590] 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 205/590] 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 206/590] 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 207/590] 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 208/590] 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 209/590] 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 210/590] 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 211/590] 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 1ca924044bf0de879a1648be93a56939652267d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Wed, 23 Sep 2026 12:13:23 +0200 Subject: [PATCH 212/590] selinux: fix data race on AVC latest_notif MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_policy_seqno() reads latest_notif without holding notif_lock, while avc_latest_notif_update() can update it under that lock. KCSAN reports these concurrent accesses during policy boolean updates and permission checks. Use READ_ONCE() for the lockless read and WRITE_ONCE() for the update. The spinlock continues to serialize the comparison and update against other writers. Reads made while holding notif_lock cannot race with a writer and can remain plain accesses. This marks both sides of the intentional concurrent access and constrains compiler transformations, without suppressing checking with data_race(). Cc: stable@vger.kernel.org Fixes: 788e7dd4c22e ("SELinux: Improve read/write performance") Reported-by: syzbot+a7f7cb0737bd91aa698c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=a7f7cb0737bd91aa698c Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 560a82c6d682..b91f914d292e 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -578,7 +578,7 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) } } else { if (seqno > selinux_avc.avc_cache.latest_notif) - selinux_avc.avc_cache.latest_notif = seqno; + WRITE_ONCE(selinux_avc.avc_cache.latest_notif, seqno); } spin_unlock_irqrestore(¬if_lock, flag); @@ -1206,5 +1206,6 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 avc_policy_seqno(void) { - return selinux_avc.avc_cache.latest_notif; + /* Writers hold notif_lock; readers only sample the sequence number. */ + return READ_ONCE(selinux_avc.avc_cache.latest_notif); } From 19a4f1a6ed00286d70229f4fd5f690cc2fb033dc Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Fri, 11 Sep 2026 07:06:24 -0500 Subject: [PATCH 213/590] 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 214/590] 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 215/590] 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 216/590] 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 217/590] 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 218/590] 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 219/590] 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 220/590] 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 221/590] 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 222/590] 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 223/590] 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 224/590] 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 225/590] 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 226/590] 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 227/590] 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 228/590] 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 229/590] 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 230/590] 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 231/590] 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 232/590] 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 233/590] 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 234/590] 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 5f43a2d35d5e748c09a50a98fc766dc95bb3a6bd Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sun, 13 Sep 2026 13:35:32 +0800 Subject: [PATCH 235/590] 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 236/590] 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 237/590] 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 238/590] 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 239/590] 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 240/590] 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 241/590] 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 ff82fc3a4a1d417dee1229681cc5285986edb1fa Mon Sep 17 00:00:00 2001 From: Ridham Khurana Date: Wed, 23 Sep 2026 11:39:02 +0000 Subject: [PATCH 242/590] gpio: xilinx: fix runtime PM leak on request error path pm_runtime_get_sync() leaves the usage counter incremented even when it fails, and xgpio_request() returns the error without dropping it. gpiolib does not call ->free() when ->request() fails, so xgpio_free(), which holds the only matching pm_runtime_put(), never runs. The reference is leaked and the controller can no longer runtime-suspend, so its clock stays enabled. Switch to pm_runtime_resume_and_get(), which only increments the usage counter on success. Fixes: 26b04774621e ("gpio: gpio-xilinx: Add support for suspend and resume") Cc: stable@vger.kernel.org Signed-off-by: Ridham Khurana Link: https://patch.msgid.link/20260923113905.1409290-1-khurana.ridham222@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-xilinx.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 532205175827..9406ce85f933 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -271,14 +271,7 @@ static void xgpio_save_regs(struct xgpio_instance *chip) static int xgpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void xgpio_free(struct gpio_chip *chip, unsigned int offset) From c034e8a46e4cb703018fe7e10fe5a3a974d6a7c3 Mon Sep 17 00:00:00 2001 From: Mitul Golani Date: Thu, 17 Sep 2026 13:13:22 +0530 Subject: [PATCH 243/590] 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 244/590] 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 245/590] 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 246/590] 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 247/590] 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 248/590] 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 249/590] 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 250/590] 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 251/590] 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 252/590] 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 253/590] 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 254/590] 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 255/590] 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 256/590] 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 257/590] 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 258/590] 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 259/590] 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 260/590] 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 261/590] 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 262/590] 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 263/590] 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 264/590] 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 265/590] 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 266/590] 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 267/590] 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 268/590] 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 269/590] 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 270/590] 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 271/590] 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 de0be324fcb97a49caad131c23ad33265dd4b0b8 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Mon, 28 Sep 2026 16:39:25 +0800 Subject: [PATCH 272/590] 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 273/590] 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 9d1afbf60ed53c5b051cd124153e0ae3078c8b93 Mon Sep 17 00:00:00 2001 From: Maxim Skokov Date: Tue, 15 Sep 2026 10:00:46 +0300 Subject: [PATCH 274/590] Bluetooth: RFCOMM: free the skb when the DLC has no owner rfcomm_recv_data() hands the skb to d->data_ready() and returns 0 without freeing it, so the callback owns the skb from that point on. The tty implementation frees it when the DLC has been detached from its device: static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) { struct rfcomm_dev *dev = dlc->owner; if (!dev) { kfree_skb(skb); return; } The socket implementation returns without freeing it, and the skb is leaked. A connected DLC can have no owner: rfcomm_sock_destruct() clears d->owner, while the DLC itself stays on the session until it is closed. Data frames that arrive in that window still find d->state == BT_CONNECTED in rfcomm_recv_data() and are handed to the callback, one leaked skb each. The leak does not stop on its own either. The remote runs out of credits only once RFCOMM_RX_THROTTLED is set, and for a socket the only place that sets it is rfcomm_sk_data_ready() itself, below the return. rfcomm_process_dlcs() keeps granting credits, so the remote keeps sending and the leak keeps growing. Free the skb, as the tty side does. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Mikhail Gavrilov Closes: https://lore.kernel.org/linux-bluetooth/CABXGCsPe5wykd8Aj-hm+SJ7SGQyb8yoGNnx3XDpPmBathj+EGg@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Maxim Skokov Assisted-by: Claude:claude-opus-5 Reviewed-by: Mikhail Gavrilov Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/sock.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index fb924d0e34ec..b26918dc9e9e 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -48,8 +48,11 @@ static void rfcomm_sock_kill(struct sock *sk); static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) { struct sock *sk = d->owner; - if (!sk) + + if (!sk) { + kfree_skb(skb); return; + } atomic_add(skb->len, &sk->sk_rmem_alloc); skb_queue_tail(&sk->sk_receive_queue, skb); From 74bbbc9359a592e3c0736b289c00489c1afa928c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Tue, 28 Apr 2026 17:26:44 +0200 Subject: [PATCH 275/590] i40e: skip unnecessary VF reset when setting trust The current implementation triggers a VF reset when changing the trust setting, causing a ~10 second delay during bonding setup. In all the cases, the reset causes a ~10 second delay during which: - VF must reinitialize completely - Any in-progress operations (like bonding enslave) fail with timeouts - VF is unavailable When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (ADQ/cloud filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we don't reset, we manually handle capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- .../ethernet/intel/i40e/i40e_virtchnl_pf.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c index a26c3d47ec15..c6732a24b640 100644 --- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c +++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c @@ -4943,6 +4943,20 @@ int i40e_ndo_set_vf_spoofchk(struct net_device *netdev, int vf_id, bool enable) return ret; } +/** + * i40e_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void i40e_setup_vf_trust(struct i40e_vf *vf, bool setting) +{ + assign_bit(I40E_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * i40e_ndo_set_vf_trust * @netdev: network interface device structure of the pf @@ -4987,19 +5001,20 @@ int i40e_ndo_set_vf_trust(struct net_device *netdev, int vf_id, bool setting) set_bit(__I40E_MACVLAN_SYNC_PENDING, pf->state); pf->vsi[vf->lan_vsi_idx]->flags |= I40E_VSI_FLAG_FILTER_CHANGED; - i40e_vc_reset_vf(vf, true); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!setting && + (vf->adq_enabled || vf->num_cloud_filters > 0 || + test_bit(I40E_VF_STATE_UC_PROMISC, &vf->vf_states) || + test_bit(I40E_VF_STATE_MC_PROMISC, &vf->vf_states))) { + i40e_vc_reset_vf(vf, true); + i40e_del_all_cloud_filters(vf); + } else { + i40e_setup_vf_trust(vf, setting); + } + dev_info(&pf->pdev->dev, "VF %u is now %strusted\n", vf_id, setting ? "" : "un"); - if (vf->adq_enabled) { - if (!vf->trusted) { - dev_info(&pf->pdev->dev, - "VF %u no longer Trusted, deleting all cloud filters\n", - vf_id); - i40e_del_all_cloud_filters(vf); - } - } - out: clear_bit(__I40E_VIRTCHNL_OP_PENDING, pf->state); return ret; From 436729a56dd4dac611d10b569c5359d2cf705b19 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 22 Jun 2026 15:58:24 +0200 Subject: [PATCH 276/590] iavf: send MAC change request synchronously After commit ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations"), iavf_set_mac() is called with the netdev instance lock already held. The function queues a MAC address change request via iavf_replace_primary_mac() and then waits for completion. However, in the current flow, the actual virtchnl message is sent by the watchdog task, which also needs to acquire the netdev lock to run. Additionally, the adminq_task which processes virtchnl responses also needs the netdev lock. This creates a deadlock scenario: 1. iavf_set_mac() holds netdev lock and waits for MAC change 2. Watchdog needs netdev lock to send the request -> blocked 3. Even if request is sent, adminq_task needs netdev lock to process PF response -> blocked 4. MAC change times out after 2.5 seconds 5. iavf_set_mac() returns -EAGAIN This particularly affects VFs during bonding setup when multiple VFs are enslaved in quick succession. Fix by implementing a synchronous MAC change operation similar to the approach used in commit fdadbf6e84c4 ("iavf: fix incorrect reset handling in callbacks"). The solution: 1. Send the virtchnl ADD_ETH_ADDR message directly (not via watchdog) 2. Poll the admin queue hardware directly for responses 3. Process all received messages (including non-MAC messages) 4. Return when MAC change completes or times out A new generic function iavf_poll_virtchnl_response() is introduced that can be reused for any future synchronous virtchnl operations. It takes a callback to check completion, allowing flexible condition checking. This allows the operation to complete synchronously while holding netdev_lock, without relying on watchdog or adminq_task. The function can sleep for up to 2.5 seconds polling hardware, but this is acceptable since netdev_lock is per-device and only serializes operations on the same interface. To support this, change iavf_add_ether_addrs() to return an error code instead of void, allowing callers to detect failures. Additionally, export iavf_mac_add_reject() to enable proper rollback on local failures (timeouts, send errors) - PF rejections are already handled automatically by iavf_virtchnl_completion(). Remove vc_waitqueue entirely because iavf_set_mac was the only waiter on this waitqueue and after the changes it is not needed. Fixes: ad7c7b2172c3 ("net: hold netdev instance lock during sysfs operations") cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Przemek Kitszel Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/iavf/iavf.h | 11 ++- drivers/net/ethernet/intel/iavf/iavf_main.c | 85 ++++++++++++---- .../net/ethernet/intel/iavf/iavf_virtchnl.c | 99 +++++++++++++++++-- 3 files changed, 165 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h index dc31202b2a94..8c45536fd502 100644 --- a/drivers/net/ethernet/intel/iavf/iavf.h +++ b/drivers/net/ethernet/intel/iavf/iavf.h @@ -259,7 +259,6 @@ struct iavf_adapter { struct work_struct adminq_task; struct work_struct finish_config; wait_queue_head_t down_waitqueue; - wait_queue_head_t vc_waitqueue; struct iavf_q_vector *q_vectors; struct list_head vlan_filter_list; int num_vlan_filters; @@ -588,8 +587,9 @@ void iavf_configure_queues(struct iavf_adapter *adapter); void iavf_enable_queues(struct iavf_adapter *adapter); void iavf_disable_queues(struct iavf_adapter *adapter); void iavf_map_queues(struct iavf_adapter *adapter); -void iavf_add_ether_addrs(struct iavf_adapter *adapter); +int iavf_add_ether_addrs(struct iavf_adapter *adapter); void iavf_del_ether_addrs(struct iavf_adapter *adapter); +void iavf_mac_add_reject(struct iavf_adapter *adapter); void iavf_add_vlans(struct iavf_adapter *adapter); void iavf_del_vlans(struct iavf_adapter *adapter); void iavf_set_promiscuous(struct iavf_adapter *adapter); @@ -606,6 +606,13 @@ void iavf_disable_vlan_stripping(struct iavf_adapter *adapter); void iavf_virtchnl_completion(struct iavf_adapter *adapter, enum virtchnl_ops v_opcode, enum iavf_status v_retval, u8 *msg, u16 msglen); +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms); int iavf_config_rss(struct iavf_adapter *adapter); void iavf_cfg_queues_bw(struct iavf_adapter *adapter); void iavf_cfg_queues_quanta_size(struct iavf_adapter *adapter); diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index 29b8403a066b..2f8a80a11336 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1029,6 +1029,60 @@ static bool iavf_is_mac_set_handled(struct net_device *netdev, return ret; } +/** + * iavf_mac_change_done - Check if MAC change completed + * @adapter: board private structure + * @data: MAC address being checked (as const void *) + * @v_op: virtchnl opcode from processed message + * + * Callback for iavf_poll_virtchnl_response() to check if MAC change completed. + * + * Return: true if MAC change completed, false otherwise + */ +static bool iavf_mac_change_done(struct iavf_adapter *adapter, + const void *data, enum virtchnl_ops v_op) +{ + const u8 *addr = data; + + return iavf_is_mac_set_handled(adapter->netdev, addr); +} + +/** + * iavf_set_mac_sync - Synchronously change MAC address + * @adapter: board private structure + * @addr: MAC address to set + * + * Send MAC change request to PF and poll admin queue for response. + * Caller must hold netdev_lock. This can sleep for up to 2.5 seconds. + * Event buffer is allocated before sending to avoid state mismatch if + * allocation fails after message is sent to PF. + * + * Return: 0 on success, negative on failure + */ +static int iavf_set_mac_sync(struct iavf_adapter *adapter, const u8 *addr) +{ + struct iavf_arq_event_info event; + int ret; + + netdev_assert_locked(adapter->netdev); + + event.buf_len = IAVF_MAX_AQ_BUF_SIZE; + event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL); + if (!event.msg_buf) + return -ENOMEM; + + ret = iavf_add_ether_addrs(adapter); + if (ret) + goto out; + + ret = iavf_poll_virtchnl_response(adapter, &event, + iavf_mac_change_done, addr, 2500); + +out: + kfree(event.msg_buf); + return ret; +} + /** * iavf_set_mac - NDO callback to set port MAC address * @netdev: network interface device structure @@ -1046,25 +1100,23 @@ static int iavf_set_mac(struct net_device *netdev, void *p) return -EADDRNOTAVAIL; ret = iavf_replace_primary_mac(adapter, addr->sa_data); - if (ret) return ret; - ret = wait_event_interruptible_timeout(adapter->vc_waitqueue, - iavf_is_mac_set_handled(netdev, addr->sa_data), - msecs_to_jiffies(2500)); - - /* If ret < 0 then it means wait was interrupted. - * If ret == 0 then it means we got a timeout. - * else it means we got response for set MAC from PF, - * check if netdev MAC was updated to requested MAC, - * if yes then set MAC succeeded otherwise it failed return -EACCES - */ - if (ret < 0) + ret = iavf_set_mac_sync(adapter, addr->sa_data); + if (ret) { + /* Rollback only if send failed (message never reached PF). + * Don't rollback on timeout (-EAGAIN) because the message was + * sent and PF will eventually respond. When the response arrives, + * iavf_virtchnl_completion() will handle rollback (on PF error) + * or acceptance (on PF success) automatically. + */ + if (ret != -EAGAIN) { + iavf_mac_add_reject(adapter); + ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); + } return ret; - - if (!ret) - return -EAGAIN; + } if (!ether_addr_equal(netdev->dev_addr, addr->sa_data)) return -EACCES; @@ -5394,9 +5446,6 @@ static int iavf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* Setup the wait queue for indicating transition to down status */ init_waitqueue_head(&adapter->down_waitqueue); - /* Setup the wait queue for indicating virtchannel events */ - init_waitqueue_head(&adapter->vc_waitqueue); - INIT_LIST_HEAD(&adapter->ptp.aq_cmds); init_waitqueue_head(&adapter->ptp.phc_time_waitqueue); mutex_init(&adapter->ptp.aq_cmd_lock); diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c index ec234cc8bd9d..e6b7e8f82c7c 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c +++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c @@ -2,6 +2,7 @@ /* Copyright(c) 2013 - 2018 Intel Corporation. */ #include +#include #include "iavf.h" #include "iavf_ptp.h" @@ -555,20 +556,23 @@ iavf_set_mac_addr_type(struct virtchnl_ether_addr *virtchnl_ether_addr, * @adapter: adapter structure * * Request that the PF add one or more addresses to our filters. - **/ -void iavf_add_ether_addrs(struct iavf_adapter *adapter) + * + * Return: 0 on success, negative on failure + */ +int iavf_add_ether_addrs(struct iavf_adapter *adapter) { struct virtchnl_ether_addr_list *veal; struct iavf_mac_filter *f; int i = 0, count = 0; bool more = false; size_t len; + int ret; if (adapter->current_op != VIRTCHNL_OP_UNKNOWN) { /* bail because we already have a command pending */ dev_err(&adapter->pdev->dev, "Cannot add filters, command %d pending\n", adapter->current_op); - return; + return -EBUSY; } spin_lock_bh(&adapter->mac_vlan_list_lock); @@ -580,7 +584,7 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) if (!count) { adapter->aq_required &= ~IAVF_FLAG_AQ_ADD_MAC_FILTER; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return 0; } adapter->current_op = VIRTCHNL_OP_ADD_ETH_ADDR; @@ -594,8 +598,9 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) veal = kzalloc(len, GFP_ATOMIC); if (!veal) { + adapter->current_op = VIRTCHNL_OP_UNKNOWN; spin_unlock_bh(&adapter->mac_vlan_list_lock); - return; + return -ENOMEM; } veal->vsi_id = adapter->vsi_res->vsi_id; @@ -615,8 +620,15 @@ void iavf_add_ether_addrs(struct iavf_adapter *adapter) spin_unlock_bh(&adapter->mac_vlan_list_lock); - iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); + ret = iavf_send_pf_msg(adapter, VIRTCHNL_OP_ADD_ETH_ADDR, (u8 *)veal, len); kfree(veal); + if (ret) { + dev_err(&adapter->pdev->dev, + "Unable to send ADD_ETH_ADDR message to PF, error %d\n", ret); + adapter->current_op = VIRTCHNL_OP_UNKNOWN; + } + + return ret; } /** @@ -712,8 +724,8 @@ static void iavf_mac_add_ok(struct iavf_adapter *adapter) * @adapter: adapter structure * * Remove filters from list based on PF response. - **/ -static void iavf_mac_add_reject(struct iavf_adapter *adapter) + */ +void iavf_mac_add_reject(struct iavf_adapter *adapter) { struct net_device *netdev = adapter->netdev; struct iavf_mac_filter *f, *ftmp; @@ -2364,7 +2376,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, iavf_mac_add_reject(adapter); /* restore administratively set MAC address */ ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr); - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_DEL_ETH_ADDR: dev_err(&adapter->pdev->dev, "Failed to delete MAC filter, error %s\n", @@ -2555,7 +2566,6 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, eth_hw_addr_set(netdev, adapter->hw.mac.addr); netif_addr_unlock_bh(netdev); } - wake_up(&adapter->vc_waitqueue); break; case VIRTCHNL_OP_GET_STATS: { struct iavf_eth_stats *stats = @@ -2950,3 +2960,72 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter, } /* switch v_opcode */ adapter->current_op = VIRTCHNL_OP_UNKNOWN; } + +/** + * iavf_poll_virtchnl_response - Poll admin queue for virtchnl response + * @adapter: adapter structure + * @event: pre-allocated event buffer to use for polling + * @condition: callback to check if desired response received + * @cond_data: context data passed to condition callback + * @timeout_ms: maximum time to wait in milliseconds + * + * Polls the admin queue and processes all incoming virtchnl messages. + * After processing each valid message, calls the condition callback to check + * if the expected response has been received. The callback receives the opcode + * of the processed message to identify which response was received. Continues + * polling until the callback returns true or timeout expires. + * + * Caller must allocate event buffer before sending any messages to PF to avoid + * state mismatch if allocation fails after message is sent. + * + * Caller must hold netdev_lock. This can sleep for up to timeout_ms while + * polling hardware. + * + * Return: 0 on success (condition met), -EAGAIN on timeout, or error code + */ +int iavf_poll_virtchnl_response(struct iavf_adapter *adapter, + struct iavf_arq_event_info *event, + bool (*condition)(struct iavf_adapter *adapter, + const void *data, + enum virtchnl_ops v_op), + const void *cond_data, + unsigned int timeout_ms) +{ + struct iavf_hw *hw = &adapter->hw; + enum virtchnl_ops received_op; + unsigned long timeout; + int ret = -EAGAIN; + u16 pending = 0; + u32 v_retval; + + netdev_assert_locked(adapter->netdev); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + if (!pending) + usleep_range(50, 75); + + if (iavf_clean_arq_element(hw, event, &pending) == IAVF_SUCCESS) { + received_op = (enum virtchnl_ops)le32_to_cpu(event->desc.cookie_high); + if (received_op != VIRTCHNL_OP_UNKNOWN) { + v_retval = le32_to_cpu(event->desc.cookie_low); + + iavf_virtchnl_completion(adapter, received_op, + (enum iavf_status)v_retval, + event->msg_buf, event->msg_len); + + if (condition(adapter, cond_data, received_op)) { + ret = 0; + break; + } + } + + memset(event->msg_buf, 0, IAVF_MAX_AQ_BUF_SIZE); + + if (pending) + continue; + } + } while (time_before(jiffies, timeout)); + + return ret; +} From 0a01e1df137fd7e51f045a5e97d40f42fb58ae0c Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Mon, 15 Jun 2026 20:00:00 +0200 Subject: [PATCH 277/590] ice: skip unnecessary VF reset when setting trust Similar to the i40e fix, ice_set_vf_trust() unconditionally calls ice_reset_vf() when the trust setting changes. While the delay is smaller than i40e, this reset is still unnecessary in most cases. When granting trust, no reset is needed - we can just set the capability flag to allow privileged operations. When revoking trust, we only need to reset (conservative approach) if the VF has actually configured advanced features that require cleanup (MAC LLDP filters, promiscuous mode). For VFs in a clean state, we can safely change the trust setting without the disruptive reset. When we do reset, we maintain the original ice pattern that has been reliable in production: cleanup LLDP filters first, then set vf->trusted, then reset. This ensures the privilege capability bit is handled correctly during reset rebuild. When we don't reset, we manually handle the capability flag via helper function, eliminating the delay. Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Aleksandr Loktionov Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_sriov.c | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_sriov.c b/drivers/net/ethernet/intel/ice/ice_sriov.c index e04de0215596..f893dff39aac 100644 --- a/drivers/net/ethernet/intel/ice/ice_sriov.c +++ b/drivers/net/ethernet/intel/ice/ice_sriov.c @@ -1366,6 +1366,20 @@ int ice_set_vf_mac(struct net_device *netdev, int vf_id, u8 *mac) return __ice_set_vf_mac(ice_netdev_to_pf(netdev), vf_id, mac); } +/** + * ice_setup_vf_trust - Enable/disable VF trust mode without reset + * @vf: VF to configure + * @setting: trust setting + * + * Update VF flags when changing trust without performing a VF reset. + * This is only called when it's safe to skip the reset (VF has no advanced + * features configured that need cleanup). + */ +static void ice_setup_vf_trust(struct ice_vf *vf, bool setting) +{ + assign_bit(ICE_VIRTCHNL_VF_CAP_PRIVILEGE, &vf->vf_caps, setting); +} + /** * ice_set_vf_trust * @netdev: network interface device structure @@ -1401,11 +1415,19 @@ int ice_set_vf_trust(struct net_device *netdev, int vf_id, bool trusted) mutex_lock(&vf->cfg_lock); - while (!trusted && vf->num_mac_lldp) - ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); - - vf->trusted = trusted; - ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + /* Reset only if revoking trust and VF has advanced features configured */ + if (!trusted && + (vf->num_mac_lldp > 0 || + test_bit(ICE_VF_STATE_UC_PROMISC, vf->vf_states) || + test_bit(ICE_VF_STATE_MC_PROMISC, vf->vf_states))) { + while (vf->num_mac_lldp) + ice_vf_update_mac_lldp_num(vf, ice_get_vf_vsi(vf), false); + vf->trusted = trusted; + ice_reset_vf(vf, ICE_VF_RESET_NOTIFY); + } else { + vf->trusted = trusted; + ice_setup_vf_trust(vf, trusted); + } dev_info(ice_pf_to_dev(pf), "VF %u is now %strusted\n", vf_id, trusted ? "" : "un"); From d35a535d3e3e71f92a051d94e415f43612c4edfc Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 23 Sep 2026 12:39:20 -1000 Subject: [PATCH 278/590] sched_ext: Fix CPU hotplug hang when a dying CPU's tasks sit in the BPF scheduler A CPU going down has to empty its own rq. Its hotplug thread waits in sched_cpu_wait_empty() until nothing else is left, and the only thing that wakes it is balance_push(), which runs from __schedule() on the dying CPU and pushes off the migratable tasks the CPU picks. sched_ext breaks this. A task sitting on a user DSQ or held by the BPF scheduler is still counted on its rq, but an inactive CPU no longer calls ops.dispatch() and can't pull the task back. The dying CPU goes idle with the task still counted, and one of two things happens: - Another CPU consumes the task. The rq empties without a __schedule() on the dying CPU, so the hotplug thread is never woken and cpu_down() hangs holding cpu_hotplug_lock. - The task is affine only to the dying CPU. Nothing can move it, and the offline stalls until the watchdog ejects the BPF scheduler. When the rq goes offline, re-enqueue every task on it that isn't already on the local DSQ. An enqueue on an offline rq lands on the local DSQ, so the dying CPU itself picks the tasks and balance_push() pushes them off, the same as for the other sched classes. From then on no sched_ext path on another CPU can pull a task off the rq. ops.dispatch() currently stops as soon as the CPU goes inactive, and CPU hotplug then waits for an RCU grace period before taking the rq offline. A BPF-held task affine only to the dying CPU and preempted inside an RCU read-side critical section would block that grace period, and the rq would never go offline. Test SCX_RQ_ONLINE directly so that ops.dispatch() keeps running until the rq goes offline. Only the dying CPU's own hotplug thread clears the flag during teardown, and task_can_run_on_remote_rq() still keeps other rqs' tasks off the CPU. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Fixes: 991ef53a4832 ("sched_ext: Make scx_rq_online() also test cpu_active() in addition to SCX_RQ_ONLINE") Cc: stable@vger.kernel.org # v6.12+ Reported-by: Alap Mohan Reported-by: Joonwoo Park Signed-off-by: Tejun Heo Reviewed-by: Andrea Righi --- kernel/sched/ext/ext.c | 22 ++++++++++++++++++++-- kernel/sched/ext/inlines.h | 8 +++++++- kernel/sched/ext/sub.c | 4 ---- kernel/sched/sched.h | 5 +++-- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index ad391a8cbd05..493b7aac7087 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2123,8 +2123,8 @@ static void set_task_runnable(struct rq *rq, struct task_struct *p) } /* - * list_add_tail() must be used. scx_bypass() depends on tasks being - * appended to the runnable_list. + * list_add_tail() must be used. scx_bypass() and rq_offline_scx() + * depend on tasks being appended to the runnable_list. */ list_add_tail(&p->scx.runnable_node, &rq->scx.runnable_list); @@ -3731,8 +3731,26 @@ static void rq_online_scx(struct rq *rq) static void rq_offline_scx(struct rq *rq) { + struct task_struct *p, *n; + rq->scx.flags &= ~SCX_RQ_ONLINE; + + /* sched domain rebuilds call rq_offline with the CPU staying alive */ + if (cpu_active(cpu_of(rq))) + return; + scx_rescue_flush(rq); + + /* + * An offline CPU no longer calls ops.dispatch(). Re-enqueue its tasks + * onto the local DSQ so that they run here and balance_push() moves + * them off. + */ + list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list, scx.runnable_node) { + if (p->scx.dsq == &rq->scx.local_dsq) + continue; + guard(sched_change)(p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK); + } } static bool check_rq_for_timeouts(struct rq *rq) diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index 2ff5479334cb..8de9d8fb4b25 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -70,7 +70,13 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, #endif /* CONFIG_EXT_SUB_SCHED */ } - if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !scx_rq_online(rq)) + /* + * scx_rq_online() can't be used. Its cpu_active() test goes false + * before CPU hotplug waits for an RCU grace period, and + * rq_offline_scx() moves this CPU's tasks to the local DSQ only after + * the wait. The grace period can depend on those tasks running. + */ + if (unlikely(!SCX_HAS_OP(sch, dispatch)) || !(rq->scx.flags & SCX_RQ_ONLINE)) return SCX_DSP_NONE; dspc->rq = rq; diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 3036190b0662..c9b44ab3bd0a 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -585,10 +585,6 @@ void scx_rescue_flush(struct rq *rq) lockdep_assert_rq_held(rq); - /* sched domain rebuilds call rq_offline with the CPU staying alive */ - if (cpu_active(cpu_of(rq))) - return; - /* end the current rescue */ if (rq->scx.rescue.curr) scx_task_slice_ended(rq, rq->scx.rescue.curr); diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 7701a5a60972..742cce9da4e9 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -4205,8 +4205,9 @@ extern void balance_callbacks(struct rq *rq, struct balance_callback *head); * after which it is enqueued again. * * Typically this must be called while holding task_rq_lock, since most/all - * properties are serialized under those locks. There is currently one - * exception to this rule in sched/ext which only holds rq->lock. + * properties are serialized under those locks. There are currently two + * exceptions to this rule in sched/ext which only hold rq->lock: scx_bypass() + * and rq_offline_scx(). */ /* From 816fb1590a4c8cf8d05cd076057616be02e276f0 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Sat, 26 Sep 2026 18:21:45 -0300 Subject: [PATCH 279/590] Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata() sco_recv_scodata() upgrades the weak hcon->sco_data back-pointer to a strong reference under hdev->lock: hci_dev_lock(hdev); hcon = hci_conn_hash_lookup_handle(hdev, handle); ... conn = sco_conn_hold_unless_zero(hcon->sco_data); hci_dev_unlock(hdev); but the pointer is cleared from the other side without that lock. When the last sco_conn reference is dropped, sco_conn_free() ran conn->hcon->sco_data = NULL; with no hdev->lock held, so the RX path could read hcon->sco_data and call kref_get_unless_zero() on an sco_conn that was concurrently freed: BUG: KASAN: slab-use-after-free in sco_conn_hold_unless_zero+0xbe/0x160 Write of size 4 by task kworker/u17:0 Workqueue: hci0 hci_rx_work Call Trace: sco_conn_hold_unless_zero+0xbe/0x160 sco_recv_scodata+0x13f/0x490 hci_rx_work+0x3af/0x730 kref_get_unless_zero() only guards against a zero refcount, not against the backing memory already being freed. Give hcon->sco_data an actual reference on the sco_conn it points to, so the object cannot be freed while the pointer is still installed, and only clear and drop it from sco_conn_del(), which runs under hdev->lock (its callers, sco_connect_cfm() and sco_disconn_cfm(), hold it). The reader in sco_recv_scodata() also takes hdev->lock, so the store and the read are now serialised: the RX path either observes NULL or a reference that is guaranteed to stay valid until it drops its own. sco_conn_add() no longer hands its kref_init() reference to the caller as the connection's only reference; that initial reference is the one owned by hcon->sco_data, and callers get their own via sco_conn_hold(). Because the sco_conn can now outlive the socket (the association keeps it alive until the link goes down), the hci_conn can no longer be dropped from sco_conn_free() without regressing socket close: closing a connected SCO socket must still tear the link down. Move hci_conn ownership to the socket instead: __sco_chan_add() takes an hci_conn reference and it is released from sco_chan_del() and sco_sock_destruct(), exactly once. The sco_conn no longer owns an hci_conn reference, so sco_conn_add() stops consuming one and sco_connect() drops the hci_connect_sco() reference on every path; sco_connect_cfm() no longer needs its hci_conn_hold() dance. Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Signed-off-by: Aldo Ariel Panzardo Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/sco.c | 87 +++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 3d4362a09df4..f4a20d7b5f9c 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -84,12 +84,14 @@ static void sco_conn_free(struct kref *ref) if (conn->sk) sco_pi(conn->sk)->conn = NULL; - if (conn->hcon) { - conn->hcon->sco_data = NULL; - hci_conn_drop(conn->hcon); - } + /* hcon->sco_data is cleared and the association's reference on the + * sco_conn is dropped in sco_conn_del() under hdev->lock, and the + * hci_conn is now owned by the socket (held in __sco_chan_add() and + * dropped in sco_chan_del()/sco_sock_destruct()), so there is nothing + * left to release towards hcon here. + */ - /* Ensure no more work items will run since hci_conn has been dropped */ + /* Ensure no more work items will run before the connection is freed */ disable_delayed_work_sync(&conn->timeout_work); kfree(conn); @@ -188,25 +190,19 @@ static void sco_sock_clear_timer(struct sock *sk) } /* ---- SCO connections ---- */ -/* Consumes a reference on @hcon, which the returned sco_conn owns until it is - * freed. On failure (NULL return) the reference is left for the caller to drop. +/* Returns a new reference the caller must drop with sco_conn_put(). The + * hcon->sco_data association holds its own reference on the sco_conn for the + * connection's lifetime; it is dropped in sco_conn_del() under hdev->lock. + * @hcon is not consumed: the hci_conn reference is taken and owned by the + * socket in __sco_chan_add(). */ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) { struct sco_conn *conn = hcon->sco_data; conn = sco_conn_hold_unless_zero(conn); - if (conn) { - if (!conn->hcon) { - sco_conn_lock(conn); - conn->hcon = hcon; - sco_conn_unlock(conn); - } else { - /* conn already owns a reference on hcon */ - hci_conn_drop(hcon); - } + if (conn) return conn; - } conn = kzalloc_obj(struct sco_conn); if (!conn) @@ -227,7 +223,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon) BT_DBG("hcon %p conn %p", hcon, conn); - return conn; + /* kref_init() above set the association reference owned by + * hcon->sco_data; hand the caller its own reference. + */ + return sco_conn_hold(conn); } /* Delete channel. @@ -242,6 +241,19 @@ static void sco_chan_del(struct sock *sk, int err) BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + /* Drop the socket's hci_conn reference BEFORE clearing + * conn->sk, so sco_conn_del() on another CPU cannot free + * the hci_conn while we still hold a pointer to it. + */ + if (hcon) + hci_conn_drop(hcon); + sco_conn_lock(conn); conn->sk = NULL; sco_conn_unlock(conn); @@ -266,6 +278,13 @@ static void sco_conn_del(struct hci_conn *hcon, int err) BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + /* Detach from the hci_conn and drop the association's reference. + * The caller holds hdev->lock, which serialises this against the + * read of hcon->sco_data in sco_recv_scodata(). + */ + hcon->sco_data = NULL; + sco_conn_put(conn); + sco_conn_lock(conn); sk = sco_sock_hold(conn); sco_conn_unlock(conn); @@ -290,6 +309,11 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, sco_pi(sk)->conn = sco_conn_hold(conn); conn->sk = sk; + /* The socket owns an hci_conn reference for as long as it stays + * attached; it is dropped in sco_chan_del()/sco_sock_destruct(). + */ + hci_conn_hold(conn->hcon); + if (parent) bt_accept_enqueue(parent, sk, true); } @@ -371,6 +395,7 @@ static int sco_connect(struct sock *sk) if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) { release_sock(sk); sco_conn_put(conn); + hci_conn_drop(hcon); err = -EBADFD; goto unlock; } @@ -379,9 +404,13 @@ static int sco_connect(struct sock *sk) sco_conn_put(conn); if (err) { release_sock(sk); + hci_conn_drop(hcon); goto unlock; } + /* __sco_chan_add() took its own hci_conn reference; drop ours. */ + hci_conn_drop(hcon); + /* Update source addr of the socket */ bacpy(&sco_pi(sk)->src, &hcon->src); @@ -495,9 +524,25 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src) static void sco_sock_destruct(struct sock *sk) { + struct sco_conn *conn = sco_pi(sk)->conn; + BT_DBG("sk %p", sk); - sco_conn_put(sco_pi(sk)->conn); + /* If the channel was not already torn down via sco_chan_del(), drop + * the socket's own references here. + */ + if (conn) { + struct hci_conn *hcon; + + sco_conn_lock(conn); + hcon = conn->hcon; + sco_conn_unlock(conn); + + if (hcon) + hci_conn_drop(hcon); + sco_pi(sk)->conn = NULL; + sco_conn_put(conn); + } skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); @@ -1511,12 +1556,10 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (!status) { struct sco_conn *conn; - conn = sco_conn_add(hci_conn_hold(hcon)); + conn = sco_conn_add(hcon); if (conn) { sco_conn_ready(conn); sco_conn_put(conn); - } else { - hci_conn_drop(hcon); } } else sco_conn_del(hcon, bt_to_errno(status)); From 980db94e3eeefdd8a1c46f86412c33a7f7806f51 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Mon, 28 Sep 2026 09:18:56 +0530 Subject: [PATCH 280/590] workqueue: Fix NULL current_pwq deref in chained work check current_wq_worker() only tells us that %current is a kworker. It does not guarantee that the worker is currently executing a work item, as worker->current_pwq is populated only while process_one_work() is running the work function. is_chained_work() can be reached while queueing on a draining or destroying workqueue. If a kworker outside work-item execution reaches that path, current_wq_worker() returns a worker but worker->current_pwq is NULL, and the chained-work test can fault before it emits the intended warning. Guard the chained-work test with worker->current_pwq. A kworker without current_pwq is not executing work on the target workqueue, so the helper should return false and let the draining/destroying warning fire. Fixes: 8d03ecfe4718 ("workqueue: reimplement is_chained_work() using current_wq_worker()") Cc: stable@vger.kernel.org Signed-off-by: Pavankumar Kondeti Signed-off-by: Tejun Heo --- kernel/workqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 4a73d305d88a..acf5d4a97ecb 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2290,7 +2290,7 @@ static bool is_chained_work(struct workqueue_struct *wq) * Return %true iff I'm a worker executing a work item on @wq. If * I'm @worker, it's safe to dereference it without locking. */ - return worker && worker->current_pwq->wq == wq; + return worker && worker->current_pwq && worker->current_pwq->wq == wq; } /* From a661c34fe693c8f9ef29b45ee71cfa1fe593502b Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Fri, 21 Aug 2026 15:45:42 -0700 Subject: [PATCH 281/590] {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 282/590] 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 283/590] 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 284/590] 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 285/590] 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 286/590] 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 287/590] 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 27a868b40e7d18e4be57d2bbbeac7581fd1219db Mon Sep 17 00:00:00 2001 From: Preston Lam Date: Mon, 28 Sep 2026 19:16:21 -0400 Subject: [PATCH 288/590] 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 289/590] 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 290/590] 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 291/590] 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 763183f76d6bc093fa603377862d6739cb7ccad6 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Tue, 29 Sep 2026 17:11:09 +0800 Subject: [PATCH 292/590] 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 293/590] 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 f341dc4d934a69ffbe6c21e3609ee725790188f1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:12:00 +0200 Subject: [PATCH 294/590] 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 a92193c91e8839fe5fc209616ea95465ae4b919d Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 29 Sep 2026 06:52:17 -0600 Subject: [PATCH 295/590] 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 296/590] 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 baa53af641c9e3644e717b8dfd1b95a55a0c111b Mon Sep 17 00:00:00 2001 From: Ravindra Date: Tue, 29 Sep 2026 12:26:41 +0530 Subject: [PATCH 297/590] Bluetooth: btintel_pcie: fix TX descriptor bounds check btintel_pcie_prepare_tx() uses tfd_index to access the TFD and data buffer arrays before the index is advanced modulo txq->count. An index equal to txq->count is one past the end of both arrays, so reject it in the bounds check. Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware") Signed-off-by: Ravindra 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 f333b01f7465..d525269be501 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -401,7 +401,7 @@ static int btintel_pcie_send_sync(struct btintel_pcie_data *data, tfd_index = data->ia.tr_hia[BTINTEL_PCIE_TXQ_NUM]; - if (tfd_index > txq->count) + if (tfd_index >= txq->count) return -ERANGE; if (skb->len > BTINTEL_PCIE_BUFFER_SIZE - BTINTEL_PCIE_HCI_TYPE_LEN) { From ac022970e9fb0a5f7e7d1f6c8e104c1ed924dd1c Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Tue, 29 Sep 2026 17:22:26 +0500 Subject: [PATCH 298/590] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex An RFCOMM connect() issued while a BR/EDR link is being authenticated makes lockdep report a circular dependency, and the reported cycle is a real AB/BA between rfcomm_mutex and hdev->lock. rfcomm_security_cfm() is called from the HCI event path, which already holds hdev->lock: hci_rx_work() hci_event_packet() hci_cc_read_enc_key_size() [hdev->lock] hci_encrypt_cfm() [hci_cb_list_lock] rfcomm_security_cfm() [rfcomm_mutex] while an RFCOMM connect() from userspace takes the same two locks the other way round: rfcomm_sock_connect() rfcomm_dlc_open() [rfcomm_mutex] __rfcomm_dlc_open() rfcomm_session_create() kernel_connect() l2cap_sock_connect() l2cap_chan_connect() [hdev->lock] WARNING: possible circular locking dependency detected kworker/u131:1/1128 is trying to acquire lock: rfcomm_mutex, at: rfcomm_security_cfm+0x31/0x3e0 [rfcomm] but task is already holding lock: hci_cb_list_lock, at: hci_cc_read_enc_key_size+0x1d2/0xcc0 Chain exists of: rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock hci_auth_complete_evt() and hci_encrypt_change_evt() reach the callback the same way. Both orders have to be seen in the same boot, which is why a BR/EDR connection alone is not enough to show it: a session set up by the remote side is created by rfcomm_accept_connection() in krfcommd, which calls kernel_accept() and never takes hdev->lock under rfcomm_mutex. Connecting a device that authenticates and encrypts the link and then calling connect() on an RFCOMM socket towards any address - the connect does not have to succeed, the order is recorded before the page timeout - reports it every time. Only the session socket has to be connected with the lock held, and it does not: nothing else can see the socket before it is put on the session list. So connect it first and take rfcomm_mutex afterwards, which removes the rfcomm_mutex -> hdev->lock order for good, rather than keeping the HCI event path out of rfcomm_mutex. rfcomm_session_create() becomes rfcomm_session_connect(), which returns the connected socket without touching the session list, and rfcomm_dlc_open() adds the session once it holds the lock again. If another opener added a session for the same pair while this socket was connecting, that session is used and this socket is dropped. __rfcomm_dlc_open() now takes the session it should use, and its state check runs after the lock is re-acquired, so a DLC that was opened or closed in the meantime is still handled. Over an existing ACL link the connection can complete before the session reaches the list, and the wakeup from the socket callback is then lost, so krfcommd is woken once the session is visible. Connecting without the lock opens a window in which the socket can be closed. The DLC is not on a session yet, so rfcomm_dlc_close() finds nothing to do and returns without touching it, and rfcomm_dlc_open() would then attach a DLC whose owner is gone. rfcomm_dlc_close() now marks such a DLC with RFCOMM_CLOSED, and rfcomm_dlc_open() checks the mark once it holds rfcomm_mutex again and drops the socket instead of attaching the DLC. The mark is cleared when an open starts, so a DLC that is reused is not affected. This covers both callers of rfcomm_dlc_open(), the socket and the tty layer. Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling") Suggested-by: Pauli Virtanen Reported-by: Pauli Virtanen Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827c2d64af5a88be.camel@iki.fi/ Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008f.GAE@google.com/ Reported-by: Jiaming Zhang Closes: https://lore.kernel.org/all/CANypQFabseTuRiyz2pGkc_Qj0moGDnY7KEp1qdTOqkw6gAL3hw@mail.gmail.com/ Cc: stable@vger.kernel.org Signed-off-by: Mikhail Gavrilov Tested-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/rfcomm.h | 1 + net/bluetooth/rfcomm/core.c | 134 +++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index 102c278e3584..9dc5fd4cb8c8 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -207,6 +207,7 @@ struct rfcomm_dlc { #define RFCOMM_AUTH_REJECT 7 #define RFCOMM_DEFER_SETUP 8 #define RFCOMM_ENC_DROP 9 +#define RFCOMM_CLOSED 10 /* Scheduling flags and events */ #define RFCOMM_SCHED_WAKEUP 31 diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index d91e2a6ee26c..54ec1136f87e 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); static void rfcomm_process_connect(struct rfcomm_session *s); -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err); +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err); +static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state); static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s); @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel) return channel < 1 || channel > 30; } -static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s, + u8 channel) { - struct rfcomm_session *s; - int err = 0; u8 dlci; - BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", - d, d->state, src, dst, channel); - - if (rfcomm_check_channel(channel)) - return -EINVAL; + BT_DBG("dlc %p state %ld session %p channel %d", + d, d->state, s, channel); if (d->state != BT_OPEN && d->state != BT_CLOSED) return 0; - s = rfcomm_session_get(src, dst); - if (!s) { - s = rfcomm_session_create(src, dst, d->sec_level, &err); - if (!s) - return err; - } - dlci = __dlci(__session_dir(s), channel); /* Check if DLCI already exists */ @@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) { - int r; + struct rfcomm_session *s; + struct socket *sock; + int err; + + BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d", + d, d->state, src, dst, channel); + + if (rfcomm_check_channel(channel)) + return -EINVAL; rfcomm_lock(); - r = __rfcomm_dlc_open(d, src, dst, channel); + clear_bit(RFCOMM_CLOSED, &d->flags); + + /* Do not page the remote device for a DLC that cannot be opened + * anyway. __rfcomm_dlc_open() looks at the state again once the + * lock has been re-acquired below. + */ + if (d->state != BT_OPEN && d->state != BT_CLOSED) { + rfcomm_unlock(); + return 0; + } + + s = rfcomm_session_get(src, dst); + if (s) { + err = __rfcomm_dlc_open(d, s, channel); + rfcomm_unlock(); + return err; + } + rfcomm_unlock(); + + /* There is no session for this pair yet. kernel_connect() ends up in + * l2cap_chan_connect(), which takes hdev->lock, and the HCI event + * path takes rfcomm_mutex while holding hdev->lock, so the socket has + * to be connected with rfcomm_mutex released. + */ + sock = rfcomm_session_connect(src, dst, d->sec_level, &err); + if (!sock) + return err; + + rfcomm_lock(); + + /* The DLC may have been closed while the socket was connecting. It + * was not on a session, so rfcomm_dlc_close() could only mark it; + * attaching it now would leave it with no owner. + */ + if (test_bit(RFCOMM_CLOSED, &d->flags)) { + rfcomm_unlock(); + sock_release(sock); + return -ECONNRESET; + } + + /* Another opener may have added a session for the same pair in the + * meantime; that one is used and this socket is dropped. + */ + s = rfcomm_session_get(src, dst); + if (!s) { + s = rfcomm_session_add(sock, BT_BOUND); + if (s) { + s->initiator = 1; + sock = NULL; + } + } + + err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM; rfcomm_unlock(); - return r; + + if (sock) + sock_release(sock); + + /* Over an existing ACL link the connection can complete before the + * session reaches the list, and that wakeup is then lost, so let + * krfcommd look at the socket state now. + */ + rfcomm_schedule(); + + return err; } static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d) @@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) rfcomm_lock(); s = d->session; - if (!s) + if (!s) { + /* Not on a session yet: rfcomm_dlc_open() may be connecting a + * socket for it with rfcomm_mutex released. Leave a mark so + * that it does not attach the DLC once it holds the lock again. + */ + set_bit(RFCOMM_CLOSED, &d->flags); goto no_session; + } /* after waiting on the mutex check the session still exists * then check the dlc still exists @@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s, return rfcomm_session_del(s); } -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, - bdaddr_t *dst, - u8 sec_level, - int *err) +/* Creates the L2CAP socket a new session will run on and starts connecting + * it. Must be called with rfcomm_mutex released. + */ +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst, + u8 sec_level, int *err) { - struct rfcomm_session *s = NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; @@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM; release_sock(sk); - s = rfcomm_session_add(sock, BT_BOUND); - if (!s) { - *err = -ENOMEM; - goto failed; - } - - s->initiator = 1; - bacpy(&addr.l2_bdaddr, dst); addr.l2_family = AF_BLUETOOTH; addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM); addr.l2_cid = 0; addr.l2_bdaddr_type = BDADDR_BREDR; *err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK); - if (*err == 0 || *err == -EINPROGRESS) - return s; + if (*err && *err != -EINPROGRESS) + goto failed; - return rfcomm_session_del(s); + return sock; failed: sock_release(sock); From 1465494e8d07426fa41c0021530cccb2a1ac38fa Mon Sep 17 00:00:00 2001 From: Sai Teja Aluvala Date: Tue, 29 Sep 2026 11:42:33 +0530 Subject: [PATCH 299/590] Bluetooth: btintel_pcie: Add shared HW reset for clean start Reset shared Bluetooth hardware after mapping the BAR and before setting up MSI-X interrupts, so stale device state is cleared before IRQ handlers and interrupt masks are configured. Propagate reset errors so probe aborts rather than continuing when the reset fails. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Signed-off-by: Sai Teja Aluvala Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index d525269be501..0194badeeff3 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1877,6 +1877,13 @@ static int btintel_pcie_config_pcie(struct pci_dev *pdev, if (IS_ERR(data->base_addr)) return PTR_ERR(data->base_addr); + /* Do shared hardware reset to ensure a clean start before + * configuring interrupts. + */ + err = btintel_pcie_reset_bt(data); + if (err) + return err; + err = btintel_pcie_setup_irq(data); if (err) return err; From b5c4823cf3e49b3419a3bc8c6715101e1ac6cc59 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 29 Sep 2026 14:29:25 +0200 Subject: [PATCH 300/590] 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 301/590] 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 86ef0f58bdecdedb3a1240971c56d71b7e4ce3fc Mon Sep 17 00:00:00 2001 From: Iaroslav Voitovych Date: Thu, 24 Sep 2026 19:11:58 +0200 Subject: [PATCH 302/590] Bluetooth: MGMT: Fix status of pending commands flushed on power off cmd_complete_rsp() receives a struct cmd_lookup and, for a pending command that has no cmd_complete callback, falls through to cmd_status_rsp(cmd, data). cmd_status_rsp() reads data as a u8 *, so the status sent comes from the first byte of match->sk's representation, not from match->mgmt_status. commit f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") changed the callers' data from &status to &match and updated the cmd_complete branch to match->mgmt_status, but left the fall-through passing data unchanged. In mgmt_index_removed() match->sk is NULL, so every such command is answered with status 0x00 (Success) instead of MGMT_STATUS_INVALID_INDEX. In __mgmt_power_off() it is NULL too, unless a Set Powered command was pending: then settings_rsp() has stored that command's socket in match->sk, and the byte sent comes from the socket pointer. Either way the status the caller set, MGMT_STATUS_NOT_POWERED (or MGMT_STATUS_INVALID_INDEX when the device is being unregistered), is lost. Pass the status the callers set. This was seen on a QCA9377 laptop: bluetoothd's Start Discovery, pending when the adapter was powered off, was answered with Command Status 0x00 and no parameters, and bluetoothd 5.72 crashed on the zero-length success. BlueZ master has since added the length check on that path ("adapter: Fix crash on short start discovery reply", BlueZ a734b06059cb), but userspace should still be told Not Powered, not Success. Tested on Ubuntu 7.0.0-31 with a virtual controller (hci_vhci): power off is held in HCI Write Scan Enable while a Start Discovery is submitted, and the flush answers it with status 0x00 before this change and 0x0f (Not Powered) after. Fixes: f53e1c9c726d ("Bluetooth: MGMT: Fix possible crash on mgmt_index_removed") Cc: stable@vger.kernel.org Signed-off-by: Iaroslav Voitovych Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/mgmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 41956cdde982..251a896babd5 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1495,7 +1495,7 @@ static void cmd_complete_rsp(struct mgmt_pending_cmd *cmd, void *data) return; } - cmd_status_rsp(cmd, data); + cmd_status_rsp(cmd, &match->mgmt_status); } static int generic_cmd_complete(struct mgmt_pending_cmd *cmd, u8 status) From e079ef0b1c40c7a4a1be0c1ac5dbff95685c3beb Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 29 Sep 2026 10:35:44 +0100 Subject: [PATCH 303/590] 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 304/590] 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 305/590] 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 306/590] 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 307/590] 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 308/590] 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 309/590] 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 310/590] 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 311/590] 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 312/590] 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 313/590] 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 314/590] 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 315/590] 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 316/590] 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 e0a6249190402a28f1e2925a81acf573157d55ef Mon Sep 17 00:00:00 2001 From: "Masami Hiramatsu (Google)" Date: Tue, 29 Sep 2026 09:19:12 +0900 Subject: [PATCH 317/590] 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 cfb3293c4573289f11f913611f530ea6b1926d67 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Tue, 22 Sep 2026 11:37:56 +0100 Subject: [PATCH 318/590] usb: mtu3: Fix double dereference in TP_printk Paul reported kernel splat: [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_set_halt has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] ------------[ cut here ]------------ [ 0.000000] Event mtu3_gadget_ep_set_halt has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] WARNING: kernel/trace/trace_events.c:420 at test_double_dereference+0x144/0x14c, CPU#0: swapper/0/0 [ 0.000000] Modules linked in: [ 0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.3.0-rc1 #15247 PREEMPT [ 0.000000] Hardware name: linux,dummy-virt (DT) [ 0.000000] pstate: 600000c5 (nZCv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 0.000000] pc : test_double_dereference+0x144/0x14c [ 0.000000] lr : test_double_dereference+0x144/0x14c [ 0.000000] sp : ffffc80aa7633bf0 [ 0.000000] x29: ffffc80aa7633bf0 x28: ffffc80aa7c0f047 x27: 000508b58019388f [ 0.000000] x26: 0000000000000003 x25: 0000000000000007 x24: ffffc80aa5fceff8 [ 0.000000] x23: ffffc80aa7c0f05a x22: ffffc80aa64edfe8 x21: ffffc80aa7c0fce8 [ 0.000000] x20: ffffc80aa7c0f047 x19: 0000000000000013 x18: 0000000000000001 [ 0.000000] x17: 6572656420656c62 x16: 756f642073616820 x15: 746c61685f746573 [ 0.000000] x14: 0000000000000000 x13: ffff000139d90000 x12: 0000000000000045 [ 0.000000] x11: 00000000000000cf x10: ffff00013f546428 x9 : ffff000139d90000 [ 0.000000] x8 : 3fffffffffffc000 x7 : 0000000000000001 x6 : 0000000000000001 [ 0.000000] x5 : ffff00013f4e6440 x4 : 0000000000000000 x3 : 0000000000000000 [ 0.000000] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffffc80aa764a700 [ 0.000000] Call trace: [ 0.000000] test_double_dereference+0x144/0x14c (P) [ 0.000000] trace_event_raw_init+0x37c/0x5d8 [ 0.000000] event_init+0x34/0xc0 [ 0.000000] trace_event_init+0xec/0x588 [ 0.000000] trace_init+0x24/0x6e0 [ 0.000000] start_kernel+0x4a0/0x8ec [ 0.000000] __primary_switched+0x88/0x90 [ 0.000000] irq event stamp: 0 [ 0.000000] hardirqs last enabled at (0): [<0000000000000000>] 0x0 [ 0.000000] hardirqs last disabled at (0): [<0000000000000000>] 0x0 [ 0.000000] softirqs last enabled at (0): [<0000000000000000>] 0x0 [ 0.000000] softirqs last disabled at (0): [<0000000000000000>] 0x0 [ 0.000000] ---[ end trace 0000000000000000 ]--- [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_disable has double dereference in TP_printk: &REC->gpd_ring->dma [ 0.000000] TRACE EVENT ERROR: Event mtu3_gadget_ep_enable has double dereference in TP_printk: &REC->gpd_ring->dma Which also observed by Mark and myself. The splat it result of new check introduced by b5cc230af5e5 ("tracing: Warn when an event dereferences a pointer in TP_printk()") which correctly catches issue with %pad dereferencing the address saved in the ring buffer. TP_fast_assign() logic gets executed when the tracepoint is triggered, however the TP_printk() is executed when the user reads the trace buffer which could be seconds, minutes, hours, days, even months later and nothing guarantee that __entry->gpd_ring pointer will still be pointing to what it was when it was recorded. Fix the issue by capturing immediate value of gpd_ring.dma when trace point is triggered. Reported-by: Paul E. McKenney Tested-by: Mark Rutland Reviewed-by: Steven Rostedt Signed-off-by: Vladimir Murzin Signed-off-by: Paul E. McKenney --- drivers/usb/mtu3/mtu3_trace.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_trace.h b/drivers/usb/mtu3/mtu3_trace.h index 89870175d635..9aaa167d69c1 100644 --- a/drivers/usb/mtu3/mtu3_trace.h +++ b/drivers/usb/mtu3/mtu3_trace.h @@ -224,6 +224,7 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __field(unsigned int, flags) __field(unsigned int, direction) __field(struct mtu3_gpd_ring *, gpd_ring) + __field(dma_addr_t, gpd_ring_dma) ), TP_fast_assign( __assign_str(name); @@ -235,12 +236,13 @@ DECLARE_EVENT_CLASS(mtu3_log_ep, __entry->flags = mep->flags; __entry->direction = mep->is_in; __entry->gpd_ring = &mep->gpd_ring; + __entry->gpd_ring_dma = mep->gpd_ring.dma ), TP_printk("%s: type %s maxp %d slot %d mult %d burst %d ring %p/%pad flags %c:%c%c%c:%c", __get_str(name), usb_ep_type_string(__entry->type), __entry->maxp, __entry->slot, __entry->mult, __entry->maxburst, - __entry->gpd_ring, &__entry->gpd_ring->dma, + __entry->gpd_ring, &__entry->gpd_ring_dma, __entry->flags & MTU3_EP_ENABLED ? 'E' : 'e', __entry->flags & MTU3_EP_STALL ? 'S' : 's', __entry->flags & MTU3_EP_WEDGE ? 'W' : 'w', From bc8ce2cea5f77c4bd1a5184705e300e83546c0c4 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Tue, 29 Sep 2026 12:41:35 -0400 Subject: [PATCH 319/590] ata: libata-scsi: do not lose CHECK CONDITION for failed ATAPI commands Since commit 2e1d2e65e773 ("ata: libata-scsi: terminate deferred commands on time out"), atapi_qc_complete() sets SAM_STAT_CHECK_CONDITION for a failed command only if cmd->result is zero, so that the DID_TIME_OUT or DID_REQUEUE host byte of a terminated command is preserved. However, cmd->result can also be non-zero for a regular failed ATAPI command completed through libata EH: ata_eh_analyze_tf() calls ata_eh_decide_disposition(), that is, scsi_check_sense(), on the sense data obtained with REQUEST SENSE, and scsi_check_sense() sets the SCSI midlayer internal byte of the result for some sense codes, e.g. SCSIML_STAT_TGT_FAILURE for ILLEGAL REQUEST with ASC 0x20 (INVALID COMMAND OPERATION CODE) or 0x24 (INVALID FIELD IN CDB), or SCSIML_STAT_MED_ERROR for MEDIUM ERROR with ASC 0x11 (UNRECOVERED READ ERROR). For such commands, atapi_qc_complete() keeps the result as is and the command completes with a GOOD status byte, even though it failed and valid sense data is available. As a result, passthrough users see these failed ATAPI commands as successful. For example, with the emulated IDE CD-ROM drive of QEMU and no medium loaded, a PLAY AUDIO MSF command issued with SG_IO completes with status 0x00 (and no DRIVER_SENSE) instead of 0x02 (CHECK CONDITION) with sense key ILLEGAL REQUEST, ASC/ASCQ 0x20/0x00, and the CDROM ioctls CDROMPLAYTRKIND, CDROMSUBCHNL, CDROMPAUSE, CDROMRESUME, CDROMPLAYMSF and CDROM_GET_MCN return 0 instead of -ENOMEDIUM or -EOPNOTSUPP. Fix this by setting the status byte to SAM_STAT_CHECK_CONDITION whenever the host byte is not set. Use set_status_byte() rather than overwriting the whole result, so that the SCSI midlayer internal byte set by scsi_check_sense() is preserved, as ata_scsi_qc_complete() already does for ATA commands. Fixes: 2e1d2e65e773 ("ata: libata-scsi: terminate deferred commands on time out") Cc: stable@vger.kernel.org Suggested-by: Niklas Cassel Signed-off-by: Hengyu Liang Reviewed-by: Damien Le Moal Link: https://lore.kernel.org/r/20260929164135.266708-1-hengyul@cs.unc.edu Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 8f9aa97a519d..fb5517d51095 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -3059,10 +3059,15 @@ static void atapi_qc_complete(struct ata_queued_cmd *qc) if (qc->cdb[0] == ALLOW_MEDIUM_REMOVAL && qc->dev->sdev) qc->dev->sdev->locked = 0; - if (cmd->result) - ata_scsi_qc_done(qc, false, 0); - else - ata_scsi_qc_done(qc, true, SAM_STAT_CHECK_CONDITION); + /* + * Report CHECK CONDITION unless the command was terminated + * with a host byte set. Only set the status byte, so that the + * SCSI midlayer internal byte, which scsi_check_sense() may + * have set from the sense data, is preserved. + */ + if (get_host_byte(cmd) == DID_OK) + set_status_byte(cmd, SAM_STAT_CHECK_CONDITION); + ata_scsi_qc_done(qc, false, 0); goto schedule_deferred; } From c9417c1c5269e2bd0570fbe53e4ec03a8d31f008 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 23 Sep 2026 12:40:16 +0200 Subject: [PATCH 320/590] dm: fix reading free memory in do_resume When do_resume calls dm_table_get_mode(new_map), the call is done without holding any locks (it only holds a reference to the md). It may be possible that another concurrent ioctl on the same device will swap table after dm_swap_table and before dm_table_get_mode. In this case, dm_table_get_mode(new_map) reads freed memory. This race condition was triggered by syzbot. This commit fixes the race by reading mode of the new table before dm_swap_table. Note that new_map is also passed to dm_ima_need_measure, but this function doesn't attempt to dereference it, so this call should be safe. Note that this is not a security bug because only root can trigger it and the commonly used tools such as lvm or cryptsetup do not call the ioctls concurrently, so that they can't trigger it acceidentally. Signed-off-by: Mikulas Patocka Reported-by: syzbot+bdff1ecf726d2af66afd@syzkaller.appspotmail.com Cc: stable@vger.kernel.org --- drivers/md/dm-ioctl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a005f124d706..8f945f0aeda5 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1273,6 +1273,7 @@ static int do_resume(struct dm_ioctl *param) /* Do we need to load a new map ? */ if (new_map) { sector_t old_size, new_size; + blk_mode_t new_map_mode; dm_ima_context_table_op(md, ima_context, DM_IMA_TABLE_SAVE); /* Suspend if it isn't already suspended */ @@ -1305,6 +1306,7 @@ static int do_resume(struct dm_ioctl *param) } } + new_map_mode = dm_table_get_mode(new_map); old_size = dm_get_size(md); old_map = dm_swap_table(md, new_map); if (IS_ERR(old_map)) { @@ -1320,7 +1322,7 @@ static int do_resume(struct dm_ioctl *param) if (old_size && new_size && old_size != new_size) need_resize_uevent = true; - if (dm_table_get_mode(new_map) & BLK_OPEN_WRITE) + if (new_map_mode & BLK_OPEN_WRITE) set_disk_ro(dm_disk(md), 0); else set_disk_ro(dm_disk(md), 1); From fcbde6227fcc53c73f08c56b2539e462d545cac6 Mon Sep 17 00:00:00 2001 From: Ma Xinmeng <1564938642@qq.com> Date: Fri, 25 Sep 2026 11:05:39 +0800 Subject: [PATCH 321/590] dm-snap: reject the transient exception store for snapshot-merge snapshot_merge_next_chunks() calls s->store->type->prepare_merge() without checking it. The transient store ("N") implements neither prepare_merge nor commit_merge, and snapshot_ctr() validates those callbacks only on the handover path (__validate_exception_handover()), so a directly constructed snapshot-merge calls a NULL prepare_merge on resume: snapshot_merge_resume -> snapshot_merge_next_chunks -> prepare_merge(...) commit_merge() is NULL for the same reason. Userspace only issues documented device-mapper ioctls (CAP_SYS_ADMIN); this is a misconfiguration the kernel must reject rather than oops on. Reject the combination in snapshot_ctr() at table-load time (-EINVAL), the same check the handover path already performs; this also covers the commit_merge() call site, which a guard local to the prepare_merge call would miss. Tested on Linux v6.6.157 KASAN: unpatched, the dmsetup reproducer oopses in snapshot_merge_next_chunks(); with this patch, the table load fails with -EINVAL and no oops. Also present on v6.6.145; still present on the linux-dm tree (555f3d7be91a) and on torvalds/linux master (62f4c998b297) as of 2026-09-22. Note that this is not a security bug because only root can trigger it. Signed-off-by: Ma Xinmeng <1564938642@qq.com> Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm-snap.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index c8a9ca1eeae7..7c3725e4ac54 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -1292,6 +1292,20 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad_store; } + /* + * snapshot-merge requires the optional prepare_merge/commit_merge + * callbacks; the transient exception store does not implement them. + * The handover path checks this in __validate_exception_handover(), + * but a directly constructed snapshot-merge never goes through it. + */ + if (dm_target_is_snapshot_merge(ti) && + (!s->store->type->prepare_merge || + !s->store->type->commit_merge)) { + ti->error = "Snapshot exception store does not support snapshot-merge."; + r = -EINVAL; + goto bad_hash_tables; + } + argv += args_used; argc -= args_used; From 4fa0c91c2c4c604e6080690a698d4c6405ba96ca Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 30 Sep 2026 20:58:24 +0900 Subject: [PATCH 322/590] 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 323/590] 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 324/590] 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 325/590] 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 326/590] 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 327/590] 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 328/590] 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 329/590] 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 330/590] 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 331/590] 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 332/590] 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 333/590] 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 334/590] 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 335/590] 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 336/590] 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 337/590] 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 338/590] 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 339/590] 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 340/590] 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 341/590] 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 e06b678e3b4e12fdaa0b51a7a5a54abc5dbd6469 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Tue, 29 Sep 2026 23:18:33 -0400 Subject: [PATCH 342/590] cgroup/cpuset: Don't access cpuset_cgrp_subsys.root in is_in_v2_mode() After seeing the patch [1] to guard is_in_v2_mode() with RCU, it makes me realize that is_in_v2_mode() may be called in a context where a new cgroup filesystem is being rebound with stale cpuset_cgrp_subsys.root pointer. Avoid this potential UaF situation by adding a new cpuset_v2_mode flag which is set when the cpuset_v2_mode mount option is used. This flag is written into only when cpuset_bind() is being called with a stable cpuset_cgrp_subsys.root value. The is_in_v2_mode() helper is modified to read the new cpuset_v2_mode flag instead of accessing cpuset_cgrp_subsys.root directly. [1] https://lore.kernel.org/lkml/20260929084124.626693-2-arighi@nvidia.com Fixes: b8d1b8ee93df ("cpuset: Allow v2 behavior in v1 cgroup") Signed-off-by: Waiman Long Reviewed-by: Ridong Chen Reviewed-by: Andrea Righi Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 1fcec89a28b9..98af44a7f399 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -152,6 +152,12 @@ static cpumask_var_t isolated_cpus; /* CSCB */ */ static bool update_housekeeping; /* RWCS */ +/* + * Set if "cpuset_v2_mode" mount option is used + * Cached at bind time and not lock protected; accessed via {READ,WRITE}_ONCE + */ +static bool cpuset_v2_mode; + /* * Copy of isolated_cpus to be passed to housekeeping_update() */ @@ -439,8 +445,7 @@ static inline bool cpuset_v2(void) */ static inline bool is_in_v2_mode(void) { - return cpuset_v2() || - (cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE); + return cpuset_v2() || READ_ONCE(cpuset_v2_mode); } /** @@ -3727,6 +3732,8 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css) mutex_lock(&cpuset_mutex); spin_lock_irq(&callback_lock); + WRITE_ONCE(cpuset_v2_mode, + !!(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE)); if (is_in_v2_mode()) { cpumask_copy(top_cpuset.cpus_allowed, cpu_possible_mask); cpumask_copy(top_cpuset.effective_xcpus, cpu_possible_mask); From af701b8d3238b9eb7cd450b0491300452c700bf5 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Wed, 30 Sep 2026 14:23:58 +0000 Subject: [PATCH 343/590] sched_ext: Call ops.dequeue() when a task arrives on a remote local DSQ When a task in the BPF scheduler's custody is moved to another CPU's local DSQ, ops.dequeue() is only called once the task is picked for execution, from set_next_task_scx() with SCX_DEQ_CORE_SCHED_EXEC, even though no core-sched pick took place. It should instead be called without flags when the task is inserted into the destination DSQ, as it is for same-rq dispatches. move_remote_task_to_local_dsq() sets p->scx.sticky_cpu across the migration so that deactivate_task() on the source rq doesn't end custody. Since commit b75aaea24c9f ("sched_ext: Properly mark SCX-internal migrations via sticky_cpu"), enqueue_task_scx() only clears it after inserting the task, so task_leave_custody() still sees the migration in progress and skips the custody exit. Clear p->scx.sticky_cpu as soon as enqueue_task_scx() has read it, as was done before that commit. The source side is unaffected. Fixes: ebf1ccff79c4 ("sched_ext: Fix ops.dequeue() semantics") Cc: stable@vger.kernel.org # 7.1.x: 18d62044cda7: sched_ext: Preserve rq tracking across local DSQ dispatch Cc: stable@vger.kernel.org # 7.1.x Reviewed-by: Andrea Righi Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- kernel/sched/ext/ext.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 493b7aac7087..5209510d6a5e 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2152,6 +2152,13 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; + /* + * An SCX-internal migration ends on arrival. Clear sticky_cpu so @p can + * leave custody when inserted into the destination DSQ. + */ + if (sticky_cpu >= 0) + p->scx.sticky_cpu = -1; + /* * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue * returns. Only the core's wakeup path delivers one. The flags stashed @@ -2190,9 +2197,6 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ dl_server_start(&rq->ext_server); scx_do_enqueue_task(rq, p, enq_flags, sticky_cpu); - - if (sticky_cpu >= 0) - p->scx.sticky_cpu = -1; out: rq->scx.flags &= ~SCX_RQ_IN_WAKEUP; From cc07fae1de5dbf514a9a3978050ee3cf5fe20d3c Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Wed, 30 Sep 2026 14:23:59 +0000 Subject: [PATCH 344/590] selftests/sched_ext: Add a test for ops.dequeue() on remote local DSQ moves Add a dequeue_remote test that makes moves of tasks in the BPF scheduler's custody to another CPU's local DSQ the common case, both via SCX_DSQ_LOCAL_ON dispatch and via scx_bpf_dsq_move_to_local(). The BPF scheduler tracks each task's custody state and triggers scx_bpf_error() if a custody period doesn't end with exactly one ops.dequeue() before the task runs, or if it ends with an SCX_DEQ_CORE_SCHED_EXEC dequeue of a task without a core cookie. Without the previous patch, the test fails with: sched_ext: dequeue_remote: dequeue_remote.bpf.c:141: 15 (rcu_preempt): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=3 cpu=2 seq=1) ... ops_dequeue+0x114/0x170 set_next_task_scx+0x104/0x1e0 __pick_next_task+0xc7/0x180 __schedule+0x154/0x1870 Reviewed-by: Andrea Righi Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- tools/testing/selftests/sched_ext/Makefile | 1 + .../selftests/sched_ext/dequeue_remote.bpf.c | 270 ++++++++++++++++++ .../selftests/sched_ext/dequeue_remote.c | 204 +++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 tools/testing/selftests/sched_ext/dequeue_remote.bpf.c create mode 100644 tools/testing/selftests/sched_ext/dequeue_remote.c diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 4e06d0baaeec..af919a56a4f4 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -165,6 +165,7 @@ auto-test-targets := \ create_dsq \ dequeue \ dequeue_iter \ + dequeue_remote \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c new file mode 100644 index 000000000000..983590c6d498 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.bpf.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called when a task leaves the BPF scheduler's + * custody by being moved to the local DSQ of a CPU other than the one whose + * rq it's on (move_remote_task_to_local_dsq()). + * + * ops.enqueue() puts every task into custody and ops.dispatch() moves it to + * the dispatching CPU's local DSQ, so most moves cross CPUs. With + * @test_use_move_to_local, tasks are queued on a user DSQ and consumed with + * scx_bpf_dsq_move_to_local(). Otherwise, they are queued in a BPF queue and + * dispatched with SCX_DSQ_LOCAL_ON. + * + * Copyright (c) 2026 Google LLC. + */ + +#include + +#define SHARED_DSQ 0 +#define MAX_DISPATCH_POPS 8 + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +struct { + __uint(type, BPF_MAP_TYPE_QUEUE); + __uint(max_entries, 32768); + __type(value, s32); +} global_queue SEC(".maps"); + +enum task_state { + TASK_NONE = 0, + TASK_ENQUEUED, /* in BPF custody, waiting for ops.dequeue() */ + TASK_DISPATCHED, /* left custody */ +}; + +struct task_ctx { + enum task_state state; + s32 enq_cpu; /* scx_bpf_task_cpu() at ops.enqueue() */ + u64 enqueue_seq; +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* core_cookie only exists with CONFIG_SCHED_CORE */ +struct task_struct___core_sched { + unsigned long core_cookie; +} __attribute__((preserve_access_index)); + +bool test_use_move_to_local; + +u64 enqueue_cnt, dequeue_cnt, dispatch_dequeue_cnt, change_dequeue_cnt; +u64 remote_dispatch_cnt, remote_running_cnt, missed_dequeue_cnt; +u64 core_sched_exec_dequeue_cnt; + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + return bpf_task_storage_get(&task_ctx_stor, p, 0, 0); +} + +static bool task_has_core_cookie(struct task_struct *p) +{ + struct task_struct___core_sched *t = (void *)p; + + if (!bpf_core_field_exists(t->core_cookie)) + return false; + return BPF_CORE_READ(t, core_cookie); +} + +s32 BPF_STRUCT_OPS(dequeue_remote_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + /* no direct dispatch, always go through ops.enqueue() */ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_remote_enqueue, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + s32 pid = p->pid; + + tctx = lookup_task_ctx(p); + if (!tctx) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + return; + } + + /* the previous custody period must have ended with ops.dequeue() */ + if (tctx->state == TASK_ENQUEUED) + scx_bpf_error("%d (%s): enqueue while in ENQUEUED state seq=%llu", + p->pid, p->comm, tctx->enqueue_seq); + + /* + * Mark @p as enqueued before making it visible to ops.dispatch() on + * other CPUs, which skips queue entries of tasks not in ENQUEUED + * state as stale. + */ + tctx->state = TASK_ENQUEUED; + tctx->enq_cpu = scx_bpf_task_cpu(p); + tctx->enqueue_seq++; + + if (test_use_move_to_local) { + scx_bpf_dsq_insert(p, SHARED_DSQ, SCX_SLICE_DFL, enq_flags); + } else if (bpf_map_push_elem(&global_queue, &pid, 0)) { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); + tctx->state = TASK_DISPATCHED; + tctx->enq_cpu = -1; + goto out; + } + + __sync_fetch_and_add(&enqueue_cnt, 1); +out: + scx_bpf_kick_cpu(scx_bpf_task_cpu(p), SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct task_ctx *tctx; + + __sync_fetch_and_add(&dequeue_cnt, 1); + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* + * Only core scheduling can pick a task straight out of custody, and + * only if the task has a core cookie. Otherwise, the custody exit was + * missed when @p was inserted into a local DSQ and got deferred until + * @p was picked. + */ + if ((deq_flags & SCX_DEQ_CORE_SCHED_EXEC) && !task_has_core_cookie(p)) { + __sync_fetch_and_add(&core_sched_exec_dequeue_cnt, 1); + scx_bpf_error("%d (%s): late ops.dequeue() with SCX_DEQ_CORE_SCHED_EXEC (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + } + + /* ops.dequeue() ends the custody period started by ops.enqueue() */ + if (tctx->state != TASK_ENQUEUED) + scx_bpf_error("%d (%s): dequeue outside custody deq_flags=0x%llx state=%d seq=%llu", + p->pid, p->comm, deq_flags, tctx->state, + tctx->enqueue_seq); + + if (deq_flags & SCX_DEQ_SCHED_CHANGE) { + __sync_fetch_and_add(&change_dequeue_cnt, 1); + tctx->state = TASK_NONE; + } else { + __sync_fetch_and_add(&dispatch_dequeue_cnt, 1); + tctx->state = TASK_DISPATCHED; + } +} + +void BPF_STRUCT_OPS(dequeue_remote_dispatch, s32 cpu, struct task_struct *prev) +{ + struct task_ctx *tctx; + struct task_struct *p; + s32 pid; + int i; + + if (test_use_move_to_local) { + scx_bpf_dsq_move_to_local(SHARED_DSQ, 0); + return; + } + + /* pop past stale entries so that they don't leave this CPU idle */ + bpf_for(i, 0, MAX_DISPATCH_POPS) { + if (bpf_map_pop_elem(&global_queue, &pid)) + return; + + p = bpf_task_from_pid(pid); + if (!p) + continue; + + /* + * Entries are stale if @p left custody through a property + * change dequeue or was dispatched from a duplicate entry. + */ + tctx = lookup_task_ctx(p); + if (!tctx || tctx->state != TASK_ENQUEUED) { + bpf_task_release(p); + continue; + } + + if (bpf_cpumask_test_cpu(cpu, p->cpus_ptr)) { + if (scx_bpf_task_cpu(p) != cpu) + __sync_fetch_and_add(&remote_dispatch_cnt, 1); + scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL_ON | cpu, + SCX_SLICE_DFL, 0); + } else { + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, 0); + } + + bpf_task_release(p); + return; + } + + /* out of pops with entries left, retry instead of idling this CPU */ + if (!bpf_map_peek_elem(&global_queue, &pid)) + scx_bpf_kick_cpu(cpu, SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(dequeue_remote_running, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + /* tasks can only run from a local DSQ, i.e. after leaving custody */ + if (tctx->state == TASK_ENQUEUED) { + __sync_fetch_and_add(&missed_dequeue_cnt, 1); + scx_bpf_error("%d (%s): running without ops.dequeue() (enq_cpu=%d cpu=%d seq=%llu)", + p->pid, p->comm, tctx->enq_cpu, + scx_bpf_task_cpu(p), tctx->enqueue_seq); + return; + } + + if (tctx->enq_cpu >= 0 && tctx->enq_cpu != scx_bpf_task_cpu(p)) + __sync_fetch_and_add(&remote_running_cnt, 1); + tctx->enq_cpu = -1; +} + +s32 BPF_STRUCT_OPS(dequeue_remote_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, + BPF_LOCAL_STORAGE_GET_F_CREATE); + if (!tctx) + return -ENOMEM; + + /* task storage persists across attachments, start from scratch */ + tctx->state = TASK_NONE; + tctx->enq_cpu = -1; + tctx->enqueue_seq = 0; + + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_remote_init) +{ + return scx_bpf_create_dsq(SHARED_DSQ, -1); +} + +void BPF_STRUCT_OPS(dequeue_remote_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_remote_ops = { + .select_cpu = (void *)dequeue_remote_select_cpu, + .enqueue = (void *)dequeue_remote_enqueue, + .dequeue = (void *)dequeue_remote_dequeue, + .dispatch = (void *)dequeue_remote_dispatch, + .running = (void *)dequeue_remote_running, + .init_task = (void *)dequeue_remote_init_task, + .init = (void *)dequeue_remote_init, + .exit = (void *)dequeue_remote_exit, + .flags = SCX_OPS_ENQ_LAST, + .name = "dequeue_remote", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_remote.c b/tools/testing/selftests/sched_ext/dequeue_remote.c new file mode 100644 index 000000000000..f2c1901030de --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_remote.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verify that ops.dequeue() is called for tasks leaving BPF custody through + * an SCX-internal cross-CPU migration (move_remote_task_to_local_dsq()). + * + * Copyright (c) 2026 Google LLC. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scx_test.h" +#include "dequeue_remote.bpf.skel.h" + +#define MAX_WORKERS 64 +#define RUN_MS 2000 + +static int nr_cpus; + +static long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000; +} + +/* mix of short bursts and sleeps to generate lots of enqueues and wakeups */ +static void worker_fn(int id) +{ + long long end = now_ms() + RUN_MS; + volatile unsigned long sum = 0; + + while (now_ms() < end) { + unsigned long j; + + for (j = 0; j < 20000 + id * 1000; j++) + sum += j; + if (id & 1) + usleep(100); + else + sched_yield(); + } + + exit(0); +} + +static enum scx_test_status run_scenario(struct dequeue_remote *skel, + bool use_move_to_local, + const char *name) +{ + enum scx_test_status ret = SCX_TEST_PASS; + struct bpf_link *link; + pid_t pids[MAX_WORKERS]; + int nr_workers, nr_forked, i; + + nr_workers = 2 * nr_cpus; + if (nr_workers < 4) + nr_workers = 4; + if (nr_workers > MAX_WORKERS) + nr_workers = MAX_WORKERS; + + skel->bss->test_use_move_to_local = use_move_to_local; + skel->bss->enqueue_cnt = 0; + skel->bss->dequeue_cnt = 0; + skel->bss->dispatch_dequeue_cnt = 0; + skel->bss->change_dequeue_cnt = 0; + skel->bss->remote_dispatch_cnt = 0; + skel->bss->remote_running_cnt = 0; + skel->bss->missed_dequeue_cnt = 0; + skel->bss->core_sched_exec_dequeue_cnt = 0; + memset(&skel->data->uei, 0, sizeof(skel->data->uei)); + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_remote_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops for %s", name); + + fflush(stdout); + fflush(stderr); + + for (nr_forked = 0; nr_forked < nr_workers; nr_forked++) { + pid_t pid = fork(); + + if (pid < 0) { + SCX_ERR("Failed to fork worker %d", nr_forked); + ret = SCX_TEST_FAIL; + break; + } + if (pid == 0) + worker_fn(nr_forked); + pids[nr_forked] = pid; + } + + /* on failure, kill the remaining workers but still reap them */ + for (i = 0; i < nr_forked; i++) { + int status; + + if (ret != SCX_TEST_PASS) + kill(pids[i], SIGKILL); + + if (waitpid(pids[i], &status, 0) != pids[i]) { + SCX_ERR("Failed to wait for worker %d", i); + ret = SCX_TEST_FAIL; + } else if (ret == SCX_TEST_PASS && status != 0) { + SCX_ERR("Worker %d exited with status %d", i, status); + ret = SCX_TEST_FAIL; + } + } + + bpf_link__destroy(link); + + if (ret != SCX_TEST_PASS) + return ret; + + printf("%s:\n", name); + printf(" workers: %d\n", nr_workers); + printf(" enqueues: %lu\n", (unsigned long)skel->bss->enqueue_cnt); + printf(" dequeues: %lu (dispatch: %lu, property_change: %lu)\n", + (unsigned long)skel->bss->dequeue_cnt, + (unsigned long)skel->bss->dispatch_dequeue_cnt, + (unsigned long)skel->bss->change_dequeue_cnt); + if (!use_move_to_local) + printf(" remote SCX_DSQ_LOCAL_ON dispatches: %lu\n", + (unsigned long)skel->bss->remote_dispatch_cnt); + printf(" ran on a CPU other than the enqueue CPU: %lu\n", + (unsigned long)skel->bss->remote_running_cnt); + printf(" ran without ops.dequeue(): %lu\n", + (unsigned long)skel->bss->missed_dequeue_cnt); + printf(" late SCX_DEQ_CORE_SCHED_EXEC dequeues: %lu\n", + (unsigned long)skel->bss->core_sched_exec_dequeue_cnt); + + if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_UNREG)) + SCX_ERR("Scheduler exited with kind=%lld: %s", + (long long)skel->data->uei.kind, skel->data->uei.msg); + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + /* the test is meaningless if no task was moved across CPUs */ + SCX_GT(skel->bss->remote_running_cnt, 0); + SCX_EQ(skel->bss->enqueue_cnt, skel->bss->dequeue_cnt); + + return SCX_TEST_PASS; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_remote *skel; + cpu_set_t cpus; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(cpus), &cpus), + "Failed to get CPU affinity"); + nr_cpus = CPU_COUNT(&cpus); + if (nr_cpus < 2) { + fprintf(stderr, "Skipping: requires at least 2 usable CPUs\n"); + return SCX_TEST_SKIP; + } + + skel = SCX_OPS_OPEN(dequeue_remote_ops, dequeue_remote); + SCX_OPS_LOAD(skel, dequeue_remote_ops, dequeue_remote, uei); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_remote *skel = ctx; + enum scx_test_status status; + + status = run_scenario(skel, false, + "BPF queue -> SCX_DSQ_LOCAL_ON | cpu"); + if (status != SCX_TEST_PASS) + return status; + + status = run_scenario(skel, true, + "user DSQ -> scx_bpf_dsq_move_to_local()"); + if (status != SCX_TEST_PASS) + return status; + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_remote *skel = ctx; + + dequeue_remote__destroy(skel); +} + +struct scx_test dequeue_remote_test = { + .name = "dequeue_remote", + .description = "Verify ops.dequeue() on SCX-internal cross-CPU migrations", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; + +REGISTER_SCX_TEST(&dequeue_remote_test) From c1774272c76e92fc3df10a8fedfc033716e55d1b Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Mon, 24 Aug 2026 11:55:30 +0800 Subject: [PATCH 345/590] 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 346/590] 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 347/590] 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 348/590] 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 349/590] 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 350/590] 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 351/590] 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 352/590] 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 353/590] 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 354/590] 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 355/590] 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 356/590] 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 28b7260548af3d35773477993ad4e4de41578dd7 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Mon, 28 Sep 2026 10:19:17 -0400 Subject: [PATCH 357/590] selinux: preserve NATIVE_LABELS on already-initialized sb in set_mnt_opts When an NFSv4 filesystem exported with security_label is mounted a second time, sget_fc() returns the existing superblock and nfs_get_root() calls security_sb_set_mnt_opts() again on it. Since the superblock is already SB_INITIALIZED, selinux_set_mnt_opts() takes an early exit without setting SECURITY_LSM_NATIVE_LABELS in *set_kern_flags. nfs_get_root() then sees kflags without SECURITY_LSM_NATIVE_LABELS set and clears NFS_CAP_SECURITY_LABEL on the shared nfs_server, silently disabling per-file labeling for both the new and the original mount. Commit 3815a245b501 ("security/selinux: fix SECURITY_LSM_NATIVE_LABELS on reused superblock") fixed the same problem in selinux_sb_clone_mnt_opts(). The reused-superblock path for a plain second mount now goes through sb_set_mnt_opts rather than sb_clone_mnt_opts, so it needs the same treatment: on both SE_SBINITIALIZED early exits, set SECURITY_LSM_NATIVE_LABELS in *set_kern_flags when the superblock was already using SECURITY_FS_USE_NATVE. Cc: stable@vger.kernel.org Closes: https://github.com/SELinuxProject/selinux-kernel/issues/69 Closes: https://github.com/SELinuxProject/selinux-kernel/issues/70 Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/91 Closes: https://github.com/SELinuxProject/selinux-testsuite/issues/92 Fixes: eb9ae686507b ("SELinux: Add new labeling type native labels") Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3f4a6ddee322..30776b0107f2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -690,8 +690,12 @@ static int selinux_set_mnt_opts(struct super_block *sb, * will be used for both mounts) */ if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - && !opts) + && !opts) { + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; goto out; + } root_isec = backing_inode_security_novalidate(root); @@ -735,6 +739,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, /* previously mounted with options, but not on this attempt? */ if ((sbsec->flags & SE_MNTMASK) && !opts) goto out_double_mount; + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; rc = 0; goto out; } From 77f21a59e8cf7efcc1c4b3e9176e8b99993a7ae4 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Wed, 23 Sep 2026 21:13:50 -0400 Subject: [PATCH 358/590] 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 359/590] 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 19465a9aeb664f1d710d0d22c07c31bfb7c85446 Mon Sep 17 00:00:00 2001 From: Frank Sorenson Date: Wed, 30 Sep 2026 15:47:15 -0500 Subject: [PATCH 360/590] 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 361/590] 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 362/590] 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 363/590] 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 364/590] 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 365/590] 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 366/590] 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 367/590] 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 368/590] 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 369/590] 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 370/590] 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 371/590] 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 0d6c5794fd633427f0d4ad8b51d94061295649da Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 30 Sep 2026 20:10:21 +0400 Subject: [PATCH 372/590] power: sequencing: pcie-m2: Fix leaking array from of_regulator_bulk_get_all() of_regulator_bulk_get_all() allocates the consumers array and hands it to the caller, who is expected to free it at an appropriate time. pwrseq-pcie-m2.c never did, leading to a memory leak. Add an explicit kfree() for the consumers array. Cc: stable@vger.kernel.org Fixes: 52e7b5bd62ba ("power: sequencing: Add the Power Sequencing driver for the PCIe M.2 connectors") Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260930-m2-pwrseq-kfree-v1-1-82678b02c126@flipper.net Signed-off-by: Bartosz Golaszewski --- drivers/power/sequencing/pwrseq-pcie-m2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c index 0b72e3a6992b..e42e85f47629 100644 --- a/drivers/power/sequencing/pwrseq-pcie-m2.c +++ b/drivers/power/sequencing/pwrseq-pcie-m2.c @@ -572,6 +572,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); err_free_regulators: regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); return ret; } @@ -585,6 +586,7 @@ static void pwrseq_pcie_m2_remove(struct platform_device *pdev) mutex_destroy(&ctx->list_lock); regulator_bulk_free(ctx->num_vregs, ctx->regs); + kfree(ctx->regs); } static const struct of_device_id pwrseq_pcie_m2_of_match[] = { From a3e981f42557d7bb4cb212462de463e0b6b8875b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 1 Oct 2026 10:16:44 +0200 Subject: [PATCH 373/590] 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 374/590] 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 4d69e74034b208588636ecc177e0dd43a7696af3 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:31 +0900 Subject: [PATCH 375/590] gpio: exar: initialize the ID before registering its cleanup devm_add_action_or_reset() invokes the cleanup action immediately when registering the action fails. gpio_exar_probe() currently stores the allocated ID in exar_gpio only after registering the action, so that failure makes exar_devm_ida_free() release ID 0. If the allocated ID is nonzero, this leaks that ID and can free ID 0 belonging to another device. Store the ID before registering the cleanup action so both the immediate failure path and normal device teardown release the correct allocation. Fixes: 5300ebb695fa ("gpio: exar: use devm action for freeing the IDA and drop remove()") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reviewed-by: Linus Walleij Link: https://patch.msgid.link/179070265834.12871.48183786512201899.idr-bug-79@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-exar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-exar.c b/drivers/gpio/gpio-exar.c index 9053662f1817..f629cc526f3e 100644 --- a/drivers/gpio/gpio-exar.c +++ b/drivers/gpio/gpio-exar.c @@ -199,6 +199,7 @@ static int gpio_exar_probe(struct platform_device *pdev) index = ida_alloc(&ida_index, GFP_KERNEL); if (index < 0) return index; + exar_gpio->index = index; ret = devm_add_action_or_reset(dev, exar_devm_ida_free, exar_gpio); if (ret) @@ -214,7 +215,6 @@ static int gpio_exar_probe(struct platform_device *pdev) exar_gpio->gpio_chip.set = exar_set_value; exar_gpio->gpio_chip.base = -1; exar_gpio->gpio_chip.ngpio = ngpios; - exar_gpio->index = index; exar_gpio->first_pin = first_pin; ret = devm_gpiochip_add_data(dev, &exar_gpio->gpio_chip, exar_gpio); From db0949bbd06db0a75b4b1076f1e25194be7d956a Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Sun, 27 Sep 2026 05:59:57 +0000 Subject: [PATCH 376/590] 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 377/590] 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 378/590] 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 379/590] 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 380/590] 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 381/590] 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 382/590] 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 383/590] 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 384/590] 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 385/590] 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 fc79aeb86a2c244c1f1894e2d8f2966fe2b60fa2 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 18:42:20 +0200 Subject: [PATCH 386/590] dm-integrity: validate the superblock on resume dm_integrity_resume() re-reads the superblock from the device so that it picks up the flags and the recalculate position. It performs no validation on the result, while the constructor validates the superblock it reads and sizes all the in-memory structures according to it. The user may modify the superblock on the underlying device while the dm-integrity device is suspended, so that the two no longer agree. In particular, access_journal_data() shifts the journal entry index by ic->sb->log2_sectors_per_block, while ic->journal_section_sectors and the journal page list were computed with the value that was present at constructor time. Increasing log2_sectors_per_block makes the index run past the end of the journal, and dm-integrity then writes 512 bytes through lowmem_page_address(NULL). Snapshot the validated superblock in the constructor and refuse to resume if any of the fields that describe the on-disk geometry changed. Reported-by: syzbot+675c91651049ad042c5f@syzkaller.appspotmail.com Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org Fixes: 118ba36e446c ("dm-integrity: fix recalculation in bitmap mode") Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-integrity.c | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 0862f31b8498..802b377613b0 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -180,6 +180,7 @@ struct dm_integrity_c { struct dm_bufio_client *bufio; struct workqueue_struct *metadata_wq; struct superblock *sb; + struct superblock *sb_copy; unsigned int journal_pages; unsigned int n_bitmap_blocks; @@ -3858,6 +3859,35 @@ static void dm_integrity_postsuspend(struct dm_target *ti) ic->journal_uptodate = true; } +/* + * The superblock is re-read from the device on every resume, so that we pick + * up the flags and the recalculate position. The geometry described by the + * superblock must not change though - the in-memory structures (and the + * journal in particular) were sized according to the superblock that was + * validated in the constructor. Reject a superblock that was modified behind + * our back. + * + * Only the fields that the driver never rewrites may be tested here. In + * particular, "version" is recalculated by sb_set_version on every superblock + * write and it depends on SB_FLAG_RECALCULATING and SB_FLAG_DIRTY_BITMAP, and + * SB_FLAG_DISCARD_KEYED may be set by dm_integrity_resume itself. + */ +static bool superblock_changed(struct dm_integrity_c *ic) +{ + const __le32 immutable_flags = cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC | + SB_FLAG_FIXED_PADDING | + SB_FLAG_FIXED_HMAC | + SB_FLAG_INLINE); + + return memcmp(ic->sb->magic, ic->sb_copy->magic, sizeof(ic->sb->magic)) != 0 || + ic->sb->log2_interleave_sectors != ic->sb_copy->log2_interleave_sectors || + ic->sb->integrity_tag_size != ic->sb_copy->integrity_tag_size || + ic->sb->journal_sections != ic->sb_copy->journal_sections || + ic->sb->log2_sectors_per_block != ic->sb_copy->log2_sectors_per_block || + ((ic->sb->flags ^ ic->sb_copy->flags) & immutable_flags) != 0 || + memcmp(ic->sb->salt, ic->sb_copy->salt, SALT_SIZE) != 0; +} + static void dm_integrity_resume(struct dm_target *ti) { struct dm_integrity_c *ic = ti->private; @@ -3876,6 +3906,18 @@ static void dm_integrity_resume(struct dm_target *ti) if (r) dm_integrity_io_error(ic, "reading superblock", r); + if (unlikely(superblock_changed(ic))) { + /* + * Restore the superblock that we validated in the constructor, + * so that the rest of the driver doesn't operate on values + * that don't match the in-memory structures. + */ + memcpy(ic->sb, ic->sb_copy, sizeof(struct superblock)); + DMERR("The superblock was changed while the device was suspended"); + dm_integrity_io_error(ic, "superblock check", -EINVAL); + goto skip_writes; + } + if (ic->mode == 'R') goto skip_writes; @@ -5427,6 +5469,13 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv ic->just_formatted = true; } + ic->sb_copy = kmemdup(ic->sb, sizeof(struct superblock), GFP_KERNEL); + if (!ic->sb_copy) { + ti->error = "Cannot allocate superblock copy"; + r = -ENOMEM; + goto bad; + } + if (!ic->meta_dev && ic->mode != 'I') { r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors); if (r) @@ -5525,6 +5574,7 @@ static void dm_integrity_dtr(struct dm_target *ti) kvfree(ic->journal_tree); if (ic->sb) free_pages_exact(ic->sb, SB_SECTORS << SECTOR_SHIFT); + kfree(ic->sb_copy); if (ic->internal_shash) crypto_free_shash(ic->internal_shash); From c98bf3b86609a18ab16d067280257326e557c636 Mon Sep 17 00:00:00 2001 From: Hongjian Dai Date: Wed, 30 Sep 2026 00:43:40 +0800 Subject: [PATCH 387/590] 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 388/590] 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 389/590] 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 390/590] 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 391/590] 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 392/590] 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 7efa1a1953cd6b20b23a3524d5b4b81789c6ef25 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 29 Sep 2026 09:36:46 +0000 Subject: [PATCH 393/590] gpio: mpsse: fix race when arming the IRQ poll worker gpio_mpsse_irq_enable() arms the poll worker before publishing it: schedule_work() runs before the worker is added to priv->workers. If gpio_mpsse_disconnect() walks the list in that window it misses the worker, and once disconnect returns, the USB core frees mpsse_priv while the orphaned gpio_mpsse_poll() work keeps accessing it, causing a use-after-free. Fix this by publishing and arming the worker in one irq_spin critical section. Teardown walks the same list under irq_spin, so a worker found on the list is guaranteed to be armed, and cancel_work_sync() handles it whether it is queued or running. A worker armed after the disconnect walk would still be missed, so also set a new priv->dying flag under irq_spin before the teardown walk, and check it in the same critical section, freeing it via kfree_rcu() instead when the device is going away, since kfree() would be called with the IRQ core's raw spinlock held and may sleep on PREEMPT_RT. schedule_work() is safe to call with irq_spin held, and the next probe gets a fresh mpsse_priv, so the flag never needs to be cleared. This issue was found by an in-house static analysis tool. Fixes: 179ef1127d7a ("gpio: mpsse: ensure worker is torn down") Cc: stable@vger.kernel.org Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260929093646.18419-1-fanwu01@zju.edu.cn Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-mpsse.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c index 30e8009b5fcc..61c5ca96314b 100644 --- a/drivers/gpio/gpio-mpsse.c +++ b/drivers/gpio/gpio-mpsse.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ struct mpsse_priv { raw_spinlock_t irq_spin; /* protects worker list */ atomic_t irq_type[16]; /* pin -> edge detection type */ atomic_t irq_enabled; + atomic_t dying; /* no new workers after disconnect */ int id; u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */ @@ -46,6 +48,7 @@ struct mpsse_worker { atomic_t cancelled; struct list_head list; /* linked list */ struct list_head destroy; /* teardown linked list */ + struct rcu_head rcu; /* deferred free for dying path */ }; struct bulk_desc { @@ -525,10 +528,24 @@ static void gpio_mpsse_irq_enable(struct irq_data *irqd) worker->priv = priv; INIT_LIST_HEAD(&worker->list); INIT_WORK(&worker->work, gpio_mpsse_poll); - schedule_work(&worker->work); - scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) { + if (atomic_read(&priv->dying)) { + /* + * Cannot kfree() here: this callback + * runs with the IRQ descriptor's raw + * spinlock held and kfree() may sleep + * on PREEMPT_RT. The worker is not + * yet published, so the deferred free + * is unobservable. + */ + kfree_rcu(worker, rcu); + return; + } + list_add(&worker->list, &priv->workers); + schedule_work(&worker->work); + } } } @@ -704,6 +721,9 @@ static void gpio_mpsse_disconnect(struct usb_interface *intf) { struct mpsse_priv *priv = usb_get_intfdata(intf); + scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) + atomic_set(&priv->dying, 1); + /* * Lock prevents double-free of worker from here and the teardown * step at the beginning of gpio_mpsse_poll From 0a04c0cb8606ff88399489d03ef255afe90d0d5f Mon Sep 17 00:00:00 2001 From: zjamg Date: Thu, 17 Sep 2026 20:37:16 +0800 Subject: [PATCH 394/590] can: dev: init_can_skb(): restore skb header initialization Commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") removed the skb_reset_mac_header()/skb_reset_network_header()/ skb_reset_transport_header() calls from init_can_skb(). As a result, RX skbs from alloc_can_skb() and friends again carry mac_header = 0xFFFF. When such an skb reaches packet_rcv_spkt() (SOCK_PACKET), the push length calculation overflows and triggers skb_under_panic -> kernel BUG -> full machine panic. The same issue was originally reported in 2014 on linux-can and fixed by commit 969439016d2c ("can: add missing initialisations in CAN related skbuffs"). packet_rcv_spkt() itself has never been hardened: only packet_rcv() and tpacket_rcv() gained dev_has_header() checks in commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header"). Fixes: 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") Cc: stable@vger.kernel.org Reviewed-by: Oliver Hartkopp Acked-by: Oliver Hartkopp Signed-off-by: zjamg Tested-by: Quchaosheng Link: https://patch.msgid.link/20260917123716.63116-1-ndaugoing@gmail.com Reported-by: Shaunak Datar Closes: https://lore.kernel.org/all/20260918151606.765490-1-shaunakkdatar@gmail.com Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/skb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/can/dev/skb.c b/drivers/net/can/dev/skb.c index 95fcdc1026f8..14edec5afb57 100644 --- a/drivers/net/can/dev/skb.c +++ b/drivers/net/can/dev/skb.c @@ -210,6 +210,10 @@ static void init_can_skb(struct sk_buff *skb) { skb->pkt_type = PACKET_BROADCAST; skb->ip_summed = CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_reset_transport_header(skb); } struct sk_buff *alloc_can_skb(struct net_device *dev, struct can_frame **cf) From 7093c3b8314a4ef7405c4066a2521398c455efe7 Mon Sep 17 00:00:00 2001 From: Oliver Hartkopp Date: Thu, 1 Oct 2026 13:57:24 +0200 Subject: [PATCH 395/590] can: fix unique skb identifier regression under RPS Commit d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") moved the per-skb unique identifier used for raw_rcv() duplicate detection into skb->hash. With RPS enabled, get_rps_cpu() calls skb_get_hash() before the frame reaches the CAN subsystem. Since CAN skbs have no L3/L4 headers, the flow dissector assigns every CAN frame the same non-zero software hash. can_set_skb_uid() only generated a new identifier when skb->hash was 0, so it kept this constant hash. When the SLAB allocator later reused the same skb address, raw_rcv() mistook the next legitimate frame for a duplicate and dropped it. Fix this by storing the CAN UID in the CAN skb extension (struct can_skb_ext::can_skb_uid) instead of skb->hash, decoupling it from any hash the network stack may compute. can_set_skb_uid() keeps its "assign only if unset" behaviour, which preserves UIDs set before transmission (e.g. by isotp to identify echo frames) across the local loopback path. Frames whose extension is shared with another clone (e.g. via tc mirred or netem duplicate) are given a private extension copy before the UID is assigned, so that independently received skb clones from real CAN interfaces (can_skb_uid = 0) don't end up with the same UID. The limitation that tc mirred/netem skb duplicates might be dropped in raw_rcv for looped back isotp echo frame skbs is accepted. There is no valid use-case for tc mirred or netem together with isotp which is not capable to operate on different CAN interfaces simultaneously. For routing and modifying CAN frames together with isotp use can-gw. can-gw and vxcan additionally clear the UID of forwarded/duplicated frames so each newly routed frame gets its own unique identifier. Fixes: d4fb6514ff8e ("can: use skb hash instead of private variable in headroom") Reported-by: Joerg Willmann Closes: https://lore.kernel.org/linux-can/2859AD3D-C805-41A0-9036-C5E8EE152419@clnt.de/ Cc: stable@vger.kernel.org Tested-by: Oliver Hartkopp Tested-by: Joerg Willmann Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20261001115724.27192-1-socketcan@hartkopp.net Signed-off-by: Marc Kleine-Budde --- drivers/net/can/vxcan.c | 3 +++ include/linux/can/core.h | 3 ++- include/linux/can/skb.h | 4 +++- include/net/can.h | 2 ++ net/can/af_can.c | 47 ++++++++++++++++++++++++++++++---------- net/can/gw.c | 3 +++ net/can/isotp.c | 16 +++++++++----- net/can/raw.c | 10 ++++++--- 8 files changed, 66 insertions(+), 22 deletions(-) diff --git a/drivers/net/can/vxcan.c b/drivers/net/can/vxcan.c index 9e2e25d02471..51148a81d1d9 100644 --- a/drivers/net/can/vxcan.c +++ b/drivers/net/can/vxcan.c @@ -79,6 +79,9 @@ static netdev_tx_t vxcan_xmit(struct sk_buff *oskb, struct net_device *dev) /* reset CAN GW hop counter */ csx->can_gw_hops = 0; + /* start with new CAN skb UID in the other namespace */ + csx->can_skb_uid = 0; + skb->pkt_type = PACKET_BROADCAST; skb->dev = peer; skb->ip_summed = CHECKSUM_UNNECESSARY; diff --git a/include/linux/can/core.h b/include/linux/can/core.h index 3287232e3cad..2de74c2b78b6 100644 --- a/include/linux/can/core.h +++ b/include/linux/can/core.h @@ -17,6 +17,7 @@ #include #include #include +#include #define DNAME(dev) ((dev) ? (dev)->name : "any") @@ -58,7 +59,7 @@ extern void can_rx_unregister(struct net *net, struct net_device *dev, void *data); extern int can_send(struct sk_buff *skb, int loop); -void can_set_skb_uid(struct sk_buff *skb); +void can_set_skb_uid(struct can_skb_ext *csx); void can_sock_destruct(struct sock *sk); #endif /* !_CAN_CORE_H */ diff --git a/include/linux/can/skb.h b/include/linux/can/skb.h index a70a02967071..5d27843862fc 100644 --- a/include/linux/can/skb.h +++ b/include/linux/can/skb.h @@ -43,8 +43,10 @@ static inline struct can_skb_ext *can_skb_ext_add(struct sk_buff *skb) struct can_skb_ext *csx = skb_ext_add(skb, SKB_EXT_CAN); /* skb_ext_add() returns uninitialized space */ - if (csx) + if (csx) { csx->can_gw_hops = 0; + csx->can_skb_uid = 0; + } return csx; } diff --git a/include/net/can.h b/include/net/can.h index 6db9e826f0e0..2b8af2598732 100644 --- a/include/net/can.h +++ b/include/net/can.h @@ -17,12 +17,14 @@ * @can_framelen: cached echo CAN frame length for bql * @can_gw_hops: can-gw CAN frame time-to-live counter * @can_ext_flags: CAN skb extensions flags + * @can_skb_uid: CAN skb UID for raw_rcv and isotp echo handling */ struct can_skb_ext { int can_iif; u16 can_framelen; u8 can_gw_hops; u8 can_ext_flags; + u32 can_skb_uid; }; #endif /* _NET_CAN_H */ diff --git a/net/can/af_can.c b/net/can/af_can.c index 7bc86b176b4d..34fe3b28d576 100644 --- a/net/can/af_can.c +++ b/net/can/af_can.c @@ -641,13 +641,10 @@ static int can_rcv_filter(struct can_dev_rcv_lists *dev_rcv_lists, struct sk_buf return matches; } -void can_set_skb_uid(struct sk_buff *skb) +void can_set_skb_uid(struct can_skb_ext *csx) { - /* create non-zero unique skb identifier together with *skb */ - while (!(skb->hash)) - skb->hash = atomic_inc_return(&skbcounter); - - skb->sw_hash = 1; + while (!(csx->can_skb_uid)) + csx->can_skb_uid = atomic_inc_return(&skbcounter); } EXPORT_SYMBOL(can_set_skb_uid); @@ -662,8 +659,6 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) atomic_long_inc(&pkg_stats->rx_frames); atomic_long_inc(&pkg_stats->rx_frames_delta); - can_set_skb_uid(skb); - rcu_read_lock(); /* deliver the packet to sockets listening on all devices */ @@ -687,8 +682,10 @@ static void can_receive(struct sk_buff *skb, struct net_device *dev) static int can_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_can_skb(skb))) { + !csx || !can_is_can_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -696,6 +693,14 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -703,8 +708,10 @@ static int can_rcv(struct sk_buff *skb, struct net_device *dev, static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canfd_skb(skb))) { + !csx || !can_is_canfd_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN FD skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -712,6 +719,14 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } @@ -719,8 +734,10 @@ static int canfd_rcv(struct sk_buff *skb, struct net_device *dev, static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { + struct can_skb_ext *csx = can_skb_ext_find(skb); + if (unlikely(dev->type != ARPHRD_CAN || !can_get_ml_priv(dev) || - !can_skb_ext_find(skb) || !can_is_canxl_skb(skb))) { + !csx || !can_is_canxl_skb(skb))) { pr_warn_once("PF_CAN: dropped non conform CAN XL skbuff: dev type %d, len %d\n", dev->type, skb->len); @@ -728,6 +745,14 @@ static int canxl_rcv(struct sk_buff *skb, struct net_device *dev, return NET_RX_DROP; } + /* create unshared CAN skb_extension for netem/mirred skb clones */ + csx = skb_ext_add(skb, SKB_EXT_CAN); + if (unlikely(!csx)) { + kfree_skb_reason(skb, SKB_DROP_REASON_NOMEM); + return NET_RX_DROP; + } + + can_set_skb_uid(csx); can_receive(skb, dev); return NET_RX_SUCCESS; } diff --git a/net/can/gw.c b/net/can/gw.c index 0ec99f68aa45..5793cb2420ed 100644 --- a/net/can/gw.c +++ b/net/can/gw.c @@ -527,6 +527,9 @@ static void can_can_gw_rcv(struct sk_buff *skb, void *data) /* put the incremented hop counter in the cloned skb */ ncsx->can_gw_hops = csx->can_gw_hops + 1; + /* force a new CAN UID generation for the routed frame */ + ncsx->can_skb_uid = 0; + /* first processing of this CAN frame -> adjust to private hop limit */ if (gwj->limit_hops && ncsx->can_gw_hops == 1) ncsx->can_gw_hops = max_hops - gwj->limit_hops + 1; diff --git a/net/can/isotp.c b/net/can/isotp.c index 155530aedce2..f5dc9d04bd68 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -891,7 +891,7 @@ static void isotp_send_cframe(struct isotp_sock *so) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); cf = (struct canfd_frame *)skb->data; skb_put_zero(skb, so->ll.mtu); @@ -917,7 +917,7 @@ static void isotp_send_cframe(struct isotp_sock *so) pr_notice_once("can-isotp: cfecho is %08X != 0\n", old_cfecho); /* set consecutive frame echo tag */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); /* send frame with local echo enabled */ can_send_ret = can_send(skb, 1); @@ -969,18 +969,22 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) { struct sock *sk = (struct sock *)data; struct isotp_sock *so = isotp_sk(sk); + struct can_skb_ext *csx = can_skb_ext_find(skb); /* only handle my own local echo CF/SF skb's (no FF!) */ if (skb->sk != sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock * (no isotp_rcv() caller here), so take it ourselves */ spin_lock(&so->rx_lock); /* so->cfecho may since belong to a new transfer; recheck under lock */ - if (READ_ONCE(so->cfecho) != skb->hash) + if (READ_ONCE(so->cfecho) != csx->can_skb_uid) goto out_unlock; /* cancel local echo timeout */ @@ -1222,7 +1226,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) csx->can_iif = dev->ifindex; /* set uid in tx skb to identify CF echo frames */ - can_set_skb_uid(skb); + can_set_skb_uid(csx); so->tx.len = size; so->tx.idx = 0; @@ -1261,7 +1265,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) cf->data[ae] |= size; /* set CF echo tag for isotp_rcv_echo() (SF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* send first frame */ @@ -1278,7 +1282,7 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->txfc.bs = 0; /* set CF echo tag for isotp_rcv_echo() (CF-mode) */ - WRITE_ONCE(so->cfecho, skb->hash); + WRITE_ONCE(so->cfecho, csx->can_skb_uid); } else { /* standard flow control check */ new_state = ISOTP_WAIT_FIRST_FC; diff --git a/net/can/raw.c b/net/can/raw.c index 82d9c0499c95..0acd4f6c6dd6 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -77,7 +77,7 @@ MODULE_ALIAS("can-proto-1"); struct uniqframe { const struct sk_buff *skb; - u32 hash; + u32 can_skb_uid; unsigned int join_rx_count; }; @@ -133,12 +133,16 @@ static void raw_rcv(struct sk_buff *oskb, void *data) enum skb_drop_reason reason; struct sockaddr_can *addr; struct sk_buff *skb; + struct can_skb_ext *csx = can_skb_ext_find(oskb); unsigned int *pflags; /* check the received tx sock reference */ if (!ro->recv_own_msgs && oskb->sk == sk) return; + if (WARN_ON_ONCE(!csx)) + return; + /* make sure to not pass oversized frames to the socket */ if (!ro->fd_frames && can_is_canfd_skb(oskb)) return; @@ -165,7 +169,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && - this_cpu_ptr(ro->uniq)->hash == oskb->hash) { + this_cpu_ptr(ro->uniq)->can_skb_uid == csx->can_skb_uid) { if (!ro->join_filters) return; @@ -175,7 +179,7 @@ static void raw_rcv(struct sk_buff *oskb, void *data) return; } else { this_cpu_ptr(ro->uniq)->skb = oskb; - this_cpu_ptr(ro->uniq)->hash = oskb->hash; + this_cpu_ptr(ro->uniq)->can_skb_uid = csx->can_skb_uid; this_cpu_ptr(ro->uniq)->join_rx_count = 1; /* drop first frame to check all enabled filters? */ if (ro->join_filters && ro->count > 1) From 7f85d1170575645f9f327062d78017a23b2714eb Mon Sep 17 00:00:00 2001 From: "Ji-Ze Hong (Peter Hong)" Date: Mon, 24 Aug 2026 21:18:14 +0800 Subject: [PATCH 396/590] usb: f81604: fix struct f81604_int_data size mismatch The struct f81604_int_data defines 9 bytes of interrupt data: - Byte 0: Status register (sr) - Byte 1: Interrupt register (isrc) - Byte 2: Interrupt enable register (ier) - Byte 3: Arbitration lost capture (alc) - Byte 4: Error code capture (ecc) - Byte 5: Error warning limit register (ewlr) - Byte 6: RX error counter (rxerr) - Byte 7: TX error counter (txerr) - Byte 8: Reserved (val) The hardware sends exactly 9 bytes for the interrupt endpoint. However, the struct was defined with __aligned(4) attribute which caused the compiler to pad the struct to 12 bytes. This causes a problem in f81604_read_int_callback() where the short URB check compares urb->actual_length against sizeof(*data). When sizeof(struct f81604_int_data) is 12 but the hardware only sends 9 bytes, the check fails and valid interrupt messages are discarded. This results in the driver only being able to transmit once because the TX complete interrupt is never processed. Fix this by removing the __aligned(4) attribute so the struct size matches the actual hardware data size of 9 bytes. Fixes: 7299b1b39a25 ("can: usb: f81604: handle short interrupt urb messages properly") Cc: stable@vger.kernel.org Reported-by: Dynetrex, Admin Closes: https://lore.kernel.org/all/A3834A07-5639-4779-844F-C5843DFC3928@dynetrex.com/ Signed-off-by: Ji-Ze Hong (Peter Hong) Acked-by: Greg Kroah-Hartman Tested-by: Drew Willey Link: https://patch.msgid.link/20260824-f81604-fix-v2-1-fc9be5581394@fintek.com.tw Signed-off-by: Marc Kleine-Budde --- drivers/net/can/usb/f81604.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/can/usb/f81604.c b/drivers/net/can/usb/f81604.c index f12318268e46..4c147b9d6d69 100644 --- a/drivers/net/can/usb/f81604.c +++ b/drivers/net/can/usb/f81604.c @@ -169,7 +169,7 @@ struct f81604_int_data { u8 rxerr; u8 txerr; u8 val; -} __packed __aligned(4); +} __packed; struct f81604_sff { __be16 id; From f1d565dc92955f05383794e0fa27e61ee0555027 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 1 Oct 2026 17:10:35 +0200 Subject: [PATCH 397/590] 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 398/590] 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 399/590] 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 400/590] 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 401/590] 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 402/590] 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 403/590] 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 404/590] 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 405/590] 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 406/590] 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 407/590] 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 408/590] 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 409/590] 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 410/590] 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 411/590] 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 412/590] 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 413/590] 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 414/590] 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 415/590] 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 416/590] 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 417/590] 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 418/590] 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 419/590] 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 420/590] 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 421/590] 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 422/590] 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 423/590] 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 424/590] 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 425/590] 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 426/590] 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 427/590] 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 428/590] 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 429/590] 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 430/590] 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 431/590] 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 432/590] 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 433/590] 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 434/590] 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 435/590] 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 436/590] 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 af0524bf4ce1a4cbd2946aa81751582951cfcab5 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:29 -0700 Subject: [PATCH 437/590] ibmveth: h_free logical LAN on open-fail after register ibmveth_open() registers the logical LAN, then allocates the RX buffer pools. A pool failure jumps to out_free_buffer_pools without h_free_logical_lan(), so PHYP still owns the buffer-list page when it is unmapped. request_irq() failure already issued the hypercall before taking the same label. Move that h_free to the shared post-register unwind so both paths deregister before the buffer list is unmapped. Fixes: d43732ce021f ("ibmveth: properly unwind on init errors") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/7a56fbfb9e0674f011f4e310fbd7dfd8e67c30aa.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 73e051d26b9d..3e44f6b714d4 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -718,10 +718,6 @@ static int ibmveth_open(struct net_device *netdev) if (rc != 0) { netdev_err(netdev, "unable to request irq 0x%x, rc %d\n", netdev->irq, rc); - do { - lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); - } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); - goto out_free_buffer_pools; } @@ -737,6 +733,9 @@ static int ibmveth_open(struct net_device *netdev) return 0; out_free_buffer_pools: + do { + lpar_rc = h_free_logical_lan(adapter->vdev->unit_address); + } while (H_IS_LONG_BUSY(lpar_rc) || (lpar_rc == H_BUSY)); while (--i >= 0) { if (adapter->rx_buff_pool[i].active) ibmveth_free_buffer_pool(adapter, From 84bec0bf035258a1073ffd6f6dc8ea61806aa7d4 Mon Sep 17 00:00:00 2001 From: Mingming Cao Date: Fri, 25 Sep 2026 11:05:30 -0700 Subject: [PATCH 438/590] ibmveth: fix TX LTB and filter unwind on open-fail out_free_buffer_pools reuses open()'s loop index for the TX LTB walk. After the pool unwind i is -1, so the TX buffers allocated earlier are leaked. request_irq() failure has the same leak. The TX-fail goto on that walk also skips dma_unmap of the filter list (pre-existing since the LTB was added). Rewrite the TX walk to real_num_tx_queues with a pointer check, and unmap the filter list before those frees. Fixes: d926793c1de9 ("ibmveth: Implement multi queue on xmit") Cc: stable@vger.kernel.org Signed-off-by: Mingming Cao Reviewed-by: Dave Marquardt Link: https://patch.msgid.link/d14314c2b10f125f5e578782484ba3cc50564d53.1790357373.git.mmc@linux.ibm.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/ibm/ibmveth.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c index 3e44f6b714d4..abebdb1fc262 100644 --- a/drivers/net/ethernet/ibm/ibmveth.c +++ b/drivers/net/ethernet/ibm/ibmveth.c @@ -553,10 +553,15 @@ static int ibmveth_rxq_harvest_buffer(struct ibmveth_adapter *adapter, static void ibmveth_free_tx_ltb(struct ibmveth_adapter *adapter, int idx) { + void *ptr = adapter->tx_ltb_ptr[idx]; + + if (!ptr) + return; + + adapter->tx_ltb_ptr[idx] = NULL; dma_unmap_single(&adapter->vdev->dev, adapter->tx_ltb_dma[idx], adapter->tx_ltb_size, DMA_TO_DEVICE); - kfree(adapter->tx_ltb_ptr[idx]); - adapter->tx_ltb_ptr[idx] = NULL; + kfree(ptr); } static int ibmveth_allocate_tx_ltb(struct ibmveth_adapter *adapter, int idx) @@ -667,7 +672,7 @@ static int ibmveth_open(struct net_device *netdev) for (i = 0; i < netdev->real_num_tx_queues; i++) { if (ibmveth_allocate_tx_ltb(adapter, i)) - goto out_free_tx_ltb; + goto out_unmap_filter_list; } adapter->rx_queue.index = 0; @@ -745,10 +750,8 @@ static int ibmveth_open(struct net_device *netdev) dma_unmap_single(dev, adapter->filter_list_dma, 4096, DMA_BIDIRECTIONAL); -out_free_tx_ltb: - while (--i >= 0) { + for (i = netdev->real_num_tx_queues - 1; i >= 0; i--) ibmveth_free_tx_ltb(adapter, i); - } out_unmap_buffer_list: dma_unmap_single(dev, adapter->buffer_list_dma, 4096, From 048739be3e9cacae5301b68245668002fe6f0ce5 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Mon, 21 Sep 2026 21:11:56 +0800 Subject: [PATCH 439/590] 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 17e73bae400f6a434dc006e3bee92c94cf781954 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Tue, 29 Sep 2026 00:12:46 +0800 Subject: [PATCH 440/590] tipc: hold a reference to nodes found by link name tipc_node_find_by_name() returns a node after dropping its RCU read lock without taking a reference. The LINK_SET, LINK_GET and LINK_RESET_STATS handlers then lock and access the node, racing with timer-driven cleanup of a down peer. Generic netlink serialization does not exclude the node timer. The following interleaving can leave a handler using a freed node: CPU 0: find the node under RCU and release the node read lock CPU 1: tipc_node_timeout() clears the links and unlinks the down node CPU 1: drop the list and timer references, queuing tipc_node_free() CPU 0: leave the RCU read-side critical section CPU 1: complete the grace period and free the node CPU 0: acquire the node lock through the stale pointer LINK_SET also uses the node's media address after releasing the node lock, when passing queued packets to tipc_bearer_xmit(). KASAN on v7.3-rc5 reported: BUG: KASAN: slab-use-after-free in _raw_read_lock_bh Write of size 4 at addr ffff88807e06f008 by task poc/92 Call Trace: _raw_read_lock_bh kernel/locking/spinlock.c:287 tipc_nl_node_set_link net/tipc/node.c:2475 genl_family_rcv_msg_doit net/netlink/genetlink.c:1114 genl_rcv_msg net/netlink/genetlink.c:1209 netlink_rcv_skb net/netlink/af_netlink.c:2575 Allocated by task 0: tipc_node_create net/tipc/node.c:539 tipc_node_check_dest net/tipc/node.c:1196 tipc_disc_rcv net/tipc/discover.c:252 Freed by task 92: kfree mm/slub.c:6801 rcu_core kernel/rcu/tree.c:2919 Last potentially related work creation: __call_rcu_common.constprop.0 kernel/rcu/tree.c:3181 tipc_node_timeout net/tipc/node.c:814 Acquire a reference to the selected node with kref_get_unless_zero() before leaving RCU, returning NULL if the node has already been released. Release that reference on every caller exit after the last node access, including transmission in LINK_SET. Keep the existing link lookup order and locking so concurrent link removal still takes the existing error paths. Fixes: 6a939f365bdb ("tipc: Auto removal of peer down node instance") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Link: https://patch.msgid.link/20260928161246.1895273-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/tipc/node.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/net/tipc/node.c b/net/tipc/node.c index bd91378b7540..d7cbfa786c13 100644 --- a/net/tipc/node.c +++ b/net/tipc/node.c @@ -2421,8 +2421,11 @@ static struct tipc_node *tipc_node_find_by_name(struct net *net, } } tipc_node_read_unlock(n); - if (found_node) + if (found_node) { + if (!kref_get_unless_zero(&found_node->kref)) + found_node = NULL; break; + } } rcu_read_unlock(); @@ -2507,6 +2510,7 @@ int tipc_nl_node_set_link(struct sk_buff *skb, struct genl_info *info) tipc_node_read_unlock(node); tipc_bearer_xmit(net, bearer_id, &xmitq, &node->links[bearer_id].maddr, NULL); + tipc_node_put(node); return res; } @@ -2558,12 +2562,14 @@ int tipc_nl_node_get_link(struct sk_buff *skb, struct genl_info *info) link = node->links[bearer_id].link; if (!link) { tipc_node_read_unlock(node); + tipc_node_put(node); err = -EINVAL; goto err_free; } err = __tipc_nl_add_link(net, &msg, link, 0); tipc_node_read_unlock(node); + tipc_node_put(node); if (err) goto err_free; } @@ -2634,11 +2640,13 @@ int tipc_nl_node_reset_link_stats(struct sk_buff *skb, struct genl_info *info) if (!link) { spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return -EINVAL; } tipc_link_reset_stats(link); spin_unlock_bh(&le->lock); tipc_node_read_unlock(node); + tipc_node_put(node); return 0; } From 26f90559a77cac4b8fdfa3491732b7c7e08210ac Mon Sep 17 00:00:00 2001 From: Sunho Park Date: Sun, 30 Aug 2026 18:56:05 +0900 Subject: [PATCH 441/590] srcu: Fix WARN_ON() for rcu_segcblist_n_cbs() in cleanup_srcu_struct() The WARN_ON() added by commit 78a38cbf6f20 ("srcu: Queue sdp->work when the delay timer is successfully deleted") uses rcu_segcblist_n_cbs() to detect callbacks that srcu_barrier() failed to wait for. However, the ->len counter is decremented only at the end of srcu_invoke_callbacks(), after the invoking loop has finished. Since srcu_barrier() can return right after the barrier callback is invoked, cleanup_srcu_struct() can see a non-zero n_cbs even though the cblist is already physically empty, falsely triggering the WARN_ON() together with a still-pending delay_work timer. This can be triggered as follows, as seen in the syzbot report against kvm_destroy_vm() -> cleanup_srcu_struct(&kvm->srcu): 1. call_srcu(&kvm->srcu, &bus->rcu, __free_bus) starts SRCU grace period GP1. 2. GP1 ends: a delay timer is armed and sdp->work is queued, but sdp->work has not run yet. 3. Another call_srcu(&kvm->srcu, &bus->rcu, __free_bus) call invokes srcu_segcblist_advance(), which moves the GP1 callback to RCU_DONE_TAIL, and starts SRCU grace period GP2. 4. srcu_barrier() is called. It queues its barrier callback after the GP2 callback and waits for srcu_invoke_callbacks() to invoke it. 5. GP2 ends: another delay timer is armed, and the sdp->work queued in step 2 begins to run. Its srcu_invoke_callbacks() call invokes srcu_segcblist_advance() again, moving the GP2 and barrier callbacks to RCU_DONE_TAIL as well, and then invokes all of them. However, rcu_segcblist_add_len(), which updates srcu_cblist's ->len, has not run yet at this point. 6. srcu_barrier() returns once its callback has been invoked, and cleanup_srcu_struct() starts running. It finds the delay timer armed in step 5 still pending and srcu_cblist's ->len still non-zero (because step 5 has not reached rcu_segcblist_add_len() yet), and WARN_ON() fires even though every callback has actually been invoked. Use rcu_segcblist_empty(), which checks the actual head of the cblist, instead of rcu_segcblist_n_cbs(), which checks the racy ->len counter. Callbacks that have genuinely not been invoked yet still leave the list non-empty, so the WARN_ON() still catches callers that skip srcu_barrier() or queue callbacks after it. Link: https://lore.kernel.org/rcu/e6350377085ddd85d6ef00d8e9a67bd50c762d3c@linux.dev/T/#t Reported-by: syzbot+d4faf7db59e11f6fd1ab@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d4faf7db59e11f6fd1ab Fixes: 78a38cbf6f20 ("srcu: Queue sdp->work when the delay timer is successfully deleted") Suggested-by: Zqiang Signed-off-by: Sunho Park Signed-off-by: Paul E. McKenney Tested-by: Sean Christopherson --- kernel/rcu/srcutree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index ed204b3f4b84..ad27880dd690 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -704,7 +704,7 @@ void cleanup_srcu_struct(struct srcu_struct *ssp) // Call srcu_barrier() before this cleanup_srcu_struct() // to avoid triggering this WARN_ON(). if (WARN_ON(timer_delete_sync(&sdp->delay_work) && - rcu_segcblist_n_cbs(&sdp->srcu_cblist)) && + !rcu_segcblist_empty(&sdp->srcu_cblist)) && rcu_cpu_beenfullyonline(sdp->cpu)) queue_work_on(sdp->cpu, rcu_gp_wq, &sdp->work); flush_work(&sdp->work); From c9728fcf2e7fb4a7a8d08ebd9524c3b02ae1a021 Mon Sep 17 00:00:00 2001 From: Jamal Hadi Salim Date: Wed, 30 Sep 2026 03:31:02 -0400 Subject: [PATCH 442/590] net/sched: em_text: reject unterminated algo before autoload This is a follow-up to commit 9c572a83037a ("net/sched: fix potential stack infoleak in em_text_dump()"), which zero-initialised the dump-side struct. Sashiko pointed at a pre-existing bug that exposed the change-side input-validation gap open. em_text_change() casts the raw netlink attribute payload to struct tcf_em_text and passes conf->algo, a 16-byte array with no enforced NUL terminator, to textsearch_prepare(). On the TS_AUTOLOAD retry textsearch_prepare() calls request_module("ts_%s", algo); vsnprintf() walks %s until it finds a NUL, so an algo[] with no NUL reads past the array into the adjacent struct fields and payload and feeds those bytes to the usermode helper command line. This is an out-of-bounds read and a kernel memory disclosure. Reject the attribute when no NUL exists within TC_EM_TEXT_ALGOSIZ bytes, matching the check xt_string has had since 3ab720881b6e. Conditions to recreate the bug: - CAP_NET_ADMIN on the target netns (namespace-local via unshare -Urn is enough) - install clsact on an interface, then add a basic filter with one text ematch whose algo[] is 16 bytes with no NUL (e.g. "A"*16) and pattern_len at least 1 - the add fails with -ENOENT and the kernel invokes modprobe with a module name made of "ts_" plus the 16 bytes and the adjacent payload Fixes: d675c989ed2d4 ("[PKT_SCHED]: Packet classification based on textsearch (ematch)") Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260918133953.12494-1-bernard.ladenthin%40gmail.com Signed-off-by: Jamal Hadi Salim Link: https://patch.msgid.link/QDISC-2DIU.v1.20260929183844@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/em_text.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 4132f8c3c5fc..bc45edb8c03b 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -58,6 +58,9 @@ static int em_text_change(struct net *net, void *data, int len, if (len < sizeof(*conf) || len < (sizeof(*conf) + conf->pattern_len)) return -EINVAL; + if (!memchr(conf->algo, '\0', sizeof(conf->algo))) + return -EINVAL; + if (conf->from_layer > conf->to_layer) return -EINVAL; From afae89de73dd693cfa329580ba0fba63bd3f0a11 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:11 +0300 Subject: [PATCH 443/590] amt: send the relay's General Query directly from the receive path An skb queued in a qdisc can outlive the tunnel it references through a raw pointer in skb->cb. For example, with igmp_qrv set to 1 on the relay a tunnel lives for 135s, so a netem delay of 180s on the amt device outlives it; when the tunnel expires and is freed, the subsequent dequeue triggers a use-after-free in amt_dev_xmit(). BUG: KASAN: slab-use-after-free in amt_dev_xmit+0x2763/0x2e20 Call Trace: amt_dev_xmit+0x2763/0x2e20 [drivers/net/amt.c:1262] dev_hard_start_xmit+0x22f/0x620 sch_direct_xmit+0x12e/0xac0 netem_dequeue+0x333/0xc50 net_tx_action+0x35c/0xa60 amt_send_igmp_gq() and amt_send_mld_gq() are only called from amt_request_handler(), inside the rcu_read_lock_bh() section of amt_rcv(). amt_request_handler() already has the tunnel the query is for: it found or created it inside that section. Queuing the query with dev_queue_xmit() only leads back into amt_dev_xmit(), which strips the Ethernet header and calls amt_send_membership_query() for that tunnel. Make that call directly from the two senders instead, the same way amt_send_advertisement() transmits from the receive path. The query never waits in a qdisc, the tunnel is only dereferenced inside the RCU section that found or created it, and nothing is stored in skb->cb, so no lookup or refcount is needed. Remove the query branch of amt_dev_xmit(), amt_skb_cb() and struct amt_skb_cb, which have no users left. Behaviour changes: - The relay's own General Queries no longer pass through the amt device's egress path: its qdisc, tc egress (clsact/tcx), the netfilter egress hook and packet taps. They are still visible as UDP on the underlay. - A query that is sent successfully is no longer counted as tx_dropped. The old query branch left through the unlock label, which counted every sent query as dropped. - A query that reaches amt_dev_xmit() on a relay from elsewhere, such as a userspace querier, is now dropped at the IGMP/MLD type switch. Before, it trusted whatever skb->cb held, and a NULL tunnel hit the WARN_ON(1). Fixes: cbc21dc1cfe9 ("amt: add data plane of amt interface") Reported-by: AutonomousCodeSecurity@microsoft.com Reported-by: Xiang Mei (Microsoft) Reported-by: Cen Zhang (Microsoft Security FORGE Labs) Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-2-omar@blockcast.net Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 48 +++++++++++++++-------------------------------- include/net/amt.h | 4 ---- 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/drivers/net/amt.c b/drivers/net/amt.c index bddc24e1856d..b53f8ec55661 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -80,15 +80,6 @@ static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT; static struct mld2_grec mldv2_zero_grec; #endif -static struct amt_skb_cb *amt_skb_cb(struct sk_buff *skb) -{ - BUILD_BUG_ON(sizeof(struct amt_skb_cb) + sizeof(struct tc_skb_cb) > - sizeof_field(struct sk_buff, cb)); - - return (struct amt_skb_cb *)((void *)skb->cb + - sizeof(struct tc_skb_cb)); -} - static void __amt_source_gc_work(void) { struct amt_source_node *snode; @@ -791,6 +782,11 @@ static void amt_send_request(struct amt_dev *amt, bool v6) rcu_read_unlock(); } +static bool amt_send_membership_query(struct amt_dev *amt, + struct sk_buff *skb, + struct amt_tunnel_list *tunnel, + bool v6); + static void amt_send_igmp_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) { @@ -800,8 +796,11 @@ static void amt_send_igmp_gq(struct amt_dev *amt, if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, false)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #if IS_ENABLED(CONFIG_IPV6) @@ -885,8 +884,11 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) if (!skb) return; - amt_skb_cb(skb)->tunnel = tunnel; - dev_queue_xmit(skb); + skb_pull(skb, sizeof(struct ethhdr)); + if (amt_send_membership_query(amt, skb, tunnel, true)) { + amt->dev->stats.tx_dropped++; + kfree_skb(skb); + } } #else static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel) @@ -1186,7 +1188,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) #endif bool report = false; struct igmphdr *ih; - bool query = false; struct iphdr *iph; bool data = false; bool v6 = false; @@ -1204,9 +1205,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case IGMP_HOST_MEMBERSHIP_REPORT: report = true; break; - case IGMP_HOST_MEMBERSHIP_QUERY: - query = true; - break; default: goto free; } @@ -1228,9 +1226,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) case ICMPV6_MLD2_REPORT: report = true; break; - case ICMPV6_MGM_QUERY: - query = true; - break; default: goto free; } @@ -1261,19 +1256,6 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev) goto free; goto unlock; } else if (amt->mode == AMT_MODE_RELAY) { - if (query) { - tunnel = amt_skb_cb(skb)->tunnel; - if (!tunnel) { - WARN_ON(1); - goto free; - } - - /* Do not forward unexpected query */ - if (amt_send_membership_query(amt, skb, tunnel, v6)) - goto free; - goto unlock; - } - if (!data) goto free; list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) { diff --git a/include/net/amt.h b/include/net/amt.h index a0255491f5b0..2846dde0cadc 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -231,10 +231,6 @@ struct amt_relay_headers { }; } __packed; -struct amt_skb_cb { - struct amt_tunnel_list *tunnel; -}; - struct amt_tunnel_list { struct list_head list; /* Protect All resources under an amt_tunne_list */ From c41817fcaf397f6ff7c530b682581f5eb5465844 Mon Sep 17 00:00:00 2001 From: Omar Ramadan Date: Mon, 28 Sep 2026 23:23:12 +0300 Subject: [PATCH 444/590] selftests: net: amt: check that the relay's queries bypass the amt device The relay used to hand its General Queries to dev_queue_xmit() on the amt device, where a query could wait in a qdisc and outlive the tunnel it pointed to. The previous patch sends them directly from the receive path instead. Count the IGMP and MLD queries that leave the relay through amtr with tc flower filters on its egress, installed before the gateway comes up, and check that there are none. The forwarding tests before it already show that the gateway received its queries, since it cannot join without one. Without the previous patch the new test fails (one run counted 7 IGMP and 6 MLD queries); with it, all of amt.sh passes. Signed-off-by: Omar Ramadan Link: https://patch.msgid.link/20260928202312.74574-3-omar@blockcast.net Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/amt.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/testing/selftests/net/amt.sh b/tools/testing/selftests/net/amt.sh index 663744305e52..d13b20cccba9 100755 --- a/tools/testing/selftests/net/amt.sh +++ b/tools/testing/selftests/net/amt.sh @@ -150,6 +150,13 @@ setup_interface() ip netns exec "${RELAY}" ip a a 10.0.0.2/24 dev relay_gw ip netns exec "${RELAY}" ip link add amtr type amt mode relay \ local 10.0.0.2 dev relay_gw relay_port 2268 max_tunnels 4 + # Count the IGMP and MLD queries that leave the relay through its own + # amt device; test_query_egress expects none. + ip netns exec "${RELAY}" tc qdisc add dev amtr clsact + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 1 \ + protocol ip flower ip_proto 0x2 action pass + ip netns exec "${RELAY}" tc filter add dev amtr egress pref 2 \ + protocol ipv6 flower ip_proto icmpv6 type 130 action pass ip netns exec "${RELAY}" ip a a 172.17.0.1/24 dev relay_src ip netns exec "${RELAY}" ip a a 2001:db8:3::1/64 dev relay_src ip netns exec "${SOURCE}" ip a a 172.17.0.2/24 dev src_relay @@ -246,6 +253,27 @@ test_ipv6_forward() fi } +# The relay sends its General Queries straight from the receive path, in +# the same context that found the tunnel. A query queued on the amt device +# instead could outlive the tunnel it was built for. The forwarding tests +# above show that the gateway got its queries. +test_query_egress() +{ + local n4 n6 + + n4=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 1 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + n6=$(ip netns exec "${RELAY}" tc -s -j filter show dev amtr egress \ + pref 2 | jq '[.[].options.actions[0].stats.packets // empty] | add // 0') + if [ "$n4" -eq 0 ] && [ "$n6" -eq 0 ]; then + printf "TEST: %-60s [ OK ]\n" "amt relay queries bypass the amt device" + else + printf "TEST: %-60s [FAIL]\n" "amt relay queries bypass the amt device" + echo "IGMP queries on amtr egress: $n4, MLD queries: $n6" >&2 + ERR=1 + fi +} + send_mcast4() { sleep 5 @@ -287,6 +315,7 @@ wait $pid || err=$? if [ $err -eq 1 ]; then ERR=1 fi +test_query_egress printf "TEST: %-50s" "IPv4 amt traffic forwarding torture" send_mcast_torture4 printf " [ OK ]\n" From 28bc1ef699610ee09ce3d46a00552f5a0a0144bd Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Mon, 28 Sep 2026 19:31:08 +0800 Subject: [PATCH 445/590] net/packet: guard the ll header push in packet_rcv_spkt() packet_rcv_spkt() restores the link layer header with skb_push(skb, skb->data - skb_mac_header(skb)); That subtraction is only meaningful when the device actually has a link layer header. packet_rcv() and tpacket_rcv() both wrap it in dev_has_header(), which is also the predicate the block comment at the top of the file states the restore in terms of; packet_rcv_spkt() does not. commit d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") introduced the helper and changed the two call sites, and this one stayed behind. A device without a visible ll header can leave skb->mac_header at the 0xFFFF sentinel that __alloc_skb() initialises it to. A CAN skb does: init_can_skb() sets pkt_type and ip_summed but does not reset the headers, and commit 9f10374bb024 ("can: remove private CAN skb headroom infrastructure") dropped the skb_reset_*_header() calls that used to be there. skb_mac_header() is then 0xFFFF, the length becomes a large negative number and skb_push() reports it through skb_under_panic() -- from softirq context, so it is a full system panic even with panic_on_oops=0: skbuff: skb_under_panic: text:ffffffff8bd21bc1 len:-65455 put:-65471 head:... data:... tail:0x50 end:0x180 dev:can0 kernel BUG at net/core/skbuff.c:214! RIP: 0010:skb_panic+0x50/0x60 Call Trace: skb_push+0x38/0x40 packet_rcv_spkt+0xe1/0x170 __netif_receive_skb_core.constprop.0+0x7e8/0xd30 ... Kernel panic - not syncing: Fatal exception in interrupt The socket type is reachable: packet_create() accepts SOCK_PACKET alongside SOCK_RAW and SOCK_DGRAM behind the same CAP_NET_RAW check, and neither the socket length nor a capability check keeps it away from a CAN interface. The missing skb_reset_*_header() calls in init_can_skb() are a regression in their own right and are being fixed separately, but a packet socket should not turn a link layer that did not initialise its mac header into a kernel panic. Guard the push the way the other two receive paths do. Tested on v7.3-rc5 under QEMU with a slcan device on a pty, which is the driver RX path: vcan does not reproduce it, because can_send() resets the headers on the way out. One SOCK_PACKET socket bound to can0 and one frame written into the line discipline panics an unpatched kernel with the trace above; the same image with this patch prints no panic and powers off normally. Both kernels are this tree, defconfig plus CONFIG_CAN_SLCAN=y, differing only in this hunk. Fixes: d549699048b4 ("net/packet: fix packet receive on L3 devices without visible hard header") Cc: stable@vger.kernel.org Signed-off-by: Quchaosheng Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260928113108.2127215-1-quchaosheng000406@163.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 8865c1a42667..6ff16eef24f4 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1911,7 +1911,8 @@ static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev, spkt = &PACKET_SKB_CB(skb)->sa.pkt; - skb_push(skb, skb->data - skb_mac_header(skb)); + if (dev_has_header(dev)) + skb_push(skb, skb->data - skb_mac_header(skb)); /* * The SOCK_PACKET socket receives _all_ frames. From 7a39c732a22ec4a369af9e19fbe3d666ec83eec4 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Wed, 30 Sep 2026 13:36:18 +0800 Subject: [PATCH 446/590] 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 0843b23890642a02ff85c36406224e8874e30664 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:42 +0530 Subject: [PATCH 447/590] lib/dim: fix 32-bit overflow in dim_calc_stats() rates dim_calc_stats() computes the per-millisecond rates as DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us) where nbytes is a u32 and USEC_PER_MSEC is 1000L. On 64-bit the product is done in 64-bit long arithmetic, but on 32-bit architectures long is 32 bits wide and the product wraps as soon as a measurement window carries more than 4294967 bytes (about 4.3 MB). The same applies to the packet and completion counts, although those need more than 4.29 million packets or completions per window. A DIM window spans DIM_NEVENTS (64) events. Drivers count events per interrupt or per NAPI poll, so under sustained load a window can easily carry more than 4.3 MB: 64 full NAPI polls of 64 MTU-sized frames are already 6.2 MB, and drivers such as mtk_eth_soc count one event per interrupt while NAPI keeps polling with the interrupt masked. On 32-bit users of the library (for example mtk_eth_soc on MT7621, bcmgenet and bcmsysport on 32-bit ARM, or virtio_net in a 32-bit guest) bpms then becomes the product modulo 2^32 divided by the window length, and net_dim_stats_compare() makes its BETTER/WORSE decisions on a value that has little to do with the real throughput. For example, a 1 Gbit/s link at line rate that moves 5 MB in a 40 ms window gives bpms = 125000 on 64-bit but 17626 on 32-bit, and 5 million packets in 2 s gives ppms = 353 instead of 2500. Widen the products to 64 bits and divide with DIV_ROUND_UP_ULL(). The results are unchanged on 64-bit. Fixes: cb3c7fd4f839 ("net/mlx5e: Support adaptive RX coalescing") Fixes: 4c4dbb4a7363 ("net/mlx5e: Move dynamic interrupt coalescing code to include/linux") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/dim/dim.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/dim/dim.c b/lib/dim/dim.c index 97c3d084ebf0..7f3eebae73cb 100644 --- a/lib/dim/dim.c +++ b/lib/dim/dim.c @@ -69,11 +69,15 @@ bool dim_calc_stats(const struct dim_sample *start, if (!delta_us) return false; - curr_stats->ppms = DIV_ROUND_UP(npkts * USEC_PER_MSEC, delta_us); - curr_stats->bpms = DIV_ROUND_UP(nbytes * USEC_PER_MSEC, delta_us); + /* u32 * USEC_PER_MSEC overflows a 32-bit long */ + curr_stats->ppms = DIV_ROUND_UP_ULL((u64)npkts * USEC_PER_MSEC, + delta_us); + curr_stats->bpms = DIV_ROUND_UP_ULL((u64)nbytes * USEC_PER_MSEC, + delta_us); curr_stats->epms = DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, delta_us); - curr_stats->cpms = DIV_ROUND_UP(ncomps * USEC_PER_MSEC, delta_us); + curr_stats->cpms = DIV_ROUND_UP_ULL((u64)ncomps * USEC_PER_MSEC, + delta_us); if (curr_stats->epms != 0) curr_stats->cpe_ratio = DIV_ROUND_DOWN_ULL( curr_stats->cpms * 100, curr_stats->epms); From 636035157a814f289f293fd0edea1a367a637797 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sun, 27 Sep 2026 10:47:43 +0530 Subject: [PATCH 448/590] lib/dim: add KUnit test for dim_calc_stats() Add a KUnit suite for the DIM library that checks the packet, byte, event and completion rates computed by dim_calc_stats(), including counter wraparound and windows whose byte or packet count times USEC_PER_MSEC does not fit in 32 bits. The latter cases fail on 32-bit architectures without the previous commit. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: David S. Miller --- lib/Kconfig.debug | 15 ++++++ lib/dim/Makefile | 2 + lib/dim/dim_kunit.c | 126 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 lib/dim/dim_kunit.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625..6b24a2c89b84 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2906,6 +2906,21 @@ config LIVEUPDATE_TEST If unsure, say N +config DIMLIB_KUNIT_TEST + tristate "KUnit test for the DIM library" if !KUNIT_ALL_TESTS + depends on KUNIT && NET + select DIMLIB + default KUNIT_ALL_TESTS + help + This builds the KUnit test for the Dynamic Interrupt Moderation + library (lib/dim/), which checks the rate statistics computed by + dim_calc_stats(). + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + config CMDLINE_KUNIT_TEST tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/lib/dim/Makefile b/lib/dim/Makefile index 5b9bfaac7ac1..47e1c36230e4 100644 --- a/lib/dim/Makefile +++ b/lib/dim/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_DIMLIB) += dimlib.o dimlib-y := dim.o net_dim.o rdma_dim.o + +obj-$(CONFIG_DIMLIB_KUNIT_TEST) += dim_kunit.o diff --git a/lib/dim/dim_kunit.c b/lib/dim/dim_kunit.c new file mode 100644 index 000000000000..2e984f6b9fe4 --- /dev/null +++ b/lib/dim/dim_kunit.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Dynamic Interrupt Moderation library + */ + +#include +#include +#include +#include + +struct dim_calc_stats_case { + const char *name; + u32 delta_us; + u32 start_pkts, end_pkts; + u32 start_bytes, end_bytes; + u32 start_comps, end_comps; + int ppms, bpms, cpms; +}; + +static const struct dim_calc_stats_case dim_calc_stats_cases[] = { + { + .name = "small", + .delta_us = 1000, + .end_pkts = 640, .end_bytes = 640 * 1500, .end_comps = 64, + .ppms = 640, .bpms = 960000, .cpms = 64, + }, + { + .name = "round_up", + .delta_us = 3000, + .end_pkts = 10, .end_bytes = 10, .end_comps = 1, + .ppms = 4, .bpms = 4, .cpms = 1, + }, + { + .name = "counter_wrap", + .delta_us = 1000, + .start_pkts = 0xffffff00, .end_pkts = 0x100, + .start_bytes = 0xfffff000, .end_bytes = 0x1000, + .start_comps = 0xfffffff0, .end_comps = 0x10, + .ppms = 0x200, .bpms = 0x2000, .cpms = 0x20, + }, + { + /* 30 MB in 10 ms (24 Gbit/s): nbytes * 1000 exceeds 32 bits */ + .name = "many_bytes", + .delta_us = 10000, + .end_pkts = 20000, .end_bytes = 30000000, .end_comps = 64, + .ppms = 2000, .bpms = 3000000, .cpms = 7, + }, + { + /* 4.3 MB in 16 ms (2.15 Gbit/s), just above the 32-bit limit */ + .name = "bytes_32bit_limit", + .delta_us = 16000, + .end_pkts = 2900, .end_bytes = 4300000, .end_comps = 64, + .ppms = 182, .bpms = 268750, .cpms = 4, + }, + { + /* 5 MB in 40 ms: a 1 Gbit/s link at line rate */ + .name = "gigabit", + .delta_us = 40000, + .end_pkts = 3300, .end_bytes = 5000000, .end_comps = 64, + .ppms = 83, .bpms = 125000, .cpms = 2, + }, + { + /* 5 million packets and completions in 2 s */ + .name = "many_packets", + .delta_us = 2000000, + .end_pkts = 5000000, .end_bytes = 5000000, .end_comps = 5000000, + .ppms = 2500, .bpms = 2500, .cpms = 2500, + }, +}; + +static void dim_calc_stats_case_desc(const struct dim_calc_stats_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(dim_calc_stats, dim_calc_stats_cases, + dim_calc_stats_case_desc); + +static void dim_calc_stats_test(struct kunit *test) +{ + const struct dim_calc_stats_case *t = test->param_value; + struct dim_sample start = {}, end = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, t->start_pkts, t->start_bytes, + t->start_comps, &start); + dim_update_sample_with_comps(DIM_NEVENTS, t->end_pkts, t->end_bytes, + t->end_comps, &end); + /* dim_update_sample() stamps ktime_get(); use fixed times instead */ + start.time = ktime_set(1000, 0); + end.time = ktime_add_us(start.time, t->delta_us); + + KUNIT_ASSERT_TRUE(test, dim_calc_stats(&start, &end, &stats)); + KUNIT_EXPECT_EQ(test, stats.ppms, t->ppms); + KUNIT_EXPECT_EQ(test, stats.bpms, t->bpms); + KUNIT_EXPECT_EQ(test, stats.cpms, t->cpms); + KUNIT_EXPECT_EQ(test, stats.epms, + (int)DIV_ROUND_UP(DIM_NEVENTS * USEC_PER_MSEC, + t->delta_us)); +} + +static void dim_calc_stats_no_time_test(struct kunit *test) +{ + struct dim_sample sample = {}; + struct dim_stats stats = {}; + + dim_update_sample_with_comps(0, 100, 1000, 10, &sample); + KUNIT_EXPECT_FALSE(test, dim_calc_stats(&sample, &sample, &stats)); +} + +static struct kunit_case dim_test_cases[] = { + KUNIT_CASE_PARAM(dim_calc_stats_test, dim_calc_stats_gen_params), + KUNIT_CASE(dim_calc_stats_no_time_test), + {} +}; + +static struct kunit_suite dim_test_suite = { + .name = "dim", + .test_cases = dim_test_cases, +}; + +kunit_test_suite(dim_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the DIM library"); +MODULE_LICENSE("GPL"); From 3acdd44385bc7e2e57605e073691ce69cc5a960d Mon Sep 17 00:00:00 2001 From: Yuqi Xu Date: Mon, 28 Sep 2026 16:24:38 +0800 Subject: [PATCH 449/590] tipc: destroy topsrv workqueues before closing connections tipc_topsrv_stop() closed subscriber connections while the topology server's send and receive workqueues were still running. Socket callbacks and subscription events could then queue more work, and in-flight send/recv work could drop the last connection reference during the conn_idr walk. That race produced several teardown failures: refcount_t addition on 0 from conn_get() on a connection whose release was blocked on idr_lock, a subsequent use-after-free in tipc_conn_close(), queue_work() on an already destroyed workqueue from the listener data-ready callback, and an RCU stall in tipc_topsrv_exit_net() while the walk spun under idr_lock. Clear srv->listener under idr_lock so it acts as a shutdown flag, skip queue_work() once it is NULL, destroy the workqueues to flush in-flight work, and only then close the remaining connections. Refuse tipc_conn_lookup() after that flag is cleared, and drop idr_lock when the teardown walk finds no connection, so an in-flight subscription event cannot pin idr_in_use while the walk holds the lock. v3 supersedes the narrower in-thread diff Tung Quang Nguyen posted on 2026-09-23. It keeps his teardown order and adds the lookup refusal and the empty-idr unlock. Fixes: c5fa7b3cf3cb ("tipc: introduce new TIPC server infrastructure") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Yuqi Xu Reviewed-by: Ren Wei Signed-off-by: David S. Miller --- net/tipc/topsrv.c | 80 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/net/tipc/topsrv.c b/net/tipc/topsrv.c index af530c9ed840..82e9f44e2fe4 100644 --- a/net/tipc/topsrv.c +++ b/net/tipc/topsrv.c @@ -55,7 +55,7 @@ /** * struct tipc_topsrv - TIPC server structure * @conn_idr: identifier set of connection - * @idr_lock: protect the connection identifier set + * @idr_lock: protect the connection identifier set and listener * @idr_in_use: amount of allocated identifier entry * @net: network namespace instance * @awork: accept work item @@ -218,6 +218,10 @@ static struct tipc_conn *tipc_conn_lookup(struct tipc_topsrv *s, int conid) struct tipc_conn *con; spin_lock_bh(&s->idr_lock); + if (!s->listener) { + spin_unlock_bh(&s->idr_lock); + return NULL; + } con = idr_find(&s->conn_idr, conid); if (!connected(con) || !kref_get_unless_zero(&con->kref)) con = NULL; @@ -301,10 +305,20 @@ static void tipc_conn_send_to_sock(struct tipc_conn *con) static void tipc_conn_send_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, swork); + struct tipc_topsrv *srv; + + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); if (connected(con)) tipc_conn_send_to_sock(con); +out: conn_put(con); } @@ -334,8 +348,14 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, list_add_tail(&e->list, &con->outqueue); spin_unlock_bh(&con->outqueue_lock); - if (queue_work(srv->send_wq, &con->swork)) - return; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + if (queue_work(srv->send_wq, &con->swork)) { + spin_unlock_bh(&srv->idr_lock); + return; + } + } + spin_unlock_bh(&srv->idr_lock); err: conn_put(con); } @@ -346,14 +366,20 @@ void tipc_topsrv_queue_evt(struct net *net, int conid, */ static void tipc_conn_write_space(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->send_wq, &con->swork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->send_wq, &con->swork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -418,8 +444,17 @@ static int tipc_conn_rcv_from_sock(struct tipc_conn *con) static void tipc_conn_recv_work(struct work_struct *work) { struct tipc_conn *con = container_of(work, struct tipc_conn, rwork); + struct tipc_topsrv *srv; int count = 0; + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (!srv->listener) { + spin_unlock_bh(&srv->idr_lock); + goto out; + } + spin_unlock_bh(&srv->idr_lock); + while (connected(con)) { if (tipc_conn_rcv_from_sock(con)) break; @@ -430,6 +465,7 @@ static void tipc_conn_recv_work(struct work_struct *work) count = 0; } } +out: conn_put(con); } @@ -438,6 +474,7 @@ static void tipc_conn_recv_work(struct work_struct *work) */ static void tipc_conn_data_ready(struct sock *sk) { + struct tipc_topsrv *srv; struct tipc_conn *con; trace_sk_data_ready(sk); @@ -445,9 +482,14 @@ static void tipc_conn_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); con = sk->sk_user_data; if (connected(con)) { - conn_get(con); - if (!queue_work(con->server->rcv_wq, &con->rwork)) - conn_put(con); + srv = con->server; + spin_lock_bh(&srv->idr_lock); + if (srv->listener) { + conn_get(con); + if (!queue_work(srv->rcv_wq, &con->rwork)) + conn_put(con); + } + spin_unlock_bh(&srv->idr_lock); } read_unlock_bh(&sk->sk_callback_lock); } @@ -503,8 +545,12 @@ static void tipc_topsrv_listener_data_ready(struct sock *sk) read_lock_bh(&sk->sk_callback_lock); srv = sk->sk_user_data; - if (srv) - queue_work(srv->rcv_wq, &srv->awork); + if (srv) { + spin_lock_bh(&srv->idr_lock); + if (srv->listener) + queue_work(srv->rcv_wq, &srv->awork); + spin_unlock_bh(&srv->idr_lock); + } read_unlock_bh(&sk->sk_callback_lock); } @@ -700,23 +746,27 @@ static void tipc_topsrv_stop(struct net *net) struct tipc_conn *con; int id; + spin_lock_bh(&srv->idr_lock); + srv->listener = NULL; + spin_unlock_bh(&srv->idr_lock); + tipc_topsrv_work_stop(srv); + spin_lock_bh(&srv->idr_lock); for (id = 0; srv->idr_in_use; id++) { con = idr_find(&srv->conn_idr, id); if (con) { - conn_get(con); spin_unlock_bh(&srv->idr_lock); tipc_conn_close(con); - conn_put(con); spin_lock_bh(&srv->idr_lock); + continue; } + spin_unlock_bh(&srv->idr_lock); + spin_lock_bh(&srv->idr_lock); } __module_get(lsock->ops->owner); __module_get(lsock->sk->sk_prot_creator->owner); - srv->listener = NULL; spin_unlock_bh(&srv->idr_lock); - tipc_topsrv_work_stop(srv); sock_release(lsock); idr_destroy(&srv->conn_idr); kfree(srv); From c83e3d2912432107148cc5e288ddd153a84c0f43 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Thu, 1 Oct 2026 16:07:27 +0200 Subject: [PATCH 450/590] braille: nbcon: Allow to use a serial console with NBCON API as Braille console The Braille console is not registered in console_list. Instead, it is integrated with the virtual terminal (VT) and shows what is displayed on the terminal. It writes the data using con->write*() callback of the associated serial console driver. braille_write() is called from the VT code under console_lock(). The associated serial console driver can be converted to the NBCON API though. The situation is similar to flushing nbcon consoles in the legacy loop when some boot consoles are still registered, see nbcon_legacy_emit_next_record(). But there is a big difference though. braille_write() is not directly called from the code paths flushing registered consoles. The VT code expects that braille_write() succeeds. It does not replay the message when it can't acquire the ownership. As a result, braille_write(): + must try harder to get the ownership. + has to be synchronized only against non-printk serial console which depend nbcon_device_try_acquire() using NBCON_PRIO_NORMAL. Let's look at it from another side and try to simulate the original locking using the NBCON API: 1. Take con->device_lock(), aka the port->lock in the legacy serial console driver. 2. Acquire nbcon context to provide some synchronization for a panic() context. Use NBCON_PRIO_NORMAL because it contends only with nbcon_device_try_acquire() users. Do it in a busy loop. It should always succeed when con->device_lock() succeeded because all other users do the same. The only exception is when the context get acquired by a CPU handling panic. 3. In panic, disable interrupts and try to acquire the nbcon context. Use NBCON_PRIO_PANIC. And try even an unsafe takeover because otherwise the Braille console won't see the text shown during panic(). It is similar to the oops_in_progress/trylock handling in the legacy serial console driver. Finally, avoid the newline prepending logic in the existing serial console drivers when they are used as a Braille console. As explained above, the Braille console shows the last modified line on the terminal (VT). braille_write() is called when single characters are added. Most messages are not ended by newline. Anyway, the VT code does not have logic to reply partially printed messages. Fixes: 13189fa73afa ("printk: nbcon: Rely on kthreads for normal operation") Reviewed-by: John Ogness [pmladek@suse.com: Remove white space changes in braille_register_console(). Typo fix.] Link: https://patch.msgid.link/20261001140727.124398-2-pmladek@suse.com Signed-off-by: Petr Mladek --- .../accessibility/braille/braille_console.c | 57 ++++++++++++- drivers/tty/serial/8250/8250_port.c | 5 +- drivers/tty/serial/amba-pl011.c | 2 +- drivers/tty/serial/imx.c | 2 +- drivers/tty/serial/sifive.c | 2 +- include/linux/console.h | 15 ++++ kernel/printk/nbcon.c | 79 +++++++++++++++++++ 7 files changed, 156 insertions(+), 6 deletions(-) diff --git a/drivers/accessibility/braille/braille_console.c b/drivers/accessibility/braille/braille_console.c index 06b43b678d6e..5a3e375f4800 100644 --- a/drivers/accessibility/braille/braille_console.c +++ b/drivers/accessibility/braille/braille_console.c @@ -62,14 +62,50 @@ static void braille_write(u16 *buf) { static u16 lastwrite[WIDTH]; unsigned char data[1 + 1 + 2*WIDTH + 2 + 1], csum = 0, *c; + struct nbcon_write_context wctxt = { }; + unsigned long flags; u16 out; int i; if (!braille_co) return; + /* + * Braille console is not registered in console_list. Instead, it + * is integrated with VT and shows what appears on the graphical + * console under console_lock(). From this POV it is a legacy + * console. But it calls serial console driver which might be + * converted to the NBCON API. It is similar to + * nbcon_legacy_emit_next_record() except that we should try + * harder to get the lock. Otherwise, the Braille device won't show + * everything what is displayed on the terminal. + * + * In short, simulate the original locking using NBCON API. + */ + if (braille_co->flags & CON_NBCON) { + if (panic_on_this_cpu()) { + /* + * This should be good enough in practice. Most/all + * serial console drivers have the atomic callback. + */ + if (!braille_co->write_atomic) + return; + + local_irq_save(flags); + /* NBCON API strictly requires the ownership. */ + if (!nbcon_braille_try_acquire(braille_co, &wctxt)) { + local_irq_restore(flags); + return; + } + } else { + braille_co->device_lock(braille_co, &flags); + while (!nbcon_braille_try_acquire(braille_co, &wctxt)) + cpu_relax(); + } + } + if (!memcmp(lastwrite, buf, WIDTH * sizeof(*buf))) - return; + goto unlock_nbcon; memcpy(lastwrite, buf, WIDTH * sizeof(*buf)); #define SOH 1 @@ -102,7 +138,24 @@ static void braille_write(u16 *buf) *c++ = csum; *c++ = ETX; - braille_co->write(braille_co, data, c - data); + if (braille_co->flags & CON_NBCON) { + nbcon_write_context_set_buf(&wctxt, (char *)data, c - data); + if (panic_on_this_cpu()) + braille_co->write_atomic(braille_co, &wctxt); + else + braille_co->write_thread(braille_co, &wctxt); + } else { + braille_co->write(braille_co, data, c - data); + } + +unlock_nbcon: + if (braille_co->flags & CON_NBCON) { + nbcon_braille_release(&wctxt); + if (panic_on_this_cpu()) + local_irq_restore(flags); + else + braille_co->device_unlock(braille_co, flags); + } } /* Follow the VC cursor*/ diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 38fa45e74a37..548fd68ad1fa 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -3417,8 +3417,11 @@ static void __serial8250_console_write(struct uart_8250_port *up, * If the console printer did not fully output the previous line, it * must have been handed or taken over. Insert a newline in order to * maintain clean output. + * + * Braille consoles are an exception. The serial port is not used + * for printk(). The driver is supposed to write exactly what it gets. */ - if (!up->console_line_ended) { + if (unlikely(!up->console_line_ended && !nbcon_write_context_is_braille(wctxt))) { if (use_fifo) __serial8250_console_fifo_write(up, wctxt, "\n", 1); else diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index c4824c201e1c..e67f2ac454b6 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2656,7 +2656,7 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt uap, REG_CR); } - if (!uap->console_line_ended) + if (unlikely(!uap->console_line_ended && !nbcon_is_braille(co))) uart_console_write(&uap->port, "\n", 1, pl011_console_putchar); uart_console_write(&uap->port, wctxt->outbuf, wctxt->len, pl011_console_putchar); diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 513dbe95f7e9..986fefb45c63 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2136,7 +2136,7 @@ static void imx_uart_console_write_atomic(struct console *co, imx_uart_writel(sport, ucr1, UCR1); imx_uart_writel(sport, old_ucr.ucr2 | UCR2_TXEN, UCR2); - if (!sport->last_putchar_was_newline) + if (unlikely(!sport->last_putchar_was_newline && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, imx_uart_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, imx_uart_console_putchar); diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index 110d67613192..f615701fceb7 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -821,7 +821,7 @@ static void sifive_serial_console_write_atomic(struct console *co, ier = __ssp_readl(ssp, SIFIVE_SERIAL_IE_OFFS); __ssp_writel(0, SIFIVE_SERIAL_IE_OFFS, ssp); - if (!ssp->console_line_ended) + if (unlikely(!ssp->console_line_ended && !nbcon_is_braille(co))) uart_console_write(port, "\n", 1, sifive_serial_console_putchar); uart_console_write(port, wctxt->outbuf, wctxt->len, sifive_serial_console_putchar); diff --git a/include/linux/console.h b/include/linux/console.h index 502d1abe3f50..df23148987ee 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -615,6 +615,15 @@ extern bool nbcon_allow_unsafe_takeover(void); extern bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt); extern void nbcon_kdb_release(struct nbcon_write_context *wctxt); +extern bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt); +extern bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt); +extern void nbcon_braille_release(struct nbcon_write_context *wctxt); + +static inline bool nbcon_is_braille(struct console *con) +{ + return con && (con->flags & CON_BRL); +} /* * Check if the given console is currently capable and allowed to print @@ -678,8 +687,14 @@ static inline void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt) { } static inline bool nbcon_kdb_try_acquire(struct console *con, struct nbcon_write_context *wctxt) { return false; } static inline void nbcon_kdb_release(struct nbcon_write_context *wctxt) { } +static inline +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) { return false; } static inline bool console_is_usable(struct console *con, short flags, bool use_atomic) { return false; } +static inline bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) { return false; } +static inline void nbcon_braille_release(struct nbcon_write_context *wctxt) { } +static inline bool nbcon_is_braille(struct console *con) { return false; } #endif extern int console_set_on_cmdline; diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index d17704fe93ae..fb1b37741952 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -1887,6 +1887,7 @@ bool nbcon_device_try_acquire(struct console *con) memset(ctxt, 0, sizeof(*ctxt)); ctxt->console = con; + /* Keep in sync with nbcon_braille_try_acquire(). */ ctxt->prio = NBCON_PRIO_NORMAL; if (!nbcon_context_try_acquire(ctxt, false)) @@ -2002,3 +2003,81 @@ void nbcon_kdb_release(struct nbcon_write_context *wctxt) */ __nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb)); } + +/** + * nbcon_is_braille - Checks whether the nbcon write context is using Braille console + * + * @wctxt: checked nbcon write context + * + * Return: True when the write context is associated with a Braille console. + * Otherwise, return false. + * + * Context: Can be called in any context but only when Braille console is + * registered and the struct console could not disappear. + */ +bool nbcon_write_context_is_braille(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + struct console *con = ctxt->console; + + return con && con->flags & CON_BRL; +} +EXPORT_SYMBOL_GPL(nbcon_write_context_is_braille); + +/** + * nbcon_braille_try_acquire - Try to acquire nbcon console for braille_write() + * + * @con: The nbcon console to acquire + * @wctxt: The nbcon write context to be used on success + * + * Context: braille_write() for emitting a single buffer on Braille console. + * + * Return: True if the console was acquired. False otherwise. + * + * Braille console is not registered as a proper printk consoles. Instead, + * it is integrated with the graphical virtual terminal. + * + * This function is going to synchronize the Braille write against other + * operations on the used serial port. The port can be used also for a user + * input but printk() won't emit the messages there directly. It means + * the other operations will get synchronized using nbcon_device_try_acquire(). + */ +bool nbcon_braille_try_acquire(struct console *con, + struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + bool success; + + memset(ctxt, 0, sizeof(*ctxt)); + ctxt->console = con; + /* Keep in sync with nbcon_device_try_acquire(). */ + if (panic_on_this_cpu()) + ctxt->prio = NBCON_PRIO_PANIC; + else + ctxt->prio = NBCON_PRIO_NORMAL; + + success = nbcon_context_try_acquire(ctxt, false); + /* + * Try hard in panic, otherwise the Braille console would + * miss a text on the related VT. + */ + if (panic_on_this_cpu() && !success) { + ctxt->allow_unsafe_takeover = true; + success = nbcon_context_try_acquire(ctxt, false); + } + + return success; +} + +/** + * nbcon_braille_release - Release the nbcon console + * + * @wctxt: The nbcon write context initialized by a successful + * nbcon_braille_try_acquire() + */ +void nbcon_braille_release(struct nbcon_write_context *wctxt) +{ + struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); + + nbcon_context_release(ctxt); +} From 4c9dc0faca98084922caeb689579d338cf3e1c29 Mon Sep 17 00:00:00 2001 From: Sang-Hoon Choi Date: Wed, 30 Sep 2026 02:37:34 +0900 Subject: [PATCH 451/590] cxgb4/ch_ktls: disable softirqs around tid_list erases chcr_ktls_cpl_act_open_rpl() inserts into tid_list from the receive path using xa_insert_bh(). The array is shared by the adapter's connections and initialized with XA_FLAGS_LOCK_BH. However, chcr_ktls_dev_del() and the chcr_ktls_dev_add() error path use xa_erase(), which takes the XArray lock without disabling softirqs. When cleanup runs with softirqs enabled, a receive softirq for another connection on the same CPU can interrupt the erase and spin on the lock held by the interrupted task. Use xa_erase_bh() at both sites to match the insertion path. Fixes: 65e302a9bd57 ("cxgb4/ch_ktls: Clear resources when pf4 device is removed") Reported-by: Changyul Lee Assisted-by: LLM Signed-off-by: Sang-Hoon Choi Reviewed-by: Ayush Sawal Signed-off-by: David S. Miller --- .../net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c index f5acd4be1e69..dbec95b49777 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c +++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c @@ -389,7 +389,7 @@ static void chcr_ktls_dev_del(struct net_device *netdev, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); } port_stats = &tx_info->adap->ch_ktls_stats.ktls_port[tx_info->port_id]; @@ -577,7 +577,7 @@ static int chcr_ktls_dev_add(struct net_device *netdev, struct sock *sk, cxgb4_remove_tid(&tx_info->adap->tids, tx_info->tx_chan, tx_info->tid, tx_info->ip_family); - xa_erase(&u_ctx->tid_list, tx_info->tid); + xa_erase_bh(&u_ctx->tid_list, tx_info->tid); put_module: /* release module refcount */ From 6c95ca52f27855dd2fb74131c8d4e8325d2af7de Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 2 Oct 2026 09:27:36 +0200 Subject: [PATCH 452/590] 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 f5e0b20f6c6d548d985acdd620c5110776c915c2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:21 +0000 Subject: [PATCH 453/590] wifi: mt76: mt7996: program a link again if the driver holds it The firmware needs the driver to remove active links from the BSSINFO table when it destroys the interface. A link therefore stays registered from its first chanctx assignment until the interface goes away. If the driver holds the link already, a second add does nothing. mac80211 gives a non-AP MLD a new address each time it sets a link up. A link that comes back after a failed attempt thus carries an address that the hardware does not know, on a BSS that the driver never programs again. The station then authenticates from an address that nothing answers. The failure repeats until the driver tears the interface down for good. Program the address and the BSS again. This is the pair that the initial add sets up. For a station it does what mt7996_mcu_mld_link_oper() does for an AP. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Link: https://patch.msgid.link/20260818125825.395538-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e218856b0c45..d17d1dbb2038 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); - return 0; + + /* update the link address */ + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + msta_link, true); } mlink->idx = __ffs64(~dev->mt76.vif_mask); From 232d49dd4b40a666283de9e722899f088ed581b2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 29 Sep 2026 15:10:11 +0200 Subject: [PATCH 454/590] net: stmmac: propagate PTP addend and system time programming errors stmmac_update_subsecond_increment() ignores the error returned by stmmac_config_addend(), and stmmac_init_tstamp_counter() discards the addend and system time programming errors, always returning success. A failure to program the addend (PTP_TCR_TSADDREG) or to initialize the system time counter (PTP_TCR_TSINIT) is therefore silently swallowed, leaving the hardware timestamp counter in a non-running or partially configured state while the driver keeps operating as if timestamping were up. This matters for TAPRIO/EST offloading, which derives the gate base time from the hardware timestamp counter. The same hooks are also called from the PHC callbacks: settime64 and adjfine drop the error and report success to clock_settime() and clock_adjtime(), so a dead PTP reference clock goes unnoticed by ptp4l/phc2sys. Return error codes from stmmac_update_subsecond_increment(), stmmac_init_tstamp_counter(), stmmac_dl_ts_coarse_set() and the settime64/adjfine callbacks instead of silently returning success. On failure, roll back the partially applied configuration so the hardware and the driver bookkeeping stay consistent, and report the reason through the devlink extack. Also guard against a zero sub-second increment, which would otherwise divide by zero when computing the addend. Reset the persistent timestamping state (hwts_tx_en, hwts_rx_en, tstamp_config, systime_flags and tsfupdt_coarse) when (re)initializing timestamping, so a failed init does not leave TX/RX timestamping enabled on a counter that never started. Fixes: cc4c9001ce31 ("net: stmmac: Switch stmmac_hwtimestamp to generic HW Interface Helpers") Signed-off-by: Lorenzo Bianconi Reviewed-by: Maxime Chevallier Signed-off-by: David S. Miller --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++++++++++----- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ec62fa7418f4..9741f97fa37a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,31 +601,64 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) +static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, + u32 default_addend) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - u64 temp = 0; + stmmac_config_addend(priv, priv->ptpaddr, default_addend); stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, + xmac, &sec_inc); + priv->default_addend = default_addend; + priv->sub_second_inc = sec_inc; +} + +static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, + u32 systime_flags) +{ + bool xmac = dwmac_is_xmac(priv->plat->core_type); + u32 sec_inc = 0, val; + u64 temp = 0; + int ret; + + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - temp = div_u64(1000000000ULL, sec_inc); - - /* Store sub second increment for later use */ - priv->sub_second_inc = sec_inc; + if (!sec_inc) { + ret = -EINVAL; + goto error; + } /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ + temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); - stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); + val = div_u64(temp, priv->plat->clk_ptp_rate); + + ret = stmmac_config_addend(priv, priv->ptpaddr, val); + if (ret) + goto error; + + priv->sub_second_inc = sec_inc; + priv->default_addend = val; + + return 0; +error: + /* Restore previous configuration */ + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); + stmmac_config_sub_second_increment(priv, priv->ptpaddr, + priv->plat->clk_ptp_rate, xmac, + NULL); + return ret; } /** @@ -854,35 +887,42 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure - * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, - u32 systime_flags) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) { + u32 default_addend = priv->default_addend; struct timespec64 now; + int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); - priv->systime_flags = systime_flags; - - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); + if (ret) + return ret; /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, + now.tv_nsec); + if (ret) + goto error; return 0; +error: + /* Restore previous configuration */ + stmmac_restore_subsecond_increment(priv, default_addend); + + return ret; } /** @@ -905,8 +945,14 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | - PTP_TCR_TSCFUPDT); + /* Reset hw ts configuration */ + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = false; + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + + ret = stmmac_init_tstamp_counter(priv); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -927,10 +973,6 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7711,18 +7753,26 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; + u32 systime_flags = priv->systime_flags; + int ret; - priv->tsfupdt_coarse = ctx->val.vbool; - - if (priv->tsfupdt_coarse) - priv->systime_flags &= ~PTP_TCR_TSCFUPDT; + if (ctx->val.vbool) + systime_flags &= ~PTP_TCR_TSCFUPDT; else - priv->systime_flags |= PTP_TCR_TSCFUPDT; + systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - stmmac_update_subsecond_increment(priv); + ret = stmmac_update_subsecond_increment(priv, systime_flags); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, + "failed to reconfigure PTP adjustment"); + return ret; + } + + priv->tsfupdt_coarse = ctx->val.vbool; + priv->systime_flags = systime_flags; return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 3bfcc9760dce..493c5d81a36b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,14 +28,15 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; + int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_config_addend(priv, priv->ptpaddr, addend); + ret = stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } /** @@ -153,12 +154,13 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; + int ret; write_lock_irqsave(&priv->ptp_lock, flags); - stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return 0; + return ret; } static int stmmac_enable(struct ptp_clock_info *ptp, From 26f6b6357b1b06e6aaa9b8d796989cca785d8e1d Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Thu, 1 Oct 2026 10:25:15 +0200 Subject: [PATCH 455/590] 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 456/590] 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 457/590] 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 71a77ab76e74131a101f4d2d2afb0dcbf81b4e3c Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Sep 2026 15:06:36 -0400 Subject: [PATCH 458/590] net: allwinner: remove dmaengine_desc_free() dmaengine_desc_free() is designed for DMA_CTRL_REUSE. If DMA_CTRL_REUSE is not set, dmaengine_desc_free() will return -EPERM. if (!dmaengine_desc_test_reuse(desc)) return -EPERM; So calling dmaengine_desc_free() does nothing. dmaengine_terminate_(a)sync() will free all already allocated descriptors. Signed-off-by: Frank Li Reviewed-by: Joe Damato Signed-off-by: David S. Miller --- drivers/net/ethernet/allwinner/sun4i-emac.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/allwinner/sun4i-emac.c b/drivers/net/ethernet/allwinner/sun4i-emac.c index 0ba67a55705d..09b2abdfc9e8 100644 --- a/drivers/net/ethernet/allwinner/sun4i-emac.c +++ b/drivers/net/ethernet/allwinner/sun4i-emac.c @@ -300,7 +300,7 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, if (!req) { dev_err(db->dev, "alloc emac dma req error.\n"); ret = -ENOMEM; - goto alloc_req_err; + goto prepare_err; } desc->callback_param = req; @@ -319,9 +319,6 @@ static int emac_dma_inblk_32bit(struct emac_board_info *db, submit_err: emac_free_dma_req(req); -alloc_req_err: - dmaengine_desc_free(desc); - prepare_err: dma_unmap_single(db->dev, rxbuf, count, DMA_FROM_DEVICE); return ret; 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 459/590] 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 b7f925fd5226deb953c8dcdc9677d3fcc2717c00 Mon Sep 17 00:00:00 2001 From: Georgiy Osokin Date: Mon, 28 Sep 2026 17:31:13 +0300 Subject: [PATCH 460/590] tee: shm: reject zero-sized allocations in tee_dyn_shm_alloc_helper() tee_dyn_shm_alloc_helper() derives nr_pages from a caller-supplied size and passes it to alloc_pages_exact() without checking it. For size == 0 nr_pages is 0, and alloc_pages_exact(0) calls get_order(0), which is documented as undefined and returns BITS_PER_LONG - PAGE_SHIFT. The page allocator then trips its order > MAX_PAGE_ORDER warning and fails the allocation; on a panic_on_warn kernel that ends the boot. This can be triggered by TEE_IOC_SHM_ALLOC with struct tee_ioctl_shm_alloc_data where size is 0. Reject a zero page count, as register_shm_helper() already does for the register path. Fixes: cf4441503e20 ("tee: optee: Move pool_op helper functions") Cc: stable@vger.kernel.org Cc: lvc-project@linuxtesting.org Signed-off-by: Georgiy Osokin Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/tee_shm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/tee/tee_shm.c b/drivers/tee/tee_shm.c index 6742b3579c86..daa4af1e089e 100644 --- a/drivers/tee/tee_shm.c +++ b/drivers/tee/tee_shm.c @@ -343,6 +343,10 @@ int tee_dyn_shm_alloc_helper(struct tee_shm *shm, size_t size, size_t align, unsigned int i; int rc = 0; + /* get_order(0) is undefined and exceeds MAX_PAGE_ORDER. */ + if (!nr_pages) + return -EINVAL; + /* * Ignore alignment since this is already going to be page aligned * and there's no need for any larger alignment. From c108feca0a096f8db25d1b69130035025e9e936d Mon Sep 17 00:00:00 2001 From: Shao-Fu Chen Date: Fri, 18 Sep 2026 18:10:46 +0800 Subject: [PATCH 461/590] optee: register TEE devices only once fully initialized optee_probe() called tee_device_register() on both the client and the supplicant device before the rest of struct optee had been initialized. tee_device_register() calls cdev_device_add(), which does two things at once: it creates /dev/tee0 and /dev/teepriv0, and it links the device into the tee class so that class_find_device() can find it. From that moment on the device is reachable both from user space via tee_open() and from kernel space via tee_client_open_context(). The only gate in teedev_open() is tee_device_get(), which merely checks that teedev->desc is non-NULL, which was already set by tee_device_alloc(). Therefore, there is effectively no gate at all. A context opened in that window can run against a struct optee where - optee->call_queue.mutex is not initialized by optee_cq_init() - optee->supp mutex and completions is not initialized by optee_supp_init() - optee->rpmb_dev_mutex is not initialized yet, - the message argument cache is not initialized by optee_shm_arg_cache_init() - optee->ctx is still NULL Any open session or invoke during this window takes uninitialized mutexes and dereferences a NULL pointer. Fix it by moving both tee_device_register() calls down to the point where all of struct optee is set up. Fixes: 4fb0a5eb364d ("tee: add OP-TEE driver") Cc: stable@vger.kernel.org Signed-off-by: Shao-Fu Chen [JW: adding fixes and cc] Signed-off-by: Jens Wiklander --- drivers/tee/optee/ffa_abi.c | 16 ++++++++-------- drivers/tee/optee/smc_abi.c | 17 ++++++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..d3cc7c5fc1e9 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -1123,14 +1123,6 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - rc = rhashtable_init(&optee->ffa.global_ids, &shm_rhash_params); if (rc) goto err_unreg_supp_teedev; @@ -1159,6 +1151,14 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev) if (optee_ffa_protmem_pool_init(optee, sec_caps)) pr_info("Protected memory service not available\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_unregister_devices; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_unregister_devices; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_unregister_devices; diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c index b8a2bdac3208..51624443359b 100644 --- a/drivers/tee/optee/smc_abi.c +++ b/drivers/tee/optee/smc_abi.c @@ -1849,14 +1849,6 @@ static int optee_probe(struct platform_device *pdev) optee_set_dev_group(optee); - rc = tee_device_register(optee->teedev); - if (rc) - goto err_unreg_supp_teedev; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err_unreg_supp_teedev; - optee_cq_init(&optee->call_queue, thread_count); optee_supp_init(&optee->supp); optee->smc.memremaped_shm = memremaped_shm; @@ -1916,6 +1908,14 @@ static int optee_probe(struct platform_device *pdev) if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) pr_info("dynamic shared memory is enabled\n"); + rc = tee_device_register(optee->teedev); + if (rc) + goto err_disable_shm_cache; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err_disable_shm_cache; + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); if (rc) goto err_disable_shm_cache; @@ -1942,7 +1942,6 @@ static int optee_probe(struct platform_device *pdev) optee_shm_arg_cache_uninit(optee); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); -err_unreg_supp_teedev: tee_device_unregister(optee->supp_teedev); err_unreg_teedev: tee_device_unregister(optee->teedev); From 3c0d2f1f431c51a11253f28763be723ac782fd7a Mon Sep 17 00:00:00 2001 From: Mahantesh Salimath Date: Thu, 10 Sep 2026 16:11:24 +0000 Subject: [PATCH 462/590] tee: optee: ffa: support shared memory offsets on large-page kernels OP-TEE FF-A memory objects use 4 KiB pages, while the kernel page size may be larger. Consequently, tee_shm->offset can be greater than or equal to FFA_PAGE_SIZE, but OP-TEE rejects such a value in internal_offs. Do not encode the excess page offset in offs_low/offs_high. Those fields describe the logical memref offset and are copied back into tee_param->shm_offs on return. Folding the page offset into them breaks parameter round trips when a memref is reused. They are also ignored by the OPTEE_RPC_CMD_SHM_ALLOC response path, which uses only global_id and internal_offs to construct the shared-memory mobj. Instead, start the FF-A descriptor at the 4 KiB page containing the shared buffer, the same approach as optee_fill_pages_list() in the SMC ABI. Store the remaining in-page offset in internal_offs and preserve shm_offs in offs_low/offs_high. This keeps internal_offs within the FF-A page size, maps RPC allocations at the correct address, and preserves normal memref offsets across repeated invocations. Tested on ARMv8-A with 64 KiB PAGE_SIZE. OP-TEE OS ran as a secure partition under Hafnium (SPMC) over FF-A. Verified registered shared memory with tee_shm->offset >= 4 KiB, memref reuse on the same TEEC_Operation, and RPC OPTEE_RPC_CMD_SHM_ALLOC (xtest regression 6007-6009). optee_hello_world, optee_aes, and xtest regression 1005, 1007, 1008, 4001-4003 and 6001-6003 also passed. Fixes: 4615e5a34b95 ("optee: add FF-A support") Acked-by: Liming Sun Acked-by: James Hurley Acked-by: Dave Thompson Signed-off-by: Mahantesh Salimath Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- drivers/tee/optee/ffa_abi.c | 22 ++++++++++++++++++---- drivers/tee/optee/optee_msg.h | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index d3cc7c5fc1e9..d027fe7ce86f 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -198,7 +198,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, if (shm) { u64 shm_offs = p->u.memref.shm_offs; - mp->u.fmem.internal_offs = shm->offset; + mp->u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1); mp->u.fmem.offs_low = shm_offs; mp->u.fmem.offs_high = shm_offs >> 32; @@ -284,14 +285,26 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt; + size_t ffa_offs; int rc; + if (!num_pages) + return -EINVAL; + rc = optee_check_mem_type(start, num_pages); if (rc) return rc; - rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, 0, - num_pages * PAGE_SIZE, GFP_KERNEL); + /* + * Start the FF-A descriptor at the 4 KiB page containing the shared + * buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is + * larger. Same approach as optee_fill_pages_list() in the SMC ABI. + * This leaves only the offset into that 4 KiB page for internal_offs. + */ + ffa_offs = round_down(tee_shm_get_page_offset(shm), FFA_PAGE_SIZE); + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs, + num_pages * PAGE_SIZE - ffa_offs, + GFP_KERNEL); if (rc) return rc; args.sg = sgt.sgl; @@ -458,7 +471,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .attr = OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT, .u.fmem.size = tee_shm_get_size(shm), .u.fmem.global_id = shm->sec_world_id, - .u.fmem.internal_offs = shm->offset, + .u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1), }; arg->ret = TEEC_SUCCESS; diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c03..6c3043f8da33 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem { * struct optee_msg_param_fmem - FF-A memory reference parameter * @offs_low: lower bits of offset into shared memory reference * @offs_high: higher bits of offset into shared memory reference - * @internal_offs: internal offset into the first page of shared memory - * reference + * @internal_offs: offset into the first 4 KiB page of the FF-A shared + * memory region * @size: size of the buffer * @global_id: global identifier of the shared memory */ From 25016fe8c1ed0fccae107bd629089139ffbafe08 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 30 Sep 2026 15:44:53 +0800 Subject: [PATCH 463/590] Bluetooth: btintel_pcie: use managed IRQ teardown The PCIe driver registers its MSI-X handlers with devm_request_threaded_irq(), but remove() uses free_irq() directly. The devres actions then retain registrations and attempt to release them again during device cleanup. Use devm_free_irq() for the remove path and for already registered vectors when a later setup request fails. This removes the managed resource at the same time as the IRQ is released and keeps MSI-X vector teardown ordered after IRQ ownership is gone. Fixes: c2b636b3f788 ("Bluetooth: btintel_pcie: Add support for PCIe transport") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Runyu Xiao Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 0194badeeff3..ac7193400769 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1777,7 +1777,7 @@ static irqreturn_t btintel_pcie_irq_msix_handler(int irq, void *dev_id) static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) { int err; - int num_irqs, i; + int num_irqs, i, j; for (i = 0; i < BTINTEL_PCIE_MSIX_VEC_MAX; i++) data->msix_entries[i].entry = i; @@ -1806,6 +1806,10 @@ static int btintel_pcie_setup_irq(struct btintel_pcie_data *data) KBUILD_MODNAME, msix_entry); if (err) { + for (j = 0; j < i; j++) + devm_free_irq(&data->pdev->dev, + data->msix_entries[j].vector, + &data->msix_entries[j]); pci_free_irq_vectors(data->pdev); data->alloc_vecs = 0; return err; @@ -3145,7 +3149,7 @@ static void btintel_pcie_remove(struct pci_dev *pdev) struct msix_entry *msix_entry; msix_entry = &data->msix_entries[i]; - free_irq(msix_entry->vector, msix_entry); + devm_free_irq(&pdev->dev, msix_entry->vector, msix_entry); } pci_free_irq_vectors(pdev); From 08e90633377f1b2567ab5ad6810b74c552246a07 Mon Sep 17 00:00:00 2001 From: Palla Raghunath Date: Fri, 2 Oct 2026 08:07:52 +0100 Subject: [PATCH 464/590] Bluetooth: RFCOMM: Fix NULL tty_dev dereference in rfcomm_dev_shutdown syzbot hit a NULL pointer dereference in rfcomm_dev_shutdown() when an RFCOMM tty is closed. rfcomm_dev_add() puts the new device on rfcomm_dev_list first and only then registers the tty device and saves it in dev->tty_dev. If that registration fails, dev->tty_dev is never set at all. The device can be looked up in the meantime, so an open() of /dev/rfcommN gets through rfcomm_tty_install() and attaches to it. When that tty is closed again, rfcomm_dev_shutdown() uses dev->tty_dev without checking it: Oops: general protection fault, probably for non-canonical address KASAN: null-ptr-deref in range [0x0000000000000040-0x0000000000000047] RIP: 0010:rfcomm_dev_shutdown+0x48/0xb0 net/bluetooth/rfcomm/tty.c:131 Call Trace: tty_port_shutdown+0x1bf/0x220 drivers/tty/tty_port.c:372 tty_port_close+0x4e/0x150 drivers/tty/tty_port.c:705 tty_release+0x359/0x1670 drivers/tty/tty_io.c:1718 __fput+0x418/0xa50 fs/file_table.c:512 rfcomm_dev_destruct() already copes with a NULL dev->tty_dev. Add the same check to rfcomm_dev_shutdown(), so the tty device is only moved when it was actually registered. Fixes: cad348a17e17 ("Bluetooth: Implement .activate, .shutdown and .carrier_raised methods") Reported-by: syzbot+4a807caf7680652d53b6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4a807caf7680652d53b6 Cc: stable@vger.kernel.org Signed-off-by: Palla Raghunath Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/rfcomm/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index dc3cdf614def..198c7dd1a95b 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -128,7 +128,7 @@ static void rfcomm_dev_shutdown(struct tty_port *port) { struct rfcomm_dev *dev = container_of(port, struct rfcomm_dev, port); - if (dev->tty_dev->parent) + if (dev->tty_dev && dev->tty_dev->parent) device_move(dev->tty_dev, NULL, DPM_ORDER_DEV_LAST); /* close the dlc */ From 40cce45b921f7930886bcba43069edc579725457 Mon Sep 17 00:00:00 2001 From: Nikola Prica Date: Mon, 21 Sep 2026 13:19:03 +0200 Subject: [PATCH 465/590] 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 23b1ab15ca91f2cffaf149d9bb0fafeb700c7db2 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Fri, 2 Oct 2026 12:54:38 -0400 Subject: [PATCH 466/590] cgroup/cpuset: Handle cpu hotplug race in guarantee_active_cpus() With commit 2125c0034c5d ("cgroup/cpuset: Make cpuset hotplug processing synchronous"), the cpuset hotplug operation becomes synchronous. That commit also removes the code that handles race between cpuset_hotplug_work and cpu hotplug notifier with the assumption that race is now gone. Later commit 7a0aabd9ce69 ("cgroup/cpuset: Always use cpu_active_mask") updates the cpuset code to always use cpu_active_mask instead of cpu_online_mask in some places including guarantee_online_cpus() which is renamed to guarantee_active_cpus() in that commit. In the case of CPU offline operation, cpu_active_mask is updated first in sched_cpu_deactivate() to remove the offline CPU before cpuset_handle_hotplug() is called to update the effective_cpus of the affected cpusets. The cpu_online_mask is updated after that near the end of the offline operation to remove the offline CPU. As a result, the race comes back and the top cpuset may not have any active CPU leading to NULL pointer dereference during the race window when guarantee_active_cpus() is called after cpu_active_mask is updated to remove the CPU to be torn down but before cpuset_handle_hotplug() is able to properly update the effective_cpus of the top cpuset. Fix this by adding back the NULL cs check to avoid this problem. Fixes: 7a0aabd9ce69 ("cgroup/cpuset: Always use cpu_active_mask") Cc: stable@vger.kernel.org # v6.16+ Reported-by: Farhad Alemi Link: https://lore.kernel.org/lkml/CA+0ovChh3VjsKN1g+ZGjwwY2fGTpP7uD+aCCByLj5Qbymw=bfQ@mail.gmail.com Signed-off-by: Waiman Long Tested-by: Farhad Alemi Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 98af44a7f399..d2ec7b128960 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -518,10 +518,26 @@ static void guarantee_active_cpus(struct task_struct *tsk, rcu_read_lock(); cs = task_cs(tsk); - while (!cpumask_intersects(cs->effective_cpus, pmask)) + while (!cpumask_intersects(cs->effective_cpus, pmask)) { cs = parent_cs(cs); - + if (unlikely(!cs)) { + /* + * The top cpuset doesn't have any active cpu as a + * consequence of a race between its caller and the cpu + * hotplug operation where cpu_active_mask is updated + * asynchronously before cpuset_handle_hotplug() is + * being called to adjust the effective_cpus of the + * affected cpusets. But we know the top cpuset's + * effective_cpus is on its way to be identical to + * cpu_active_mask minus the exclusive CPUs dedicated + * to other valid cpuset partitions. Just pass back + * the filtered cpu_active_mask in this case. + */ + goto out_unlock; + } + } cpumask_and(pmask, pmask, cs->effective_cpus); +out_unlock: rcu_read_unlock(); } From 184b5ddf82d45d6f75cc83719a2e453392512bd2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:26:39 -0700 Subject: [PATCH 467/590] bonding: fix the program leak when XDP is replaced We need to release the reference on the old BPF prog, whether we are going to no-prog, or replacing the prog with another. Spotted by AI while running tests for the propagation series (leaked programs were accumulating). Selftest will come in the net-next series to avoid conflicts. Fixes: 9e2ee5c7e7c3 ("net, bonding: Add XDP support to the bonding driver") Fixes: 6d5f1ef83868 ("bonding: Fix negative jump label count on nested bonding") Acked-by: Daniel Borkmann Reviewed-by: Jiayuan Chen Link: https://patch.msgid.link/20261001012639.298690-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/bonding/bond_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index de2489c3d9bf..09aa45d71fab 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -5732,12 +5732,12 @@ static int bond_xdp_set(struct net_device *dev, struct bpf_prog *prog, bpf_prog_inc(prog); } - if (prog) { + if (prog && !old_prog) static_branch_inc(&bpf_master_redirect_enabled_key); - } else if (old_prog) { - bpf_prog_put(old_prog); + else if (!prog && old_prog) static_branch_dec(&bpf_master_redirect_enabled_key); - } + if (old_prog) + bpf_prog_put(old_prog); return 0; From bcf2573a68a808a5b54cadafa72b6672e662c2bf Mon Sep 17 00:00:00 2001 From: Suraj Gupta Date: Tue, 29 Sep 2026 00:12:07 +0530 Subject: [PATCH 468/590] net: xilinx: axienet: Free outstanding DMA buffers on dmaengine stop In the dmaengine path the driver pre-submits RX buffers and holds in-flight TX buffers whose SKBs are DMA-mapped by the driver and freed only in the completion callbacks. On ndo_stop() dmaengine_terminate_sync() aborts these descriptors without running their callbacks, and the driver then frees only the ring shells, leaking every SKB still owned by the engine and its DMA mapping on each ifdown. With 128 RX buffers pre-posted per channel, the mapping leak can eventually exhaust a limited IOMMU aperture. Clear the slot's skb in the TX and RX callbacks so a non-NULL skb marks a slot that still owns a live, DMA-mapped buffer, and on stop unmap and free every such buffer. axienet_dma_rx_cb() runs from the DMA tasklet and re-arms the RX ring on each completion, so it can race axienet_stop(): a completion may submit a fresh buffer after dmaengine_terminate_sync() has returned, leaving the channel armed with a buffer the teardown then frees while the engine may still write into it (dma_release_channel() does not stop it either). Add a lock that axienet_dma_rx_cb() holds across the @stopping check and the resubmit, and axienet_stop() holds to set @stopping before terminating. Once @stopping is set no callback can arm a new buffer, and any armed just before is aborted by the terminate, so teardown only frees buffers the engine no longer owns. Fixes: 6a91b846af85 ("net: axienet: Introduce dmaengine support") Cc: stable@vger.kernel.org Signed-off-by: Suraj Gupta Link: https://patch.msgid.link/20260928184207.2361931-1-suraj.gupta2@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/xilinx/xilinx_axienet.h | 7 ++- .../net/ethernet/xilinx/xilinx_axienet_main.c | 53 +++++++++++++++---- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index fcd3aaef27fc..c63d99686d0b 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -523,8 +523,9 @@ struct skbuf_dma_descriptor { * @stats_work: Work for reading the hardware statistics counters often enough * to catch overflows. * @dma_err_task: Work structure to process Axi DMA errors - * @stopping: Set when @dma_err_task shouldn't do anything because we are - * about to stop the device. + * @stopping: Set when we are about to stop the device: makes @dma_err_task + * a no-op (legacy DMA path) and fences RX descriptor + * resubmission in axienet_dma_rx_cb() (dmaengine path). * @tx_irq: Axidma TX IRQ number * @rx_irq: Axidma RX IRQ number * @eth_irq: Ethernet core IRQ number @@ -545,6 +546,7 @@ struct skbuf_dma_descriptor { * @tx_ring_tail: TX skb ring buffer tail index. * @rx_ring_head: RX skb ring buffer head index. * @rx_ring_tail: RX skb ring buffer tail index. + * @rx_submit_lock: Protects RX ring resubmission vs teardown in dmaengine path. */ struct axienet_local { struct net_device *ndev; @@ -626,6 +628,7 @@ struct axienet_local { int tx_ring_tail; int rx_ring_head; int rx_ring_tail; + spinlock_t rx_submit_lock; }; /** diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index 1722b7038f34..09443623a3e2 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -881,6 +881,7 @@ static void axienet_dma_tx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->tx_stat_sync); dma_unmap_sg(lp->dev, skbuf_dma->sgl, skbuf_dma->sg_len, DMA_TO_DEVICE); dev_consume_skb_any(skbuf_dma->skb); + skbuf_dma->skb = NULL; netif_txq_completed_wake(txq, 1, len, CIRC_SPACE(lp->tx_ring_head, lp->tx_ring_tail, TX_BD_NUM_MAX), 2); @@ -1171,6 +1172,7 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) &meta_max_len); dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size, DMA_FROM_DEVICE); + skbuf_dma->skb = NULL; if (IS_ERR(app_metadata)) { if (net_ratelimit()) @@ -1193,10 +1195,17 @@ static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result) u64_stats_update_end(&lp->rx_stat_sync); rx_submit: + spin_lock(&lp->rx_submit_lock); + if (lp->stopping) { + spin_unlock(&lp->rx_submit_lock); + return; + } + for (i = 0; i < CIRC_SPACE(lp->rx_ring_head, lp->rx_ring_tail, RX_BUF_NUM_DEFAULT); i++) axienet_rx_submit_desc(lp->ndev); dma_async_issue_pending(lp->rx_chan); + spin_unlock(&lp->rx_submit_lock); } /** @@ -1541,6 +1550,7 @@ static int axienet_init_dmaengine(struct net_device *ndev) lp->tx_ring_head = 0; lp->rx_ring_tail = 0; lp->rx_ring_head = 0; + lp->stopping = false; lp->tx_skb_ring = kzalloc_objs(*lp->tx_skb_ring, TX_BD_NUM_MAX); if (!lp->tx_skb_ring) { ret = -ENOMEM; @@ -1752,20 +1762,42 @@ static int axienet_stop(struct net_device *ndev) free_irq(lp->rx_irq, ndev); axienet_dma_bd_release(ndev); } else { - dmaengine_terminate_sync(lp->tx_chan); - dmaengine_synchronize(lp->tx_chan); - dmaengine_terminate_sync(lp->rx_chan); - dmaengine_synchronize(lp->rx_chan); + struct skbuf_dma_descriptor *skbuf_dma; - for (i = 0; i < TX_BD_NUM_MAX; i++) - kfree(lp->tx_skb_ring[i]); - kfree(lp->tx_skb_ring); - for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) - kfree(lp->rx_skb_ring[i]); - kfree(lp->rx_skb_ring); + spin_lock_bh(&lp->rx_submit_lock); + lp->stopping = true; + spin_unlock_bh(&lp->rx_submit_lock); + + dmaengine_terminate_sync(lp->tx_chan); + dmaengine_terminate_sync(lp->rx_chan); dma_release_channel(lp->rx_chan); dma_release_channel(lp->tx_chan); + + /* Unmap and free any buffer the terminate did not reclaim, so it + * is not leaked; a non-NULL skb marks such a slot. + */ + for (i = 0; i < TX_BD_NUM_MAX; i++) { + skbuf_dma = lp->tx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_sg(lp->dev, skbuf_dma->sgl, + skbuf_dma->sg_len, DMA_TO_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->tx_skb_ring); + + for (i = 0; i < RX_BUF_NUM_DEFAULT; i++) { + skbuf_dma = lp->rx_skb_ring[i]; + if (skbuf_dma && skbuf_dma->skb) { + dma_unmap_single(lp->dev, skbuf_dma->dma_address, + lp->max_frm_size, DMA_FROM_DEVICE); + dev_kfree_skb_any(skbuf_dma->skb); + } + kfree(skbuf_dma); + } + kfree(lp->rx_skb_ring); } netdev_reset_queue(ndev); @@ -3065,6 +3097,7 @@ static int axienet_probe(struct platform_device *pdev) spin_lock_init(&lp->rx_cr_lock); spin_lock_init(&lp->tx_cr_lock); + spin_lock_init(&lp->rx_submit_lock); INIT_WORK(&lp->rx_dim.work, axienet_rx_dim_work); lp->rx_dim_enabled = true; lp->rx_dim.profile_ix = 1; From 8b1f0af1fd5fd113432233ed8a3862ee7c30a84f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 30 Sep 2026 19:25:24 +0900 Subject: [PATCH 469/590] tcp: reject net_iov in zerocopy receive mapping hints After copying a readable prefix, receive_fallback_to_copy() asks tcp_zerocopy_set_hint_for_skb() where page mapping can resume. If the next skb is unreadable, find_next_mappable_frag() passes its net_iov fragment to can_map_frag(). skb_frag_page() returns NULL for a net_iov, but can_map_frag() dereferences it in PageCompound(). A v7.2 KASAN run on a connected TCP socket with 64 readable bytes followed by a 4096-byte NET_IOV_DMABUF fragment reported: BUG: KASAN: null-ptr-deref in can_map_frag tcp_zerocopy_receive -> can_map_frag Kernel panic - not syncing: KASAN: panic_on_warn set The diagnostic inserted the net_iov directly because the test host has no devmem-capable NIC. Hardware end-to-end reachability remains untested and requires CONFIG_NET_DEVMEM plus a supported DMA-buf-bound RX queue. Reject all net_iov fragments before skb_frag_page(). This covers both DMABUF and IOURING net_iov types while leaving page-backed checks unchanged. With the guard, the same queue copied the readable prefix, returned a 4096-byte skip hint, and completed without a fault. Fixes: 9f6b619edf2e ("net: support non paged skb frags") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20260930102524.1659847-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/tcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 562752352afe..87ef6d5cbfeb 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1908,6 +1908,8 @@ static bool can_map_frag(const skb_frag_t *frag) if (skb_frag_size(frag) != PAGE_SIZE || skb_frag_off(frag)) return false; + if (skb_frag_is_net_iov(frag)) + return false; page = skb_frag_page(frag); From 1402fc67d2f4e2e1fc7bf8ee0825b8ad994557ff Mon Sep 17 00:00:00 2001 From: Xuanqiang Luo Date: Mon, 28 Sep 2026 16:04:23 -0700 Subject: [PATCH 470/590] ice: fix use-after-free in dynamic port cleanup ice_dealloc_dynamic_port() uses dyn_port->vsi->idx to erase the dynamic port from pf->dyn_ports. However, it frees the VSI before reading the index for the erase, resulting in a use-after-free. Follow the reverse of the allocation order in ice_alloc_dynamic_port() by erasing the xarray entry before freeing the VSI. Fixes: eda69d654c7e ("ice: add basic devlink subfunctions support") Cc: stable@vger.kernel.org Signed-off-by: Xuanqiang Luo Reviewed-by: Marcin Szycik Reviewed-by: Aleksandr Loktionov Tested-by: Patryk Holda Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-3-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/devlink/port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/devlink/port.c b/drivers/net/ethernet/intel/ice/devlink/port.c index 2a2e56777f9f..3ede24649002 100644 --- a/drivers/net/ethernet/intel/ice/devlink/port.c +++ b/drivers/net/ethernet/intel/ice/devlink/port.c @@ -590,8 +590,8 @@ static void ice_dealloc_dynamic_port(struct ice_dynamic_port *dyn_port) xa_erase(&pf->sf_nums, devlink_port->attrs.pci_sf.sf); ice_eswitch_detach_sf(pf, dyn_port); - ice_vsi_free(dyn_port->vsi); xa_erase(&pf->dyn_ports, dyn_port->vsi->idx); + ice_vsi_free(dyn_port->vsi); kfree(dyn_port); } From 7a579dec8423bba7bec96fcfcb2e30838b0858ee Mon Sep 17 00:00:00 2001 From: Bryan Fraschetti Date: Mon, 28 Sep 2026 16:04:24 -0700 Subject: [PATCH 471/590] ice: Restore Ordered MMIO Writes for Tx Doorbells The DQL accounting state which tracks the number of bytes queued for the NIC to transmit, namely dql->num_queued, is updated by the ICE driver when it invokes __netdev_tx_sent_queue(). Subsequently the driver updates the hardware queue tail allowing the NIC to begin processing the work queue. The queue accounting update should be ordered before the NIC begins transmitting descriptors. The transmit path currently updates the hardware doorbell using writel_relaxed(), which (on arm64) does not provide the same ordering guarantees between writes to normal memory and writes to MMIO registers that writel() does. This introduces a potential race where the NIC begins transmitting descriptors before the dql->num_queued update is globally visible. If the NIC finishes before the update is observed by dql_completed(), it detects an invalid state where more bytes have been completed than have been queued. When this happens the following BUG_ON is triggered. BUG_ON(count > num_queued - dql->num_completed); This has been observed and manifests as the following crash (note that the trace has been trimmed) in an environment with sustained network load that uses an Intel Corporation Ethernet Controller E810-XXV for SFP (rev 02) on an arm64 machine. Replacing writel_relaxed() with writel() in a test kernel eliminated the crash in the user's workload, which previously reproduced the issue reliably. kernel BUG at lib/dynamic_queue_limits.c:99 Internal error: Oops - BUG: 00000000f2000800 [#1] SMP pc : dql_completed+0x268/0x2a0 lr : ice_clean_tx_irq+0x1d4/0x620 [ice] Call trace: dql_completed+0x268/0x2a0 (P) ice_napi_poll+0x94/0x520 [ice] __napi_poll+0x48/0x3f0 net_rx_action+0x194/0x420 This restores the behaviour prior to commit ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support"), which changed the notification mechanism from writel() to writel_relaxed(). Link: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2161572 Fixes: ccde82e90946 ("ice: add E830 Earliest TxTime First Offload support") Signed-off-by: Bryan Fraschetti Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-4-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_txrx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_txrx.c b/drivers/net/ethernet/intel/ice/ice_txrx.c index 31303ab5be17..a2c7c4962882 100644 --- a/drivers/net/ethernet/intel/ice/ice_txrx.c +++ b/drivers/net/ethernet/intel/ice/ice_txrx.c @@ -1561,10 +1561,10 @@ ice_tx_map(struct ice_tx_ring *tx_ring, struct ice_tx_buf *first, } } tstamp_ring->next_to_use = j; - writel_relaxed(j, tstamp_ring->tail); + writel(j, tstamp_ring->tail); } else { ring_kick: - writel_relaxed(i, tx_ring->tail); + writel(i, tx_ring->tail); } return; From 58858b8f14803a7eeac297b3ce20f3b21bf2ff66 Mon Sep 17 00:00:00 2001 From: Tristan Madani Date: Mon, 28 Sep 2026 16:04:25 -0700 Subject: [PATCH 472/590] ice: fix metadata_dst refcount handling on representor teardown ice_eswitch_release_repr() uses metadata_dst_free() to release the representor's metadata_dst. metadata_dst_free() directly frees the underlying memory without checking the dst_entry refcount. When ice_eswitch_port_start_xmit() processes a packet, it takes a reference via dst_hold() and attaches the metadata_dst to the skb. If the representor is torn down while packets are still queued on the lower device (e.g. in a qdisc), the metadata_dst is freed while references are still held. Use dst_release() instead, which correctly decrements the refcount and only frees the object when all references are dropped. The dst subsystem already handles metadata_dst cleanup in dst_destroy() when DST_METADATA is set. Other drivers sharing this pattern (nfp, airoha, bnxt) already use dst_release() for their metadata_dst lifecycle. Fixes: f5396b8a663f7 ("ice: switchdev slow path") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani Reviewed-by: Simon Horman Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260928230429.495442-5-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_eswitch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c index b069e6c514fb..6e7bba473898 100644 --- a/drivers/net/ethernet/intel/ice/ice_eswitch.c +++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c @@ -95,7 +95,7 @@ ice_eswitch_release_repr(struct ice_pf *pf, struct ice_repr *repr) return; ice_vsi_update_security(vsi, ice_vsi_ctx_set_antispoof); - metadata_dst_free(repr->dst); + dst_release(&repr->dst->dst); repr->dst = NULL; ice_fltr_add_mac_and_broadcast(vsi, repr->parent_mac, ICE_FWD_TO_VSI); From f2e695a6c27241374c9b007b718361badccb2f63 Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Thu, 24 Sep 2026 12:12:45 +0200 Subject: [PATCH 473/590] MAINTAINERS: update my email address Switch my contact address to kaloz@kernel.org and update .mailmap. Signed-off-by: Imre Kaloz Link: https://lore.kernel.org/r/20260924101245.5011-1-kaloz@kernel.org Signed-off-by: Arnd Bergmann --- .mailmap | 1 + MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 389b94a0124e..2b43f748d4f7 100644 --- a/.mailmap +++ b/.mailmap @@ -484,6 +484,7 @@ Juha Yrjola Juha Yrjola Julien Thierry Justin Iurman +Imre Kaloz Ira Weiny Iskren Chernev Kalle Valo diff --git a/MAINTAINERS b/MAINTAINERS index 72294ddfa5b7..7270b031d40d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3012,7 +3012,7 @@ F: arch/arm/boot/dts/ti/omap/omap3-igep* ARM/INTEL IXP4XX ARM ARCHITECTURE M: Linus Walleij -M: Imre Kaloz +M: Imre Kaloz L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/arm/intel-ixp4xx.yaml From 3bf8d603fd45649c106cae6290a10f20b1bbb569 Mon Sep 17 00:00:00 2001 From: Martino Papero Date: Wed, 30 Sep 2026 22:10:41 +0200 Subject: [PATCH 474/590] 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 e6ac89b8b1c12e9104df45a14a26e2dfa96ded06 Mon Sep 17 00:00:00 2001 From: Kuba Piecuch Date: Sat, 3 Oct 2026 11:53:17 +0000 Subject: [PATCH 475/590] sched_ext: Generate qseq from a per-task counter finish_dispatch() uses the qseq embedded in p->scx.ops_state to tell whether the QUEUED instance of a task it's about to claim is the one scx_bpf_dsq_insert() saw. qseq is generated from rq->scx.ops_qseq, but the counters of different rqs are independent, so if a task is dequeued and re-enqueued on a different rq between scx_bpf_dsq_insert() and finish_dispatch(), the new QUEUED instance can end up with the same qseq as the old one: CPU X CPU Z ----- ----- enqueue p on rq A, qseq = N ops.dispatch() scx_bpf_dsq_insert(p) records qseq N sched_setaffinity(p) dequeue p from rq A enqueue p on rq B, qseq = N finish_dispatch(p, N) qseq matches, p is claimed The claim itself is still atomic so the core stays consistent, but an insert issued for a previous QUEUED instance gets applied to a new one which the BPF scheduler has just received through ops.enqueue(). This breaks the guarantee that dispatches targeting a stale instance are ignored. Generate qseq from a per-task counter, p->scx.ops_qseq, instead so that consecutive QUEUED instances of a task never share a qseq regardless of which rq they're on. The counter is only updated in scx_do_enqueue_task() with the task's rq locked, so no additional synchronization is needed, and it fits in an existing hole in struct sched_ext_entity on 64bit. Remove the now unused rq->scx.ops_qseq. Never generate qseq 0. NONE and DISPATCHING don't carry a qseq, so scx_bpf_dsq_insert() on a task in either state records 0. With a per-task counter, every task's first QUEUED instance would otherwise get qseq 0 and could be claimed by such an insert. Wrap the counter where the QSEQ field wraps so that it can't reach a value that shifts to 0 on 32bit either. Fixes: f0e1a0643a59 ("sched_ext: Implement BPF extensible scheduler class") Cc: stable@vger.kernel.org # v6.12+ Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kuba Piecuch Signed-off-by: Tejun Heo --- include/linux/sched/ext.h | 1 + kernel/sched/ext/ext.c | 14 ++++++++++---- kernel/sched/ext/internal.h | 5 +++++ kernel/sched/sched.h | 1 - 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/linux/sched/ext.h b/include/linux/sched/ext.h index 582d7cd4a983..36c04797436c 100644 --- a/include/linux/sched/ext.h +++ b/include/linux/sched/ext.h @@ -207,6 +207,7 @@ struct sched_ext_entity { s32 holding_cpu; s32 selected_cpu; s32 runnable_cpu; /* cpu @p is runnable on, -1 if not */ + u32 ops_qseq; /* protected by rq lock */ struct task_struct *kf_tasks[2]; /* see SCX_CALL_OP_TASK() */ struct list_head runnable_node; /* rq->scx.runnable_list */ diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 5209510d6a5e..ac8fe70a9fb6 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -2057,8 +2057,14 @@ void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, if (unlikely(!SCX_HAS_OP(sch, enqueue))) goto global; - /* DSQ bypass didn't trigger, enqueue on the BPF scheduler */ - qseq = rq->scx.ops_qseq++ << SCX_OPSS_QSEQ_SHIFT; + /* + * DSQ bypass didn't trigger, enqueue on the BPF scheduler. Wrap the + * per-task qseq counter where the QSEQ field wraps and skip 0, which is + * what scx_bpf_dsq_insert() records for a task in NONE or DISPATCHING. + */ + p->scx.ops_qseq = ((p->scx.ops_qseq + 1) & + (SCX_OPSS_QSEQ_MASK >> SCX_OPSS_QSEQ_SHIFT)) ?: 1; + qseq = (unsigned long)p->scx.ops_qseq << SCX_OPSS_QSEQ_SHIFT; WARN_ON_ONCE(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE); atomic_long_set(&p->scx.ops_state, SCX_OPSS_QUEUEING | qseq); @@ -6969,9 +6975,9 @@ static void scx_dump_cpu(struct scx_sched *sch, struct seq_buf *s, seq_buf_init(&ns, buf, avail); dump_newline(&ns); - scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ops_qseq=%lu ksync=%lu", + scx_dump_line(&ns, "CPU %-4d: nr_run=%u flags=0x%x cpu_rel=%d ksync=%lu", cpu, rq->scx.nr_running, rq->scx.flags, rq->scx.cpu_released, - rq->scx.ops_qseq, rq->scx.kick_sync); + rq->scx.kick_sync); scx_rescue_dump(&ns, rq); scx_dump_line(&ns, " curr=%s[%d] class=%ps", rq->curr->comm, rq->curr->pid, rq->curr->sched_class); diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 1df8f583b0ec..5b37faa532d6 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -1998,6 +1998,11 @@ enum scx_ops_state { * dequeue/requeue, the dispatcher can tell whether it still has a claim * on the task being dispatched. * + * QSEQ is generated from the per-task p->scx.ops_qseq counter so that + * it doesn't repeat across QUEUED instances of the same task even if + * the task moves between rqs. 0 is never used as a valid QSEQ since + * NONE and DISPATCHING map to this value. + * * As some 32bit archs can't do 64bit store_release/load_acquire, * p->scx.ops_state is atomic_long_t which leaves 30 bits for QSEQ on * 32bit machines. The dispatch race window QSEQ protects is very narrow diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 742cce9da4e9..9ed0cb605c33 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -813,7 +813,6 @@ struct scx_rq { #endif struct list_head runnable_list; /* runnable tasks on this rq */ struct list_head ddsp_deferred_locals; /* deferred ddsps from enq */ - unsigned long ops_qseq; /* both stashed across the activate_task() in move_remote_task_to_local_dsq() */ u64 remote_activate_enq_flags; struct scx_sched *remote_activate_sch; From 25bf14f817168079f731975b8998fc2af380f29e Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 11:54:40 +0200 Subject: [PATCH 476/590] keys: finalize persistent keyring timeout after link attempt When no keyring exists for the requested UID, KEYCTL_GET_PERSISTENT creates and registers one before linking it to the requested destination. The configured timeout is set only after the destination link succeeds. A destination restricted with KEYCTL_RESTRICT_KEYRING makes that link fail with -EPERM. With persistent_keyring_expiry set to 60 seconds, /proc/keys still reports the registered keyring's expiry as "perm". The failed call therefore leaves a quota-exempt keyring in the namespace's hidden register, where it may remain until namespace teardown. Rename the write-locked helper to key_get_or_create_persistent() and return the key reference through a result parameter. Return 0 when it creates a keyring and 1 when the retry finds an existing one. Return a negative error on failure. Another caller may create the keyring between the initial read-locked lookup and the retry under the write lock. Set the timeout after permission checking and linking. Do this on success, or on failure if the call allocated the keyring. This leaves a new keyring collectible after failure without starting its timeout while linking is still in progress. Cc: stable@vger.kernel.org # v5.10+ Fixes: f36f8c75ae2e ("KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260912095440.79864-1-kmehltretter@gmail.com Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- security/keys/persistent.c | 55 ++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 97af230aa4b2..bdab99539326 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -33,25 +33,31 @@ static int key_create_persistent_register(struct user_namespace *ns) } /* - * Create the persistent keyring for the specified user. + * Get or create the persistent keyring for the specified user. * * Called with the namespace's sem locked for writing. + * + * Returns a boolean indicating whether the keyring already existed, + * or a negative error. On success, *persistent_ref holds a reference + * to the keyring. */ -static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, - struct keyring_index_key *index_key) +static int key_get_or_create_persistent(struct user_namespace *ns, kuid_t uid, + struct keyring_index_key *index_key, + key_ref_t *persistent_ref) { struct key *persistent; - key_ref_t reg_ref, persistent_ref; + key_ref_t reg_ref; if (!ns->persistent_keyring_register) { - long err = key_create_persistent_register(ns); + int err = key_create_persistent_register(ns); + if (err < 0) - return ERR_PTR(err); + return err; } else { reg_ref = make_key_ref(ns->persistent_keyring_register, true); - persistent_ref = find_key_to_update(reg_ref, index_key); - if (persistent_ref) - return persistent_ref; + *persistent_ref = find_key_to_update(reg_ref, index_key); + if (*persistent_ref) + return 1; } persistent = keyring_alloc(index_key->description, @@ -61,9 +67,10 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, KEY_ALLOC_NOT_IN_QUOTA, NULL, ns->persistent_keyring_register); if (IS_ERR(persistent)) - return ERR_CAST(persistent); + return PTR_ERR(persistent); - return make_key_ref(persistent, true); + *persistent_ref = make_key_ref(persistent, true); + return 0; } /* @@ -78,6 +85,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, key_ref_t reg_ref, persistent_ref; char buf[32]; long ret; + bool created = false; /* Look in the register if it exists */ memset(&index_key, 0, sizeof(index_key)); @@ -100,23 +108,24 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, * also need to create the register. */ down_write(&ns->keyring_sem); - persistent_ref = key_create_persistent(ns, uid, &index_key); + ret = key_get_or_create_persistent(ns, uid, &index_key, + &persistent_ref); up_write(&ns->keyring_sem); - if (!IS_ERR(persistent_ref)) - goto found; - - return PTR_ERR(persistent_ref); + if (ret < 0) + return ret; + created = ret == 0; found: + persistent = key_ref_to_ptr(persistent_ref); ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK); - if (ret == 0) { - persistent = key_ref_to_ptr(persistent_ref); + if (ret == 0) ret = key_link(key_ref_to_ptr(dest_ref), persistent); - if (ret == 0) { - key_set_timeout(persistent, persistent_keyring_expiry); - ret = persistent->serial; - } - } + + if (ret == 0 || created) + key_set_timeout(persistent, persistent_keyring_expiry); + + if (ret == 0) + ret = persistent->serial; key_ref_put(persistent_ref); return ret; From dd3ea3fcba7c75493760cdbccd35f029597e8d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=B1=EB=B3=91=EC=B0=AC?= Date: Wed, 30 Sep 2026 13:18:01 +0900 Subject: [PATCH 477/590] KEYS: Fix add_key() race with keyring restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __key_create_or_update() snapshots keyring->restrict_link before taking the destination keyring's semaphore. keyring_restrict() installs a restriction while holding that semaphore. This allows a writer to observe no restriction, wait for the keyring owner to install a reject-all restriction and return successfully, and then link a key using the stale NULL snapshot. The writer only needs write permission on the destination keyring. Move the restrict_link read after __key_link_lock() and __key_link_begin(). The read and the subsequent restriction check are then serialized with restriction installation by keyring->sem. The race was reproduced on v7.2.8 in 19 executions where restriction installation returned before the link completed. All 19 linked the key despite the reject-all restriction. With this change, 312 executions reached the same ordering and every add_key() call failed with -EPERM. The issue was found by manual concurrency analysis assisted by AI-based analysis and independently verified with a QEMU reproducer and kernel instrumentation. Fixes: 5ac7eace2d00 ("KEYS: Add a facility to restrict new links into a keyring") Cc: stable@vger.kernel.org Signed-off-by: 성병찬 Reviewed-by: Jarkko Sakkinen Link: https://lore.kernel.org/r/20260930041802.6114-1-tjdqudcks0424@naver.com Signed-off-by: Jarkko Sakkinen --- security/keys/key.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/keys/key.c b/security/keys/key.c index b34a64d81d47..a438c4508595 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -840,9 +840,6 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, key_check(keyring); - if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) - restrict_link = keyring->restrict_link; - key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) goto error_put_type; @@ -880,6 +877,9 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, goto error_link_end; } + if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) + restrict_link = keyring->restrict_link; + if (restrict_link && restrict_link->check) { ret = restrict_link->check(keyring, index_key.type, &prep.payload, restrict_link->key); From a90ee4305c4a5df72c11b31dacfdc76e00fcf78a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 4 Oct 2026 13:45:25 -0700 Subject: [PATCH 478/590] 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* From 5158353121bae4f552cb677ce8c5c3ef52f93a0d Mon Sep 17 00:00:00 2001 From: Zixuan Chai Date: Sun, 27 Sep 2026 01:04:39 +0800 Subject: [PATCH 479/590] net: bridge: avoid recursive multicast port cleanup br_multicast_sg_del_exclude_ports() removes automatically added STAR_EXCL port groups by calling br_multicast_del_pg(). For S,G entries, that deletion calls back into br_multicast_sg_del_exclude_ports(), so a large number of STAR_EXCL ports consumes one kernel stack frame per port and can hit the stack guard page. Keep the complete port-group deletion path for cleanup, but suppress the S,G exclude-port cleanup while that helper is already walking the same entry. Normal deletion callers retain the existing behavior. Fixes: 8266a0491e92 ("net: bridge: mcast: handle port group filter modes") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Signed-off-by: Zixuan Chai Signed-off-by: Ren Wei Signed-off-by: David S. Miller --- net/bridge/br_multicast.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 2f9bb30e1a1f..3fbad7b59769 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -81,6 +81,10 @@ __br_multicast_add_group(struct net_bridge_mcast *brmctx, bool blocked); static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg); +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports); static void __br_multicast_stop(struct net_bridge_mcast *brmctx); static int br_mc_disabled_update(struct net_device *dev, bool value, @@ -458,7 +462,7 @@ static void br_multicast_sg_del_exclude_ports(struct net_bridge_mdb_entry *sgmp) for (pp = &sgmp->ports; (p = mlock_dereference(*pp, sgmp->br)) != NULL;) { if (!(p->flags & MDB_PG_FLAGS_PERMANENT)) - br_multicast_del_pg(sgmp, p, pp); + __br_multicast_del_pg(sgmp, p, pp, false); else pp = &p->next; } @@ -799,9 +803,10 @@ static void br_multicast_destroy_port_group(struct net_bridge_mcast_gc *gc) kfree_rcu(pg, rcu); } -void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, - struct net_bridge_port_group *pg, - struct net_bridge_port_group __rcu **pp) +static void __br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp, + bool sg_del_exclude_ports) { struct net_bridge *br = pg->key.port->br; struct net_bridge_group_src *ent; @@ -820,7 +825,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, if (!br_multicast_is_star_g(&mp->addr)) { rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode, br_sg_port_rht_params); - br_multicast_sg_del_exclude_ports(mp); + if (sg_del_exclude_ports) + br_multicast_sg_del_exclude_ports(mp); } else { br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE); } @@ -832,6 +838,13 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, mod_timer(&mp->timer, jiffies); } +void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, + struct net_bridge_port_group *pg, + struct net_bridge_port_group __rcu **pp) +{ + __br_multicast_del_pg(mp, pg, pp, true); +} + static void br_multicast_find_del_pg(struct net_bridge *br, struct net_bridge_port_group *pg) { From aaaaf87ea99b8766c9a8aa0e71aa42e6bc8a5320 Mon Sep 17 00:00:00 2001 From: "Denis V. Lunev" Date: Tue, 29 Sep 2026 09:25:19 +0200 Subject: [PATCH 480/590] openvswitch: fix soft lockup in the netlink flow dump A production compute node carrying a few thousand datapath flows hit a soft lockup inside a single netlink flow dump and panicked. ovs_flow_cmd_dump() calls ovs_flow_stats_get() for every flow it emits, and that releases stats->lock with spin_unlock_bh() once per CPU that has touched the flow. Every release is a local_bh_enable(), and each one runs the pending softirq backlog in the dumping thread's own context. The skb bounds the flows one callback emits, but not the softirq work it absorbs. On a CPU that carries the box's packet load the backlog refills as fast as it drains, so the dumping thread becomes that CPU's softirq engine. It never sleeps and it has no reschedule point, so under voluntary preemption nothing can take the CPU away from it: neither the ksoftirqd the kernel woke to take the work over, nor the stopper thread the softlockup detector dispatches to refresh its timestamp. Hold BH off across the whole callback instead, the way ctnetlink_dump_table() does, so the nested spin_unlock_bh() stop draining softirqs. The loop already runs under rcu_read_lock() and cannot sleep. What it gives up is preemption under CONFIG_PREEMPT, since a BH-off region is not preemptible outside PREEMPT_RT. The region stays short: the skb caps the flows one callback emits, and empty buckets cost no skb space but are each visited once per dump, as the cursor only moves forward. The table grows on insert and shrinks only on flush, so the walk is bounded by the largest flow count the datapath has held. The softirq backlog the callback used to absorb has no bound at all. Fixes: 63e7959c4b9b ("openvswitch: Per NUMA node flow stats.") Cc: stable@vger.kernel.org Signed-off-by: Denis V. Lunev Reviewed-by: Ilya Maximets Signed-off-by: David S. Miller --- net/openvswitch/datapath.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index 631a03136fa1..4fc5d0bebd85 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -1532,6 +1532,12 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) return -ENODEV; } + /* + * Not needed for safety. Stops every spin_unlock_bh() in + * ovs_flow_stats_get() from running the softirq backlog. + */ + local_bh_disable(); + ti = rcu_dereference(dp->table.ti); for (;;) { struct sw_flow *flow; @@ -1552,6 +1558,8 @@ static int ovs_flow_cmd_dump(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = bucket; cb->args[1] = obj; } + + local_bh_enable(); rcu_read_unlock(); return skb->len; } From 3e6ff34afc7ec7a6156e58502b5a56771517cf93 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 18 Sep 2026 11:58:00 +0100 Subject: [PATCH 481/590] btrfs: clear BTRFS_ROOT_IN_TRANS_SETUP on early exit from record_root_in_trans() If we exit early because the transaction that last used the root already matches the current transaction, we leave the BTRFS_ROOT_IN_TRANS_SETUP bit set in the root (which we just set right before the exit). While this does not cause any functional issue, it makes callers of btrfs_record_root_in_trans() lock fs_info->reloc_mutex and call record_root_in_trans() for nothing, causing unnecessary lock contention, until one of them clears the bit in record_root_in_trans(). One caller of btrfs_record_root_in_trans() is start_transaction(), used to start new transaction or joining an existing one, which is a hot path. So clear BTRFS_ROOT_IN_TRANS_SETUP on early exit. Assisted-by: LLM Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 6802b94ed76f..11b38133f737 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -432,6 +432,7 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, spin_lock(&fs_info->fs_roots_radix_lock); if (btrfs_get_root_last_trans(root) == trans->transid && !force) { spin_unlock(&fs_info->fs_roots_radix_lock); + clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state); return 0; } radix_tree_tag_set(&fs_info->fs_roots_radix, From 1fd742c7479961da6d0bb794ef8ac6b7b9442847 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:16:10 +0000 Subject: [PATCH 482/590] btrfs: scrub: fix local_root reference leak in scrub_print_warning_inode() When paths_from_inode() fails, scrub_print_warning_inode() jumps to err without dropping the reference taken by btrfs_get_fs_root(), leaking a reference to the root every time path resolution fails while printing scrub warnings. Every other error and success path of the function drops the reference. Drop the reference on the paths_from_inode() failure path too. Fixes: 558540c17771 ("btrfs scrub: print paths of corrupted files") CC: stable@vger.kernel.org Reviewed-by: Qu Wenruo Signed-off-by: Wentao Liang Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/scrub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index c09d4213ad89..479c0e037015 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -536,8 +536,10 @@ static int scrub_print_warning_inode(u64 inum, u64 offset, u64 num_bytes, } ret = paths_from_inode(inum, ipath); - if (ret < 0) + if (ret < 0) { + btrfs_put_root(local_root); goto err; + } /* * we deliberately ignore the bit ipath might have been too small to From 80a8dbb30d2b00b9c671b02bd73f700f6d20100f Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:32 +0800 Subject: [PATCH 483/590] btrfs: always return -EIOCBQUEUED after btrfs_uring_read_extent_endio If all bios finish before btrfs_encoded_read_regular_fill_pages() returns, it calls btrfs_uring_read_extent_endio() and previously returned the I/O status. A negative errno then made btrfs_uring_read_extent() unlock and free while btrfs_uring_read_finished() did the same again. Return -EIOCBQUEUED so only the deferred path cleans up. Reported-by: Yue Sun Closes: https://lore.kernel.org/linux-btrfs/20260630091609.3414-1-samsun1006219@gmail.com/ Suggested-by: Jens Axboe Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e97446fc9e10..6a085a4d5d61 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9623,7 +9623,6 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, struct completion sync_reads; unsigned long i = 0; struct btrfs_bio *bbio; - int ret; /* * Fast path for synchronous reads which completes in this call, io_uring @@ -9670,10 +9669,10 @@ int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, if (uring_ctx) { if (refcount_dec_and_test(&priv->pending_refs)) { - ret = blk_status_to_errno(READ_ONCE(priv->status)); - btrfs_uring_read_extent_endio(uring_ctx, ret); + int error = blk_status_to_errno(READ_ONCE(priv->status)); + + btrfs_uring_read_extent_endio(uring_ctx, error); kfree(priv); - return ret; } return -EIOCBQUEUED; From a8ea92830b3e0e163d4e829bdcb17ca83c0f951c Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:33 +0800 Subject: [PATCH 484/590] btrfs: free iov when btrfs_uring_read_extent() fails After btrfs_uring_read_extent(), the caller always jumped to out_acct. That skips kfree(data->iov), which is only correct for -EIOCBQUEUED where the deferred path owns the iov. On failure, fall through to out_free instead. Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 21c4e755f9c5..58eba94a73b8 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4865,8 +4865,8 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue cached_state, disk_bytenr, disk_io_size, count, data->args.compression, data->iov, cmd); - - goto out_acct; + if (ret == -EIOCBQUEUED) + goto out_acct; } out_free: From b6e8add5d79d1f6f819a054c833bab452bd7365d Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:34 +0800 Subject: [PATCH 485/590] btrfs: unlock inode and extent in caller when io_uring read extent fails btrfs_uring_read_extent() runs only after btrfs_encoded_read() has taken the inode shared lock and the extent lock. On failure it used to unlock in out_fail, and a pages-array allocation failure returned -ENOMEM without unlocking at all. Unlock in the caller instead on all failure returns, matching the copy_to_user() error path. The deferred -EIOCBQUEUED path still unlocks in btrfs_uring_read_finished(). Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Suggested-by: Qu Wenruo Reviewed-by: Qu Wenruo Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 58eba94a73b8..131373bf1e30 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4601,7 +4601,7 @@ static void btrfs_uring_read_finished(struct io_tw_req tw_req, io_tw_token_t tw) size_t page_offset; ssize_t ret; - /* The inode lock has already been acquired in btrfs_uring_read_extent. */ + /* The inode lock has already been acquired in btrfs_encoded_read(). */ btrfs_lockdep_inode_acquire(inode, i_rwsem); if (priv->err) { @@ -4667,7 +4667,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, struct iovec *iov, struct io_uring_cmd *cmd) { struct btrfs_inode *inode = BTRFS_I(file_inode(iocb->ki_filp)); - struct extent_io_tree *io_tree = &inode->io_tree; struct page **pages = NULL; struct btrfs_uring_priv *priv = NULL; unsigned long nr_pages; @@ -4723,8 +4722,6 @@ static int btrfs_uring_read_extent(struct kiocb *iocb, struct iov_iter *iter, return -EIOCBQUEUED; out_fail: - btrfs_unlock_extent(io_tree, start, lockend, &cached_state); - btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); kfree(priv); for (int i = 0; i < nr_pages; i++) { if (pages[i]) @@ -4867,6 +4864,8 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue data->iov, cmd); if (ret == -EIOCBQUEUED) goto out_acct; + btrfs_unlock_extent(io_tree, start, lockend, &cached_state); + btrfs_inode_unlock(inode, BTRFS_ILOCK_SHARED); } out_free: From 51562a8cb11628d3a5e105b4c347ec5473e5745f Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:35 +0800 Subject: [PATCH 486/590] btrfs: don't stash io_uring encoded data across -EAGAIN Returning -EAGAIN while leaving btrfs_uring_encoded_data in the cmd PDU leaks if the request is cancelled or the ring exits before reissue. io_uring does not free driver PDU allocations on cleanup. Write: io_queue_sqe() always issues with IO_URING_F_NONBLOCK first, so return -EAGAIN before allocating and free data on every exit. Read: free on nowait -EAGAIN too; only -EIOCBQUEUED keeps the allocation for btrfs_uring_read_finished(). Fixes: 34310c442e17 ("btrfs: add io_uring command for encoded reads (ENCODED_READ ioctl)") Fixes: e32dcdb0af9f ("btrfs: add io_uring interface for encoded writes") Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 131373bf1e30..44d582d8cf3f 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4834,7 +4834,7 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue ret = btrfs_encoded_read(&kiocb, &data->iter, &data->args, &cached_state, &disk_bytenr, &disk_io_size); if (ret == -EAGAIN) - goto out_acct; + goto out_free; if (ret < 0 && ret != -EIOCBQUEUED) goto out_free; @@ -4876,8 +4876,10 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue add_rchar(current, ret); inc_syscr(current); - if (ret != -EIOCBQUEUED && ret != -EAGAIN) + if (ret != -EIOCBQUEUED) { kfree(data); + bc->data = NULL; + } return ret; } @@ -4906,6 +4908,11 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu goto out_acct; } + if (issue_flags & IO_URING_F_NONBLOCK) { + ret = -EAGAIN; + goto out_acct; + } + if (!data) { data = kzalloc_obj(*data, GFP_NOFS); if (!data) { @@ -4974,11 +4981,6 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu } } - if (issue_flags & IO_URING_F_NONBLOCK) { - ret = -EAGAIN; - goto out_acct; - } - pos = data->args.offset; ret = rw_verify_area(WRITE, file, &pos, data->args.len); if (ret < 0) @@ -5004,8 +5006,8 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu add_wchar(current, ret); inc_syscw(current); - if (ret != -EAGAIN) - kfree(data); + kfree(data); + bc->data = NULL; return ret; } From 858bee1c77857aabcc2327cd32dd8266332de332 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 25 Sep 2026 13:47:21 +0100 Subject: [PATCH 487/590] btrfs: fix xattr replace when multiple xattrs are packed in the same item If we have a btrfs_dir_item item that packs multiple xattrs and then we replace the value of one of them (with the setxattr(2) family of syscalls) with another value of a different size, we end up not having a fully initialized btrfs_dir_item, resulting in a corruption that the tree checker will detect at extent buffer writeback time. This is because in btrfs_setxattr() when we find a btrfs_dir_item with multiple xattrs (due to the crc32c hash of their name being the same) we delete one of the xattr items (btrfs_dir_item) and then insert a new one, but the deletion and insertion results in shifting existing data in the leaf and therefore when the new value of a xattr has a different size, the new btrfs_dir_item is placed in a leaf section that was not initialized and we only copy the value's data and set the value's length in the new btrfs_dir_item, without setting the name, the name's length, the key (which must be all zeroes for xattrs), flags (BTRFS_FT_XATTR) and transaction ID. The following script reproduces the issue: $ cat test.sh #!/bin/bash DEV=/dev/sdi MNT=/mnt/sdi mkfs.btrfs -f $DEV mount $DEV $MNT touch $MNT/testfile # Add two xattrs that, on btrfs, have the same hash (crc32c) for their # name and therefore are packed into the same btrfs_dir_item. setfattr -n user.foobar -v 123 $MNT/testfile setfattr -n user.WvG1c1Td -v qwerty $MNT/testfile # Verify the xattrs are present. echo "xattrs before:" getfattr --absolute-names --dump $MNT/testfile # Now replace the value of the foobar xattr with a significantly larger # value. setfattr -n user.foobar -v abcdefghijklmnopqrstuvwxyz $MNT/testfile # Check the xattrs have the expected values. echo "xattrs after:" getfattr --absolute-names --dump $MNT/testfile umount $MNT Running it: $ ./test.sh (...) xattrs before: # file: /mnt/sdi/testfile user.WvG1c1Td="qwerty" user.foobar="123" xattrs after: # file: /mnt/sdi/testfile user.WvG1c1Td="qwerty" So the "user.foobar" xattr is missing and there was a transaction abort when unmounting the fs with the following traces in dmesg: $ dmesg [869800.159271] BTRFS warning (device sdi): access to eb bytenr 30474240 len 16384 out of range start 16015 len 25964 [869800.159293] ------------[ cut here ]------------ [869800.159296] WARNING: fs/btrfs/extent_io.c:4408 at report_eb_range+0x44/0x60 [btrfs], CPU#8: getfattr/2605179 [869800.168961] Modules linked in: btrfs dm_thin_pool (...) [869800.190527] CPU: 8 UID: 0 PID: 2605179 Comm: getfattr Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full) [869800.193726] Tainted: [W]=WARN [869800.194502] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [869800.197576] RIP: 0010:report_eb_range+0x44/0x60 [btrfs] [869800.198985] Code: 48 8b 7b 18 (...) [869800.203531] RSP: 0018:ffffce4541937d58 EFLAGS: 00010246 [869800.204609] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000 [869800.206137] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffffffffc04c92a0 [869800.207645] RBP: 0000000000003e8f R08: 0000000000000000 R09: 3fffffffffefffff [869800.226175] R10: ffffce4541937a88 R11: 0000000000000003 R12: 000000000000656c [869800.227594] R13: ffff8dde14be800f R14: 000000000000656c R15: 0000000000000069 [869800.229097] FS: 00007f59023bb780(0000) GS:ffff8de5788ed000(0000) knlGS:0000000000000000 [869800.231137] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [869800.232321] CR2: 0000558dc45e6a78 CR3: 0000000765b16004 CR4: 0000000000370ef0 [869800.233783] Call Trace: [869800.234330] [869800.234791] read_extent_buffer+0x4d/0x100 [btrfs] [869800.235898] btrfs_listxattr+0x199/0x240 [btrfs] [869800.236912] vfs_listxattr+0x51/0xa0 [869800.237683] listxattr+0x7e/0x100 [869800.238398] path_listxattrat+0x9e/0x190 [869800.239117] do_syscall_64+0x89/0x470 [869800.239877] entry_SYSCALL_64_after_hwframe+0x76/0x7e [869800.240914] RIP: 0033:0x7f59024cdcb7 [869800.241686] Code: f0 ff ff 73 (...) [869800.245353] RSP: 002b:00007ffd056dcdb8 EFLAGS: 00000246 ORIG_RAX: 00000000000000c2 [869800.246892] RAX: ffffffffffffffda RBX: 00007ffd056df2e2 RCX: 00007f59024cdcb7 [869800.248853] RDX: 0000000000006600 RSI: 0000558dc45e0470 RDI: 00007ffd056df2e2 [869800.250514] RBP: 00007ffd056df2e2 R08: 0000000000006600 R09: 0000000000006600 [869800.252295] R10: 0000000000000004 R11: 0000000000000246 R12: 00000000ffffff9c [869800.254105] R13: 0000558dc45e0470 R14: 0000000000006600 R15: 0000000000000000 [869800.255931] [869800.256529] ---[ end trace 0000000000000000 ]--- [869800.260071] page: refcount:2 mapcount:0 mapping:000000007ccfc77f index:0x1d10 pfn:0x608c69 [869800.260075] memcg:ffff8dde00344d40 [869800.260076] aops:btree_aops [btrfs] ino:1 [869800.260151] flags: 0x17fffc00000402a(uptodate|lru|private|writeback|node=0|zone=2|lastcpupid=0x1ffff) [869800.260154] raw: 017fffc00000402a fffff4adc773d4c8 fffff4adc48f3d88 ffff8de36504ba30 [869800.260155] raw: 0000000000001d10 ffff8dde054b8738 00000002ffffffff ffff8dde00344d40 [869800.260156] page dumped because: eb page dump [869800.260157] BTRFS critical (device sdi): corrupt leaf: root=5 block=30474240 slot=5 ino=257, invalid location key type, have 46, expect 132 or 1 [869800.260161] BTRFS info (device sdi): leaf 30474240 gen 9 total ptrs 6 free space 15629 owner 5 [869800.260163] BTRFS info (device sdi): refs 3 lock_owner 0 current 2550294 [869800.260164] item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 [869800.260165] inode generation 3 transid 0 size 0 nbytes 16384 [869800.260166] block group 0 mode 40755 links 1 uid 0 gid 0 [869800.260167] rdev 0 sequence 0 flags 0x0 [869800.260168] atime 1790340637.0 [869800.260169] ctime 1790340637.0 [869800.260169] mtime 1790340637.0 [869800.260170] otime 1790340637.0 [869800.260170] item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 [869800.260172] index 0 name_len 2 [869800.260172] item 2 key (256 DIR_ITEM 982728850) itemoff 16073 itemsize 38 [869800.260173] location key (257 1 0) type 1 [869800.260174] transid 9 data_len 0 name_len 8 [869800.260175] item 3 key (257 INODE_ITEM 0) itemoff 15913 itemsize 160 [869800.260176] inode generation 9 transid 9 size 0 nbytes 0 [869800.260177] block group 0 mode 100664 links 1 uid 0 gid 0 [869800.260177] rdev 0 sequence 0 flags 0x0 [869800.260178] atime 1790340638.38778502 [869800.260179] ctime 1790340638.38778502 [869800.260179] mtime 1790340638.38778502 [869800.260180] otime 1790340638.38778502 [869800.260180] item 4 key (257 INODE_REF 256) itemoff 15895 itemsize 18 [869800.260181] index 2 name_len 8 [869800.260182] item 5 key (257 XATTR_ITEM 751495445) itemoff 15779 itemsize 116 [869800.260183] location key (0 0 0) type 8 [869800.266052] transid 9 data_len 6 name_len 13 [869800.266053] location key (8243121639454149888 46 7229457603934778967) type 0 [869800.266055] transid 113 data_len 26 name_len 0 [869800.266056] location key (8608196880778817904 120 162425) type 9 [869800.266057] transid 8391162079612502016 data_len 26982 name_len 25964 [869800.266058] BTRFS error (device sdi): block=30474240 write time tree block corruption detected [869800.266091] ------------[ cut here ]------------ [869800.266092] WARNING: fs/btrfs/disk-io.c:336 at btree_csum_one_bio+0x20b/0x220 [btrfs], CPU#7: kworker/u50:7/2550294 [869800.268392] Modules linked in: btrfs dm_thin_pool (...) [869800.365851] CPU: 7 UID: 0 PID: 2550294 Comm: kworker/u50:7 Tainted: G W 7.3.0-rc3-btrfs-next-244+ #1 PREEMPT(full) [869800.368937] Tainted: [W]=WARN [869800.369834] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [869800.372778] Workqueue: writeback wb_workfn (flush-btrfs-3821) [869800.374314] RIP: 0010:btree_csum_one_bio+0x20b/0x220 [btrfs] [869800.375915] Code: 89 44 24 04 (...) [869800.380639] RSP: 0018:ffffce4548e3f7d0 EFLAGS: 00010246 [869800.382008] RAX: 0000000000000000 RBX: ffff8dde054b8738 RCX: 0000000000000000 [869800.383850] RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff8de091c2ddc0 [869800.385710] RBP: ffff8dde196a2000 R08: 0000000000000000 R09: 3fffffffffefffff [869800.387388] R10: ffffce4548e3f500 R11: 0000000000000003 R12: ffffce4548e3f7d8 [869800.388973] R13: ffff8dde196a2000 R14: ffff8de36504b750 R15: ffff8dde4c497b00 [869800.390414] FS: 0000000000000000(0000) GS:ffff8de5788ad000(0000) knlGS:0000000000000000 [869800.392002] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [869800.393160] CR2: 000055cd6e92ad5c CR3: 00000007cb264001 CR4: 0000000000370ef0 [869800.394595] Call Trace: [869800.395113] [869800.395570] btrfs_submit_bbio+0x872/0x890 [btrfs] [869800.397284] write_meta_extent_buffer+0x70/0x80 [btrfs] [869800.398940] btree_writepages+0x141/0x4f0 [btrfs] [869800.400426] ? get_random_u32+0x8a/0xf0 [869800.401417] ? build_slab_freelist+0x47/0x130 [869800.402574] ? preempt_count_add+0x6b/0xa0 [869800.403633] ? _raw_spin_lock_irqsave+0x23/0x50 [869800.404807] ? _raw_spin_unlock_irqrestore+0x22/0x40 [869800.406085] ? alloc_from_new_slab+0x18f/0x330 [869800.407223] do_writepages+0xc6/0x160 [869800.408191] ? refill_objects+0xd8/0x300 [869800.409211] __writeback_single_inode+0x42/0x350 [869800.410408] writeback_sb_inodes+0x231/0x560 [869800.411511] wb_writeback+0x8a/0x300 [869800.412440] wb_workfn+0xbf/0x460 [869800.413291] ? _raw_spin_unlock+0x14/0x30 [869800.414328] ? finish_task_switch.isra.0+0xb9/0x380 [869800.415105] process_one_work+0x1d1/0x3d0 [869800.416633] worker_thread+0x1c4/0x330 [869800.417467] ? __pfx_worker_thread+0x10/0x10 [869800.418452] kthread+0xfc/0x130 [869800.419257] ? __pfx_kthread+0x10/0x10 [869800.420089] ret_from_fork+0x1f7/0x2c0 [869800.420863] ? __pfx_kthread+0x10/0x10 [869800.421654] ret_from_fork_asm+0x1a/0x30 [869800.422484] [869800.422953] ---[ end trace 0000000000000000 ]--- [869800.424005] BTRFS error (device sdi state A): Transaction 9 aborted (-EIO) [869800.424010] BTRFS: error (device sdi state A) in __btrfs_run_delayed_items:1162: errno=-5 IO failure [869800.424011] BTRFS info (device sdi state EA): forced readonly [869800.424013] BTRFS warning (device sdi state EA): Skipping commit of aborted transaction. [869800.424014] BTRFS: error (device sdi state EA) in cleanup_transaction:2076: errno=-5 IO failure Fix this by always setting all fields in the new btrfs_dir_item when we replace an existing xattr. Fixes: 5f5bc6b1e2d5 ("Btrfs: make xattr replace operations atomic") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/xattr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index ab55d10bd71f..e8762e12f3e1 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -161,6 +161,7 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, const u16 old_data_len = btrfs_dir_data_len(leaf, di); const u32 item_size = btrfs_item_size(leaf, slot); const u32 data_size = sizeof(*di) + name_len + size; + unsigned long name_ptr; unsigned long data_ptr; char *ptr; @@ -189,8 +190,16 @@ int btrfs_setxattr(struct btrfs_trans_handle *trans, struct inode *inode, ptr = btrfs_item_ptr(leaf, slot, char); ptr += btrfs_item_size(leaf, slot) - data_size; di = (struct btrfs_dir_item *)ptr; + memzero_extent_buffer(leaf, (unsigned long)ptr + + offsetof(struct btrfs_dir_item, location), + sizeof(struct btrfs_disk_key)); + btrfs_set_dir_flags(leaf, di, BTRFS_FT_XATTR); + btrfs_set_dir_transid(leaf, di, trans->transid); + btrfs_set_dir_name_len(leaf, di, name_len); btrfs_set_dir_data_len(leaf, di, size); + name_ptr = (unsigned long)(di + 1); data_ptr = ((unsigned long)(di + 1)) + name_len; + write_extent_buffer(leaf, name, name_ptr, name_len); write_extent_buffer(leaf, value, data_ptr, size); } else { /* From 41c8899d59898f6fa51ceebd2b6e55257191a8ee Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 25 Sep 2026 17:12:45 +0100 Subject: [PATCH 488/590] btrfs: fix lost error return value in btrfs_listxattr() If the input buffer does not have enough space to store the current xattr, we set 'iter_ret' to -ERANGE and then do "break", but that only exits the while loop over the xattrs in the current btrfs_dir_item, and then we continue the btrfs_for_each_slot() iteration, which overwrites the value of 'iter_ret' causing us to lose the error return value and proceed as if the buffer has enough space. Fix this by returning -ERANGE directly (the path is automatically freed) instead of breaking from the while loop. Fixes: 184b3d190087 ("btrfs: use btrfs_for_each_slot in btrfs_listxattr") Assisted-by: LLM (found the bug) Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/xattr.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index e8762e12f3e1..cb001a211f55 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -329,10 +329,8 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size) if (!size) goto next; - if (!buffer || (name_len + 1) > size_left) { - iter_ret = -ERANGE; - break; - } + if (!buffer || (name_len + 1) > size_left) + return -ERANGE; read_extent_buffer(leaf, buffer, name_ptr, name_len); buffer[name_len] = '\0'; From 381a0925c556a8d91a80a9b9e3e86651d60ffee3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:24:42 +0800 Subject: [PATCH 489/590] Bluetooth: hci_sock: Serialize dead-device detachment Rebinding an HCI socket after its controller is unregistered can race monitor control replay and crash the kernel. hci_sock_bind() clears hci_pi(sk)->hdev and drops the device reference under only the socket lock, whereas send_monitor_control_replay() holds hci_sk_list.lock for reading. Replay can observe a non-NULL hdev, then bind can clear it and drop the last reference before create_monitor_ctrl_open() reads hdev->id. This can cause a NULL pointer dereference if the pointer is reloaded, or a use-after-free if the old pointer is retained. The kernel reported: KASAN: null-ptr-deref in range [0x0000000000000068-0x000000000000006f] RIP: 0010:create_monitor_ctrl_open+0x579/0x800 Call Trace: hci_sock_bind+0xd96/0x1190 __sys_bind+0x166/0x200 __x64_sys_bind+0x6d/0xb0 do_syscall_64+0xdd/0x4a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The same missing serialization lets hci_send_to_sock() select a socket for the old device and queue its frame after bind has detached the socket or installed a new binding. Take hci_sk_list.lock for writing when clearing the device pointer and resetting the socket state. This waits for existing replay and delivery readers to finish before detachment and prevents later readers from using the old binding. Keep hci_dev_put() outside the critical section because the final device release can sleep. Fixes: e04480920d1e ("Bluetooth: defer cleanup of resources in hci_unregister_dev()") 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_sock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 6d56c77741e1..81068b585764 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1223,8 +1223,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, */ hdev = hci_pi(sk)->hdev; if (hdev && hci_dev_test_flag(hdev, HCI_UNREGISTER)) { + write_lock(&hci_sk_list.lock); hci_pi(sk)->hdev = NULL; sk->sk_state = BT_OPEN; + write_unlock(&hci_sk_list.lock); hci_dev_put(hdev); } hdev = NULL; From 9af12272061d206af8bc3e40215cb694421ab73a Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:26:12 +0800 Subject: [PATCH 490/590] Bluetooth: hci_sync: Fix command skb lifetime during scan setup During PA sync scan setup, hci_le_set_ext_scan_param_sync() gets the address used for the PA_LINK lookup from hci_sent_cmd_data(). The returned pointer borrows storage from sent_cmd or req_skb, without holding a reference to either skb. The scan worker runs on req_workqueue while hci_cmd_work() runs on the separate device workqueue. After the scan worker loads sent_cmd, the command worker can free and replace it before hci_sent_cmd_data() dereferences the skb. The command payload can also be freed between returning from the helper and comparing the address. The connection lookup's RCU critical section does not protect the command skb. KASAN reported: BUG: KASAN: slab-use-after-free in hci_sent_cmd_data+0x27c/0x2f0 Workqueue: hci0 hci_cmd_sync_work Call Trace: hci_sent_cmd_data+0x27c/0x2f0 hci_le_set_scan_param_sync+0x43a/0x850 hci_passive_scan_sync+0xb09/0x1460 hci_update_passive_scan_sync+0x47c/0x6e0 hci_le_pa_create_sync+0x200/0xa70 hci_cmd_sync_work+0x13c/0x290 Allocated by task 95: skb_clone+0x13f/0x340 hci_cmd_work+0x2a9/0x7e0 Freed by task 95: kmem_cache_free+0xba/0x3a0 hci_cmd_work+0x29c/0x7e0 Use hdev->lock to serialize the address snapshot with sent_cmd replacement. Protect req_skb replacement and completion cleanup with the same lock because the helper falls back to that skb. Copy the address before releasing the lock and use the copy for the existing RCU-protected connection lookup. Release the mutex before sending or waiting for commands, preserving the lookup and completion ordering. Fixes: 22cbf4f84c00 ("Bluetooth: hci_sync: Use QoS to determine which PHY to scan") 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_core.c | 4 ++++ net/bluetooth/hci_sync.c | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 2076689eb302..74d9865e08c2 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4115,9 +4115,11 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) bt_dev_dbg(hdev, "skb %p", skb); + hci_dev_lock(hdev); kfree_skb(hdev->sent_cmd); hdev->sent_cmd = skb_clone(skb, GFP_KERNEL); + hci_dev_unlock(hdev); if (!hdev->sent_cmd) { skb_queue_head(&hdev->cmd_q, skb); queue_work(hdev->workqueue, &hdev->cmd_work); @@ -4138,8 +4140,10 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = skb_get(hdev->sent_cmd); + hci_dev_unlock(hdev); } return err; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c01c8b58d9e8..a92a81846e9d 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -32,8 +32,10 @@ static void hci_cmd_sync_complete(struct hci_dev *hdev, u8 result, u16 opcode, WRITE_ONCE(hdev->req_status, HCI_REQ_DONE); /* Free the request command so it is not used as response */ + hci_dev_lock(hdev); kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_unlock(hdev); if (skb) { struct sock *sk = hci_skb_sk(skb); @@ -3064,15 +3066,21 @@ static int hci_le_set_ext_scan_param_sync(struct hci_dev *hdev, u8 type, */ if (hci_dev_test_flag(hdev, HCI_PA_SYNC)) { struct hci_cp_le_add_to_accept_list *sent; + bdaddr_t bdaddr; + hci_dev_lock(hdev); sent = hci_sent_cmd_data(hdev, HCI_OP_LE_ADD_TO_ACCEPT_LIST); + if (sent) + bacpy(&bdaddr, &sent->bdaddr); + hci_dev_unlock(hdev); + if (sent) { struct hci_conn *conn; rcu_read_lock(); conn = hci_conn_hash_lookup_ba(hdev, PA_LINK, - &sent->bdaddr); + &bdaddr); if (conn) { struct bt_iso_qos *qos = &conn->iso_qos; From 555ff78fa719f50ff4cdbdb40aa1acc6e8e327d3 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 00:25:48 +0800 Subject: [PATCH 491/590] Bluetooth: ISO: Reject concurrent BIS listener setup iso_sock_listen() drops the socket lock before calling iso_listen_bis() to preserve the hdev->lock ordering. Two concurrent listen() calls on the same broadcast socket can therefore both observe BT_BOUND. Caller A can attach connA and release the locks, then caller B can acquire them and attach connB before A publishes BT_LISTEN. __iso_chan_add() checks whether connB already has a socket, but not whether the socket already has a connection. The second attachment overwrites iso_pi(sk)->conn, leaking the connA reference and leaving connA->sk intact. Socket teardown follows connB, so later events on connA can cause a stale socket access. Recheck BT_BOUND and require iso_pi(sk)->conn to be NULL after taking both the HCI device and socket locks in iso_listen_bis(). The connection check also covers the interval between attachment and publication of BT_LISTEN. Reject a competing setup with -EBADFD through the existing unlock path before allocating another connection. A recorded KASAN report from an instrumented kernel shows: BUG: KASAN: slab-use-after-free in iso_sock_hold+0x119/0x1f0 Call Trace: iso_sock_hold+0x119/0x1f0 iso_conn_del+0xe4/0x270 hci_disconn_complete_evt+0x351/0x8c0 hci_event_packet+0x71b/0xb20 hci_rx_work+0x293/0x730 Allocated by task 93: sk_prot_alloc+0x113/0x220 sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 93: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 sock_close+0x10/0x20 Fixes: 168e28305b87 ("Bluetooth: iso: Fix circular lock in iso_listen_bis") Cc: stable@vger.kernel.org Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 7657c2a0abbf..2e70c0df9ef3 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -1340,6 +1340,11 @@ static int iso_listen_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (sk->sk_state != BT_BOUND || iso_pi(sk)->conn) { + err = -EBADFD; + goto unlock; + } + /* Fail if user set invalid QoS */ if (iso_pi(sk)->qos_user_set && !check_bcast_qos(&iso_pi(sk)->qos)) { iso_pi(sk)->qos = default_qos; From e32d80293a0e1a160d5e5101daa8c82148c07510 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Mon, 5 Oct 2026 15:50:31 +0800 Subject: [PATCH 492/590] Bluetooth: ISO: Serialize concurrent connect calls iso_sock_connect() checks the socket state before taking the socket lock and drops the lock again before setting up a connection. Two callers can both pass the admission check while the socket is open or bound. Caller A can copy its destination for route selection, then caller B can replace the socket destination before A binds or connects the CIS. A then uses B's destination with the route selected for its own request. B can also proceed after A attaches a connection and sets BT_CONNECT. For deferred BIS setup, B can bind a second BIS and overwrite iso_pi(sk)->conn, leaving the first connection's reference and conn->sk back-pointer stranded. Concurrent CIS connects can likewise replace the socket's connection. Later teardown only detaches the current connection, so a callback on the old connection can race with socket release and access a freed socket. KASAN reported: BUG: KASAN: slab-use-after-free in iso_sock_hold+0xf7/0x1b0 Call Trace: iso_sock_hold+0xf7/0x1b0 iso_conn_del+0x7b/0x1d0 iso_connect_cfm+0x186/0x16a0 hci_conn_failed+0x154/0x280 hci_abort_conn_sync+0x3dc/0x7d0 hci_cmd_sync_work+0x173/0x300 Allocated by task 121: sk_alloc+0x2b/0x6d0 bt_sock_alloc+0x29/0x370 iso_sock_alloc.constprop.0+0x19/0x300 iso_sock_create+0x94/0x100 Freed by task 121: kfree+0x121/0x3c0 __sk_destruct+0x42b/0x540 iso_sock_release+0x29d/0x340 __sock_release+0xa1/0x260 Check admission under the socket lock and mark the connect operation in progress before publishing its destination. Keep that flag set across route lookup and connection setup, rejecting another connect with -EBADFD before it can change the destination or attach a connection. Clear the flag after either helper returns, including on failure, so a failed setup can be retried. A separate flag is needed because the socket lock must be released before acquiring the HCI device lock, while BT_CONNECT requires an attached connection. Keep the existing state transitions and the deferred CIS completion through iso_sock_recvmsg(). Reject an existing socket attachment in __iso_chan_add() before taking a new reference or publishing either pointer, so every caller preserves the connection association. Keep the same-socket, same-connection success case for deferred CIS setup. Reject an attached socket before BIS setup so a reusable BIS is not claimed before the attachment is rejected. In CIS setup, reject a different HCI connection before iso_conn_add() and drop the per-attempt HCI hold directly. An unowned iso_conn may still carry another reference after detachment, so its destruction cannot be relied on to release that hold. iso_listen_bis() creates a fresh PA HCI/ISO pair under the device lock, so the temporary iso_conn_put() frees the ISO candidate and drops its HCI hold on rejection. iso_conn_ready() uses a freshly allocated, unpublished child with no connection, making the socket-side rejection unreachable. Fixes: ccf74f2390d6 ("Bluetooth: Add BTPROTO_ISO socket type") Cc: stable@vger.kernel.org Suggested-by: Pauli Virtanen Assisted-by: GPT-6 Astra Signed-off-by: Chengfeng Ye Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/iso.c | 47 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 2e70c0df9ef3..d6ac5b1f49bc 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -61,6 +61,7 @@ enum { BT_SK_BIG_SYNC, BT_SK_PA_SYNC, BT_SK_KILLED, + BT_SK_CONNECTING, }; struct iso_pinfo { @@ -350,6 +351,9 @@ static int __iso_chan_add(struct iso_conn *conn, struct sock *sk, return -EBUSY; } + if (iso_pi(sk)->conn) + return -EISCONN; + if (!conn->hcon) { BT_ERR("conn->hcon missing"); return -EIO; @@ -410,6 +414,11 @@ static int iso_connect_bis(struct sock *sk) hci_dev_lock(hdev); lock_sock(sk); + if (iso_pi(sk)->conn) { + err = -EISCONN; + goto unlock; + } + if (!bis_capable(hdev)) { err = -EOPNOTSUPP; goto unlock; @@ -562,6 +571,13 @@ static int iso_connect_cis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + /* The socket lock keeps the current attachment and its hcon stable. */ + if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon != hcon) { + hci_conn_drop(hcon); + err = -EISCONN; + goto unlock; + } + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -1269,17 +1285,26 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, addr->sa_family != AF_BLUETOOTH) return -EINVAL; - if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) - return -EBADFD; + lock_sock(sk); - if (sk->sk_type != SOCK_SEQPACKET) - return -EINVAL; + if ((sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) || + test_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags)) { + err = -EBADFD; + goto done; + } + + if (sk->sk_type != SOCK_SEQPACKET) { + err = -EINVAL; + goto done; + } /* Check if the address type is of LE type */ - if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) - return -EINVAL; + if (!bdaddr_type_is_le(sa->iso_bdaddr_type)) { + err = -EINVAL; + goto done; + } - lock_sock(sk); + set_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr); iso_pi(sk)->dst_type = sa->iso_bdaddr_type; @@ -1291,16 +1316,18 @@ static int iso_sock_connect(struct socket *sock, struct sockaddr_unsized *addr, else err = iso_connect_bis(sk); - if (err) - return err; - lock_sock(sk); + clear_bit(BT_SK_CONNECTING, &iso_pi(sk)->flags); + if (err) + goto done; + if (!test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { err = bt_sock_wait_state(sk, BT_CONNECTED, sock_sndtimeo(sk, flags & O_NONBLOCK)); } +done: release_sock(sk); return err; } From d333c8bca6dc3c5e978f6698d39dd799f3b0dc8d Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 30 Sep 2026 18:25:59 -0700 Subject: [PATCH 493/590] net: make dev_xdp_sb_prog_count() see programs attached through a link ethtool refuses to enable tcp-data-split on a device running a single-buffer XDP program (which can't deal with the payload landing in a separate buffer). dev_xdp_sb_prog_count() only looks at xdp_state[].prog though, ignoring bpf_link integration. Get the prog pointer from dev_xdp_prog(), which will consult both. bpf_xdp_link_update() needs a bit of a touch-up, too, since ethtool calls dev_xdp_sb_prog_count() with just the instance lock (for ops-locked devices) - we now have to make sure the link updates happen under that lock. Spotted by AI while reviewing the XDP propagation series, verified and tested on netdevsim with a C test along these lines: link = bpf_program__attach_xdp(prog, ifindex); system("ethtool -G eth0 tcp-data-split on") Fixes: 197258f0ef68 ("net: ethtool: add hds_config member in ethtool_netdev_state") Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20261001012559.297751-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- net/core/dev.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/core/dev.c b/net/core/dev.c index 18dc88990510..e76762e29360 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -10364,13 +10364,15 @@ EXPORT_SYMBOL_GPL(dev_xdp_prog_count); u8 dev_xdp_sb_prog_count(struct net_device *dev) { + struct bpf_prog *prog; u8 count = 0; int i; - for (i = 0; i < __MAX_XDP_MODE; i++) - if (dev->xdp_state[i].prog && - !dev->xdp_state[i].prog->aux->xdp_has_frags) + for (i = 0; i < __MAX_XDP_MODE; i++) { + prog = dev_xdp_prog(dev, i); + if (prog && !prog->aux->xdp_has_frags) count++; + } return count; } @@ -10750,11 +10752,12 @@ static int bpf_xdp_link_update(struct bpf_link *link, struct bpf_prog *new_prog, bpf_op = dev_xdp_bpf_op(xdp_link->dev, mode); err = dev_xdp_install(xdp_link->dev, mode, bpf_op, NULL, xdp_link->flags, new_prog); + if (!err) + old_prog = xchg(&link->prog, new_prog); netdev_unlock_ops(xdp_link->dev); if (err) goto out_unlock; - old_prog = xchg(&link->prog, new_prog); bpf_prog_put(old_prog); out_unlock: From fe4114221364899ab33c3f9574da6b108d3fa79e Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Wed, 30 Sep 2026 20:04:06 +0300 Subject: [PATCH 494/590] net/mlx5: Lag, split aggregate speed into oper and max helpers mlx5_lag_sum_devices_speed computes the LAG aggregate by summing oper speeds across all ports. This has two bugs. First, it relies on the assumption that a port whose carrier is down will report an oper speed of zero and therefore not contribute to the sum. This assumption does not always hold: when the link partner disconnects the port transitions to DOWN state but firmware may still report a non-zero oper speed, causing the aggregate to include a port that is not actively carrying traffic. Second, in active-backup mode only one port transmits at a time, so the aggregate should reflect a single port speed rather than the sum of all ports. Fix this by splitting mlx5_lag_sum_devices_speed into two helpers. mlx5_lag_get_devices_oper_speed reflects the speed currently available: it queries the vport state of each port and skips any port that is not UP, rather than relying on oper speed being zero. mlx5_lag_get_devices_max_speed is state-independent and returns the maximum achievable speed used as a fallback when speed is 0; for active-backup it takes the maximum single-port speed instead of the sum. The bugs were discovered during integration and reproduced on a back-to-back ConnectX-8 setup. The first bug (stale oper speed when link goes down) was observed: after the link partner disconnected, the aggregate TX speed reported to vports remained full speed because firmware still returned the last oper speed, causing vports to be rate-limited to the old aggregate speed. The second bug (active-backup summing all port speeds instead of the active port's speed) was also triggered: in active-backup mode the computed aggregate was double the expected value since both port speeds were summed even though only one port was active. Fixes: 28ea6036dad2 ("net/mlx5: Handle port and vport speed change events in MPESW") Signed-off-by: Or Har-Toov Reviewed-by: Shay Drori Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260930170406.148548-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/mellanox/mlx5/core/lag/lag.c | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 3b34bec559e0..4e173b08cb37 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1432,30 +1432,40 @@ static bool mlx5_lag_should_disable_lag(struct mlx5_lag *ldev, bool do_bond) } #ifdef CONFIG_MLX5_ESWITCH -static int -mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, - int (*get_speed)(struct mlx5_core_dev *, u32 *)) +static int mlx5_lag_get_devices_oper_speed(struct mlx5_lag *ldev, + u32 *sum_speed) { - struct mlx5_core_dev *pf_mdev; - struct lag_func *pf; int pf_idx; - u32 speed; - int ret; *sum_speed = 0; mlx5_ldev_for_each(pf_idx, 0, ldev) { + u8 opmod = MLX5_VPORT_STATE_OP_MOD_VNIC_VPORT; + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + u8 state; + int ret; + pf = mlx5_lag_pf(ldev, pf_idx); if (!pf) continue; pf_mdev = pf->dev; if (!pf_mdev) continue; + ret = mlx5_query_vport_max_tx_speed(pf_mdev, opmod, 0, 0, + &speed, &state); + if (ret) { + mlx5_core_dbg(pf_mdev, "State query failed (err=%d)\n", + ret); + return ret; + } + if (state != VPORT_STATE_UP) + continue; - ret = get_speed(pf_mdev, &speed); + ret = mlx5_port_oper_linkspeed(pf_mdev, &speed); if (ret) { mlx5_core_dbg(pf_mdev, - "Failed to get device speed using %ps. Device %s speed is not available (err=%d)\n", - get_speed, dev_name(pf_mdev->device), + "Failed to get oper speed (err=%d)\n", ret); return ret; } @@ -1466,17 +1476,42 @@ mlx5_lag_sum_devices_speed(struct mlx5_lag *ldev, u32 *sum_speed, return 0; } -static int mlx5_lag_sum_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) +static int mlx5_lag_get_devices_max_speed(struct mlx5_lag *ldev, u32 *max_speed) { - return mlx5_lag_sum_devices_speed(ldev, max_speed, - mlx5_port_max_linkspeed); -} + bool take_max; + int pf_idx; -static int mlx5_lag_sum_devices_oper_speed(struct mlx5_lag *ldev, - u32 *oper_speed) -{ - return mlx5_lag_sum_devices_speed(ldev, oper_speed, - mlx5_port_oper_linkspeed); + take_max = ldev->tracker.tx_type == NETDEV_LAG_TX_TYPE_ACTIVEBACKUP; + if (ldev->mode == MLX5_LAG_MODE_MPESW) + take_max = false; + + *max_speed = 0; + mlx5_ldev_for_each(pf_idx, 0, ldev) { + struct mlx5_core_dev *pf_mdev; + struct lag_func *pf; + u32 speed; + int ret; + + pf = mlx5_lag_pf(ldev, pf_idx); + if (!pf) + continue; + pf_mdev = pf->dev; + if (!pf_mdev) + continue; + + ret = mlx5_port_max_linkspeed(pf_mdev, &speed); + if (ret) { + mlx5_core_dbg(pf_mdev, + "Failed to get max speed (err=%d)\n", + ret); + return ret; + } + + *max_speed = take_max ? + max(*max_speed, speed) : *max_speed + speed; + } + + return 0; } static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, @@ -1525,7 +1560,7 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) int pf_idx; if (ldev->mode == MLX5_LAG_MODE_MPESW) { - if (mlx5_lag_sum_devices_oper_speed(ldev, &speed)) + if (mlx5_lag_get_devices_oper_speed(ldev, &speed)) return; } else { speed = ldev->tracker.bond_speed_mbps; @@ -1533,8 +1568,8 @@ void mlx5_lag_set_vports_agg_speed(struct mlx5_lag *ldev) return; } - /* If speed is not set, use the sum of max speeds of all PFs */ - if (!speed && mlx5_lag_sum_devices_max_speed(ldev, &speed)) + /* If speed is not set, fall back to the max achievable speed */ + if (!speed && mlx5_lag_get_devices_max_speed(ldev, &speed)) return; speed = speed / MLX5_MAX_TX_SPEED_UNIT; From e3f33b0a1d89d738e2b913dab519226eaf76e15b Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 22:12:53 +0000 Subject: [PATCH 495/590] ipv4: free inet_opt and ireq_opt after an RCU grace period tcp_v4_syn_recv_sock() transfers ownership of ireq->ireq_opt to the child socket (newinet->inet_opt) without copying it. Another cpu can concurrently process a retransmitted SYN for the same request socket, and send a SYNACK from tcp_check_req(). tcp_v4_send_synack() and inet_csk_route_req() read ireq->ireq_opt under rcu_read_lock() only, and ip_build_and_send_pkt() and ip_options_build() then read opt->optlen twice. Note that the SYNACK timer itself is not an issue: it holds a reference on its request socket, and inet_csk_reqsk_queue_drop() calls timer_delete_sync() before the child can be freed. Since commit 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table"), request sockets are processed without holding the listener lock, so nothing prevents the child socket from being freed while the SYNACK is still being built. TCP child sockets do not have SOCK_RCU_FREE, and inet_sock_destruct() frees inet_opt with a plain kfree(), leading to a use-after-free in ip_options_build(). A similar issue exists with request socket migration (net.ipv4.tcp_migrate_req=1, or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program). reqsk_timer_handler() clones the request socket with inet_reqsk_clone(), so that the old request socket and its clone share the same ireq_opt, then reqsk_migrate_reset() clears the pointer in the old one. Another cpu holding a reference on the old request socket can still be using these options (sending a SYNACK, or creating a child in tcp_v4_syn_recv_sock()) when the clone is freed, and tcp_v4_reqsk_destructor() also uses a plain kfree(). Readers already use RCU, and other paths replacing inet_opt (do_ip_setsockopt(), cipso_v4_sock_setattr()...) already use kfree_rcu(). Use kfree_rcu() in inet_sock_destruct() and tcp_v4_reqsk_destructor() as well. IPv6 is not affected by the first issue, because tcp_v6_syn_recv_sock() duplicates the options. tcp_v6_reqsk_destructor() has the same migration issue with ipv6_opt, which is only set by CALIPSO. This will be addressed in a separate patch. Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table") Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.") Reported-by: Xinyang Ge Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261001221253.2964024-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/ipv4/af_inet.c | 2 +- net/ipv4/tcp_ipv4.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 4ce38c99fef9..14ce01092fda 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -161,7 +161,7 @@ void inet_sock_destruct(struct sock *sk) WARN_ON_ONCE(sk->sk_wmem_queued); WARN_ON_ONCE(sk->sk_forward_alloc); - kfree(rcu_dereference_protected(inet->inet_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu); dst_release(rcu_dereference_protected(sk->sk_dst_cache, 1)); dst_release(rcu_dereference_protected(sk->sk_rx_dst, 1)); psp_sk_assoc_free(sk); diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 04dbb2babbcd..bebc5a8d1ab6 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1209,7 +1209,7 @@ static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst, */ static void tcp_v4_reqsk_destructor(struct request_sock *req) { - kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu); } #ifdef CONFIG_TCP_MD5SIG From 11705541e7d421706a54f7f6ead8a3316c91c2bc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:37 +0000 Subject: [PATCH 496/590] net: stmmac: Remove VLAN perfect matching dead code stmmac_vlan_update() falls back to "perfect matching" when the VLAN hash filter is unavailable (!priv->dma_cap.vlhash). This fallback has been unreachable in normal operation since its introduction in commit c7ab0b8088d7 ("net: stmmac: Fallback to VLAN Perfect filtering if HASH is not available") because the NETIF_F_HW_VLAN_{CTAG,STAG}_FILTER features are advertised only when priv->dma_cap.vlhash is true. The fallback is also duplicating the code in vlan_add_hw_rx_fltr(), which is always available since stmmac_get_num_vlan() returns at least 1. Therefore, remove it. Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-2-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 13 +----- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 41 +------------------ 3 files changed, 4 insertions(+), 52 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 857f7562c6c6..df2126d71c2f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double); + bool is_double); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9741f97fa37a..a92940473ccd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6895,29 +6895,18 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) { u32 crc, hash = 0; - u16 pmatch = 0; - int count = 0; u16 vid = 0; for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); - count++; - } - - if (!priv->dma_cap.vlhash) { - if (count > 2) /* VID = 0 always passes filter */ - return -EOPNOTSUPP; - - pmatch = vid; - hash = 0; } if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); } /* FIXME: This may need RXC to be running, but it may be called with BH diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index e24efe3bfedb..983a90cb9767 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -184,20 +184,6 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, } writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = VLAN_ETV; - - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); @@ -251,7 +237,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; @@ -279,29 +265,6 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); From 69aa9e76677c97ba992df03a3c74d839470e7363 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:38 +0000 Subject: [PATCH 497/590] net: stmmac: Stop toggling the EDVLP bit Currently, the double VLAN EDVLP bit is toggled whenever an 802.1ad VLAN is registered. This bit enables processing of the inner VLAN tag, which is completely unrelated to S-Tag VLAN handling. Move EDVLP handling into vlan_set_hw_mode() instead, and keep it always enabled, so that COE can work for packets with an inner VLAN header. Add a dedicated callback for dwxlgmac2, as it doesn't implement the set_hw_vlan_mode callback, like the other cores. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Link: https://patch.msgid.link/20260928203441.34876-3-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 983a90cb9767..200b34588c7f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -174,19 +174,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } writel(value, ioaddr + VLAN_TAG); } else { - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -222,6 +219,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); + value |= VLAN_EDVLP; value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (hw->hw_vlan_en) @@ -254,11 +252,9 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value |= VLAN_VTHM | VLAN_ETV; if (is_double) { - value |= VLAN_EDVLP; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } @@ -274,8 +270,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~(VLAN_VTHM | VLAN_ETV | VLAN_ESVL); value &= ~VLAN_DOVLTC; value &= ~VLAN_VID; @@ -283,6 +278,14 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, } } +static void dwxlgmac2_set_hw_vlan_mode(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value = readl(ioaddr + VLAN_TAG); + + writel(value | VLAN_EDVLP, ioaddr + VLAN_TAG); +} + const struct stmmac_vlan_ops dwmac_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, @@ -296,6 +299,7 @@ const struct stmmac_vlan_ops dwmac_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = dwxgmac2_update_vlan_hash, .enable_vlan = vlan_enable, + .set_hw_vlan_mode = dwxlgmac2_set_hw_vlan_mode, }; const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { From 354b07bc1dcaa94b40a5904159c1a53b33d7cf59 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:39 +0000 Subject: [PATCH 498/590] net: stmmac: Rename double VLAN references to svlan The ESVL and DOVLTC bits control S-VLAN tag processing and have nothing to do with the double VLAN feature, which only provides a way to process an additional inner VLAN tag. However, the driver code that handles them always refers to "double VLAN", which is unrelated and makes the implementation confusing. The driver does not use any of the inner VLAN tag features, and the networking core does not support offloads for the inner tag anyway. To reduce the confusion regarding S-Tag vs double VLAN handling, rename double -> svlan. No functional change intended. Suggested-by: Joseph Steel Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-4-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 34 +++++++++---------- .../stmicro/stmmac/stmmac_selftests.c | 30 ++++++++-------- .../net/ethernet/stmicro/stmmac/stmmac_vlan.c | 8 ++--- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index df2126d71c2f..5e2654c91b41 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - bool is_double); + bool is_svlan); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7582fca63741..d2d387f45c10 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -344,7 +344,7 @@ struct stmmac_priv { void __iomem *ptpaddr; void __iomem *estaddr; unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_double_vlans; + unsigned int num_svlans; int sfty_irq; struct stmmac_msi *msi; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a92940473ccd..26248e042f77 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6892,7 +6892,7 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) +static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) { u32 crc, hash = 0; u16 vid = 0; @@ -6906,7 +6906,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -6915,8 +6915,8 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6924,11 +6924,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; set_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans + is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans + is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { clear_bit(vid, priv->active_vlans); goto err_pm_put; @@ -6938,12 +6938,12 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto err_pm_put; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; err_pm_put: pm_runtime_put(priv->device); @@ -6957,8 +6957,8 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -6966,11 +6966,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; clear_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans - is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans - is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { set_bit(vid, priv->active_vlans); goto del_vlan_error; @@ -6980,12 +6980,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto del_vlan_error; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; del_vlan_error: pm_runtime_put(priv->device); @@ -7001,7 +7001,7 @@ static void stmmac_vlan_restore(struct stmmac_priv *priv) if (priv->hw->num_vlan) stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); } static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index c25dc9f89270..c485217ba880 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -243,7 +243,7 @@ struct stmmac_test_priv { int (*func)(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev); bool capture_all; - int double_vlan; + int svlan; int vlan_id; int ok; }; @@ -285,7 +285,7 @@ static int stmmac_test_loopback_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (tpriv->packet->tcp) { @@ -936,7 +936,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, struct iphdr *ihdr; u16 proto; - proto = tpriv->double_vlan ? ETH_P_8021AD : ETH_P_8021Q; + proto = tpriv->svlan ? ETH_P_8021AD : ETH_P_8021Q; skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) @@ -963,7 +963,7 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } ihdr = ip_hdr(skb); - if (tpriv->double_vlan) + if (tpriv->svlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (ihdr->protocol != IPPROTO_UDP) goto out; @@ -1080,7 +1080,7 @@ static int stmmac_test_vlanfilt_perfect(struct stmmac_priv *priv) return ret; } -static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int __stmmac_test_svlanfilt(struct stmmac_priv *priv) { struct stmmac_packet_attrs attr = { }; struct stmmac_test_priv *tpriv; @@ -1092,7 +1092,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return -ENOMEM; tpriv->ok = false; - tpriv->double_vlan = true; + tpriv->svlan = true; init_completion(&tpriv->comp); tpriv->pt.type = htons(ETH_P_8021Q); @@ -1155,15 +1155,15 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) return ret; } -static int stmmac_test_dvlanfilt(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt(struct stmmac_priv *priv) { if (!priv->dma_cap.vlhash) return -EOPNOTSUPP; - return __stmmac_test_dvlanfilt(priv); + return __stmmac_test_svlanfilt(priv); } -static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) +static int stmmac_test_svlanfilt_perfect(struct stmmac_priv *priv) { int ret, prev_cap = priv->dma_cap.vlhash; @@ -1171,7 +1171,7 @@ static int stmmac_test_dvlanfilt_perfect(struct stmmac_priv *priv) return -EOPNOTSUPP; priv->dma_cap.vlhash = 0; - ret = __stmmac_test_dvlanfilt(priv); + ret = __stmmac_test_svlanfilt(priv); priv->dma_cap.vlhash = prev_cap; return ret; @@ -1372,7 +1372,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) proto = svlan ? ETH_P_8021AD : ETH_P_8021Q; tpriv->ok = false; - tpriv->double_vlan = svlan; + tpriv->svlan = svlan; init_completion(&tpriv->comp); tpriv->pt.type = svlan ? htons(ETH_P_8021Q) : htons(ETH_P_IP); @@ -1960,11 +1960,11 @@ static const struct stmmac_test { .name = "VLAN Filtering (perf) ", .fn = stmmac_test_vlanfilt_perfect, }, { - .name = "Double VLAN Filter ", - .fn = stmmac_test_dvlanfilt, + .name = "SVLAN Filtering ", + .fn = stmmac_test_svlanfilt, }, { - .name = "Double VLAN Filter (perf) ", - .fn = stmmac_test_dvlanfilt_perfect, + .name = "SVLAN Filtering (perf) ", + .fn = stmmac_test_svlanfilt_perfect, }, { .name = "Flexible RX Parser ", .fn = stmmac_test_rxp, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 200b34588c7f..fb9aad748cb3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -162,7 +162,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -173,7 +173,7 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { @@ -235,7 +235,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) } static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; @@ -251,7 +251,7 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value = readl(ioaddr + VLAN_TAG); value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { From c63ce7b9c7e7b68267899e4a0fc14b35d2c1dfcc Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:40 +0000 Subject: [PATCH 499/590] net: stmmac: Do not advertise S-VLAN stripping when it is disabled C-VLAN and S-VLAN tag stripping are both controlled by the EVLS bit, so disabling rx-vlan-offload also disables S-VLAN tag stripping. However, rx-vlan-stag-hw-parse keeps being advertised as enabled: root@rzv2h-evk:~# ethtool -K end1 rx-vlan-offload off root@rzv2h-evk:~# ethtool -k end1 | grep -i vlan rx-vlan-offload: off tx-vlan-offload: off [fixed] rx-vlan-filter: on [fixed] vlan-challenged: off [fixed] tx-vlan-stag-hw-insert: off [fixed] rx-vlan-stag-hw-parse: on [fixed] rx-vlan-stag-filter: on [fixed] Fix this inconsistency by making NETIF_F_HW_VLAN_STAG_RX follow NETIF_F_HW_VLAN_CTAG_RX. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-5-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 26248e042f77..13e11df7f4c8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,6 +6268,13 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + if (dwmac_is_xmac(priv->plat->core_type)) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_STAG_RX; + else + features &= ~NETIF_F_HW_VLAN_STAG_RX; + } + return features; } From da17990421d79f1bfb4142f9d767fd33daecc3d8 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Mon, 28 Sep 2026 20:34:41 +0000 Subject: [PATCH 500/590] net: stmmac: Disable S-Tag processing on dwmac4 Currently, hardware VLAN stripping is broken for 802.1ad tags. vlan_rx_hw() hardcodes ETH_P_8021Q when putting the hardware tag into the skb, rather than using the actual protocol from the packet. Because of this, packets that contain a 802.1ad outer tag are incorrectly passed up the stack as having an 802.1Q tag. This causes QinQ ping between two hosts to fail. vlan_rx_hw() is shared by dwxgmac2 and dwmac4: on dwxgmac2 the tag type is available in the RDES3 write-back descriptor (the ET_LT field), so the outer tag type can be determined based on that info. However, dwmac4 doesn't seem to provide the tag type. The Length/Type field in RDES3 only indicates whether the packet is single or double-tagged, not which tag type was stripped. Since dwmac4 cannot report the stripped tag type, it cannot support hardware S-Tag stripping correctly. Therefore, restrict the NETIF_F_HW_VLAN_STAG_RX and NETIF_F_HW_VLAN_STAG_FILTER advertisement to dwxgmac2 only. With this, 802.1ad tags are left in place and handled by the software VLAN path. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Ovidiu Panait Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260928203441.34876-6-ovidiu.panait.rb@renesas.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 13e11df7f4c8..77d2bffb8dc1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6268,7 +6268,7 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; - if (dwmac_is_xmac(priv->plat->core_type)) { + if (priv->plat->core_type == DWMAC_CORE_XGMAC) { if (features & NETIF_F_HW_VLAN_CTAG_RX) features |= NETIF_F_HW_VLAN_STAG_RX; else @@ -8055,14 +8055,18 @@ static int __stmmac_dvr_probe(struct device *device, ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ - ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; + ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_RX; + if (dwmac_is_xmac(priv->plat->core_type)) { ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; priv->hw->hw_vlan_en = true; } if (priv->dma_cap.vlhash) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; - ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; + if (priv->plat->core_type == DWMAC_CORE_XGMAC) + ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; } if (priv->dma_cap.vlins) ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; From 73670ddb91a4d22e1deb3efd962242de1ec48e90 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:33 -0700 Subject: [PATCH 501/590] gve: fix XSK buffer leak when rings are stopped GVE does not free XSK buffers when resetting ring state as a part of stopping queues. This causes all XSK buffers which are posted to the NIC to be leaked. Free XSK buffers attached to an allocated buf_state when stopping rings. This issue was hit in production. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Tim Hostetler Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-2-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index 5cf242b28557..a2c4a08ce68d 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -113,6 +113,12 @@ static void gve_rx_reset_ring_dqo(struct gve_priv *priv, int idx) gve_free_to_page_pool(rx, bs, false); else gve_free_qpl_page_dqo(bs); + + if (gve_buf_state_is_allocated(rx, bs) && + bs->xsk_buff) { + xsk_buff_free(bs->xsk_buff); + bs->xsk_buff = NULL; + } } } From 9fed6a8ab8a5bacba2c709cb19d2dd6d2fa9112e Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:34 -0700 Subject: [PATCH 502/590] gve: fix XSK buffer leak on error descriptor When the error bit is set in the RX completion descriptor, the buf_state and its attached buffer should be freed. In the case of AF_XDP ZC, the XSK buffer was not freed, leading to a leak. This issue was caught by LLM. Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy") Cc: stable@vger.kernel.org Reviewed-by: Jordan Rhee Reviewed-by: Tim Hostetler Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-3-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_rx_dqo.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c index a2c4a08ce68d..82ed5e48b00a 100644 --- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c @@ -842,7 +842,12 @@ static int gve_rx_dqo(struct napi_struct *napi, struct gve_rx_ring *rx, } if (unlikely(compl_desc->rx_error)) { - gve_free_buffer(rx, buf_state); + if (buf_state->xsk_buff) { + xsk_buff_free(buf_state->xsk_buff); + gve_free_buf_state(rx, buf_state); + } else { + gve_free_buffer(rx, buf_state); + } return -EINVAL; } From 0cb6939a4dfefa822007b7a71986e06bc4f61f54 Mon Sep 17 00:00:00 2001 From: Joshua Washington Date: Wed, 30 Sep 2026 14:47:35 -0700 Subject: [PATCH 503/590] gve: fix napi_disable deadlock when attempting to disable XSK pools When disabling XSK pools, GVE calls the unlocked versions of napi_disable and napi_enable. However, the netdev lock has already been acquired before ndo_bpf is called because GVE supports queue management ops. Calling the unlocked versions of napi_disable/enable results in a deadlock when attempting to disable XSK pools, as the thread attempts to re-acquire a lock it already holds. Update the NAPI calls to use the locked versions. This issue was found in production. Fixes: 606048cbd834 ("net: designate XSK pool pointers in queues as "ops protected"") Cc: stable@vger.kernel.org Reviewed-by: Harshitha Ramamurthy Reviewed-by: Jordan Rhee Signed-off-by: Joshua Washington Link: https://patch.msgid.link/20260930214735.3288780-4-joshwash@google.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 9cc343a16271..a6c18e2431cc 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -1708,17 +1708,17 @@ static int gve_xsk_pool_disable(struct net_device *dev, } napi_rx = &priv->ntfy_blocks[priv->rx[qid].ntfy_id].napi; - napi_disable(napi_rx); /* make sure current rx poll is done */ + napi_disable_locked(napi_rx); /* make sure current rx poll is done */ tx_qid = gve_xdp_tx_queue_id(priv, qid); napi_tx = &priv->ntfy_blocks[priv->tx[tx_qid].ntfy_id].napi; - napi_disable(napi_tx); /* make sure current tx poll is done */ + napi_disable_locked(napi_tx); /* make sure current tx poll is done */ gve_unreg_xsk_pool(priv, qid); smp_mb(); /* Make sure it is visible to the workers on datapath */ - napi_enable(napi_rx); - napi_enable(napi_tx); + napi_enable_locked(napi_rx); + napi_enable_locked(napi_tx); if (gve_is_gqi(priv)) { if (gve_rx_work_pending(&priv->rx[qid])) napi_schedule(napi_rx); From 7e170da2edec9fb0717543571cc14b0aabee2b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leto=20Liu=20=28=E5=88=98=E6=B6=9B=29?= Date: Wed, 30 Sep 2026 08:58:48 +0200 Subject: [PATCH 504/590] net: airoha: Add retry mechanism to airoha_qdma_set_trtcm_param() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPU accesses QDMA via the bus. When multiple modules are using the bus simultaneously, CPU access to QDMA may encounter bus timeouts and fails, resulting in QDMA configuration failures and potentially causing packet transmission issues. In order to mitigate the issue, introduce a retry mechanism to airoha_qdma_set_trtcm_param routine in order to ensure the configuration is correctly applied to the hardware. It's enough to try a second time for the TRTCM config to be actually applied to make sure we are not in the middle of bucket handling as it does operate on fixed time slot internally. Fixes: ef1ca9271313b ("net: airoha: Add sched HTB offload support") Signed-off-by: Leto Liu (刘涛) [ improve commit description, drop memory block, apply better loop logic] Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260930065855.46171-1-ansuelsmth@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 32 +++++++++++++++++++----- drivers/net/ethernet/airoha/airoha_eth.h | 2 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index 64619e9a704d..6ca57875b6d6 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -2800,14 +2800,34 @@ static int airoha_qdma_set_trtcm_param(struct airoha_qdma *qdma, int channel, FIELD_PREP(TRTCM_METER_GROUP_MASK, group) | FIELD_PREP(TRTCM_PARAM_INDEX_MASK, idx) | FIELD_PREP(TRTCM_PARAM_RATE_TYPE_MASK, mode); + int i; - airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); - airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + /* + * HW might be in the middle of bucket handling and might + * require an additional try to actually apply the new TRTCM + * config. + */ + for (i = 0; i < TRTCM_CONFIG_RETRY_MAX; i++) { + u32 data = 0; - return read_poll_timeout(airoha_qdma_rr, val, - val & TRTCM_PARAM_RW_DONE_MASK, - USEC_PER_MSEC, 10 * USEC_PER_MSEC, true, - qdma, REG_TRTCM_CFG_PARAM(addr)); + airoha_qdma_wr(qdma, REG_TRTCM_DATA_LOW(addr), val); + airoha_qdma_wr(qdma, REG_TRTCM_CFG_PARAM(addr), config); + + if (read_poll_timeout(airoha_qdma_rr, data, + data & TRTCM_PARAM_RW_DONE_MASK, + USEC_PER_MSEC, 10 * USEC_PER_MSEC, + true, qdma, REG_TRTCM_CFG_PARAM(addr))) + continue; + + if (airoha_qdma_get_trtcm_param(qdma, channel, addr, param, + mode, &data, NULL)) + continue; + + if (data == val) + return 0; + } + + return -EBUSY; } static int airoha_qdma_set_trtcm_config(struct airoha_qdma *qdma, int channel, diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h index 8277c1c87bb3..338fde03a149 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.h +++ b/drivers/net/ethernet/airoha/airoha_eth.h @@ -171,6 +171,8 @@ enum trtcm_param { #define TRTCM_TOKEN_RATE_MASK GENMASK(23, 6) #define TRTCM_TOKEN_RATE_FRACTION_MASK GENMASK(5, 0) +#define TRTCM_CONFIG_RETRY_MAX 2 + enum airoha_dma_map_type { AIROHA_DMA_UNMAPPED, AIROHA_DMA_MAP_SINGLE, From d5a007b9b457c915ab1a53227e8939e4018aa97a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 5 Oct 2026 18:19:20 -0700 Subject: [PATCH 505/590] Revert "net: stmmac: propagate PTP addend and system time programming errors" This reverts commit 232d49dd4b40a666283de9e722899f088ed581b2. Anirudh reports a regression from this change. It is not appropriate for net this late in the release cycle in the first place, so let's revert and revisit in net-next. Reported-by: Anirudh Srinivasan Link: https://lore.kernel.org/CAEev2e8+fr1tJnSErD+2TtLDw5fGJbQOMcM5eefMnLyFUR+m7w@mail.gmail.com Fixes: 232d49dd4b40 ("net: stmmac: propagate PTP addend and system time programming errors") Signed-off-by: Jakub Kicinski --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 106 +++++------------- .../net/ethernet/stmicro/stmmac/stmmac_ptp.c | 10 +- 2 files changed, 32 insertions(+), 84 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 77d2bffb8dc1..b837e8e27a35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -601,64 +601,31 @@ static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv, struct dma_desc *p, } } -static void stmmac_restore_subsecond_increment(struct stmmac_priv *priv, - u32 default_addend) +static void stmmac_update_subsecond_increment(struct stmmac_priv *priv) { bool xmac = dwmac_is_xmac(priv->plat->core_type); u32 sec_inc = 0; - - stmmac_config_addend(priv, priv->ptpaddr, default_addend); - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, - xmac, &sec_inc); - priv->default_addend = default_addend; - priv->sub_second_inc = sec_inc; -} - -static int stmmac_update_subsecond_increment(struct stmmac_priv *priv, - u32 systime_flags) -{ - bool xmac = dwmac_is_xmac(priv->plat->core_type); - u32 sec_inc = 0, val; u64 temp = 0; - int ret; - stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); /* program Sub Second Increment reg */ stmmac_config_sub_second_increment(priv, priv->ptpaddr, priv->plat->clk_ptp_rate, xmac, &sec_inc); - if (!sec_inc) { - ret = -EINVAL; - goto error; - } + temp = div_u64(1000000000ULL, sec_inc); + + /* Store sub second increment for later use */ + priv->sub_second_inc = sec_inc; /* calculate default added value: * formula is : * addend = (2^32)/freq_div_ratio; * where, freq_div_ratio = 1e9ns/sec_inc */ - temp = div_u64(1000000000ULL, sec_inc); temp = (u64)(temp << 32); - val = div_u64(temp, priv->plat->clk_ptp_rate); - - ret = stmmac_config_addend(priv, priv->ptpaddr, val); - if (ret) - goto error; - - priv->sub_second_inc = sec_inc; - priv->default_addend = val; - - return 0; -error: - /* Restore previous configuration */ - stmmac_config_hw_tstamping(priv, priv->ptpaddr, priv->systime_flags); - stmmac_config_sub_second_increment(priv, priv->ptpaddr, - priv->plat->clk_ptp_rate, xmac, - NULL); - return ret; + priv->default_addend = div_u64(temp, priv->plat->clk_ptp_rate); + stmmac_config_addend(priv, priv->ptpaddr, priv->default_addend); } /** @@ -887,42 +854,35 @@ static int stmmac_hwtstamp_get(struct net_device *dev, /** * stmmac_init_tstamp_counter - init hardware timestamping counter * @priv: driver private structure + * @systime_flags: timestamping flags * Description: * Initialize hardware counter for packet timestamping. * This is valid as long as the interface is open and not suspended. * Will be rerun after resuming from suspend, case in which the timestamping * flags updated by stmmac_hwtstamp_set() also need to be restored. */ -static int stmmac_init_tstamp_counter(struct stmmac_priv *priv) +static int stmmac_init_tstamp_counter(struct stmmac_priv *priv, + u32 systime_flags) { - u32 default_addend = priv->default_addend; struct timespec64 now; - int ret; if (!priv->plat->clk_ptp_rate) { netdev_err(priv->dev, "Invalid PTP clock rate"); return -EINVAL; } - ret = stmmac_update_subsecond_increment(priv, priv->systime_flags); - if (ret) - return ret; + stmmac_config_hw_tstamping(priv, priv->ptpaddr, systime_flags); + priv->systime_flags = systime_flags; + + stmmac_update_subsecond_increment(priv); /* initialize system time */ ktime_get_real_ts64(&now); /* lower 32 bits of tv_sec are safe until y2106 */ - ret = stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, - now.tv_nsec); - if (ret) - goto error; + stmmac_init_systime(priv, priv->ptpaddr, (u32)now.tv_sec, now.tv_nsec); return 0; -error: - /* Restore previous configuration */ - stmmac_restore_subsecond_increment(priv, default_addend); - - return ret; } /** @@ -945,14 +905,8 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) return -EOPNOTSUPP; } - /* Reset hw ts configuration */ - memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); - priv->systime_flags = STMMAC_HWTS_ACTIVE | PTP_TCR_TSCFUPDT; - priv->tsfupdt_coarse = false; - priv->hwts_tx_en = 0; - priv->hwts_rx_en = 0; - - ret = stmmac_init_tstamp_counter(priv); + ret = stmmac_init_tstamp_counter(priv, STMMAC_HWTS_ACTIVE | + PTP_TCR_TSCFUPDT); if (ret) { netdev_warn(priv->dev, "PTP init failed\n"); return ret; @@ -973,6 +927,10 @@ static int stmmac_init_timestamping(struct stmmac_priv *priv) netdev_info(priv->dev, "IEEE 1588-2008 Advanced Timestamp supported\n"); + memset(&priv->tstamp_config, 0, sizeof(priv->tstamp_config)); + priv->hwts_tx_en = 0; + priv->hwts_rx_en = 0; + if (priv->plat->flags & STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY) stmmac_hwtstamp_correct_latency(priv, priv); @@ -7749,26 +7707,18 @@ static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, { struct stmmac_devlink_priv *dl_priv = devlink_priv(dl); struct stmmac_priv *priv = dl_priv->stmmac_priv; - u32 systime_flags = priv->systime_flags; - int ret; - if (ctx->val.vbool) - systime_flags &= ~PTP_TCR_TSCFUPDT; + priv->tsfupdt_coarse = ctx->val.vbool; + + if (priv->tsfupdt_coarse) + priv->systime_flags &= ~PTP_TCR_TSCFUPDT; else - systime_flags |= PTP_TCR_TSCFUPDT; + priv->systime_flags |= PTP_TCR_TSCFUPDT; /* In Coarse mode, we can use a smaller subsecond increment, let's * reconfigure the systime, subsecond increment and addend. */ - ret = stmmac_update_subsecond_increment(priv, systime_flags); - if (ret) { - NL_SET_ERR_MSG_MOD(extack, - "failed to reconfigure PTP adjustment"); - return ret; - } - - priv->tsfupdt_coarse = ctx->val.vbool; - priv->systime_flags = systime_flags; + stmmac_update_subsecond_increment(priv); return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c index 493c5d81a36b..3bfcc9760dce 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.c @@ -28,15 +28,14 @@ static int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; u32 addend; - int ret; addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_config_addend(priv, priv->ptpaddr, addend); + stmmac_config_addend(priv, priv->ptpaddr, addend); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } /** @@ -154,13 +153,12 @@ static int stmmac_set_time(struct ptp_clock_info *ptp, struct stmmac_priv *priv = container_of(ptp, struct stmmac_priv, ptp_clock_ops); unsigned long flags; - int ret; write_lock_irqsave(&priv->ptp_lock, flags); - ret = stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); + stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); write_unlock_irqrestore(&priv->ptp_lock, flags); - return ret; + return 0; } static int stmmac_enable(struct ptp_clock_info *ptp, From 0ec14f8c8c5da1c9c314bce4f0bf53c009fbc998 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:23 +0000 Subject: [PATCH 506/590] wifi: mt76: mt7996: take over connection monitoring mt76 runs its own beacon monitor. mt76_beacon_mon_check() counts a miss for each affiliated link of an MLD, and it stops the count while the phy is off channel. mt7996 supplies it through mt76_rx_beacon(), but it does not set CONNECTION_MONITOR. Therefore mac80211 also starts its legacy monitor. The legacy monitor knows one interface, not a set of links. It also counts while the radio serves a different link. After seven beacon intervals it removes an association that still carries traffic on the other links. Set the CONNECTION_MONITOR flag to disable the legacy monitor. Fixes: 4df75606ece5 ("wifi: mt76: add per-link beacon monitoring for MLO") Link: https://patch.msgid.link/20260818125825.395538-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index fb635a092584..2db63c3be9d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, CONNECTION_MONITOR); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ From 752770d7a58c821c17909c3ca67f9f14ab146a40 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:25 +0000 Subject: [PATCH 507/590] wifi: mt76: account non-AQL frames per peer rather than per link The driver increments wcid->non_aql_packets when a frame goes to the hardware queue. The completion path decrements the counter again. For an MLO peer, the hardware reports the completion on the link that it used for the transmission. This link can differ from the link that the driver queued the frame on. In that case, the increment and the decrement go to different link wcids, and the count drifts upwards until it blocks the station. Keep the count on the wcid of the primary link, like the other peer-wide state. Fixes: 86c051f2c418 ("wifi: mt76: mt7925: enabling MLO when the firmware supports it") Link: https://patch.msgid.link/20260818125825.395538-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ drivers/net/wireless/mediatek/mt76/tx.c | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 62b41c8bb7c0..2ade848fe3bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* peer-wide state uses the wcid of the primary link */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 3707ee19e4ae..b1e894b19cbd 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* the hardware can report the completion on a different link */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { From 7c2e6b43c3082c2242ecfa5875b2b4b2cd06140f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 1 Sep 2026 11:22:32 +0200 Subject: [PATCH 508/590] wifi: mt76: use ALTX queue for packets to disassociated stations Running these packets through regular buffering queues could cause them to hang, especially when the packets were for stations that in disassoc state. Fixes: dedf2ec30fe4 ("wifi: mt76: fix queue assignment for deauth packets") Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901092232.461379-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index a8286f8becf9..a03d89aabe80 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -805,6 +805,7 @@ __mt7915_drop_sta(void *ptr, u8 *mac, struct ieee80211_vif *vif) msta = (struct mt7915_sta *)sta->drv_priv; mt7915_mcu_add_sta(data->dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; } @@ -871,6 +872,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d17d1dbb2038..dd7a01c02a7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1433,6 +1433,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, else if (sta->mlo && links == BIT(link_id)) /* last link */ mt7996_mcu_teardown_mld_sta(dev, link, msta_link); + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b1e894b19cbd..3c623df2e741 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -680,7 +680,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data_present(hdr->frame_control) && - (!ieee80211_is_bufferable_mmpdu(skb) || + (!(wcid->tx_info & MT_WCID_TX_INFO_SET) || + !ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) From 8f9e5807671ee97cacf1957f4a09e43f4f1017a9 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 26 Aug 2026 12:36:58 +0800 Subject: [PATCH 509/590] wifi: mt76: mt7925: restore the legacy BSS after MLO teardown Removing the last link of an MLD interface leaves the firmware with the MLD BSS and DEV entry: the removal loop skips the deflink, and the hweight16(mvif->valid_links) fallback reads the stale pre-update valid_links, which is still non-zero. The entry keeps the per-link MAC address, so every subsequent legacy association fails authentication until the interface is removed or the module is reloaded. Remove the deflink BSS and add it back as a legacy BSS on the transition to zero links. Fixes: 69acd6d910b0 ("wifi: mt76: mt7925: add mt7925_change_vif_links") Signed-off-by: Aaron Ma Link: https://patch.msgid.link/20260826043658.2255553-1-aaron.ma@canonical.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5993b31e1aae..549f9c06b363 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2294,6 +2294,18 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mvif->valid_links = new_links; + /* Restore the legacy BSS after disabling MLO */ + if (old_links && !new_links) { + mt792x_mac_link_bss_remove(dev, &mvif->bss_conf, + &mvif->sta.deflink); + err = mt7925_mac_link_bss_add(dev, &vif->bss_conf, + &mvif->sta.deflink); + if (err < 0) { + mt792x_mutex_release(dev); + return err; + } + } + mt792x_mutex_release(dev); return 0; From 9c9e4e4c8b45873e53774ab8207825b37b7565ec Mon Sep 17 00:00:00 2001 From: Tao Gong Date: Sun, 23 Aug 2026 05:37:39 +0000 Subject: [PATCH 510/590] wifi: mt76: mt7996: fix struct mt7996_mcu_wed_rro_ba_delete_event layout Correct the definition of struct mt7996_mcu_wed_rro_ba_delete_event to match the 12-byte event record layout observed from firmware. The event TLV sent by firmware is 12 bytes in length rather than 8 bytes. It contains an additional 4 bytes that appear to be wlan_id and tid. For example, a captured batch deletion of sessions 8, 9, and 10 (TIDs 6, 0, and 1) shows: 00000000: 02 00 0c 00 08 00 01 00 06 00 00 00 02 00 0c 00 00000010: 09 00 01 00 00 00 00 00 02 00 0c 00 0a 00 01 00 00000020: 01 00 00 00 The previous 8-byte structure corrupts the parsing of subsequent TLVs, leading to unhandled delete events and erroneous session deletions. Consequently, clients that create and tear down BA sessions in batches (such as Apple Wi-Fi clients) fail to properly clean up stale sessions, causing HW_RRO to be lost upon subsequent reassociations. Fixes: a5d028d66836 ("wifi: mt76: mt7996: add wed rro delete session garbage collector") Signed-off-by: Tao Gong Link: https://patch.msgid.link/20260823053739.186748-1-gongtao0607@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3d..1b272e904100 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -284,6 +284,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event { __le16 len; __le16 session_id; + __le16 wlan_id; + u8 tid; + u8 __rsv1; u8 __rsv2[2]; } __packed; From 916c484d29d82f45c281da2786fcd6a1284430ce Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 10 Aug 2026 17:45:10 +0200 Subject: [PATCH 511/590] wifi: mt76: mt7915: disable rx napi when removing device Unloading the mt7915e module with a MT7916 triggers multiples WARN in __netif_napi_del_locked() and in page_pool_disable_direct_recycling() because the driver does not disable the napi before destroying it. This is troublesome since on MT7916 it is required to unload the module and reinsert it with a different enable_6ghz parameter to change the frequency. The system generally becomes unstable after reinserting the module. Fix it by disabling rx napi inside mt7915e itself, even through napi rx is enabled in the mt76 generic code. Disabling it in the mt76 generic code was previously attempted and reverted because napi was disabled too late and rx handlers could access freed structures. Note that the tx side is enabled and disabled by the mt76 generic code. It is disabled in mt7915_stop_hardware() since, unlike mt7915_unregister_device(), is it also called in the probe path if an error happens after rx napi is enabled. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Nicolas Cavallari Link: https://patch.msgid.link/20260810154923.32546-1-nicolas.cavallari@green-communications.fr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index ca46a203aa48..c8e389db63a7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1238,6 +1238,12 @@ static void mt7915_unregister_ext_phy(struct mt7915_dev *dev) static void mt7915_stop_hardware(struct mt7915_dev *dev) { + int i; + + mt76_for_each_q_rx(&dev->mt76, i) { + if (!mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + napi_disable(&dev->mt76.napi[i]); + } mt7915_mcu_exit(dev); mt76_connac2_tx_token_put(&dev->mt76); mt7915_dma_cleanup(dev); From 5796b2bf9ff718342773ac760d86eb126c112a4f Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Thu, 10 Sep 2026 19:44:28 -0700 Subject: [PATCH 512/590] wifi: mt76: mt7925: don't put the band auto marker in TGID band_idx starts out as 0xff, which tells the firmware to pick the band on its own. A remain-on-channel grant normally writes a real index over it before anything else looks. Two cases never see that: an MLD station throws the grant away, and an AP has no association to trigger one. The TX descriptor reads the same field into a two-bit slot, so the marker comes out as 3. Every data frame from those two goes out tagged band 3. Beacons are fine, they reach the firmware separately and never build a descriptor. Same numbers on an MT7925 and on a Netgear A9000: stock patched station, one link 0x00000001 0x00000001 station, two links 0x00003001 0x00000001 AP 0x00003013 0x00000013 real band index, STA 0x00001001 0x00001001 real band index, AP 0x00001013 0x00001013 Only the marker cases move. Fixes: 0cb349d742d1 ("wifi: mt76: mt7925: update mt7925_mac_link_bss_add for MLO") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260911024428.65244-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 101f571b027f..3b82c9de26c5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -798,7 +798,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (band_idx) + /* 0xff means BAND_AUTO to the firmware, and TGID cannot encode it */ + if (band_idx && band_idx != 0xff) val |= FIELD_PREP(MT_TXD1_TGID, band_idx); txwi[1] = cpu_to_le32(val); From 456de496b53ae73e6e3020fec815dbdc88aa7c07 Mon Sep 17 00:00:00 2001 From: Cristian Papa Date: Thu, 3 Sep 2026 22:07:02 -0300 Subject: [PATCH 513/590] wifi: mt76: mt7603: initialize global station WCID mt7603 uses a driver-private global station WCID for frames that have neither a station nor a vif, including frames injected through a monitor interface. Unlike the core global WCID initialized by mt76_register_device(), this WCID is published without ever being passed to mt76_wcid_init(). Since commit 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status"), mt76_tx() queues every such frame on the WCID's tx_pending or tx_offchannel queue and links its tx_list. The first injected frame therefore reaches __skb_queue_tail() with an uninitialized sk_buff_head and dereferences a NULL prev pointer. Initialize the WCID before publishing it. The fix was tested on an MT7603 PCIe device. Three wildcard probe requests were injected through a monitor interface. The hardware queue head and tail advanced from 0 to 3 and a second radio captured the third request over the air. No oops or reset occurred. After restoring AP mode, a 150 MB transfer also completed without a reset. Fixes: 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status") Cc: stable@vger.kernel.org Signed-off-by: Cristian Papa Link: https://patch.msgid.link/20260904010702.16306-1-pcristian292@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/init.c b/drivers/net/wireless/mediatek/mt76/mt7603/init.c index 10f2ec70c792..218ec406d2a8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/init.c @@ -230,6 +230,7 @@ mt7603_mac_init(struct mt7603_dev *dev) eth_broadcast_addr(bc_addr); mt7603_wtbl_init(dev, MT7603_WTBL_RESERVED, -1, bc_addr); + mt76_wcid_init(&dev->global_sta.wcid, 0); dev->global_sta.wcid.idx = MT7603_WTBL_RESERVED; rcu_assign_pointer(dev->mt76.wcid[MT7603_WTBL_RESERVED], &dev->global_sta.wcid); From 45f53d98cbaa078907b6a300ff3974dfb5ae8776 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 10 Sep 2026 09:23:00 +0800 Subject: [PATCH 514/590] wifi: mt76: mt792x: treat 0xff ACPI SAR entries as no limit Some ASUS firmware carries 0xff in MTGS power slots. The driver narrows the byte to s8, so the slot reads as -1 in its 0.5 dBm units, wins the min() against the regulatory limit and caps every rate the driver programs. On an ASUS Zenbook 14 UM3406HA (MT7922) all six MTGS tables hold 0xff in every power byte, and txpower_sku shows tmac 3 on every supported OFDM/HT/VHT/HE rate against EEPROM entries of 25-39, under both the FR and the world regulatory domain. Huy Nguyen Dinh Quang saw the same clamp on a Vivobook S 14 M5406WA, with 5 GHz TX at ~40 Mbps, and proposed skipping the geo entry. Skip a geo entry whose power byte is 0xff. The MTDS entry is narrowed the same way on its way into the geo lookup, so give it the same treatment, and clamp the dynamic value plus offset at 127 so a no-limit entry cannot wrap. Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Link: https://bugzilla.redhat.com/show_bug.cgi?id=2515420 Reported-by: Huy Nguyen Dinh Quang Closes: https://lore.kernel.org/linux-wireless/20260827134820.2607530-1-huynguyendinhquang@gmail.com/ Signed-off-by: Junjie Cao Tested-by: Cristian Fleischer Link: https://patch.msgid.link/acb947435fc78d5c06039c7bd5609c735468b0ee.1789002236.git.junjie.cao@intel.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index b468051fbe68..0ffb277f9909 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -279,10 +279,18 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, if (idx >= max) return dyn_power; - geo_power = (band_pwr + idx)->pwr; - dyn_power += (band_pwr + idx)->offset; + band_pwr += idx; - return min(geo_power, dyn_power); + /* Some OEM tables carry 0xff here; narrowed to s8 that is -1 and + * wins the min() below. Take it as no limit. + */ + if (band_pwr->pwr == 0xff) + return dyn_power; + + geo_power = band_pwr->pwr; + + return min_t(int, geo_power, + min_t(int, dyn_power + band_pwr->offset, 127)); } static s8 @@ -315,7 +323,9 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, else band = NL80211_BAND_2GHZ; - return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); + /* 0xff: no dynamic limit, see mt792x_asar_get_geo_pwr() */ + return mt792x_asar_get_geo_pwr(phy, band, + limit[idx] == 0xff ? 127 : limit[idx]); } int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) From af97bf5a7e72da9026de184676df8e2fa57e1058 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 18:35:51 -0700 Subject: [PATCH 515/590] wifi: mt76: mt792x: pick the SAR table layout from the table itself The SAR power tables are parsed with a version byte from a different ACPI table, never their own. Some firmware declares version 1 and ships version 2 tables. The entries then start a byte early, and the 6 GHz limits the table carries are dropped, because the older layout has no room for them. ASUS GA503RM, MTCL declares v1: MTGS 28 bytes v1: (28-6) % 5 = 2, no fit v2: 3 tables MTDS 19 bytes v1: (19-6) % 6 = 1, no fit v2: 1 table The layouts accept disjoint lengths, so take whichever one fits and keep it per table. Reported-by: Sagun Kayastha Closes: https://github.com/morrownr/mt76/issues/62 Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260905013551.24635-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 116 +++++++++++------- .../wireless/mediatek/mt76/mt792x_acpi_sar.h | 2 + 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 0ffb277f9909..e87dfb0f1b3a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -108,69 +108,99 @@ mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) return ret; } +/* A table's layout is decided by the version byte in MTCL, but some firmware + * labels a version 2 table as version 1. Accept whichever layout the table + * actually has: for every accepted entry count the two do not overlap, since + * one is 6 + 5n bytes and the other 7 + 7n. + */ +static bool +mt792x_asar_fits(u8 *table, int len, int prelen, int sarlen, int nr_off, + int min, int max) +{ + int tblcnt; + + if (len < prelen || (len - prelen) % sarlen) + return false; + + tblcnt = (len - prelen) / sarlen; + if (tblcnt < min || tblcnt > max) + return false; + + return tblcnt == table[nr_off]; +} + /* MTDS : Dynamic SAR Power Table */ static int -mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; - bool enable; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - enable = ((struct mt792x_asar_dyn *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit); - prelen = sizeof(struct mt792x_asar_dyn); - break; - case 2: - enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); - prelen = sizeof(struct mt792x_asar_dyn_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (!enable || - tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn), + sizeof(struct mt792x_asar_dyn_limit), + offsetof(struct mt792x_asar_dyn, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn_v2), + sizeof(struct mt792x_asar_dyn_limit_v2), + offsetof(struct mt792x_asar_dyn_v2, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 2; + else return -EINVAL; + if (!t[offsetof(struct mt792x_asar_dyn, enable)]) + return -EINVAL; + + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTDS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } /* MTGS : Geo SAR Power Table */ static int -mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - sarlen = sizeof(struct mt792x_asar_geo_limit); - prelen = sizeof(struct mt792x_asar_geo); - break; - case 2: - sarlen = sizeof(struct mt792x_asar_geo_limit_v2); - prelen = sizeof(struct mt792x_asar_geo_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo), + sizeof(struct mt792x_asar_geo_limit), + offsetof(struct mt792x_asar_geo, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo_v2), + sizeof(struct mt792x_asar_geo_limit_v2), + offsetof(struct mt792x_asar_geo_v2, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 2; + else return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTGS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } @@ -205,14 +235,16 @@ int mt792x_init_acpi_sar(struct mt792x_dev *dev) asar->countrylist = NULL; } - ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); + ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver, + &asar->dyn_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->dyn); asar->dyn = NULL; } /* MTGS is optional */ - ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); + ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver, + &asar->geo_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->geo); asar->geo = NULL; @@ -254,7 +286,7 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, break; } - if (asar->ver == 1) { + if (asar->geo_ver == 1) { band_pwr = &asar->geo->tbl[idx].band[0]; max = ARRAY_SIZE(asar->geo->tbl[idx].band); } else { @@ -305,7 +337,7 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, if (!capa) return 127; - if (asar->ver == 1) { + if (asar->dyn_ver == 1) { limit = &asar->dyn->tbl[0].frp[0]; max = ARRAY_SIZE(asar->dyn->tbl[0].frp); } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h index 474033073831..1d42f24b05f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h @@ -105,6 +105,8 @@ struct mt792x_asar_fg { struct mt792x_acpi_sar { u8 ver; + u8 dyn_ver; + u8 geo_ver; union { struct mt792x_asar_dyn *dyn; struct mt792x_asar_dyn_v2 *dyn_v2; From 52f6a065e0810c739a5dabd33a58047f08100f50 Mon Sep 17 00:00:00 2001 From: Lu Huang Date: Wed, 30 Sep 2026 16:54:35 +0800 Subject: [PATCH 516/590] wifi: mt76: mt7996: fix uninitialized buf read in mt7996_variant_fem_init() When the eFuse block covering MT7976C_EFUSE_OFFSET is invalid, mt7996_mcu_get_eeprom() returns -EINVAL without writing buf, but mt7996_variant_fem_init() tolerates -EINVAL and keeps running, so buf[idx] == 0xc reads uninitialized stack memory. A stale 0x0c byte misclassifies a 0x7976 aDie as 7976C, selects MT7996_FEM_INT and the wrong default EEPROM, overriding valid calibration data. Only consult the eFuse byte when the read succeeded; with an invalid eFuse, fall back to the three explicit aDie version checks. Fixes: e8cb33ad546a ("wifi: mt76: mt7996: add support for more variants") Cc: stable@vger.kernel.org Signed-off-by: Lu Huang Link: https://patch.msgid.link/20260930085435.1424493-1-huanglu@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2db63c3be9d1..c1d860029801 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1248,7 +1248,7 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) adie_ver = u32_get_bits(regval, MT_ADIE_VERSION_MASK); idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE; is_7976c = adie_ver == 0x8a10 || adie_ver == 0x8b00 || - adie_ver == 0x8c10 || buf[idx] == 0xc; + adie_ver == 0x8c10 || (!ret && buf[idx] == 0xc); adie_id = u32_get_bits(regval, MT_ADIE_CHIP_ID_MASK); if (adie_id == 0x7975 || adie_id == 0x7979 || From ff9b465ddb95d7842282998348df88ff55f62c4a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:53 +0800 Subject: [PATCH 517/590] wifi: nxpwifi: protect sta_list against concurrent add and delete nxpwifi_del_sta_entry() unlinks an entry from priv->sta_list without holding priv->sta_list_spinlock. The comment above it gives the reason: the function is only reached from cfg80211_ops.del_station, which runs under the wiphy mutex. That mutex serialises this callback against other users of the same lock, including wiphy_work items. It covers neither of the two other writers of the same list: nxpwifi_add_sta_entry() from nxpwifi_uap_event_sta_assoc() nxpwifi_del_all_sta_list() from nxpwifi_uap_event_bss_idle() Both run from nxpwifi_process_event() on the driver's main work queue, a plain workqueue that never takes the wiphy lock, and both take sta_list_spinlock around their update. A station associating, or the BSS going idle, can therefore modify priv->sta_list while a hostapd del_station is walking it and unlinking an entry. Take the lock here too. As the traversal then runs under the write lock, list_for_each_entry() replaces the _rcu variant, matching nxpwifi_del_all_sta_list(). The same call path reaches nxpwifi_get_sta_entry() without rcu_read_lock(). Entries are freed with kfree_rcu() and every other caller holds it, so take it around the lookup as well. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 86304ea3331c..161697e038c0 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, eth_zero_addr(deauth_mac); + rcu_read_lock(); sta_node = nxpwifi_get_sta_entry(priv, params->mac); if (sta_node) ether_addr_copy(deauth_mac, params->mac); + rcu_read_unlock(); if (is_valid_ether_addr(deauth_mac)) { ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index bbfefb81d8d3..ae8444a14105 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, } } -/* Delete a station from list; called under cfg80211 mutex. */ +/* Delete a station from list. */ void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) { struct nxpwifi_sta_node *node; - list_for_each_entry_rcu(node, &priv->sta_list, list) { + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry(node, &priv->sta_list, list) { if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { list_del_rcu(&node->list); kfree_rcu(node, rcu); break; } } + + spin_unlock_bh(&priv->sta_list_spinlock); } /* Delete all stations from list. */ From 128411832c62ca517e4abd938229f0ee40823fdb Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:54 +0800 Subject: [PATCH 518/590] wifi: nxpwifi: delete the station entry on the uAP deauth event When a station leaves, the firmware reports EVENT_UAP_STA_DEAUTH. nxpwifi_uap_event_sta_deauth() tears down the reorder, BA and WMM state for that peer but leaves its nxpwifi_sta_node on priv->sta_list. Nothing else removes it. cfg80211_del_sta() only reports the departure upwards, it does not call back into cfg80211_ops.del_station, and that callback is the only caller of nxpwifi_del_sta_entry(). The entry therefore survives until the BSS goes idle and nxpwifi_del_all_sta_list() drops everything. Until then the departed station still looks associated to the driver. nxpwifi_handle_uap_rx_forward() finds the stale node for that address, treats the frame as intra-BSS traffic and requeues it for transmission instead of passing it up the stack, so traffic for that address is misrouted. The list also keeps one entry per station that has ever associated, and a station that comes back reuses the stale node along with its old statistics. mwifiex, which nxpwifi was derived from, deletes the entry in the same event handler. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ab5c15686f54..20b7b53db236 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -75,6 +75,7 @@ nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); } nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); return 0; } From 5c9260d7c6990875fc788ebb86ea2bf08b492704 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:13 +0800 Subject: [PATCH 519/590] wifi: nxpwifi: wait for the wakeup timer before the adapter is freed nxpwifi_adapter_cleanup() stops adapter->wakeup_timer with timer_delete(), which does not wait for a running callback. The callback goes on using the adapter: wakeup_timer_fn() sets adapter->hw_status, walks the command queues through nxpwifi_cancel_all_pending_cmd() and calls adapter->if_ops.card_reset(). Both paths that reach nxpwifi_adapter_cleanup() free the adapter right afterwards, through nxpwifi_free_adapter() in nxpwifi_remove_card() and in the nxpwifi_add_card() error unwind. Use timer_delete_sync() so the callback has finished before the adapter is released. mwifiex fixed the same issue in commit ae5e95d41574 ("wifi: mwifiex: fix use-after-free in mwifiex_adapter_cleanup()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index 7c75dca5d0a8..3c4df37333d8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -328,7 +328,7 @@ static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) static void nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) { - timer_delete(&adapter->wakeup_timer); + timer_delete_sync(&adapter->wakeup_timer); nxpwifi_cancel_all_pending_cmd(adapter); wake_up_interruptible(&adapter->cmd_wait_q.wait); wake_up_interruptible(&adapter->hs_activate_wait_q); From d68d6d8d8bbb5596909479dc6cac58132d8f0dbf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:14 +0800 Subject: [PATCH 520/590] wifi: nxpwifi: free the aggregation buffer when the RA list disappears nxpwifi_11n_aggregate_pkt() drops ra_list_spinlock while it copies each subframe, so it rechecks the RA list after taking the lock again. The check inside the aggregation loop returns without releasing the skb_aggr it has been filling, leaking one tx_buf_size buffer along with the subframes already aggregated into it. Release skb_aggr there, the way the same check on the -EBUSY path already does. mwifiex fixed the same issue in commit 990a73dec3fd ("wifi: mwifiex: Fix memory leak in mwifiex_11n_aggregate_pkt()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c index be7080f2a6ce..54933c42c960 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -168,6 +168,7 @@ nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); return -ENOENT; } From 050f03bb0effd773c877cfba3b651c6d380b1c15 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:15 +0800 Subject: [PATCH 521/590] wifi: nxpwifi: zero the channel statistics array adapter->chan_stats comes from vmalloc(), which does not clear the memory, and nxpwifi_cfg80211_dump_survey() reads it as soon as user space asks for survey data. For the entries no scan has filled in, a non-zero cca_scan_dur passes the validity check and stale bytes are reported to user space as noise, time and time_busy. Use kcalloc() instead. The array holds two entries per supported channel, small enough not to need vmalloc(), and the two callers change to kfree() accordingly. mwifiex fixed the same issue in commit 0e20450829ca ("wifi: mwifiex: Initialize the chan_stats array to zero"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 5 +++-- drivers/net/wireless/nxp/nxpwifi/main.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 161697e038c0..788b83356981 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3731,8 +3731,9 @@ int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) * additional active scan request for hidden SSIDs on passive channels. */ adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); - adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), - adapter->num_in_chan_stats)); + adapter->chan_stats = kcalloc(adapter->num_in_chan_stats, + sizeof(*adapter->chan_stats), + GFP_KERNEL); if (!adapter->chan_stats) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 55b962430f37..9e13348105c3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -644,7 +644,7 @@ static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) goto done; err_add_intf: - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); err_init_chan_scan: wiphy_unregister(adapter->wiphy); wiphy_free(adapter->wiphy); @@ -1384,7 +1384,7 @@ static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) wiphy_free(adapter->wiphy); adapter->wiphy = NULL; - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); nxpwifi_free_cmd_buffers(adapter); } From 7769bb2457fcfa8216aeae84f32cb7084cab5637 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:33 +0800 Subject: [PATCH 522/590] wifi: nxpwifi: fix the authentication frame length handling nxpwifi_cfg80211_authenticate() builds a 3-address management frame in a temporary buffer and hands it to nxpwifi_form_mgmt_frame(), which copies it into the skb and splices in address4. The frame length is tracked in a single u16 that conflates the two lengths, and it gets both wrong. Truncation. req->ie_len and req->auth_data_len are both size_t. The IE policy caps req->ie_len at IEEE80211_MAX_DATA_LEN, but NL80211_ATTR_AUTH_DATA only has a four-byte minimum length policy. Since nla_len is a u16, a single authentication data attribute can carry up to 65531 bytes, enough for the combined frame length to exceed U16_MAX before it is assigned to pkt_len. With auth_data_len == 65510 and no IEs the length wraps to 6, kzalloc(6) succeeds, and the memcpy() below then writes 65506 user-provided bytes past a 6-byte heap object. Geometry. NXPWIFI_MGMT_HEADER_LEN is 30, which is the 24-byte 3-address header plus the address4 that the firmware expects. The temporary buffer only holds a struct ieee80211_hdr_3addr, so six of the bytes accounted for are never used, and passing that length to nxpwifi_form_mgmt_frame() makes it append ETH_ALEN on top of a length that already included it. The skb is sized without those six bytes, so the last skb_put_data() overruns the tailroom by exactly ETH_ALEN. It usually goes unnoticed because SKB_DATA_ALIGN() rounding leaves slack. The driver's other caller of the helper, nxpwifi_cfg80211_mgmt_tx(), adds ETH_ALEN to the skb size before allocating and passes the 3-address length, which is what this path should do too. Keep the two lengths apart. frame_len is the 3-address frame the driver builds: it sizes the temporary buffer and is what the helper is told. pkt_len is what the firmware sees, frame_len plus address4: it sizes the skb and goes into tx_info. Computing frame_len in size_t and rejecting anything that cannot be represented once ETH_ALEN is added removes the wrap. The value handed to the firmware is unchanged, so this is not a behavioural change for frames that were already valid. A tighter 802.11 bound may make sense, but that depends on the firmware's frame format and is a separate decision. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 788b83356981..3221ae167c58 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3325,6 +3325,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, struct nxpwifi_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct ieee80211_mgmt *mgmt; struct nxpwifi_txinfo *tx_info; @@ -3398,13 +3399,23 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, nxpwifi_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + - NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + frame_len = req->ie_len + req->auth_data_len + + sizeof(struct ieee80211_hdr_3addr) + NXPWIFI_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; - mgmt = kzalloc(pkt_len, GFP_KERNEL); + /* nxpwifi_form_mgmt_frame() inserts address4, so the frame handed to + * the firmware is ETH_ALEN longer than the one built here. + */ + if (frame_len > U16_MAX - ETH_ALEN) { + nxpwifi_dbg(adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len + ETH_ALEN; + + mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3450,7 +3461,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, memcpy((u8 *)varptr, req->ie, req->ie_len); } - nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, frame_len); kfree(mgmt); priv->auth_flag = HOST_MLME_AUTH_PENDING; priv->auth_alg = auth_alg; From 7fa5fb9976c4532007a666624dc241021f74c8a9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:34 +0800 Subject: [PATCH 523/590] wifi: nxpwifi: handle authentication frame allocation failures nxpwifi_cfg80211_authenticate() allocates the buffer it builds the management frame in and never checks the result: mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(...); if (!skb) { ... return -ENOMEM; } ... memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); On allocation failure the memcpy() a dozen lines later dereferences NULL. The same sequence leaks mgmt when dev_alloc_skb() fails: that path returns without freeing it, while the success path drops it after nxpwifi_form_mgmt_frame(). Bail out when the allocation fails, and free it before returning on the skb error path. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 3221ae167c58..473e81304ca8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3416,6 +3416,8 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, pkt_len = frame_len + ETH_ALEN; mgmt = kzalloc(frame_len, GFP_KERNEL); + if (!mgmt) + return -ENOMEM; skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3423,6 +3425,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, if (!skb) { nxpwifi_dbg(adapter, ERROR, "allocate skb failed for management frame\n"); + kfree(mgmt); return -ENOMEM; } From 32d1a000e99612e010335760363936d85591fd69 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:09 +0100 Subject: [PATCH 524/590] wifi: nxpwifi: fix inverted check in Tx BA stream entry deletion nxpwifi_is_tx_ba_stream_ptr_valid() returns true when the entry is still linked, and every caller passes an entry that is on the list, so the early return always fires and nothing is ever unlinked or freed. Entries leak on every teardown and, since nxpwifi_space_avail_for_new_ba_stream() counts them, Tx aggregation stops being negotiated once the stale count reaches the maximum. Changing the original dead && test to || to silence a NULL dereference report inverted the validity test along with it. Fixes: 00c786a7581e ("wifi: nxpwifi: fix multiple static analysis errors and warnings") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index c2a54d781b42..25b2e430f3f9 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || !nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, From 5e99bdb94720331e2fd38c10c5b1afa1ce640835 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:10 +0100 Subject: [PATCH 525/590] wifi: nxpwifi: do not delete Rx reorder entries under RCU The RCU guard is declared in the body of the per-TID loop, so it is still held across the teardown pass. nxpwifi_del_rx_reorder_entry() flushes the Rx workqueue and deletes the reorder timer synchronously, both of which sleep, so a station deauthenticating from the AP splats under CONFIG_DEBUG_ATOMIC_SLEEP. Scope the guard to the collection walk. The teardown does not need RCU: it serialises on priv->rx_reorder_tbl_lock[] and frees with kfree_rcu(). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index 65b628411543..87cf2ec85991 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -205,11 +205,12 @@ void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) return; for (i = 0; i < MAX_NUM_TID; i++) { - guard(rcu)(); - list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { - if (!memcmp(tbl->ta, ta, ETH_ALEN)) { - INIT_LIST_HEAD(&tbl->tmp_list); - list_add_tail(&tbl->tmp_list, &to_delete); + scoped_guard(rcu) { + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } } } From 05165f7b3b9ab6817fe216c24f90755b7c5e15f3 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Thu, 11 Jun 2026 20:23:32 -0700 Subject: [PATCH 526/590] Revert "wifi: ath12k: add panic handler" ath12k_core_panic_handler() is invoked via atomic_notifier_call_chain(), which runs inside an RCU read-side critical section. The current code calls ath12k_pci_sw_reset() synchronously from this context, which eventually reaches mhi_device_get_sync() and schedule_timeout(), triggering a voluntary context switch within RCU. Call trace: rcu_note_context_switch+0x4c4/0x508 (P) __schedule+0xbc/0x1204 schedule+0x34/0x110 schedule_timeout+0x84/0x11c __mhi_device_get_sync+0x164/0x228 [mhi] mhi_device_get_sync+0x1c/0x3c [mhi] ath12k_wifi7_pci_bus_wake_up+0x20/0x2c [ath12k_wifi7] ath12k_pci_read32+0x58/0x350 [ath12k] ath12k_pci_clear_dbg_registers+0x28/0xb8 [ath12k] ath12k_pci_panic_handler+0x20/0x44 [ath12k] ath12k_core_panic_handler+0x28/0x3c [ath12k] notifier_call_chain+0x78/0x1c0 atomic_notifier_call_chain+0x3c/0x5c Revert change "wifi: ath12k: add panic handler" to avoid this issue. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Yingying Tang Tested-by: Joonhoe Kim <26rote@gmail.com> Reviewed-by: Rameshkumar Sundaram Fixes: 809055628bce ("wifi: ath12k: add panic handler") Link: https://patch.msgid.link/20260612032332.2278338-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 33 +------------------------- drivers/net/wireless/ath/ath12k/core.h | 3 --- drivers/net/wireless/ath/ath12k/hif.h | 9 ------- drivers/net/wireless/ath/ath12k/pci.c | 8 ------- 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b..b0107cde99ff 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1776,29 +1776,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2214,18 +2191,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2250,8 +2222,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2260,7 +2230,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a98fc6e0699d..93fd227b4d19 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1139,8 +1138,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_qmi_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e9..aecdb0c5f285 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b5382..8978edb44cec 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1484,13 +1484,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1508,7 +1501,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif From b8eb5fd5bdb4c03758b056c70e5e1d962ca89757 Mon Sep 17 00:00:00 2001 From: Waiman Long Date: Mon, 5 Oct 2026 06:43:10 -0400 Subject: [PATCH 527/590] cgroup/cpuset: Call is_in_v2_mode() after acquiring cpuset_mutex in cpuset_handle_hotplug() It is reported by sashiko that calling is_in_v2_mode() outside of cpuset_mutex critical section in cpuset_handle_hotplug() can introduce a TOCTOU race where cgroup hierarchy may have changed from v1 to v2 or vice versa after is_in_v2_mode() is called leading to erroneously skip the allocation of tmpmasks or incorrectly modify cpus_allowed masks, resulting in cpuset state corruption. Fix that by calling is_in_v2_mode() after acquiring the cpuset_mutex. Fixes: b8d1b8ee93df ("cpuset: Allow v2 behavior in v1 cgroup") Signed-off-by: Waiman Long Signed-off-by: Tejun Heo --- kernel/cgroup/cpuset.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index d2ec7b128960..b61bc3ed1037 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -4062,15 +4062,16 @@ static void cpuset_handle_hotplug(void) static cpumask_t new_cpus; static nodemask_t new_mems; bool cpus_updated, mems_updated; - bool on_dfl = is_in_v2_mode(); + bool on_dfl; struct tmpmasks tmp, *ptmp = NULL; - if (on_dfl && !alloc_tmpmasks(&tmp)) - ptmp = &tmp; - lockdep_assert_cpus_held(); mutex_lock(&cpuset_mutex); + on_dfl = is_in_v2_mode(); + if (on_dfl && !alloc_tmpmasks(&tmp)) + ptmp = &tmp; + /* fetch the available cpus/mems and find out which changed how */ cpumask_copy(&new_cpus, cpu_active_mask); new_mems = node_states[N_MEMORY]; From 99bda1ecbd56905f8bd934b9cdccd3bd251192b1 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:38 +0000 Subject: [PATCH 528/590] flow_dissector: avoid u16 truncation of skb->len when computing thoff __skb_flow_dissect() computes key_control->thoff using: key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); min_t(u16, ...) casts both arguments to u16 before comparing them. Whenever skb->len (or hlen) modulo 65536 is smaller than nhoff, the truncated length wins and thoff is set to a bogus small value, even when the dissection succeeded. For example, an IPv4/TCP frame with skb->len == 65540 and nhoff == 34 gets thoff == 4, so callers such as skb_probe_transport_header() point the transport header inside the Ethernet header. Such skbs are not exotic: - At the time of commit d0c081b49137 ("flow_dissector: properly cap thoff field"), AF_PACKET with PACKET_VNET_HDR could already build GSO skbs larger than 64KB (MTU checks are skipped for GSO, and alloc_skb_with_frags() accepted up to MAX_SKB_FRAGS (17) order-0 pages on top of the linear part). - BIG TCP now makes skbs larger than 64KB common. - The following patch makes tun_get_user() dissect IFF_TAP frames before eth_type_trans() pulls the Ethernet header, so a GSO frame carrying a 65522..65535 byte L3 packet will be dissected with skb->len in [65536, 65549]. Compare as u32 instead. If the resulting offset cannot be represented in the u16 key_control->thoff, cap it to U16_MAX and report the dissection as failed rather than silently returning a wrong transport offset. thoff is still set on failure, as some callers (such as eth_get_headlen()) use it regardless of the return value. Fixes: d0c081b49137 ("flow_dissector: properly cap thoff field") Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 8aa4f9b4df81..27d8a01bc923 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1071,6 +1071,7 @@ bool __skb_flow_dissect(const struct net *net, int mpls_lse = 0; int num_hdrs = 0; u8 ip_proto = 0; + u32 thoff; bool ret; if (!data) { @@ -1692,7 +1693,13 @@ bool __skb_flow_dissect(const struct net *net, ret = true; out: - key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); + thoff = min_t(u32, nhoff, skb ? skb->len : hlen); + if (unlikely(thoff > U16_MAX)) { + /* Cannot be represented in key_control->thoff. */ + thoff = U16_MAX; + ret = false; + } + key_control->thoff = thoff; key_basic->n_proto = proto; key_basic->ip_proto = ip_proto; From 44378d02c5aaae24b60a75fbbb539b4aa4db503d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:39 +0000 Subject: [PATCH 529/590] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Commit 9e8db5913264 ("net: avoid false positives in untrusted gso validation") added a '&& skb->network_header' check before flow-dissecting GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM in __virtio_net_hdr_to_skb(), because some callers (such as tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header()) called virtio_net_hdr_*_to_skb() before initializing skb->network_header and skb->dev. Because __alloc_skb() and __build_skb_around() zero-initialize skb->network_header to 0 (unlike mac_header and transport_header which are initialized to ~0U), those four callers always had skb->network_header == 0 and bypassed flow dissection in __virtio_net_hdr_to_skb(). More generally, skb->network_header is an offset from skb->head (where 0 is also a valid offset whenever skb_headroom(skb) is 0), not a boolean flag. Whenever the 'if (gso_type && skb->network_header)' branch was skipped, the fallback 'else if (gso_type)' only pulled nh_min_len + thlen (40 bytes for TCPv4) without dissecting the packet, without validating ip_proto or n_proto, and without setting skb->transport_header. If the packet has a malformed network header, it is not rejected and a subsequent skb_probe_transport_header() also fails, leaving skb->transport_header at ~0U (0xffff). Similarly, if an IPv4 packet carries IP options (ihl > 5) or an IPv6 packet carries extension headers, pulling only nh_min_len + thlen can leave the TCP header outside skb->head. In both cases, tcp_hdrlen(skb) in skb_gso_transport_seglen() reads out-of-bounds: BUG: KASAN: slab-out-of-bounds in skb_gso_transport_seglen Read of size 2 by task poc/133 skb_gso_transport_seglen (net/core/gso.c:155) skb_gso_validate_mac_len (net/core/gso.c:270) tbf_enqueue (net/sched/sch_tbf.c:260) dev_qdisc_enqueue (net/core/dev.c:4227) __dev_queue_xmit (net/core/dev.c:4884) In addition, checking virtio_net_hdr_match_proto() only inside 'if (!skb->protocol)' before flow dissection both skipped validation when skb->protocol was pre-set by the caller and rejected VLAN-tagged frames whose outer L2 protocol is ETH_P_8021Q or ETH_P_8021AD. Fix this by: 1. Initializing skb->dev and skb->network_header (plus skb->protocol for IFF_TUN) before virtio_net_hdr_*_to_skb() in tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header(). In tun_get_user(), drop the redundant skb_reset_mac_header(skb) in the IFF_TUN case since __virtio_net_hdr_to_skb() unconditionally resets mac_header. 2. Removing '&& skb->network_header' and the unvalidated 'else if (gso_type)' fallback in __virtio_net_hdr_to_skb() so all GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM are flow-dissected, have their transport header pulled into linear data, and have skb->transport_header set. 3. Moving the virtio_net_hdr_match_proto() check to after skb_flow_dissect_flow_keys_basic(), validating keys.basic.n_proto against hdr_gso_type. Fixes: 9e8db5913264 ("net: avoid false positives in untrusted gso validation") Fixes: d5be7f632bad ("net: validate untrusted gso packets without csum offload") Fixes: 924a9bc362a5 ("net: check if protocol extracted by virtio_net_hdr_set_proto is correct") Reported-by: Weiming Shi Closes: https://lore.kernel.org/netdev/20260927163117.746432-2-bestswngs@gmail.com/ Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Cc: Michael S. Tsirkin Link: https://patch.msgid.link/20261001191140.2818991-3-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- arch/um/drivers/vector_transports.c | 1 + drivers/net/tun.c | 23 +++++++---- drivers/net/virtio_net.c | 2 + include/linux/virtio_net.h | 64 ++++++++++++----------------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c index ddd127ee9678..e1fb2a76fdff 100644 --- a/arch/um/drivers/vector_transports.c +++ b/arch/um/drivers/vector_transports.c @@ -209,6 +209,7 @@ static int raw_verify_header( if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) return 1; + skb_set_network_header(skb, ETH_HLEN); virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); return 0; } diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68a..242899f7fd07 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1897,12 +1897,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { - atomic_long_inc(&tun->rx_frame_errors); - err = -EINVAL; - goto free_skb; - } - + skb->dev = tun->dev; switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { @@ -1927,9 +1922,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - skb_reset_mac_header(skb); + skb_reset_network_header(skb); skb->protocol = pi.proto; - skb->dev = tun->dev; break; case IFF_TAP: if (!pskb_may_pull(skb, ETH_HLEN)) { @@ -1937,10 +1931,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; } - skb->protocol = eth_type_trans(skb, tun->dev); + skb_set_network_header(skb, ETH_HLEN); break; } + if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { + atomic_long_inc(&tun->rx_frame_errors); + err = -EINVAL; + goto free_skb; + } + + if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) + skb->protocol = eth_type_trans(skb, tun->dev); + /* copy skb_ubuf_info for callback when skb has no error */ if (zerocopy) { skb_zcopy_init(skb, msg_control); @@ -2600,6 +2603,8 @@ static int tun_xdp_one(struct tun_struct *tun, features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + skb->dev = tun->dev; + skb_set_network_header(skb, ETH_HLEN); if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index bf82ef9874ab..daab43ac92ce 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -2515,6 +2515,8 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue * goto frame_err; } + skb->dev = dev; + skb_set_network_header(skb, ETH_HLEN); if (virtio_net_hdr_tnl_to_skb(skb, &hdr->tnl_hdr, vi->rx_tnl, vi->rx_tnl_csum, virtio_is_little_endian(vi->vdev))) { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c381b916c1b5..d6466f96cdd0 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, p_off = nh_min_len + thlen; if (!pskb_may_pull(skb, p_off)) return -EINVAL; - } else { + } else if (gso_type) { /* gso packets without NEEDS_CSUM do not set transport_offset. * probe and drop if does not match one of the above types. */ - if (gso_type && skb->network_header) { - struct flow_keys_basic keys; + struct flow_keys_basic keys; - if (!skb->protocol) { - __be16 protocol = dev_parse_header_protocol(skb); - - if (!protocol) - virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, - hdr_gso_type)) - return -EINVAL; - else - skb->protocol = protocol; - } -retry: - if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, - NULL, 0, 0, 0, - 0)) { - /* UFO does not specify ipv4 or 6: try both */ - if (gso_type & SKB_GSO_UDP && - skb->protocol == htons(ETH_P_IP)) { - skb->protocol = htons(ETH_P_IPV6); - goto retry; - } - return -EINVAL; - } - - p_off = keys.control.thoff + thlen; - if (!pskb_may_pull(skb, p_off) || - keys.basic.ip_proto != ip_proto) - return -EINVAL; - - skb_set_transport_header(skb, keys.control.thoff); - } else if (gso_type) { - p_off = nh_min_len + thlen; - if (!pskb_may_pull(skb, p_off)) - return -EINVAL; + if (!skb->protocol) { + skb->protocol = dev_parse_header_protocol(skb); + if (!skb->protocol) + virtio_net_hdr_set_proto(skb, hdr); } +retry: + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, + NULL, 0, 0, 0, + 0)) { + /* UFO does not specify ipv4 or 6: try both */ + if (gso_type & SKB_GSO_UDP && + skb->protocol == htons(ETH_P_IP)) { + skb->protocol = htons(ETH_P_IPV6); + goto retry; + } + return -EINVAL; + } + + p_off = keys.control.thoff + thlen; + if (!pskb_may_pull(skb, p_off) || + keys.basic.ip_proto != ip_proto || + !virtio_net_hdr_match_proto(keys.basic.n_proto, + hdr_gso_type)) + return -EINVAL; + + skb_set_transport_header(skb, keys.control.thoff); } if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { From ee2d4c2d7cc18a2cd504044a75ea614e720248eb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 1 Oct 2026 19:11:40 +0000 Subject: [PATCH 530/590] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Add selftests in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). Also verify that the following are rejected with -EINVAL: - a mismatched GSO type (VIRTIO_NET_HDR_GSO_TCPV6 on a VLAN-tagged IPv4 packet). - a frame whose TCP header is truncated after 10 bytes. Pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes would accept it, so this requires the transport offset found by flow dissection. Finally, verify that a 65540-byte frame is accepted. Its skb->len is above U16_MAX while it is flow-dissected, before eth_type_trans() pulls the Ethernet header. Based on a reproducer by Michael S. Tsirkin . Signed-off-by: Eric Dumazet Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261001191140.2818991-4-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/tun.c | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50b..afaa81c9bac5 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -542,6 +543,141 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +FIXTURE(tun_vnet_gso) +{ + char ifname[IFNAMSIZ]; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_gso) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_gso) +{ + if (self->fd >= 0) + close(self->fd); +} + +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) +{ + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); + struct virtio_net_hdr vh = { 0 }; + struct tcphdr tcph = { 0 }; + uint32_t sum; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, + param_hwaddr_outer_dst); + + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + + cur += build_ipv4_header(cur, IPPROTO_TCP, + sizeof(tcph) + payload_len, + ¶m_ipaddr4_outer_src, + ¶m_ipaddr4_outer_dst); + + tcph.source = htons(12345); + tcph.dest = htons(80); + tcph.seq = htonl(1); + tcph.doff = sizeof(tcph) / 4; + tcph.ack = 1; + tcph.window = htons(65535); + memcpy(cur, &tcph, sizeof(tcph)); + memset(cur + sizeof(tcph), PKT_DATA, payload_len); + + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, + sizeof(param_ipaddr4_outer_src)); + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, + sizeof(param_ipaddr4_outer_dst)); + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); + sum += add_csum(cur, sizeof(tcph) + payload_len); + tcph.check = finish_ip_csum(sum); + memcpy(cur, &tcph, sizeof(tcph)); + cur += sizeof(tcph) + payload_len; + + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.gso_size = 1400; + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; + memcpy(buf, &vh, sizeof(vh)); + + return cur - buf; +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) +{ + struct virtio_net_hdr vh; + uint8_t pkt[4096] = { 0 }; + int len, ret; + + len = build_vlan_tcpv4_gso_packet(pkt, 2800); + memcpy(&vh, pkt, sizeof(vh)); + + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ + vh.flags = 0; + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* TCP header truncated after 10 bytes, without NEEDS_CSUM. + * Requires the transport header offset found by flow dissection: + * pulling only sizeof(struct iphdr) + sizeof(struct tcphdr) bytes + * from the mac header would accept this frame. + */ + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr) + 10; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) +{ + /* Ethernet frame of 65540 bytes: skb->len is above U16_MAX when the + * frame is flow-dissected, before eth_type_trans() pulls ETH_HLEN. + */ + const int payload_len = 65540 - ETH_HLEN - 4 - + sizeof(struct iphdr) - sizeof(struct tcphdr); + struct virtio_net_hdr vh; + uint8_t *pkt; + int len, ret; + + pkt = calloc(1, sizeof(vh) + 65540); + ASSERT_NE(pkt, NULL); + + len = build_vlan_tcpv4_gso_packet(pkt, payload_len); + ASSERT_EQ(len, sizeof(vh) + 65540); + memcpy(&vh, pkt, sizeof(vh)); + + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + free(pkt); + ASSERT_EQ(ret, len); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; From 8bc60db48e8cba35488bbd6e1f94aa990afa275a Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 12:30:58 +0900 Subject: [PATCH 531/590] ip6_gre: remove obsolete ERSPAN PMTU update A tc tunnel_key action can attach a METADATA_IP_TUNNEL dst to an skb, and mirred can redirect it to a native ip6erspan device. The legacy PMTU update in ip6erspan_tunnel_xmit() treats that metadata dst as a route. Since it has no output device, dst_dev(dst)->mtu dereferences NULL. On v7.2 with KASAN, an initial UID/GID 65534 process with CapEff 0 used self-created user and network namespaces to trigger: KASAN: null-ptr-deref RIP: ip6erspan_tunnel_xmit+0x10fc/0x2cc0 Kernel panic - not syncing: Fatal exception in interrupt The update is obsolete. Commit fe1a4ca0a2b7 ("ip6_gre: process toobig in a better way") changed IPv6 GRE Too Big handling to update the underlay route and removed the equivalent block from __gre6_xmit(). ip6_tnl_xmit() then propagates the underlay PMTU to the overlay route. Remove the stale ERSPAN copy and its now-unused dst variable. This eliminates the metadata-dst dereference and keeps ERSPAN aligned with IPv6 GRE PMTU handling. Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support") Cc: stable@vger.kernel.org Suggested-by: Ido Schimmel Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20261002033058.358137-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/ip6_gre.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index e61cb10b50dc..04f7c70b7320 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -923,7 +923,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, { struct ip_tunnel_info *tun_info = NULL; struct ip6_tnl *t = netdev_priv(dev); - struct dst_entry *dst = skb_dst(skb); IP_TUNNEL_DECLARE_FLAGS(flags) = { }; bool truncate = false; int encap_limit = -1; @@ -1058,12 +1057,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, gre_build_header(skb, 8, flags, proto, 0, htonl(atomic_fetch_inc(&t->o_seqno))); - /* TooBig packet may have updated dst->dev's mtu */ - if (!t->parms.collect_md && dst) { - mtu = READ_ONCE(dst_dev(dst)->mtu); - if (dst_mtu(dst) > mtu) - dst->ops->update_pmtu(dst, NULL, skb, mtu, false); - } err = ip6_tnl_xmit(skb, dev, dsfield, &fl6, encap_limit, &mtu, NEXTHDR_GRE); if (err != 0) { From a463f55c791b96cfd12df8b10ea737d45c80bb18 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 00:13:10 +0900 Subject: [PATCH 532/590] vsock/virtio: account only unread bytes in read_skb() After a partial stream receive, rx_bytes tracks the unread suffix while buf_used and peer credit still cover the whole packet. virtio_transport_read_skb() dequeues that packet but passes pkt_len for both counters, underflowing rx_bytes. On a connected CID_LOCAL stream with an SK_SKB verdict, a 40-byte read from a 100-byte packet made SIOCINQ report -40, an empty recv return ELOOP, and poll report the empty socket readable. After verdict detach, receiving 40 bytes wrapped the counter to zero and hid those queued bytes until one more byte arrived. The fixed run kept the counter balanced and exposed all queued data immediately. This was found during an LLM-assisted manual source audit of VSOCK receive accounting while re-evaluating virtio_transport_read_skb() after CVE-2024-50169. Subtract only skb->len minus the VSOCK offset from rx_bytes. Retain the full packet length for buf_used and peer credit. Fixes: 45ca7e9f0730 ("vsock/virtio: fix `rx_bytes` accounting for stream sockets") Cc: stable@vger.kernel.org Reviewed-by: Luigi Leonardi Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20261001151310.4101006-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/vmw_vsock/virtio_transport_common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c index f225f53ed4ba..1e762a480df4 100644 --- a/net/vmw_vsock/virtio_transport_common.c +++ b/net/vmw_vsock/virtio_transport_common.c @@ -1927,6 +1927,7 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto struct sock *sk = sk_vsock(vsk); struct virtio_vsock_hdr *hdr; struct sk_buff *skb; + u32 bytes_read; u32 pkt_len; int off = 0; int err; @@ -1946,7 +1947,8 @@ int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_acto vvs->msg_count--; pkt_len = le32_to_cpu(hdr->len); - virtio_transport_dec_rx_pkt(vvs, pkt_len, pkt_len); + bytes_read = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset; + virtio_transport_dec_rx_pkt(vvs, bytes_read, pkt_len); spin_unlock_bh(&vvs->rx_lock); virtio_transport_send_credit_update(vsk); From 6619e01855e65ef35723685a557e3eb106f4ecf5 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:58 +0900 Subject: [PATCH 533/590] ipv4: stop PMTU walk when nexthop group shrinks Commit 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") made __ip_rt_update_pmtu() update every path. For nexthop objects, fib_info_num_path() and fib_info_nhc() independently load nh->nh_grp. RCU protects each group's lifetime but does not make the loads observe the same group. If replacement shrinks the group after the loop accepts an index, fib_info_nhc() returns NULL and update_or_create_fnhe() dereferences it. A deterministic probe only widened the existing window between the real operations. On v7.2, an RTM_NEWNEXTHOP replacement published a one-member group after the PMTU reader accepted index 1 from a two-member group, producing: KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] RIP: update_or_create_fnhe+0x45/0x15b0 Stop when the indexed path is absent. Groups are dense, so no later index exists in that snapshot. The same real-writer window completed 114,423 PMTU iterations without an oops with this check. A reproducer is available on request. Replacement requires CAP_NET_ADMIN in the network namespace. Where unprivileged user namespaces are permitted, a local user can obtain it in a new user and network namespace. Fixes: 7d3f3b4367f3 ("net: ipv4: Cache pmtu for all packet paths if multipath enabled") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/4fb87ed994dfc067178fc845cdd8bc9b5446b039.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 37674d76f90f..5f2197874beb 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1082,6 +1082,8 @@ static void __ip_rt_update_pmtu(struct rtable *rt, struct flowi4 *fl4, u32 mtu) for (nhsel = 0; nhsel < fib_info_num_path(res.fi); nhsel++) { nhc = fib_info_nhc(res.fi, nhsel); + if (!nhc) + break; update_or_create_fnhe(nhc, fl4->daddr, 0, mtu, lock, jiffies + net->ipv4.ip_rt_mtu_expires); } From 4f4afc07dcb2edd4f9afa36595373f8cdb9b58c2 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:50:59 +0900 Subject: [PATCH 534/590] ipv4: stop exception dump when nexthop group shrinks Commit 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") allowed IPv4 FIB dumps to run concurrently with nexthop replacement. fib_dump_info_fnhe() uses fib_info_num_path() as the loop bound, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and the dump dereferences it through nhc_flags. Stop the walk when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 4ce5dc9316de ("inet: switch inet_dump_fib() to RCU protection") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261001170513.GA1657889@shredder/ Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/5bb7bd97411eb9db454464bf449107395e760f26.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/route.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 5f2197874beb..268d8821f7e0 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -3170,6 +3170,9 @@ int fib_dump_info_fnhe(struct sk_buff *skb, struct netlink_callback *cb, struct fnhe_hash_bucket *bucket; int err; + if (!nhc) + break; + if (nhc->nhc_flags & RTNH_F_DEAD) continue; From d3daa33c7af92e38f2274bbd2576f58bfbaf89e6 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Fri, 2 Oct 2026 13:51:00 +0900 Subject: [PATCH 535/590] ipv4: stop route notification sizing when nexthop group shrinks Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added an RCU-only fib_nlmsg_size() call for asynchronous hardware flag notifications. fib_nlmsg_size() checks fib_info_num_path() before each iteration, then fib_info_nhc() independently reloads nh->nh_grp. If RTM_NEWNEXTHOP replaces a group with fewer paths between those loads, fib_info_nhc() returns NULL and fib_nexthop_nlmsg_size() dereferences it. Stop sizing when the indexed path is absent. Nexthop groups are dense, so the current snapshot has no later path. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/1054fa46165bba1b473782fa41367804aa908e86.1790915964.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_semantics.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 5c9021ea3a79..47469b775808 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -542,6 +542,9 @@ size_t fib_nlmsg_size(struct fib_info *fi) struct fib_nh_common *nhc = fib_info_nhc(fi, i); size_t nhsize; + if (!nhc) + break; + nhsize = fib_nexthop_nlmsg_size(nhc, nhs != 1); if (nhs != 1) From 838bb60dc21553e32df90f2f3bac5b905376e7aa Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 30 Sep 2026 19:47:09 +0100 Subject: [PATCH 536/590] mm/mremap: fix locked_vm leak from MREMAP_DONTUNMAP self-merge Patch series "mm/mremap: fix two issues with MREMAP_DONTUNMAP", v2. The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Commit 397432cab17b ("mm/mremap: account mm->locked_vm correctly for MREMAP_DONTUNMAP") fixed an accidentally introduced bug around mm->locked_vm accounting, but this wasn't the only issue. And thus history repeats itself, as it turns out that mm->locked_vm accounting is broken by MREMAP_DONTUNMAP yet again by two further cases, and has been broken ever since the feature was introduced. Both relate to the fact that VMA_LOCKED_BIT is cleared on the source VMA (it has to be as all page tables are moved): 1. If an unfaulted VMA_LOCKONFAULT_BIT anonymous VMA self-merges it clears the VMA_LOCKED_BIT flag and permanently leaks mm->locked_vm pages. 2. If a partial mremap() is performed on a locked VMA there is a leak equal to the number of pages not copied. (Both for MREMAP_DONTUNMAP operations only) Both issues can be fixed by treating the source range as distinct from the destination range, which is the definition of what MREMAP_DONTUNMAP does so is appropriate. In case 1, simply disallow the self-merge, keeping adjacent source and destination VMAs distinct. In case 2, split the source range ahead of time if the VMA is mlock()'d, so accounting is always correct. Both changes were tested locally and confirmed to fix the issues. For the purposes of a backport, the fixes are kept distinct, a follow-up series can add self-tests. This patch (of 2): The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Fix another of these - self-merge. Self-merge occurs when a VMA is moved in front of or behind itself and the attributes of the VMA permit such a merge. Practically this can only happen for unfaulted anonymous VMAs due to the page offset equality requirement for merge: |------------| | | | v |...........||-----------||...........| | || unfaulted || | |...........||-----------||...........| ^ | | | |------------| This becomes problematic if the VMA is configured by the user to mlock-on-fault, i.e. the VMA_LOCKED_BIT, VMA_LOCKONFAULT_BIT VMA flags are set. MREMAP_DONTUNMAP clears mlock flags for the source VMA and maintains them for the destination VMA. Self-merge makes this impossible (there is only one VMA) and incorrectly clears the destination VMA's mlock flags. This causes a leak in mm->locked_vm as clearing this flag does not decrement the counter and the VMA no longer has VMA_LOCKED_BIT set so it is not decremented on unmap. Resolve this by simply disallowing a self-merge in this case - the source and destination VMAs are kept distinct and then are able to have distinct mlock() flags. Update dontunmap_complete() to make the now-redundant self-merge check a VM_WARN_ON_ONCE() instead to guard against future regressions. Also update the VMA userland tests to reflect the change. Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-0-f388985a0f0a@kernel.org Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-1-f388985a0f0a@kernel.org Fixes: e346b3813067 ("mm/mremap: add MREMAP_DONTUNMAP to mremap()") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reviewed-by: Pedro Falcato Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Anirudh Srinivasan Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Brian Geffon Cc: Minchan Kim Cc: --- mm/mremap.c | 8 ++++++-- mm/vma.c | 17 ++++++++++++++++- mm/vma.h | 2 +- tools/testing/vma/tests/vma.c | 10 +++++----- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 7c368440fafe..058dd66fe1d7 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1275,7 +1275,8 @@ static int copy_vma_and_data(struct vma_remap_struct *vrm, PAGETABLE_MOVE(pmc, NULL, NULL, vrm->addr, vrm->new_addr, vrm->old_len); new_vma = copy_vma(&vma, vrm->new_addr, vrm->new_len, new_pgoff, - new_anon_pgoff, &pmc.need_rmap_locks); + new_anon_pgoff, &pmc.need_rmap_locks, + vrm->flags & MREMAP_DONTUNMAP); if (!new_vma) { vrm_uncharge(vrm); *new_vma_ptr = NULL; @@ -1335,6 +1336,9 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, unsigned long old_start = vma->vm_start; unsigned long old_end = vma->vm_end; + /* Self-merge is disallowed. */ + VM_WARN_ON_ONCE(new_vma == vma); + /* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */ vma_clear_flags_mask(vma, VMA_LOCKED_MASK); @@ -1342,7 +1346,7 @@ static void dontunmap_complete(struct vma_remap_struct *vrm, * anon_vma links of the old vma is no longer needed after its page * table has been moved. */ - if (new_vma != vma && start == old_start && end == old_end) { + if (start == old_start && end == old_end) { const pgoff_t pgoff_unfaulted = vma->vm_start >> PAGE_SHIFT; unlink_anon_vmas(vma); diff --git a/mm/vma.c b/mm/vma.c index 6cde67883fb0..d8308c319daa 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -1943,7 +1943,7 @@ static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma) */ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks) + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source) { struct vm_area_struct *vma = *vmap; unsigned long old_vma_start = vma->vm_start; @@ -1981,6 +1981,21 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, vmg.pgoff = pgoff; vmg.anon_pgoff = anon_pgoff; vmg.next = vma_iter_next_rewind(&vmi, NULL); + + /* + * If the original VMA is kept (MREMAP_DONTUNMAP), the source and + * destination VMA must be treated distinctly. + * + * A merge violates this, so in this case disallow a self-merge. + */ + if (can_self_merge && keep_source) { + if (vmg.prev == vma) + vmg.prev = NULL; + if (vmg.next == vma) + vmg.next = NULL; + can_self_merge = false; + } + new_vma = vma_merge_copied_range(&vmg); if (new_vma) { diff --git a/mm/vma.h b/mm/vma.h index 024fabe63560..39514fac72fc 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -535,7 +535,7 @@ void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb, struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, unsigned long addr, unsigned long len, pgoff_t pgoff, - pgoff_t anon_pgoff, bool *need_rmap_locks); + pgoff_t anon_pgoff, bool *need_rmap_locks, bool keep_source); struct anon_vma *find_mergeable_anon_vma(struct vm_area_struct *vma); diff --git a/tools/testing/vma/tests/vma.c b/tools/testing/vma/tests/vma.c index c8ef7b8cd46b..e973e0a6d1a8 100644 --- a/tools/testing/vma/tests/vma.c +++ b/tools/testing/vma/tests/vma.c @@ -40,7 +40,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x1000, 0x2000, 1, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 1, 1, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -53,7 +53,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x2000, 0x3000, 2, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks); + vma_new = copy_vma(&vma, 0x1000, 0x1000, 2, 2, &need_locks, false); ASSERT_EQ(vma_new, vma_orig); ASSERT_EQ(vma, vma_orig); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -71,7 +71,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0x3000, 0x4000, 3, vma_flags); vma_set_anonymous(vma); vma_orig = vma; - vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks); + vma_new = copy_vma(&vma, 0x2000, 0x1000, 3, 3, &need_locks, false); ASSERT_NE(vma_new, vma_orig); ASSERT_EQ(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0x1000); @@ -82,7 +82,7 @@ static bool test_copy_vma(void) /* Move backwards and do not merge. */ vma = alloc_and_link_vma(&mm, 0x3000, 0x5000, 3, vma_flags); - vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks); + vma_new = copy_vma(&vma, 0, 0x2000, 0, 3, &need_locks, false); ASSERT_NE(vma_new, vma); ASSERT_EQ(vma_new->vm_start, 0); ASSERT_EQ(vma_new->vm_end, 0x2000); @@ -95,7 +95,7 @@ static bool test_copy_vma(void) vma = alloc_and_link_vma(&mm, 0, 0x2000, 0, vma_flags); vma_next = alloc_and_link_vma(&mm, 0x6000, 0x8000, 6, vma_flags); - vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks); + vma_new = copy_vma(&vma, 0x4000, 0x2000, 4, 4, &need_locks, false); vma_assert_attached(vma_new); ASSERT_EQ(vma_new, vma_next); From 597e4b9ab48a3f841080f627722064853d3f650e Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 30 Sep 2026 19:47:10 +0100 Subject: [PATCH 537/590] mm/mremap: fix locked_vm leak by splitting VMA for MREMAP_DONTUNMAP The MREMAP_DONTUNMAP feature is highly unusual in that it permits mremap() operations that keep the original VMA in place. Historically this has led to a lot of bugs where non-obvious interactions occur between existing mremap() operations and the original VMA. Fix another of these - partial copies. The long-standing mremap() partial VMA logic has the baked-in assumption that the originating VMA is unmapped and thus moved. However MREMAP_DONTUNMAP defeats this by performing a partial copy instead since it keeps the source VMA around. An mremap(..., MREMAP_DONTUNMAP) operation disallows resizing of the VMA, but the operation can be performed partially: |-----------------| | | | v <------> <------> .new_sz. new_sz |--.------.--| |------| | .source. | | dest | |--.------.--| |------| <------------> old_sz The page tables in the specified range are moved, but the original VMA is kept intact. This interacts poorly with mlock()'d VMAs, as the VMA_LOCKED_BIT flag is cleared for the entire source VMA and set for the entire destination VMA. This results in an mm->locked_vm leak as the change is therefore not accounted correctly. The clear solution here is to make the portion of the source VMA which is mremap()'d distinct from the rest of it, a.k.a. split it. Therefore resolve this issue by splitting it ahead of the rest of the mremap() operation if the VMA is mlock()'d. This is valid, as the source VMA will lose its VMA_LOCKED_BIT flag, so if a partial remap it will become distinct from the rest of the VMA. In order to make this change re-expose split_vma() in vma.h for CONFIG_MMU (nommu doesn't compile mremap.c and uses a static helper instead). Finally, update the sys_map_count check to account for this case. Note that the early check does not use needs_pre_split() - this is because the VMA has not been looked up by this point, so be conservative and assume that the VMA is mlock()'d in this case for the purposes of the sys_map_count check. Link: https://lore.kernel.org/20260930-fix-dontunmap-partial-self-merge-v2-2-f388985a0f0a@kernel.org Fixes: e346b3813067 ("mm/mremap: add MREMAP_DONTUNMAP to mremap()") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reviewed-by: Pedro Falcato Acked-by: Kiryl Shutsemau (Meta) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Anirudh Srinivasan Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Brian Geffon Cc: Minchan Kim Cc: --- mm/mremap.c | 62 +++++++++++++++++++++++++++++++++++++++-------------- mm/vma.c | 4 ++-- mm/vma.h | 5 +++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/mm/mremap.c b/mm/mremap.c index 058dd66fe1d7..444f37d7cd28 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -1037,6 +1037,7 @@ static void vrm_stat_account(struct vma_remap_struct *vrm, } static bool __check_map_count_against_split(struct mm_struct *mm, + bool pre_split, bool before_unmaps) { const int sys_map_count = get_sysctl_max_map_count(); @@ -1088,26 +1089,42 @@ static bool __check_map_count_against_split(struct mm_struct *mm, * Therefore we must check to ensure we have headroom of 2 additional * VMAs. */ - return map_count + 2 <= sys_map_count; + map_count += 2; + + /* If pre-split, the -1 observed above doesn't apply. */ + if (pre_split) + map_count++; + + return map_count <= sys_map_count; +} + +static bool needs_pre_split(struct vma_remap_struct *vrm) +{ + /* + * An MREMAP_DONTUNMAP of a mlock()'d VMA needs to unlock the + * source VMA, so split in this case. + */ + return (vrm->flags & MREMAP_DONTUNMAP) && + vma_test(vrm->vma, VMA_LOCKED_BIT); } /* Do we violate the map count limit if we split VMAs when moving the VMA? */ -static bool check_map_count_against_split(void) +static bool check_map_count_against_split(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/false); + needs_pre_split(vrm), /*before_unmaps=*/false); } /* Do we violate the map count limit if we split VMAs prior to early unmaps? */ -static bool check_map_count_against_split_early(void) +static bool check_map_count_against_split_early(struct vma_remap_struct *vrm) { return __check_map_count_against_split(current->mm, - /*before_unmaps=*/true); + vrm->flags & MREMAP_DONTUNMAP, /*before_unmaps=*/true); } /* - * Perform checks before attempting to write a VMA prior to it being - * moved. + * Perform checks and preparation before attempting to write a VMA prior to it + * being moved. */ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) { @@ -1116,19 +1133,17 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) unsigned long old_addr = vrm->addr; unsigned long old_len = vrm->old_len; vm_flags_t dummy = vma->vm_flags; + const bool split_before = vma->vm_start != old_addr; + const bool split_after = vma->vm_end != old_addr + old_len; - /* - * We'd prefer to avoid failure later on in do_munmap: we copy a VMA, - * which may not merge, then (if MREMAP_DONTUNMAP is not set) unmap the - * source, which may split, causing a net increase of 2 mappings. - */ - if (!check_map_count_against_split()) + /* Avoid failure later on. */ + if (!check_map_count_against_split(vrm)) return -ENOMEM; if (vma->vm_ops && vma->vm_ops->may_split) { - if (vma->vm_start != old_addr) + if (split_before) err = vma->vm_ops->may_split(vma, old_addr); - if (!err && vma->vm_end != old_addr + old_len) + if (!err && split_after) err = vma->vm_ops->may_split(vma, old_addr + old_len); if (err) return err; @@ -1146,6 +1161,21 @@ static unsigned long prep_move_vma(struct vma_remap_struct *vrm) if (err) return err; + /* + * To account mlock()'d pages correctly in the MREMAP_DONTUNMAP + * case perform any split ahead of time for an mlock()'d VMA. + */ + if (needs_pre_split(vrm)) { + VMA_ITERATOR(vmi, vma->vm_mm, old_addr); + + if (split_before) + err = split_vma(&vmi, vma, old_addr, 1); + if (!err && split_after) + err = split_vma(&vmi, vma, old_addr + old_len, 0); + vrm->vmi_needs_invalidate = true; + return err; + } + return 0; } @@ -2006,7 +2036,7 @@ static unsigned long do_mremap(struct vma_remap_struct *vrm) return -EINTR; vrm->mmap_locked = true; - if (!check_map_count_against_split_early()) { + if (!check_map_count_against_split_early(vrm)) { mmap_write_unlock(mm); return -ENOMEM; } diff --git a/mm/vma.c b/mm/vma.c index d8308c319daa..6c35f0d775ab 100644 --- a/mm/vma.c +++ b/mm/vma.c @@ -634,8 +634,8 @@ __split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, * Split a vma into two pieces at address 'addr', a new vma is allocated * either for the first part or the tail. */ -static int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, - unsigned long addr, int new_below) +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below) { if (vma->vm_mm->map_count >= get_sysctl_max_map_count()) return -ENOMEM; diff --git a/mm/vma.h b/mm/vma.h index 39514fac72fc..36973abaa015 100644 --- a/mm/vma.h +++ b/mm/vma.h @@ -556,6 +556,11 @@ int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *brkvma, unsigned long unmapped_area(struct vm_unmapped_area_info *info); unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info); +#ifdef CONFIG_MMU +int split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma, + unsigned long addr, int new_below); +#endif + static inline bool vma_wants_manual_pte_write_upgrade(struct vm_area_struct *vma) { /* From 29e0dd1f2ef82d0931ed1a4e6b1d8aa04650835d Mon Sep 17 00:00:00 2001 From: John Garry Date: Wed, 23 Sep 2026 08:53:24 +0100 Subject: [PATCH 538/590] mailmap: update addresses for John Garry Point any employment addresses at my personal dev address. Link: https://lore.kernel.org/20260923075324.1382927-1-john.garry@linux.dev Signed-off-by: John Garry Signed-off-by: Andrew Morton --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index b8d0dc185714..39f449f9c42d 100644 --- a/.mailmap +++ b/.mailmap @@ -461,7 +461,8 @@ Johan Hovold Johan Hovold John Crispin John Fastabend -John Garry +John Garry +John Garry John Keeping John Moon John Paul Adrian Glaubitz From 2fb552b0bee70c24c092720028ab4620f5eb2646 Mon Sep 17 00:00:00 2001 From: Mikhail Gavrilov Date: Fri, 25 Sep 2026 10:06:47 +0500 Subject: [PATCH 539/590] mm: don't schedule deferred kernel page table freeing while booting Booting with a boot-time function tracer and a filter, for example ftrace=function ftrace_filter=pud_free_pmd_page panics on 7.3-rc4 as soon as the tracer starts: [ 23.531178] Starting tracer 'function' [ 23.675800] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000038: 0000 [#1] SMP KASAN NOPTI [ 23.819917] KASAN: null-ptr-deref in range [0x00000000000001c0-0x00000000000001c7] [ 23.964025] CPU: 0 UID: 0 PID: 0 Comm: swapper Not tainted 7.3.0-rc4-fe2ec83746e5-with-fixes-v2+ #195 PREEMPT(undef) [ 24.252248] RIP: 0010:__queue_work+0xab/0xf00 [ 25.981629] Call Trace: [ 26.125727] [ 26.413912] ? pagetable_free_kernel+0x20/0x120 [ 26.990283] queue_work_on+0x97/0xf0 [ 27.134382] __cpa_collapse_large_pages+0x501/0x6f0 [ 27.566662] cpa_flush+0x394/0x620 [ 27.998953] change_page_attr_set_clr+0x321/0x4a0 [ 29.151729] set_memory_rox+0xa2/0xf0 [ 29.584018] create_trampoline+0x431/0x6f0 ... [ 44.343347] Kernel panic - not syncing: Attempted to kill the idle task! The boot-time tracer is started from early_trace_init(), which runs before workqueue_init_early(). Making its trampoline read-only splits a large page, and CPA collapses it again right away. The split table has been a kernel page table since commit 9e4a3ec3411b ("x86/mm/pat: Allocate split page tables as kernel page tables"), so the collapse frees it through pagetable_free_kernel(), which queues work on system_percpu_wq - still NULL at that point. That commit is correct in itself; it only lets CPA reach pagetable_free_kernel() before the workqueue that function relies on exists. Keep putting the table on the list, but don't schedule the work while the system is still booting. A core_initcall schedules it once to free whatever was queued by then. Link: https://lore.kernel.org/20260925050647.86913-1-mikhail.v.gavrilov@gmail.com Link: https://lore.kernel.org/20260924064321.23787-1-mikhail.v.gavrilov@gmail.com Fixes: 9e4a3ec3411b ("x86/mm/pat: Allocate split page tables as kernel page tables") Signed-off-by: Mikhail Gavrilov Signed-off-by: Andrew Morton Suggested-by: David Hildenbrand (Arm) Suggested-by: Lorenzo Stoakes (ARM) Reviewed-by: Andrew Morton Reviewed-by: David Hildenbrand (Arm) Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Jose A. Perez de Azpillaga Tested-by: Jose A. Perez de Azpillaga Cc: Dave Hansen Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Vishal Moola (Oracle) Cc: Ingo Molnar Cc: Lu Baolu Cc: Jason Gunthorpe Cc: Steven Rostedt Cc: --- mm/pgtable-generic.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c index b91b1a98029c..cd227fc05d2d 100644 --- a/mm/pgtable-generic.c +++ b/mm/pgtable-generic.c @@ -438,12 +438,30 @@ static void kernel_pgtable_work_func(struct work_struct *work) __pagetable_free(pt); } +static void schedule_kernel_pgtable_free(void) +{ + schedule_work(&kernel_pgtable_work.work); +} + void pagetable_free_kernel(struct ptdesc *pt) { spin_lock(&kernel_pgtable_work.lock); list_add(&pt->pt_list, &kernel_pgtable_work.list); spin_unlock(&kernel_pgtable_work.lock); - schedule_work(&kernel_pgtable_work.work); + /* + * The workqueue may not exist yet while the system is booting. + * kernel_pgtable_drain_early() schedules the work once it does. + */ + if (system_state != SYSTEM_BOOTING) + schedule_kernel_pgtable_free(); } + +static int __init kernel_pgtable_drain_early(void) +{ + /* Free the kernel page tables queued while booting. */ + schedule_kernel_pgtable_free(); + return 0; +} +core_initcall(kernel_pgtable_drain_early); #endif From 28eed9906e01d01cbdfb352e799413b2e8b33b76 Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Sun, 27 Sep 2026 16:24:18 +0000 Subject: [PATCH 540/590] selftests/mm: cleanup -Wformat issues in hugetlb-mmap Commit ae571cd6015c ("selftests/mm: hugetlb-mmap: add setup of HugeTLB pages") and commit 9c5a65f374f8 ("selftests/mm: merge map_hugetlb into hugepage-mmap") added logs of 'hugepage_size' which has a size_t type. However, the incorrect format specifier '%lu' was used which triggers -Wformat warnings when building for 32-bit: hugetlb-mmap.c:125:55: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat] 125 | ksft_print_msg("Default size hugepages (%lu kB)\n", hugepage_size >> 10); | ~~~ ^~~~~~~~~~~~~~~~~~~ | %zu hugetlb-mmap.c:134:47: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat] 134 | ksft_exit_skip("Not enough %lu Kb pages\n", hugepage_size >> 10); | ~~~ ^~~~~~~~~~~~~~~~~~~ | %zu Fix this by switching to the expected '%zu' format specifier. Link: https://lore.kernel.org/20260927162419.820609-1-cmllamas@google.com Fixes: ae571cd6015c ("selftests/mm: hugetlb-mmap: add setup of HugeTLB pages") Fixes: 9c5a65f374f8 ("selftests/mm: merge map_hugetlb into hugepage-mmap") Signed-off-by: Carlos Llamas Signed-off-by: Andrew Morton Reviewed-by: Sarthak Sharma Reviewed-by: SJ Park Acked-by: Lorenzo Stoakes (ARM) Cc: Mike Rapoport Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Shuah Khan Cc: --- tools/testing/selftests/mm/hugetlb-mmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mm/hugetlb-mmap.c b/tools/testing/selftests/mm/hugetlb-mmap.c index 0f2aad1b7dbd..2edbc992e6bc 100644 --- a/tools/testing/selftests/mm/hugetlb-mmap.c +++ b/tools/testing/selftests/mm/hugetlb-mmap.c @@ -122,7 +122,7 @@ int main(int argc, char **argv) hugepage_size = default_huge_page_size(); if (!hugepage_size) ksft_exit_skip("Could not detect default hugetlb page size."); - ksft_print_msg("Default size hugepages (%lu kB)\n", hugepage_size >> 10); + ksft_print_msg("Default size hugepages (%zu kB)\n", hugepage_size >> 10); } /* munmap will fail if the length is not page aligned */ @@ -131,7 +131,7 @@ int main(int argc, char **argv) hugetlb_set_nr_pages(hugepage_size, nr); if (hugetlb_free_pages(hugepage_size) < nr) - ksft_exit_skip("Not enough %lu Kb pages\n", hugepage_size >> 10); + ksft_exit_skip("Not enough %zu Kb pages\n", hugepage_size >> 10); ksft_set_plan(2); ksft_print_msg("Mapping %lu Mbytes\n", (unsigned long)length >> 20); From d3ed3a40951c964d30dc7fc63d13e87a873ea300 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Thu, 24 Sep 2026 15:48:24 +0100 Subject: [PATCH 541/590] drivers/char/mem: mmap readonly MAP_SHARED-/dev/zero correctly Rather surprisingly, opening /dev/zero read-only then mmap()'ing it MAP_SHARED gets you true anonymous memory (albeit in a VMA with non-NULL vma->vm_file). This is a by-product of MAP_PRIVATE-/dev/zero being how anonymous memory was mapped in Linux's distant past. It happens because mmap_zero_prepare() gates on VMA_SHARED_BIT and when mapping a read-only file MAP_SHARED, do_mmap() clears VMA_SHARED_BIT and VMA_MAYWRITE_BIT. The gating is incorrect - the (poorly named) VMA_MAYSHARE_BIT flag exists explicitly to tell you if something was originally mapped MAP_SHARED. So the fix is simple - gate on this instead. This isn't exactly a common use case, but it's unexpected behaviour which now causes an assert if CONFIG_DEBUG_VM is set. While this bug has existed since the dawn of time for linux (or at least since 2.6.12), it hasn't caused issues in the past, so while it's incorrect behaviour, it doesn't seem necessary to backport that far. The mapping is now accounted at mmap time and can fail with -ENOMEM under strict overcommit, and read faults allocate folios. However this is normal behaviour for a read-only shmem mapping. Commit 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios") is the first patch at which the debug assert fires, so target that instead. Link: https://lore.kernel.org/20260924-fix-dev-zero-readonly-shared-v1-1-153c2111e323@kernel.org Fixes: 93c0c8dc87f6 ("mm/rmap: use anon pgoff to track MAP_PRIVATE file-backed anon folios") Signed-off-by: Lorenzo Stoakes (ARM) Signed-off-by: Andrew Morton Reported-by: Closes: https://lore.kernel.org/linux-mm/6ab4ae75.80e1c6cc.1e8e5f.000d.GAE@google.com/ Acked-by: David Hildenbrand (Arm) Cc: Liam R. Howlett Cc: Vlastimil Babka Cc: Jann Horn Cc: Pedro Falcato Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Cc: Lance Yang --- drivers/char/mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 63253d1de5d7..5b93c92c2cf1 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -503,7 +503,7 @@ static int mmap_zero_prepare(struct vm_area_desc *desc) #ifndef CONFIG_MMU return -ENOSYS; #endif - if (vma_desc_test(desc, VMA_SHARED_BIT)) + if (vma_desc_test(desc, VMA_MAYSHARE_BIT)) return shmem_zero_setup_desc(desc); /* From 80ce29104123be4d61b5912dae543c70af0fcb75 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Thu, 24 Sep 2026 18:50:33 +0800 Subject: [PATCH 542/590] mailmap: update entry for Andy Yan I will use andyshrk@163.com for future review and discussion. Link: https://lore.kernel.org/20260924105052.768760-1-andyshrk@163.com Signed-off-by: Andy Yan Signed-off-by: Andrew Morton Reviewed-by: Shawn Lin Cc: Alexander Sverdlin Cc: Chuck Lever Cc: Jakub Kicinski Cc: Martin Kepplinger --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 39f449f9c42d..b325ccb35201 100644 --- a/.mailmap +++ b/.mailmap @@ -101,6 +101,7 @@ Andy Chiu Andy Chiu Andy Shevchenko Andy Shevchenko +Andy Yan Anilkumar Kolli Anirudh Ghayal Antoine Tenart From ca5012757bf027df83d51593a07656cb8c80caee Mon Sep 17 00:00:00 2001 From: Quentin Freimanis Date: Sun, 4 Oct 2026 22:28:26 -0700 Subject: [PATCH 543/590] net: sparx5: start the TOD counters on non-PTP lan969x variants Currently the base non-PTP-capable lan969x variants don't forward any Ethernet frames: $ ip link set eth10 up $ ip addr add 10.0.0.54/24 dev eth10 $ ping 10.0.0.1 PING 10.0.0.1 (10.0.0.1): 56 data bytes --- 10.0.0.1 ping statistics --- 7 packets transmitted, 0 packets received, 100% packet loss $ cat /proc/interrupts | grep fdma 20: 0 GIC-0 120 Level sparx5-fdma $ ip -s link show dev eth10 RX: bytes packets errors dropped missed mcast 20828 102 0 0 0 72 TX: bytes packets errors dropped carrier collsns 0 0 0 0 0 0 Testing showed that starting the domain 0 TOD counter gets networking working again, but start all three for consistency with the PTP variants. Fixes: 207966787b71 ("net: sparx5: add feature support") Suggested-by: Daniel Machon Signed-off-by: Quentin Freimanis Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261005052826.14251-1-quentin@q-lab.dev Signed-off-by: Jakub Kicinski --- .../ethernet/microchip/sparx5/sparx5_ptp.c | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c index 5953e04ef14e..2826e48af103 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_ptp.c @@ -622,18 +622,9 @@ int sparx5_ptp_init(struct sparx5 *sparx5) sparx5->ptp = 1; } - if (!sparx5->ptp) - return 0; - - for (i = 0; i < SPARX5_PHC_COUNT; ++i) { - err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); - if (err) - return err; - } - - spin_lock_init(&sparx5->ptp_clock_lock); - spin_lock_init(&sparx5->ptp_ts_id_lock); - mutex_init(&sparx5->ptp_lock); + /* The base, non-PTP-capable lan969x variants need the + * first TOD counter running to forward frames. + */ /* Disable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0), sparx5, PTP_PTP_DOM_CFG); @@ -657,6 +648,19 @@ int sparx5_ptp_init(struct sparx5 *sparx5) /* Enable master counters */ spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0x7), sparx5, PTP_PTP_DOM_CFG); + if (!sparx5->ptp) + return 0; + + for (i = 0; i < SPARX5_PHC_COUNT; ++i) { + err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info); + if (err) + return err; + } + + spin_lock_init(&sparx5->ptp_clock_lock); + spin_lock_init(&sparx5->ptp_ts_id_lock); + mutex_init(&sparx5->ptp_lock); + for (i = 0; i < sparx5->data->consts->n_ports; i++) { port = sparx5->ports[i]; if (!port) From c5381ae1cd1481cc431c2bd79460472c6f1d3ce3 Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Thu, 1 Oct 2026 12:22:32 +0800 Subject: [PATCH 544/590] devlink: fix devlink_rel reference leak when notify work is pending devlink_rel_nested_in_notify_work_schedule() takes a reference on the devlink_rel for the notify work and then queues the work, ignoring the return value of schedule_delayed_work(). If the work is already pending, nothing new is queued, the work runs only once and drops only one reference, so the extra one is leaked together with the devlink_rel and its index in devlink_rels. This is easy to hit. devl_register() of a nested instance queues the work, and if the instance is unregistered before the work has run, devlink_rel_put() queues it again while it is still pending. The work also keeps rescheduling itself for as long as the parent devlink lock cannot be taken, which widens the window. Registering and unregistering a nested devlink instance 100 times while holding the parent lock leaks all 100 devlink_rel objects. The reschedule path in devlink_rel_nested_in_notify_work() has the same problem: if the work was queued again while it was running, the reference it holds is never dropped. Drop the reference in both places when the work was already pending. The pending work holds its own reference, so this can not be the last one. Fixes: c137743bce02 ("devlink: introduce object and nested devlink relationship infra") Signed-off-by: Haishuang Yan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261001042232.222987-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- net/devlink/core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net/devlink/core.c b/net/devlink/core.c index c53a42e17a58..bddbbbf000fe 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -112,13 +112,19 @@ static void devlink_rel_nested_in_notify_work(struct work_struct *work) return; reschedule_work: - schedule_delayed_work(&rel->nested_in.notify_work, 1); + /* The work may have been queued again meanwhile, which took its own + * reference. Drop ours in that case. + */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 1)) + __devlink_rel_put(rel); } static void devlink_rel_nested_in_notify_work_schedule(struct devlink_rel *rel) { __devlink_rel_get(rel); - schedule_delayed_work(&rel->nested_in.notify_work, 0); + /* The pending work holds a reference already, drop the new one. */ + if (!schedule_delayed_work(&rel->nested_in.notify_work, 0)) + __devlink_rel_put(rel); } static struct devlink_rel *devlink_rel_alloc(void) From b7454f1940c5e6d92e753853c476a769e452f30a Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:25 +0800 Subject: [PATCH 545/590] net: team: stop reusing skb after queue override A nonzero dev_queue_xmit() result does not return ownership of the skb. When an override port's qdisc drops it, the queue override helper used to try another port or the mode transmit op with the freed skb. Return the handoff decision separately from transmit success. Select only the first override port under RCU and stop after handing it the skb, regardless of the lower transmit result. Keep the existing success and drop accounting. BUG: KASAN: slab-use-after-free in sk_skb_reason_drop Write of size 4 by task poc/5040 sk_skb_reason_drop (net/core/skbuff.c:1240) dev_kfree_skb_any_reason (net/core/dev.c:3470) ab_transmit (drivers/net/team/team_mode_activebackup.c:49) team_xmit (drivers/net/team/team_core.c:1849) __dev_direct_xmit (net/core/dev.c:4934) Freed by task 5040: __tcf_kfree_skb_list (net/sched/sch_generic.c:43) __dev_queue_xmit (net/core/dev.c:4831) team_xmit (drivers/net/team/team_core.c:1846) Kernel panic - not syncing: KASAN: panic_on_warn set ... Cc: stable@vger.kernel.org Fixes: 8ff5105a2b9d ("team: add support for queue override by setting queue_id for port") Reported-by: Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/team/team_core.c b/drivers/net/team/team_core.c index beffbe450612..ca75f08e3c59 100644 --- a/drivers/net/team/team_core.c +++ b/drivers/net/team/team_core.c @@ -820,7 +820,8 @@ static struct list_head *__team_get_qom_list(struct team *team, u16 queue_id) /* * note: already called with rcu_read_lock */ -static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) +static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb, + bool *tx_success) { struct list_head *qom_list; struct team_port *port; @@ -828,11 +829,11 @@ static bool team_queue_override_transmit(struct team *team, struct sk_buff *skb) if (!team->queue_override_enabled || !skb->queue_mapping) return false; qom_list = __team_get_qom_list(team, skb->queue_mapping); - list_for_each_entry_rcu(port, qom_list, qom_list) { - if (!team_dev_queue_xmit(team, port, skb)) - return true; - } - return false; + port = list_first_or_null_rcu(qom_list, struct team_port, qom_list); + if (!port) + return false; + *tx_success = !team_dev_queue_xmit(team, port, skb); + return true; } static void __team_queue_override_port_del(struct team *team, @@ -1864,8 +1865,7 @@ static netdev_tx_t team_xmit(struct sk_buff *skb, struct net_device *dev) bool tx_success; unsigned int len = skb->len; - tx_success = team_queue_override_transmit(team, skb); - if (!tx_success) + if (!team_queue_override_transmit(team, skb, &tx_success)) tx_success = READ_ONCE(team->ops.transmit)(team, skb); if (tx_success) { struct team_pcpu_stats *pcpu_stats; From 489114c9ecae44e523361a4b3e610ee5ebd9189c Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Fri, 2 Oct 2026 02:23:26 +0800 Subject: [PATCH 546/590] net: team: free skb when broadcast has no txable port bc_transmit() sends the original skb through the last txable port. When no port is txable, it returns false without consuming the skb, but team_xmit() still returns NETDEV_TX_OK. AF_PACKET sends then retain the skb and its socket write-memory charge. A TX-enabled, link-down port keeps the broadcast transmit op installed. Carrier can stay up through a second link-up, TX-disabled port or be forced on by userspace. Free the original skb on the no-port path while leaving the existing drop accounting intact. Discovered by Sashiko during review of previous commit. Cc: stable@vger.kernel.org Fixes: 5fc889911a99 ("team: add broadcast mode") Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20261001182326.572045-3-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/team/team_mode_broadcast.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/team/team_mode_broadcast.c b/drivers/net/team/team_mode_broadcast.c index 61d7d79f0c36..69dc25a5847a 100644 --- a/drivers/net/team/team_mode_broadcast.c +++ b/drivers/net/team/team_mode_broadcast.c @@ -37,6 +37,8 @@ static bool bc_transmit(struct team *team, struct sk_buff *skb) ret = !team_dev_queue_xmit(team, last, skb); if (!sum_ret) sum_ret = ret; + } else { + dev_kfree_skb_any(skb); } return sum_ret; } From b72e025ac556984766392001e74b7587e145bcd0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:00 -0300 Subject: [PATCH 547/590] net/sched: cls_route: reject change with no routing attribute route4_change on a filter change that supplies no routing attribute at all (no to/from/iif) reaches route4_set_parms with fold set, so the handle-mismatch check -- which only fires when creating -- is skipped. nhandle is built as the all-wildcard bucket (0x8000 | 0xFFFF << 16) and the filter is rekeyed to 0xFFFF8000, so the handle userspace already stored no longer addresses the filter. (The netlink reply carries the new handle; what goes stale is the handle the caller holds.) The rekey actually misclassifies the packets. The new handle's from_hash is the wildcard chain (route4_hash_wild), whose filters route4_classify applies without any id/iif comparison, and its bucket (256) is reached by every packet through the h < 256 restart. A filter that matched only a specific realm therefore becomes an unconditional catch-all that applies its classid and actions to all otherwise-unmatched traffic. Reject a change that would rekey the filter this way, with the routing-attributes-required error. A change that names at least one of to/from/iif, and an in-place replace that keeps the routing identity via a supplied attribute, are unaffected. A change of a filter that already sits at the wildcard handle is a metadata-only update and remains valid, so only a change that actually moves the handle is rejected. This is a follow-up to commit 41e85e54e564 ("net/sched: cls_route: Fix in-place replace"); this patch closes the no-routing-attribute variant that the parent series did not address. Conditions to recreate the bug: tc qdisc add dev lo clsact tc filter add dev lo ingress protocol ip pref 100 route from 1 to 1 tc filter change dev lo ingress protocol ip pref 100 handle 0x10001 \ route classid 1:2 tc filter show dev lo ingress # handle became 0xffff8000; iproute2's # stored 0x10001 no longer addresses it A change of a filter already at the wildcard handle, e.g. one created by "tc filter add dev lo ingress protocol ip pref 100 route classid 1:1" (iproute2 defaults the handle to 0xffff8000), stays a valid metadata-only update. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: Sashiko Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907192133.2639067-1-victor@mojatatu.com Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260929143022@mojatatu.com Signed-off-by: Jakub Kicinski --- net/sched/cls_route.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sched/cls_route.c b/net/sched/cls_route.c index 0f211f030fd9..dca812a75269 100644 --- a/net/sched/cls_route.c +++ b/net/sched/cls_route.c @@ -434,6 +434,12 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp, if (handle && (!fold || nhandle == (handle & ~0x7F00))) nhandle |= handle & 0x7F00; + if (fold && !tb[TCA_ROUTE4_TO] && !tb[TCA_ROUTE4_FROM] && + !tb[TCA_ROUTE4_IIF] && nhandle != fold->handle) { + NL_SET_ERR_MSG(extack, "Routing attributes required"); + return -EINVAL; + } + if (handle && !fold && nhandle != handle) { NL_SET_ERR_MSG_FMT(extack, "Handle mismatch constructed: %x (expected: %x)", From 6fa2e7db99fb9ed67496c652fcfce5a31c6b73d0 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Thu, 1 Oct 2026 12:32:01 -0300 Subject: [PATCH 548/590] selftests: tc-testing: add cls_route no-routing-attribute change tests Add a boundary pair for route4 change without a routing attribute: - a classid-only change of a wildcard filter (handle 0xffff8000) is a metadata-only update and must succeed; - the same change on a non-wildcard handle (0x10001) would rekey the filter to the wildcard bucket, leaving the handle userspace stored stale, and must be rejected with -EINVAL. The change of a wildcard filter is the flow iproute2 uses by default ("tc filter add ... route classid X:Y" gives handle 0xffff8000). Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira Reviewed-by: Simon Horman Link: https://patch.msgid.link/QDISC-0JB3.v1.20260923100921@mojatatu.com.2 Signed-off-by: Jakub Kicinski --- .../tc-testing/tc-tests/filters/route.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json index 2d5843aebd72..c3b6ff157d3e 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/route.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/route.json @@ -412,5 +412,51 @@ "teardown": [ "$TC qdisc del dev $DEV1 ingress" ] + }, + { + "id": "4000", + "name": "Change a wildcard route filter classid only (no routing attribute)", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 route classid 1:9", + "expExitCode": "0", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0xffff8000 flowid 1:9", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "907a", + "name": "Try to change a route filter with no routing attribute and a non-wildcard handle", + "category": [ + "filter", + "route" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 parent ffff: protocol ip prio 100 route from 1 to 1 classid 1:1" + ], + "cmdUnderTest": "$TC filter change dev $DEV1 parent ffff: protocol ip prio 100 handle 0x10001 route classid 1:2", + "expExitCode": "2", + "verifyCmd": "$TC filter ls dev $DEV1 parent ffff:", + "matchPattern": "fh 0x00010001 flowid 1:1 to 1 from 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] From 86b785b18f8f4a6b4a7392d1e2b29d6bb095011a Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Sun, 4 Oct 2026 11:35:31 +0300 Subject: [PATCH 549/590] Revert "net/mlx5: E-Switch, preserve max tx speed on vport state modification" This reverts commit ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification"). mlx5_modify_vport_admin_state() and mlx5_esw_adj_vport_modify() query the vport's current max_tx_speed before modifying its state, and write that value back, to avoid resetting it to 0 as a side effect of an unrelated admin-state change. That's unnecessary: max_tx_speed is optional in MODIFY_VPORT_STATE - FW skips writing it whenever it's 0, treating that as "not provided" rather than "reset to zero". Leaving it unset already preserves FW's current value, with no query needed. Fixes: ad0ae7aefa7a ("net/mlx5: E-Switch, preserve max tx speed on vport state modification") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20261004083531.216988-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- .../mellanox/mlx5/core/esw/adj_vport.c | 20 ---------------- .../net/ethernet/mellanox/mlx5/core/vport.c | 24 ------------------- 2 files changed, 44 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c index 7950b82d8b8a..3624c680a861 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/adj_vport.c @@ -11,26 +11,6 @@ int mlx5_esw_adj_vport_modify(struct mlx5_core_dev *dev, u16 vport, lockdep_assert_held(&dev->priv.eswitch->state_lock); - if (MLX5_CAP_ESW(dev, esw_vport_state_max_tx_speed)) { - u8 op_mod = MLX5_VPORT_STATE_OP_MOD_ESW_VPORT; - struct mlx5_vport *esw_vport; - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(dev, op_mod, vport, - true, &speed, NULL); - if (err) { - esw_vport = mlx5_eswitch_get_vport(dev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; - mlx5_core_dbg(dev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/vport.c index 5774ec117428..78f3d15b599d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/vport.c @@ -93,30 +93,6 @@ int mlx5_modify_vport_admin_state(struct mlx5_core_dev *mdev, u8 opmod, lockdep_assert_held(&mdev->priv.eswitch->state_lock); #endif - if (MLX5_CAP_ESW(mdev, esw_vport_state_max_tx_speed) && - opmod == MLX5_VPORT_STATE_OP_MOD_ESW_VPORT && - vport != MLX5_VPORT_UPLINK) { - u32 speed = 0; - int err; - - err = mlx5_query_vport_max_tx_speed(mdev, opmod, vport, - other_vport, &speed, NULL); - if (err) { -#ifdef CONFIG_MLX5_ESWITCH - struct mlx5_vport *esw_vport; - - esw_vport = mlx5_eswitch_get_vport(mdev->priv.eswitch, - vport); - speed = IS_ERR(esw_vport) ? 0 : - esw_vport->agg_max_tx_speed; -#endif - mlx5_core_dbg(mdev, - "Failed to query vport %d max tx speed, err=%d, using cached %u\n", - vport, err, speed); - } - MLX5_SET(modify_vport_state_in, in, max_tx_speed, speed); - } - MLX5_SET(modify_vport_state_in, in, opcode, MLX5_CMD_OP_MODIFY_VPORT_STATE); MLX5_SET(modify_vport_state_in, in, op_mod, opmod); From 538b529509c7ab0d909cb65bb7264edca1788d30 Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Wed, 30 Sep 2026 04:46:11 +0000 Subject: [PATCH 550/590] sctp: copy zeroed stats to user in sctp_getsockopt_pr_streamstatus() when !streamoute When an SCTP stream output extension structure (streamoute) has not yet been allocated, sctp_getsockopt_pr_streamstatus() sets the local stack variables params.sprstat_abandoned_unsent = 0 and params.sprstat_abandoned_sent = 0, but then immediately jumps to 'out:', bypassing copy_to_user(). As a result, when an uninitialized user buffer is passed to getsockopt(SCTP_PR_STREAM_STATUS), the kernel returns success (0) without copying the zeroed stats back to user space, leaving uninitialized data in the user buffer. Refactor the !streamoute check into an if-else chain so that when streamoute is NULL, params is zeroed and falls through to copy_to_user(), copying the zeroed stats to user space and setting retval = 0 once before returning. Tested in QEMU against Linux 7.3.0-rc3 using an uninitialized sctp_prstatus user struct passed to getsockopt(SCTP_PR_STREAM_STATUS) on a freshly created stream: on the unfixed kernel the user struct contains uninitialized stack garbage; whereas with this fix applied, getsockopt() copies zeroed stats into user space and returns 0. Cc: stable@vger.kernel.org Reviewed-by: Xin Long Signed-off-by: Hui Peng Fixes: f952be79cebd ("sctp: introduce struct sctp_stream_out_ext") Link: https://patch.msgid.link/20260930044611.204739-1-benquike@gmail.com Signed-off-by: Jakub Kicinski --- net/sctp/socket.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 4652fd90d9a6..e35692dd8d30 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -7569,11 +7569,7 @@ static int sctp_getsockopt_pr_streamstatus(struct sock *sk, int len, /* Not allocated yet, means all stats are 0 */ params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; - retval = 0; - goto out; - } - - if (policy == SCTP_PR_SCTP_ALL) { + } else if (policy == SCTP_PR_SCTP_ALL) { params.sprstat_abandoned_unsent = 0; params.sprstat_abandoned_sent = 0; for (policy = 0; policy <= SCTP_PR_INDEX(MAX); policy++) { From 0984ebc631792acf8fff105112ee74a6b8f1efcc Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Mon, 5 Oct 2026 15:50:02 +0200 Subject: [PATCH 551/590] strparser: make sure __strp_recv isn't running before tearing down the parser The comment above strp_done() claims that if strp is stopped, strp_recv will no longer be called. That's only true if the caller has a mechanism (eg locking) to guarantee that strp_recv() calls that started before strp_stop() have completed by the time we call strp_done(). This adds a dummy lock_sock/release_sock pair to guarantee that any in-flight strp_recv() (which runs under either bh_lock_sock or lock_sock, depending if it's called from ->sk_data_ready or strp_work) has completed. Only espintcp can be affected by this race condition, but this patch makes sure no future user of strp can have the bug. This could crash on strp->sk ("general mode" of strp), but this mode has been here for 9 years and has never been used. It'll be gone soon, no point worrying about it. Switch the stopped/paused/etc bits to u8's to avoid races between strp_stop() and strp_pause/unpause(). A reproducer has been published and turns the possible UAF into an exploit [1]. Reported-by: Hyunwoo Kim Link: https://lore.kernel.org/all/aZLn2Faeg1FB7XOf@v4bel/ Link: https://lore.kernel.org/all/aZgpkyTDU3aXe_V0@v4bel/ Link: https://github.com/m0x41nos/RustyTux [1] Fixes: e27cca96cd68 ("xfrm: add espintcp (RFC 8229)") Cc: stable@vger.kernel.org Signed-off-by: Sabrina Dubroca Reviewed-by: Hyunwoo Kim Link: https://patch.msgid.link/425d9d926709b542ed5331110c19fee4aee1f28d.1791206940.git.sd@queasysnail.net Signed-off-by: Jakub Kicinski --- Documentation/networking/strparser.rst | 2 +- include/net/strparser.h | 10 +++++----- net/strparser/strparser.c | 6 +++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Documentation/networking/strparser.rst b/Documentation/networking/strparser.rst index 372106b61e65..d0804c88ad0a 100644 --- a/Documentation/networking/strparser.rst +++ b/Documentation/networking/strparser.rst @@ -77,7 +77,7 @@ Functions strp_done is called to release any resources held by the stream parser instance. This must be called after the stream processor - has been stopped. + has been stopped. The socket lock must not be held. :: diff --git a/include/net/strparser.h b/include/net/strparser.h index 0ed73e364faa..881b7ae34498 100644 --- a/include/net/strparser.h +++ b/include/net/strparser.h @@ -91,11 +91,11 @@ static inline struct strp_msg *strp_msg(struct sk_buff *skb) struct strparser { struct sock *sk; - u32 stopped : 1; - u32 paused : 1; - u32 aborted : 1; - u32 interrupted : 1; - u32 unrecov_intr : 1; + u8 stopped; + u8 paused; + u8 aborted; + u8 interrupted; + u8 unrecov_intr; struct sk_buff **skb_nextp; struct sk_buff *skb_head; diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c index a23f4b4dfc67..d5b17d099a0e 100644 --- a/net/strparser/strparser.c +++ b/net/strparser/strparser.c @@ -505,12 +505,16 @@ void strp_unpause(struct strparser *strp) EXPORT_SYMBOL_GPL(strp_unpause); /* strp must already be stopped so that strp_recv will no longer be called. - * Note that strp_done is not called with the lower socket held. + * Note that strp_done must not be called with the lower socket held. */ void strp_done(struct strparser *strp) { WARN_ON(!strp->stopped); + lock_sock(strp->sk); + /* sync with pending strp_recv */ + release_sock(strp->sk); + cancel_delayed_work_sync(&strp->msg_timer_work); cancel_work_sync(&strp->work); From 5857e5a196b069766a39ceb754736326a43bc6bc Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 30 Sep 2026 16:06:34 -0700 Subject: [PATCH 552/590] net: bcmgenet: if UMAC was suspended in SW_RESET, restore it to SW_RESET When the revised suspend/resume sequence was introduced this led to an edge case where the TX is left disabled in the following sequence. 1. phy link is down, so UMAC is held in reset and then network interface is WoL enabled 2. Enter suspend, bcmgenet_wol_power_down_cfg() enables UMAC_RX since MAC is in SW_RESET 4. Enter resume, UMAC_RX is left enabled. Since we only enable UMAC_TX and UMAC_RX in SW_RESET. The UMAC_TX is never enabled again on link up. The issue was hit in production and I verified the sequence keeps the TX disabled in my standalone test. I tested on an internal development board with bcm77122, but should be reproducible on any genet HW that supports power management. Fixes: 254f3239dd07 ("net: bcmgenet: revise suspend/resume") Signed-off-by: Justin Chen Reviewed-by: Florian Fainelli Reviewed-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260930230634.3132729-1-justin.chen@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c index 96d5d4f7f51f..984432952963 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet_wol.c @@ -253,6 +253,17 @@ int bcmgenet_wol_power_up_cfg(struct bcmgenet_priv *priv, reg = bcmgenet_umac_readl(priv, UMAC_CMD); reg &= ~CMD_CRC_FWD; bcmgenet_umac_writel(priv, reg, UMAC_CMD); + + /* + * Mirror wol_power_down_cfg(). If only UMAC_RX + * is enabled, then we must place the UMAC back + * into SW_RESET. + */ + reg = bcmgenet_umac_readl(priv, UMAC_CMD); + if ((reg & CMD_RX_EN) && !(reg & CMD_TX_EN)) { + reg |= CMD_SW_RESET; + bcmgenet_umac_writel(priv, reg, UMAC_CMD); + } spin_unlock_bh(&priv->reg_lock); /* Resume link status tracking */ From 23609bce9e1de525d1d0e73fc68c6e7971d0b49e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Wed, 30 Sep 2026 20:48:18 +0800 Subject: [PATCH 553/590] pfcp: make sure the SEID is linear before reading it pfcp_encap_recv() only makes sure that the UDP header and the 4 byte PFCP header are in the linear area. When the S flag is set, pfcp_session_recv() then reads the 8 byte SEID that follows, which is not covered by the pskb_may_pull() check. A short PFCP packet with the S flag set, or one whose session header lies in a fragment, therefore makes pfcp_session_recv() read beyond the end of the packet data, and whatever it finds there is stored in the tunnel metadata that flower later classifies on. Pull up to the end of the SEID before reading it, and drop packets that are too short to contain it. Reload the header pointer afterwards since pskb_may_pull() may reallocate the skb head. Use offsetofend() rather than sizeof(struct pfcphdr_session): the structure is not packed, so its size is 16 bytes because of the alignment of the __be64 member, while the header on the wire is only 12 bytes, and valid session messages would be dropped. Fixes: 6dd514f48110 ("pfcp: always set pfcp metadata") Signed-off-by: Haishuang Yan Link: https://patch.msgid.link/20260930124818.114224-3-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/pfcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index 3ecd43cafdf0..51c67c914fad 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -65,6 +65,13 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb) goto drop; unparsed = pfcp_hdr(skb); + if (unparsed->flags & PFCP_SEID_FLAG) { + if (unlikely(!pskb_may_pull(skb, PFCP_HLEN + + offsetofend(struct pfcphdr_session, + seid)))) + goto drop; + unparsed = pfcp_hdr(skb); + } ip_tunnel_flags_zero(flags); tun_dst = udp_tun_rx_dst(skb, sk->sk_family, flags, 0, From f394cbd0980c95c9173f8551a5eb4c4f28d598eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Roux?= Date: Tue, 6 Oct 2026 10:37:51 +0200 Subject: [PATCH 554/590] wifi: mac80211: don't estimate airtime for unsupported rate widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ieee80211_fill_rate_info() copies the driver's rate_info bandwidth into the rx_status as is, but ieee80211_get_rate_duration() only handles 20/40/80/160/320 MHz. When a driver reports another width, e.g. an HE/EHT RU allocation, it hits WARN_ON_ONCE() and returns 0 without setting *overhead. ieee80211_rate_expected_tx_airtime() then goes on using the uninitialized overhead, as Dan's static checker report points out. Seen on a Microsoft Surface Pro 11 (WCN7850, ath12k) while NetworkManager dumps station info: WARNING: net/mac80211/airtime.c:532 at ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] sta_estimate_expected_throughput.isra.0+0x38/0x64 [mac80211] sta_set_sinfo+0x7fc/0x8e4 [mac80211] ieee80211_dump_station+0x4c/0x84 [mac80211] nl80211_dump_station+0x594/0x7e8 [cfg80211] Only accept widths that ieee80211_get_rate_duration() handles in ieee80211_fill_rate_info(). Other widths then give no estimate instead of a warning. Also return 0 from ieee80211_rate_expected_tx_airtime() when no duration could be computed, as ieee80211_calc_rx_airtime() already does. Tested on the Surface Pro 11 with next-20260929: Wi-Fi works and "expected throughput" is still reported (about 1049 Mbit/s for a 1297 Mbit/s HE 160 MHz tx rate). The warning itself is rare (twice in several days of use), so this does not prove it is gone, only that the change does not break the normal path. Fixes: 094dc1619cb0 ("wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aneGlCMVnqRhlf6p@stanley.mountain/ Assisted-by: LLM Signed-off-by: François Roux Link: https://patch.msgid.link/20261006083751.4015-1-franzelfranzel@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/airtime.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index 0c54cdbd753c..a4e1f33c0377 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -632,6 +632,22 @@ static bool ieee80211_fill_rate_info(struct ieee80211_hw *hw, if (!ri || !sband) return false; + /* + * ieee80211_get_rate_duration() only handles these widths. Drivers + * may also report e.g. HE/EHT RU allocations, which cannot be used + * to estimate airtime here. + */ + switch (ri->bw) { + case RATE_INFO_BW_20: + case RATE_INFO_BW_40: + case RATE_INFO_BW_80: + case RATE_INFO_BW_160: + case RATE_INFO_BW_320: + break; + default: + return false; + } + stat->bw = ri->bw; stat->nss = ri->nss; stat->rate_idx = ri->mcs; @@ -770,6 +786,8 @@ u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + if (!duration) + return 0; /* * Assume that HT/VHT transmission on any AC except VO will From 08a3f02fea636e97a7422d1512f76dacdb5b0887 Mon Sep 17 00:00:00 2001 From: Michele Dionisio Date: Thu, 24 Sep 2026 14:30:27 +0200 Subject: [PATCH 555/590] wifi: brcmfmac: fix P2P device removal race in brcmf_detach() When the driver is removed while the user space process that created the P2P device (e.g. wpa_supplicant) is still exiting, the P2P device interface is removed twice and the kernel crashes. The two paths are: - wpa_supplicant, on exit, sends NL80211_CMD_DEL_INTERFACE for the P2P device. nl80211_del_interface() holds RTNL and the wiphy mutex and calls brcmf_p2p_del_vif(), which disables discovery and waits up to 1.5 s for BRCMF_E_IF_DEL. Then it calls brcmf_remove_interface(), which unregisters the wdev and frees the vif and the ifp. - brcmf_detach() sets the bus down, so BRCMF_E_IF_DEL never arrives, and calls brcmf_remove_interface(ifp, false) for the same ifp. brcmf_p2p_ifp_removed() reads ifp->vif and then blocks on rtnl_lock(). When brcmf_p2p_del_vif() releases RTNL, it calls cfg80211_unregister_wdev() on the wdev that was already unregistered and freed. The second list_del_rcu() of wdev->list faults on LIST_POISON2: brcmfmac: brcmf_p2p_del_vif delete P2P vif brcmfmac: brcmf_p2p_deinit_discovery enter brcmfmac: brcmf_p2p_del_vif P2P: GO_NEG_PHASE status cleared brcmfmac: brcmf_detach Enter brcmfmac: brcmf_bus_change_state 1 -> 0 brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed [1.5 s later, brcmf_p2p_del_vif() timed out] brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed Unable to handle kernel paging request at virtual address dead000000000122 Internal error: Oops: 96000044 [#1] PREEMPT_RT SMP pc : _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] Call trace: _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] cfg80211_unregister_wdev+0x10/0x1c [cfg80211] brcmf_p2p_ifp_removed+0x84/0xb0 [brcmfmac] brcmf_remove_interface+0x1f4/0x254 [brcmfmac] brcmf_detach+0x88/0x180 [brcmfmac] brcmf_sdio_remove+0x90/0x7b0 [brcmfmac] brcmf_sdiod_remove+0x20/0xa0 [brcmfmac] brcmf_ops_sdio_remove+0xbc/0x12c [brcmfmac] sdio_bus_remove+0x38/0x144 device_release_driver_internal+0x1e8/0x2c0 device_release_driver+0x14/0x20 brcmf_sdio_bus_remove+0x2c/0x40 [brcmfmac] brcmf_fwvid_unregister_vendor+0xd8/0x170 [brcmfmac] __exit_compat+0x18/0x418 [brcmfmac_cyw] __arm64_sys_delete_module+0x1ac/0x244 The log was taken with debug=0x406 on an i.MX 8M Plus board with a CYW55513 (Sona IF513) on SDIO. The driver is the Ezurio backport of brcmfmac from v6.18.22 on a 5.4-rt kernel. The code involved is the same in mainline. To reproduce: 1. Start wpa_supplicant on wlan0 and let it associate. It creates the P2P device (iw dev shows "type P2P-device"). 2. Stop wpa_supplicant and remove the driver without waiting for wpa_supplicant to exit: kill $(pidof wpa_supplicant) rmmod brcmfmac_cyw brcmfmac The crash is reproducible on that board. For the interface without a netdev (the P2P device), take RTNL and the wiphy mutex in brcmf_detach() before reading iflist, and remove it with locked=true. If brcmf_p2p_del_vif() runs first, brcmf_detach() finds the slot empty. If brcmf_detach() runs first, nl80211 does not find the wdev any more. The interfaces with a netdev are removed as before, because brcmf_del_if() takes RTNL on its own for the primary interface. Fixes: 9831bcb987df ("brcmfmac: Deleting of p2p device is leaking memory.") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 Signed-off-by: Michele Dionisio Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260924123027.4122909-2-michele.dionisio@gmail.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/core.c | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index d2ae67985606..92ec269f43b2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1489,8 +1489,31 @@ void brcmf_detach(struct device *dev) brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN); /* make sure primary interface removed last */ for (i = BRCMF_MAX_IFS - 1; i > -1; i--) { - if (drvr->iflist[i]) - brcmf_remove_interface(drvr->iflist[i], false); + struct brcmf_if *ifp = drvr->iflist[i]; + + if (!ifp) + continue; + + if (ifp->ndev) { + brcmf_remove_interface(ifp, false); + continue; + } + + /* The P2P device interface has no netdev. Its removal can + * race with NL80211_CMD_DEL_INTERFACE issued by a user space + * process that is exiting (e.g. wpa_supplicant), which ends + * in brcmf_p2p_del_vif() under RTNL and the wiphy mutex. If + * both paths remove the interface, the wdev is unregistered + * twice and the vif is used after being freed. Take the same + * locks and re-read iflist, so only one path removes it. + */ + rtnl_lock(); + wiphy_lock(drvr->wiphy); + ifp = drvr->iflist[i]; + if (ifp) + brcmf_remove_interface(ifp, true); + wiphy_unlock(drvr->wiphy); + rtnl_unlock(); } brcmf_bus_stop(drvr->bus_if); From 9727d1c4e1c971fc42b049b645fdda6852758835 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 7 Oct 2026 22:42:08 +0200 Subject: [PATCH 556/590] Revert "wifi: libertas: reject short monitor TX frames" This reverts commit 13ff543e0b2c713aedeaadadde686686e949dc78. The 'goto free' just made things worse, causing a spinlock to be handled incorrectly. Revert this for now. Reported-by: Takashi Iwai Cc: stable@vger.kernel.org Fixes: 13ff543e0b2c ("wifi: libertas: reject short monitor TX frames") Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 13d08022e414..27304a98787d 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,13 +117,6 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; - if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { - lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); - dev->stats.tx_dropped++; - dev->stats.tx_errors++; - goto free; - } - /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate)); From 9b6c20f788472d2042717a3c2f1bc60b2b77083e Mon Sep 17 00:00:00 2001 From: Fredrik Nyberg Date: Fri, 2 Oct 2026 14:28:57 +0200 Subject: [PATCH 557/590] net: usb: ax88179_178a: fix rx frame length for non-last packets Commit 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") replaced skb_clone() with a copy into a new skb for every packet of a bulk-in transfer except the last one. The copy skips the 2-byte IP alignment pseudo header but still copies pkt_len bytes, and pkt_len includes that header. Each of these frames is therefore delivered with two extra trailing bytes: the 0xeeee pseudo header of the next slot when the frame ends on an 8-byte boundary, or slot padding otherwise. The last packet is still trimmed to pkt_len - 2. IPv4 and IPv6 trim the extra bytes, so most traffic is unaffected. Anything that relies on the frame length is not. MACsec takes the ICV from the last 16 bytes and drops every such frame as InPktsNotValid. In our setup that was 0.2-1.2% of received MACsec frames, depending on how many frames shared a transfer, including service discovery multicast. Allocate and copy pkt_len - 2 bytes, like the last-packet path. Fixes: 4ce62d5b2f7a ("net: usb: ax88179_178a: stop lying about skb->truesize") Cc: stable@vger.kernel.org Signed-off-by: Fredrik Nyberg Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261002-ax88179-rx-len-v1-1-f01a2a295079@volvocars.com Signed-off-by: Jakub Kicinski --- drivers/net/usb/ax88179_178a.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c index 81d8412ce8e2..cba1dc95d359 100644 --- a/drivers/net/usb/ax88179_178a.c +++ b/drivers/net/usb/ax88179_178a.c @@ -1457,11 +1457,11 @@ static int ax88179_rx_fixup(struct usbnet *dev, struct sk_buff *skb) return 1; } - ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len); + ax_skb = netdev_alloc_skb_ip_align(dev->net, pkt_len - 2); if (!ax_skb) return 0; - skb_put(ax_skb, pkt_len); - memcpy(ax_skb->data, skb->data + 2, pkt_len); + skb_put(ax_skb, pkt_len - 2); + memcpy(ax_skb->data, skb->data + 2, pkt_len - 2); ax88179_rx_checksum(ax_skb, pkt_hdr); usbnet_skb_return(dev, ax_skb); From 8f5d59b9cfa69beab3b52cca621bb301c5af98aa Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:44 -0700 Subject: [PATCH 558/590] bnxt_en: Add bnxt_clear_bars() helper In bnxt_io_slot_reset(), we clear the 6 BAR registers. Add a helper function to do that. The helper will be used again in the next patch. Reviewed-by: Kalesh AP Reviewed-by: Somnath Kotur Reviewed-by: Joe Damato Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-2-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index d7728d0c5b6e..8ce8a82d3453 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17111,6 +17111,14 @@ void bnxt_print_device_info(struct bnxt *bp) pcie_print_link_status(bp->pdev); } +static void bnxt_clear_bars(struct pci_dev *pdev) +{ + int off; + + for (off = PCI_BASE_ADDRESS_0; off <= PCI_BASE_ADDRESS_5; off += 4) + pci_write_config_dword(pdev, off, 0); +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17598,7 +17606,6 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; - int off; netdev_info(bp->dev, "PCI Slot Reset\n"); @@ -17627,11 +17634,8 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) * write the BARs to 0 to force restore, in case of fatal error. */ if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) { - for (off = PCI_BASE_ADDRESS_0; - off <= PCI_BASE_ADDRESS_5; off += 4) - pci_write_config_dword(bp->pdev, off, 0); - } + &bp->state)) + bnxt_clear_bars(pdev); pci_restore_state(pdev); bnxt_inv_fw_health_reg(bp); From 386f887c8290f0747dd0180e98ec7a82811bc506 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Mon, 5 Oct 2026 13:42:45 -0700 Subject: [PATCH 559/590] bnxt_en: Fix driver init in kdump kernel Fix and strengthen the FLR sequence when initializing in the kdump kernel. If the NIC is behind a PCIe switch in synthetic (smart) mode, the switch may need to see that the BARs have been initialized before it will pass mem read/write TLPs to the NIC. On a Dell system with a PEX89144 PCIe switch, echo c > /proc/sysrq-trigger will trigger fatal AER without this patch: bnxt_en 0000:67:00.0: enabling device (0000 -> 0002) [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 5 [Hardware Error]: event severity: recoverable [Hardware Error]: Error 0, type: fatal [Hardware Error]: section_type: PCIe error [Hardware Error]: port_type: 5, upstream switch port ... Add a new bnxt_kdump_reset() to do the expanded FLR sequence in the kdump kernel. We now disable bus master and memory, save the PCI state, do the FLR, clear the BARs, and restore the PCI state. The BARs have to be cleared to ensure that they get re-initialized and visible to the PCIe switch. Since it is the kdump kernel, we make every effort to continue in the best possible way even if pci_save_state() or pcie_flr() returns error. After FLR, we poll for an additional 5 seconds before aborting in case the device is not properly returning CRS. This is similar to the 5-second wait in bnxt_io_slot_reset(). Fixes: 8743db4a9acf ("bnxt_en: Issue PCIe FLR in kdump kernel to cleanup pending DMAs.") Reviewed-by: Kalesh AP Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-3-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 45 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 8ce8a82d3453..9ea7e172787e 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17119,6 +17119,43 @@ static void bnxt_clear_bars(struct pci_dev *pdev) pci_write_config_dword(pdev, off, 0); } +/* Clear any pending DMA transactions from crash kernel while loading driver in + * capture kernel. + */ +static int bnxt_kdump_reset(struct pci_dev *pdev) +{ + int rc, i; + u16 cmd; + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + if (pci_save_state(pdev)) + dev_warn(&pdev->dev, "Failed to save PCI state, PCI restore may be incomplete\n"); + + rc = pcie_flr(pdev); + if (rc) + dev_warn(&pdev->dev, "pcie_flr() failed (rc: %d), trying to continue\n", + rc); + + /* In case device is not returning CRS, wait 5 seconds longer */ + for (i = 0; i < 50; i++) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) + break; + msleep(100); + } + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, "PCI config space inaccessible after FLR, aborting\n"); + return -ENODEV; + } + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + return 0; +} + static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { struct bnxt_hw_resc *hw_resc; @@ -17134,12 +17171,10 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) return -ENODEV; } - /* Clear any pending DMA transactions from crash kernel - * while loading driver in capture kernel. - */ if (is_kdump_kernel()) { - pci_clear_master(pdev); - pcie_flr(pdev); + rc = bnxt_kdump_reset(pdev); + if (rc) + return rc; } max_irqs = bnxt_get_max_irq(pdev); From 2e52da27096d9dbc487f303bbc10d44ad837a8c4 Mon Sep 17 00:00:00 2001 From: Pavan Chebbi Date: Mon, 5 Oct 2026 13:42:46 -0700 Subject: [PATCH 560/590] bnxt_en: Re-write the BARs following any type of PCIe errors Currently the driver zeroes the BARs only when fatal PCIe errors are reported so that pci_restore_state() restores it. However firmware handles both fatal and non-fatal errors the same way when it sees the slot reset resulting from the PCI_ERS_RESULT_NEED_RESET return code from the driver. This means that we must re-write the BARs post recovery even during non-fatal errors. Otherwise we will see that every MMIO access returns all-ones and the firmware appears dead. Zero-out the BARs during PCIe error recovery regardless of type of PCIe error, and make the wait after the hot reset unconditional. Disable memory decode and bus mastering before rewriting the BARs so the device doesn't decode a half-updated address, bailing out if config space is still inaccessible. Defer pci_enable_device() until after the BAR rewrite and restore, so the device isn't re-enabled while its BARs are still being rewritten, then re-enable the device and re-assert bus mastering. Guard the same Command register cleanup on the re-enable failure path against an inaccessible device. Skip re-enabling the device in bnxt_io_slot_reset() if it is already enabled, so enable_cnt does not go unbalanced. A concurrent bnxt_fw_reset_task() can also be re-enabling the same device in its ENABLE_DEV state, so guard that call the same way. Fixes: f75d9a0aa967 ("bnxt_en: Re-write PCI BARs after PCI fatal error.") Reviewed-by: Kalesh AP Reviewed-by: Scott Branden Signed-off-by: Pavan Chebbi Signed-off-by: Michael Chan Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20261005204246.3822563-4-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 117 ++++++++++++---------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 - 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9ea7e172787e..5bd817479d64 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -15547,7 +15547,7 @@ static void bnxt_fw_reset_task(struct work_struct *work) if (test_and_clear_bit(BNXT_STATE_FW_ACTIVATE_RESET, &bp->state) && !test_bit(BNXT_STATE_FW_ACTIVATE, &bp->state)) bnxt_dl_remote_reload(bp); - if (pci_enable_device(bp->pdev)) { + if (!pci_is_enabled(bp->pdev) && pci_enable_device(bp->pdev)) { netdev_err(bp->dev, "Cannot re-enable PCI device\n"); rc = -ENODEV; goto fw_reset_abort; @@ -17608,10 +17608,8 @@ static pci_ers_result_t bnxt_io_error_detected(struct pci_dev *pdev, * so we disable bus master to prevent any potential bad DMAs before * freeing kernel memory. */ - if (state == pci_channel_io_frozen) { - set_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state); + if (state == pci_channel_io_frozen) bnxt_fw_fatal_close(bp); - } if (netif_running(netdev)) __bnxt_close_nic(bp, true, true); @@ -17641,64 +17639,79 @@ static pci_ers_result_t bnxt_io_slot_reset(struct pci_dev *pdev) struct bnxt *bp = netdev_priv(netdev); int retry = 0; int err = 0; + u16 cmd; netdev_info(bp->dev, "PCI Slot Reset\n"); - if (test_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, &bp->state)) { - /* After DPC, the chip should return CRS when the vendor ID - * config register is read until it is ready. On all chips, - * this is not happening reliably so add a 5-second delay as a - * workaround. - */ - msleep(5000); - } + /* After a PCIe hot reset, the chip should return CRS when the + * vendor ID config register is read until it is ready. On all + * chips, this is not happening reliably so add a 5-second delay + * as a workaround. + */ + msleep(5000); netdev_lock(netdev); - if (pci_enable_device(pdev)) { + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (PCI_POSSIBLE_ERROR(cmd)) { + dev_err(&pdev->dev, + "PCI config space inaccessible after reset\n"); + goto reset_exit; + } + + /* Upon PCIe error, our device internal logic that latches to + * BAR value is getting reset and will restore only upon + * rewriting the BARs. + * + * As pci_restore_state() does not re-write the BARs if the + * value is same as saved value earlier, driver needs to + * write the BARs to 0 to force restore. + */ + pci_clear_master(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + bnxt_clear_bars(pdev); + pci_restore_state(pdev); + + if (!pci_is_enabled(pdev) && pci_enable_device(pdev)) { dev_err(&pdev->dev, "Cannot re-enable PCI device after reset.\n"); - } else { - pci_set_master(pdev); - /* Upon fatal error, our device internal logic that latches to - * BAR value is getting reset and will restore only upon - * rewriting the BARs. - * - * As pci_restore_state() does not re-write the BARs if the - * value is same as saved value earlier, driver needs to - * write the BARs to 0 to force restore, in case of fatal error. - */ - if (test_and_clear_bit(BNXT_STATE_PCI_CHANNEL_IO_FROZEN, - &bp->state)) - bnxt_clear_bars(pdev); - pci_restore_state(pdev); - - bnxt_inv_fw_health_reg(bp); - bnxt_try_map_fw_health_reg(bp); - - /* In some PCIe AER scenarios, firmware may take up to - * 10 seconds to become ready in the worst case. - */ - do { - err = bnxt_try_recover_fw(bp); - if (!err) - break; - retry++; - } while (retry < BNXT_FW_SLOT_RESET_RETRY); - - if (err) { - dev_err(&pdev->dev, "Firmware not ready\n"); - goto reset_exit; + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!PCI_POSSIBLE_ERROR(cmd)) { + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); } - - err = bnxt_hwrm_func_reset(bp); - if (!err) - result = PCI_ERS_RESULT_RECOVERED; - - /* IRQ will be initialized later in bnxt_io_resume */ - bnxt_ulp_irq_stop(bp); - bnxt_clear_int_mode(bp); + goto reset_exit; } + pci_set_master(pdev); + + bnxt_inv_fw_health_reg(bp); + bnxt_try_map_fw_health_reg(bp); + + /* In some PCIe AER scenarios, firmware may take up to + * 10 seconds to become ready in the worst case. + */ + do { + err = bnxt_try_recover_fw(bp); + if (!err) + break; + retry++; + } while (retry < BNXT_FW_SLOT_RESET_RETRY); + + if (err) { + dev_err(&pdev->dev, "Firmware not ready\n"); + goto reset_exit; + } + + err = bnxt_hwrm_func_reset(bp); + if (!err) + result = PCI_ERS_RESULT_RECOVERED; + + /* IRQ will be initialized later in bnxt_io_resume */ + bnxt_ulp_irq_stop(bp); + bnxt_clear_int_mode(bp); reset_exit: clear_bit(BNXT_STATE_IN_FW_RESET, &bp->state); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index c673b2ce4a0d..41d05efd6307 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -2468,7 +2468,6 @@ struct bnxt { #define BNXT_STATE_ABORT_ERR 5 #define BNXT_STATE_FW_FATAL_COND 6 #define BNXT_STATE_DRV_REGISTERED 7 -#define BNXT_STATE_PCI_CHANNEL_IO_FROZEN 8 #define BNXT_STATE_NAPI_DISABLED 9 #define BNXT_STATE_FW_ACTIVATE 11 #define BNXT_STATE_RECOVER 12 From a51d233ccd4889aadb71dace863525cc2b00271e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 07:17:47 +0200 Subject: [PATCH 561/590] bnxt_en: fix DMA mapping length for padded small packets Stefan Fleischmann reported Intel IOMMU DMA Read faults on BCM57412 NetXtreme-E NICs when transmitting packets on VLAN/macvlan interfaces: DMAR: [DMA Read NO_PASID] Request device [18:00.0] fault addr 0xfc499000 [fault reason 0x06] PTE Read access is not set bnxt_en 0000:18:00.0 eno1np0: Abandoning msg {0xb4 0x41a} len: 0 due to firmware status: 0x2000001 ... NETDEV WATCHDOG: eno1np0 (bnxt_en): transmit queue 0 timed out The fault address (0xfc499000) is on an exact 4KB page boundary, pointing to a DMA read buffer overrun. In bnxt_start_xmit(), packets smaller than BNXT_MIN_PKT_SIZE (52 bytes), such as 42-byte untagged ARP frames, are padded: if (length < BNXT_MIN_PKT_SIZE) { pad = BNXT_MIN_PKT_SIZE - length; if (skb_pad(skb, pad)) goto tx_kick_pending; length = BNXT_MIN_PKT_SIZE; } mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); ... dma_unmap_len_set(tx_buf, len, len); However, 'len' was initialized earlier to skb_headlen(skb) (e.g. 42 bytes) and is left unadjusted after padding. Consequently, dma_map_single() and dma_unmap_len_set() map and track only 42 bytes. Later, the hardware TX buffer descriptor is programmed with the padded length: txbd->tx_bd_len_flags_type = cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | TX_BD_FLAGS_PACKET_END); The NIC DMA engine is thus instructed to read 52 bytes from a region where only 42 bytes were DMA-mapped. If skb->data ends near the boundary of a 4KB page (within 'pad' bytes of the next page), the hardware DMA read overruns into the unmapped adjacent page, triggering an IOMMU fault. This issue was exposed after commit 447cbe95ebb9 ("vlan: fix skb_under_panic and races when toggling HW VLAN offload") because reserving extra VLAN headroom rounded LL_RESERVED_SPACE from 48 up to 64 bytes, shifting skb->data offsets and potentially causing small frames to land right against page boundaries. Fix this by using skb_put_padto(skb, BNXT_MIN_PKT_SIZE) in bnxt_start_xmit(), before skb_shinfo(skb)->nr_frags is sampled, because padding might linearize the skb. This ensures skb->len and skb_headlen(skb) reflect the padded size so that dma_map_single() maps the full buffer and the descriptor length is consistent. This also removes the temporary 'pad' variable and masking logic. The padding is done after the SW USO branch, otherwise bnxt_sw_udp_gso_xmit() would account the padding as UDP payload and send it in extra segments. Note that SW USO segments are still not padded: with IPv4, a segment carrying less than 10 bytes of UDP payload (small gso_size, or a short last segment) is sent as a frame shorter than BNXT_MIN_PKT_SIZE. This is a separate issue, left for a followup patch. Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.") Cc: stable@vger.kernel.org Reported-by: Stefan Fleischmann Closes: https://lore.kernel.org/netdev/20261004122616.56714cbd@nargothrond/ Signed-off-by: Eric Dumazet Reviewed-by: Michael Chan Link: https://patch.msgid.link/20261007051747.455273-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 25 +++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 5bd817479d64..15a8349ccf7b 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -486,7 +486,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) struct netdev_queue *txq; int i; dma_addr_t mapping; - unsigned int length, pad = 0; + unsigned int length; u32 len, free_size, vlan_tag_flags, cfa_action, flags; struct bnxt_ptp_cfg *ptp = bp->ptp_cfg; struct pci_dev *pdev = bp->pdev; @@ -536,6 +536,16 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) return rc < 0 ? NETDEV_TX_BUSY : NETDEV_TX_OK; } + /* Pad after the SW USO branch: bnxt_sw_udp_gso_xmit() would + * otherwise account the padding as UDP payload. + * Must be done before skb_shinfo(skb)->nr_frags is sampled, + * because skb_put_padto() might linearize the skb. + */ + if (skb_put_padto(skb, BNXT_MIN_PKT_SIZE)) { + /* SKB already freed. */ + goto tx_kick_pending; + } + free_size = bnxt_tx_avail(bp, txr); if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) { /* We must have raced with NAPI cleanup */ @@ -672,14 +682,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) } normal_tx: - if (length < BNXT_MIN_PKT_SIZE) { - pad = BNXT_MIN_PKT_SIZE - length; - if (skb_pad(skb, pad)) - /* SKB already freed. */ - goto tx_kick_pending; - length = BNXT_MIN_PKT_SIZE; - } - mapping = dma_map_single(&pdev->dev, skb->data, len, DMA_TO_DEVICE); if (unlikely(dma_mapping_error(&pdev->dev, mapping))) @@ -759,10 +761,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev) txbd->tx_bd_len_flags_type = cpu_to_le32(flags); } - flags &= ~TX_BD_LEN; - txbd->tx_bd_len_flags_type = - cpu_to_le32(((len + pad) << TX_BD_LEN_SHIFT) | flags | - TX_BD_FLAGS_PACKET_END); + txbd->tx_bd_len_flags_type |= cpu_to_le32(TX_BD_FLAGS_PACKET_END); netdev_tx_sent_queue(txq, skb->len); From f202578efc738b883cfa04889eb97328914265a7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 7 Oct 2026 06:56:15 +0200 Subject: [PATCH 562/590] flow_dissector: avoid u16 truncation of hlen in bpf_flow_dissect() bpf_flow_dissect() sanitizes the offsets returned by the BPF program with clamp_t(u16, ..., hlen). If hlen is bigger than 65535, the upper bound is truncated before the comparison. For instance, with hlen == 65536, both nhoff and thoff are forced to zero, even if the program returned sane values. This is the same class of bug as the one fixed in commit 99bda1ecbd56 ("flow_dissector: avoid u16 truncation of skb->len when computing thoff"). That fix does not cover the BPF path, because __skb_flow_dissect() returns early when a BPF flow dissector handles the packet. struct bpf_flow_keys stores nhoff and thoff as u16, so cap hlen to U16_MAX before clamping. Found by sashiko while reviewing the commit above. Fixes: d58e468b1112 ("flow_dissector: implements flow dissector BPF hook") Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20261007045616.445596-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- net/core/flow_dissector.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 27d8a01bc923..1be6c739e3c7 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1023,6 +1023,8 @@ u32 bpf_flow_dissect(struct bpf_prog *prog, struct bpf_flow_dissector *ctx, result = bpf_prog_run_pin_on_cpu(prog, ctx); + /* bpf_flow_keys offsets are u16: do not let @hlen be truncated. */ + hlen = min_t(int, hlen, U16_MAX); flow_keys->nhoff = clamp_t(u16, flow_keys->nhoff, nhoff, hlen); flow_keys->thoff = clamp_t(u16, flow_keys->thoff, flow_keys->nhoff, hlen); From 1d6500523b8d5112afe0118173ebc8c1ca23784e Mon Sep 17 00:00:00 2001 From: Tony Nguyen Date: Thu, 1 Oct 2026 15:24:39 -0700 Subject: [PATCH 563/590] e1000e: add system to disable K1 list Javier reports seeing packet loss issues related to the re-enablement of K1; disabling it makes the issues go away. Add the reported system to have K1 disabled by default. Reproduction steps (from the Link): Plug the cable and ping the default gateway on the LAN. No suspend/resume involved, machine freshly booted and on AC power. Nothing e1000e-related in dmesg apart from the link up/down messages; no "Hardware Unit Hang". Default (K1 enabled): $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 18 received, 40% packet loss, time 29734ms rtt min/avg/max/mdev = 0.211/0.315/0.384/0.046 ms The lost packets are spread over the run (seq 6, 8, 9, 11, 13, 14, 16, 19, 20, 24, 25, 30), not a single burst. DHCP on this link also takes 1-2 minutes to get a lease, consistent with incoming packets being dropped. With K1 disabled at runtime: # ethtool --set-priv-flags enp0s31f6 disable-k1 on $ ethtool --show-priv-flags enp0s31f6 Private flags for enp0s31f6: s0ix-enabled: on disable-k1 : on $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29713ms rtt min/avg/max/mdev = 0.169/0.251/0.468/0.054 ms $ ping -c 30 192.168.10.1 --- 192.168.10.1 ping statistics --- 30 packets transmitted, 30 received, 0% packet loss, time 29677ms rtt min/avg/max/mdev = 0.101/0.222/0.360/0.064 ms I have only compile tested this, but Javier has tested that disabling K1 on his system (Lenovo ThinkPad P14s Gen 5 (Intel), I219-LM [8086:550a]) resolves his issues. Cc: stable@vger.kernel.org Fixes: 578294b8b60d ("e1000e: Reconfigure PLL clock gate timeout and re-enable K1 on Meteor Lake") Reported-by: Javier Herrera Link: https://lore.kernel.org/intel-wired-lan/CAKRuCQLWEcci9hPgNUmvq44Bd2H30T5_vP=exMjsknXMN5Yh9A@mail.gmail.com/ Reviewed-by: Aleksandr Loktionov Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20261001222443.3500206-7-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/e1000e/netdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 844f31ab37ad..c282f65199e4 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -67,6 +67,13 @@ static const struct dmi_system_id disable_k1_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Dell Pro 16 Plus PB16250"), }, }, + { + .ident = "Lenovo ThinkPad P14s Gen 5", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad P14s Gen 5"), + }, + }, {} }; From c57b2e60d4bf29707c51b39ed3bacbcce2f3ba2e Mon Sep 17 00:00:00 2001 From: Haishuang Yan Date: Fri, 2 Oct 2026 23:30:39 +0800 Subject: [PATCH 564/590] bareudp: set the inner protocol to the protocol of the packet bareudp_xmit_skb() sets the inner protocol of the skb to the configured ethertype, and bareudp6_xmit_skb() does not set it at all. The inner protocol is used by skb_udp_tunnel_segment() to segment the inner packet when GSO has to be done in software, for instance when the lower device does not offload the checksum. With multiproto, an IPv4 device also carries IPv6, so the IPv6 header is parsed as an IPv4 one. With IPv6 underlay, the inner protocol is whatever the skb had before. In both cases segmentation fails and the packets are dropped. With veth tx checksum offload disabled, iperf3 TCP over bareudp gets about 10 Mbit/s with thousands of retransmits instead of about 700 Mbit/s for IPv6 over IPv4 and for both families over IPv6, while IPv4 over IPv4 works. At this point skb->protocol is the protocol of the inner packet, which bareudp_xmit() has already checked against the configuration, so use it on both paths. Fixes: 571912c69f0e ("net: UDP tunnel encapsulation module for tunnelling different protocols like MPLS, IP, NSH etc.") Fixes: 4b5f67232d95 ("net: Special handling for IP & MPLS.") Signed-off-by: Haishuang Yan Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20261002153039.462663-1-yanhaishuang@cmss.chinamobile.com Signed-off-by: Jakub Kicinski --- drivers/net/bareudp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index c3b5ed52d877..b9fe54151e1a 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -356,7 +356,7 @@ static int bareudp_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; - skb_set_inner_protocol(skb, bareudp->ethertype); + skb_set_inner_protocol(skb, skb->protocol); udp_tunnel_xmit_skb(rt, sk, skb, saddr, info->key.u.ipv4.dst, tos, ttl, df, sport, bareudp->port, !net_eq(bareudp->net, dev_net(bareudp->dev)), @@ -424,6 +424,7 @@ static int bareudp6_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (err) goto free_dst; + skb_set_inner_protocol(skb, skb->protocol); daddr = info->key.u.ipv6.dst; udp_tunnel6_xmit_skb(dst, sk, skb, dev, &saddr, &daddr, prio, ttl, From 6d25ffca055a77787c21a36b66c253f76239411b Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Tue, 6 Oct 2026 12:51:08 +0900 Subject: [PATCH 565/590] cipso: adjust cached option offsets when removing CIPSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cipso_v4_skbuff_delattr() removes the CIPSO bytes but does not adjust cached offsets for options that follow them. For example, with a valid 10-byte CIPSO option followed by a seven-byte RR option, parsing records rr at offset 30. Removing CIPSO moves RR to offset 20, while the cached offset remains 30. Consumers such as ip_forward_options() and __ip_options_echo() then access the wrong bytes; the latter may interpret packet data as the option length and copy it into fixed-size option storage. Mirror cipso_v4_delopt() and subtract cipso_len from the srr, rr, ts and router_alert offsets when they follow CIPSO. cipso_len is the distance the first memmove() shifts those options. The later header move and network-header reset relocate the bytes and their offset base together. Fixes: 89aa3619d141 ("cipso: make cipso_v4_skbuff_delattr() fully remove the CIPSO options") Cc: stable@vger.kernel.org Reviewed-by: Ondrej Mosnáček Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Acked-by: Paul Moore Link: https://patch.msgid.link/20261006035108.3101440-1-4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/cipso_ipv4.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/net/ipv4/cipso_ipv4.c b/net/ipv4/cipso_ipv4.c index a05aa075de1a..1aacbbeffbc6 100644 --- a/net/ipv4/cipso_ipv4.c +++ b/net/ipv4/cipso_ipv4.c @@ -2287,6 +2287,14 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb) new_hdr_len - new_hdr_len_actual); opt->optlen -= hdr_len_delta; + if (opt->srr > opt->cipso) + opt->srr -= cipso_len; + if (opt->rr > opt->cipso) + opt->rr -= cipso_len; + if (opt->ts > opt->cipso) + opt->ts -= cipso_len; + if (opt->router_alert > opt->cipso) + opt->router_alert -= cipso_len; opt->cipso = 0; opt->is_changed = 1; if (hdr_len_delta != 0) { From 4b1d04693e5ef388d8c6110bc93ee0d0681f9711 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 23 Sep 2026 18:17:49 +0800 Subject: [PATCH 566/590] MAINTAINERS: name the ext4 dev branch The ext4 T: entry names the repository without a branch, so tools that default to the repository's HEAD select master, which still points at a merge from October 2020 (96485e446260). Development happens on dev, which is also the branch linux-next merges. Name it. The Sashiko review bot, for one, falls back to HEAD this way and has been reviewing ext4 patches against that tree. Its review of an EA inode refcount fix reported a Critical double decrement and an undefined function after applying the patch to that stale baseline; neither holds on dev. Link: https://lore.kernel.org/all/20260916072420.316321F000FF@smtp.kernel.org/ Signed-off-by: Matthias Goergens Acked-by: Jan Kara Link: https://patch.msgid.link/20260923101749.3505886-1-matthias.goergens@gmail.com Signed-off-by: Theodore Ts'o --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b..c884c4c62e3f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9859,7 +9859,7 @@ L: linux-ext4@vger.kernel.org S: Maintained W: http://ext4.wiki.kernel.org Q: http://patchwork.ozlabs.org/project/linux-ext4/list/ -T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git dev F: Documentation/filesystems/ext4/ F: fs/ext4/ F: include/trace/events/ext4.h From 3e78d7b063230d39ffc42e80ee665b6c32a6adeb Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Thu, 24 Sep 2026 10:58:12 -0400 Subject: [PATCH 567/590] ext4: mark data=journal as deprecated and will be removed in January 2028. Signed-off-by: Theodore Ts'o --- Documentation/admin-guide/ext4.rst | 6 ++++-- fs/ext4/super.c | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/ext4.rst b/Documentation/admin-guide/ext4.rst index 742a48e6fc0c..8b23b6ff3df1 100644 --- a/Documentation/admin-guide/ext4.rst +++ b/Documentation/admin-guide/ext4.rst @@ -172,7 +172,8 @@ When mounting an ext4 filesystem, the following option are accepted: data=journal All data are committed into the journal prior to being written into the main file system. Enabling this mode will disable delayed allocation - and O_DIRECT support. + and O_DIRECT support. This feature is deprecated and support + will be removed in January 2028. data=ordered (*) All data are forced directly out to the main file system prior to its @@ -418,7 +419,8 @@ There are 3 different data modes: consistent state. This mode is the slowest except when data needs to be read from and written to disk at the same time where it outperforms all others modes. Enabling this mode will disable delayed allocation and O_DIRECT - support. + support. The data=journal feature is deprecated and support will be + removed in January 2028. /proc entries ============= diff --git a/fs/ext4/super.c b/fs/ext4/super.c index bca0dc87d0b7..d636b89090a2 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -5057,6 +5057,7 @@ static int ext4_load_and_init_journal(struct super_block *sb, static int ext4_check_journal_data_mode(struct super_block *sb) { if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA) { + printk(KERN_WARNING "EXT4-fs: The data=journal mount option is deprecated and support will be removed in January 2028\n"); printk_once(KERN_WARNING "EXT4-fs: Warning: mounting with " "data=journal disables delayed allocation, " "dioread_nolock, O_DIRECT and fast_commit support!\n"); From 259c4a519100e9182f264f8009ee212b32774277 Mon Sep 17 00:00:00 2001 From: Hamza Mahfooz Date: Fri, 2 Oct 2026 21:36:47 -0400 Subject: [PATCH 568/590] net: mana: reserve RX buffer headroom to fix forwarding performance Commit 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") started handing out RX buffers with zero headroom so that two buffers fit into one page at the default MTU. The MANA TX path, however, stores the per scatter-gather entry DMA mappings in `struct mana_skb_head` at skb->head, and mana_start_xmit() therefore calls skb_cow_head(skb, MANA_HEADROOM). The port advertises this requirement as ndev->needed_headroom = MANA_HEADROOM. As a result every packet that is received and then forwarded out of a MANA port fails the skb_cow() in ip_forward() and gets reallocated and copied by pskb_expand_head(). This is invisible to a plain RX or TX workload, but it puts a full skb reallocation plus memcpy on the hot path of every single forwarded packet, which is exactly what a router/NVA workload does. Restore the headroom. Note that reserving MANA_HEADROOM (232) is not enough: ip_forward() asks for LL_RESERVED_SPACE(dev), which rounds hard_header_len + needed_headroom up to HH_DATA_MOD and is 256 bytes on ethernet. Use LL_RESERVED_SPACE() directly so the value keeps tracking both constants. Also, since LL_RESERVED_SPACE() tracks MANA_HEADROOM, it grows with MAX_SKB_FRAGS and for MAX_SKB_FRAGS >= 19 it is greater than 256, so we have to account for that by using the headroom the RX queue actually uses (instead of assuming XDP_PACKET_HEADROOM) and turning MANA_XDP_MTU_MAX into MANA_XDP_MTU_MAX(ndev) (note that at the default CONFIG_MAX_SKB_FRAGS=17 they are equivalent). At the default MTU on a 4K page this means a buffer no longer fits twice into a page (SKB_DATA_ALIGN(1500 + MANA_RXBUF_PAD + 256) = 2112), so the frag-vs-single decision is now made by computing the real buffer size instead of comparing the MTU against PAGE_SIZE / 2. The page_pool fragment path is still used wherever at least two buffers genuinely fit, e.g. on 16K and 64K page sizes. Measured on an Azure VM with a MANA NIC acting as a forwarding NVA (UDP, 1400 byte payload, 4 streams, 8 Gbps offered, only the forwarding node's kernel differs), 8 runs each, median: forwarded pps throughput before 272,830 3.06 Gbps after 390,560 4.37 Gbps (+43%) perf on the forwarding node, same workload: memset_orig __pi_memcpy pskb_expand_head before 10.07% 3.96% present after 0.94% 0.64% gone Cc: stable@vger.kernel.org Fixes: 730ff06d3f5c ("net: mana: Use page pool fragments for RX buffers instead of full pages to improve memory efficiency.") Signed-off-by: Hamza Mahfooz Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261003013647.2051416-1-hamzamahfooz@linux.microsoft.com Signed-off-by: Jakub Kicinski --- .../net/ethernet/microsoft/mana/mana_bpf.c | 8 +-- drivers/net/ethernet/microsoft/mana/mana_en.c | 63 ++++++++++++++----- .../ethernet/microsoft/mana/mana_ethtool.c | 10 +-- include/net/mana/mana.h | 14 ++++- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 5c9961ee9747..b27fd9b8c4f6 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -91,7 +91,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq, goto out; xdp_init_buff(xdp, PAGE_SIZE, &rxq->xdp_rxq); - xdp_prepare_buff(xdp, buf_va, XDP_PACKET_HEADROOM, pkt_len, true); + xdp_prepare_buff(xdp, buf_va, rxq->headroom, pkt_len, true); act = bpf_prog_run_xdp(prog, xdp); @@ -183,9 +183,9 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, if (!old_prog && !prog) return 0; - if (prog && ndev->mtu > MANA_XDP_MTU_MAX) { + if (prog && ndev->mtu > MANA_XDP_MTU_MAX(ndev)) { netdev_err(ndev, "XDP: mtu:%u too large, mtu_max:%lu\n", - ndev->mtu, MANA_XDP_MTU_MAX); + ndev->mtu, MANA_XDP_MTU_MAX(ndev)); NL_SET_ERR_MSG_MOD(extack, "XDP: mtu too large"); return -EOPNOTSUPP; @@ -238,7 +238,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog, bpf_prog_put(old_prog); if (prog) - ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX, + ndev->max_mtu = min_t(unsigned int, MANA_XDP_MTU_MAX(ndev), gc->adapter_mtu - ETH_HLEN); else ndev->max_mtu = gc->adapter_mtu - ETH_HLEN; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 591fb4191d90..a1fb06b23947 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -758,6 +758,44 @@ static void *mana_get_rxbuf_pre(struct mana_rxq *rxq, dma_addr_t *da) return va; } +/* Reserve enough headroom to satisfy the skb_cow() in ip_forward() and avoid + * reallocation: the TX path keeps the SGE DMA mappings in struct mana_skb_head + * at skb->head, so the port advertises ndev->needed_headroom = MANA_HEADROOM. + */ +static u32 mana_get_rxbuf_headroom(struct mana_port_context *apc) +{ + if (mana_xdp_get(apc)) + return mana_xdp_headroom(apc->ndev); + + return LL_RESERVED_SPACE(apc->ndev); +} + +static u32 mana_get_rxbuf_size(struct mana_port_context *apc, u32 mtu) +{ + u32 len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + + mana_get_rxbuf_headroom(apc)); + + return ALIGN(len, MANA_RX_FRAG_ALIGNMENT); +} + +/* Returns true when one RX buffer per page is already required by XDP or by + * the buffer size implied by the MTU, i.e. regardless of the + * MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF private flag. + */ +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu) +{ + /* For xdp make sure only one packet fits per page. */ + if (mana_xdp_get(apc)) + return true; + + /* Only use the page_pool fragment path when at least two buffers, + * including the headroom each of them has to reserve, actually fit + * into one page. Otherwise the fragment path degenerates into one + * buffer per page while still paying the fragment accounting cost. + */ + return PAGE_SIZE / mana_get_rxbuf_size(apc, mtu) < 2; +} + static bool mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) { @@ -770,32 +808,27 @@ mana_use_single_rxbuf_per_page(struct mana_port_context *apc, u32 mtu) if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) return true; - /* For xdp and jumbo frames make sure only one packet fits per page. */ - if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc)) - return true; - - return false; + return mana_single_rxbuf_per_page_forced(apc, mtu); } -/* Get RX buffer's data size, alloc size, XDP headroom based on MTU */ +/* Get RX buffer's data size, alloc size, headroom and frag count based on MTU */ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, int mtu, u32 *datasize, u32 *alloc_size, u32 *headroom, u32 *frag_count) { - u32 len, buf_size; + u32 buf_size; /* Calculate datasize first (consistent across all cases) */ *datasize = mtu + ETH_HLEN; + *headroom = mana_get_rxbuf_headroom(apc); + if (mana_use_single_rxbuf_per_page(apc, mtu)) { - if (mana_xdp_get(apc)) { - *headroom = XDP_PACKET_HEADROOM; + if (mana_xdp_get(apc)) *alloc_size = PAGE_SIZE; - } else { - *headroom = 0; /* no support for XDP */ + else *alloc_size = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - } *frag_count = 1; @@ -809,11 +842,7 @@ static void mana_get_rxbuf_cfg(struct mana_port_context *apc, } /* Standard MTU case - optimize for multiple packets per page */ - *headroom = 0; - - /* Calculate base buffer size needed */ - len = SKB_DATA_ALIGN(mtu + MANA_RXBUF_PAD + *headroom); - buf_size = ALIGN(len, MANA_RX_FRAG_ALIGNMENT); + buf_size = mana_get_rxbuf_size(apc, mtu); /* Calculate how many packets can fit in a page */ *frag_count = PAGE_SIZE / buf_size; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index ece7ff9cc409..33db79569d3b 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -815,12 +815,12 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags) if (!apc->port_is_up) return 0; - /* If XDP is attached or MTU is jumbo, single-buffer-per-page - * is already forced regardless of this flag. Skip the - * expensive detach/attach cycle since nothing changes. + /* If XDP is attached or the MTU already forces one buffer per + * page, single-buffer-per-page is used regardless of this + * flag. Skip the expensive detach/attach cycle since nothing + * changes. */ - if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || - mana_xdp_get(apc)) + if (mana_single_rxbuf_per_page_forced(apc, ndev->mtu)) return 0; /* Block RDMA from grabbing the vport during detach/attach */ diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 83b7eff4646e..8c3dc9d0b299 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -377,7 +377,18 @@ struct mana_recv_buf_oob { #define MANA_RXBUF_PAD (SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) \ + ETH_HLEN) -#define MANA_XDP_MTU_MAX (PAGE_SIZE - MANA_RXBUF_PAD - XDP_PACKET_HEADROOM) +/* Headroom an RX buffer has to reserve while XDP is attached: the XDP program + * needs XDP_PACKET_HEADROOM, and the TX path needs LL_RESERVED_SPACE() (see + * mana_get_rxbuf_headroom()). LL_RESERVED_SPACE() grows with MAX_SKB_FRAGS and + * exceeds XDP_PACKET_HEADROOM once CONFIG_MAX_SKB_FRAGS is 19 or more. + */ +static inline u32 mana_xdp_headroom(struct net_device *ndev) +{ + return max_t(u32, LL_RESERVED_SPACE(ndev), XDP_PACKET_HEADROOM); +} + +#define MANA_XDP_MTU_MAX(ndev) \ + (PAGE_SIZE - MANA_RXBUF_PAD - mana_xdp_headroom(ndev)) struct mana_rxq { struct gdma_queue *gdma_rq; @@ -691,6 +702,7 @@ int mana_query_link_cfg(struct mana_port_context *apc); int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, int enable_clamping); void mana_query_phy_stats(struct mana_port_context *apc); +bool mana_single_rxbuf_per_page_forced(struct mana_port_context *apc, u32 mtu); int mana_pre_alloc_rxbufs(struct mana_port_context *apc, int mtu, int num_queues); void mana_pre_dealloc_rxbufs(struct mana_port_context *apc); void mana_unmap_skb(struct sk_buff *skb, struct mana_port_context *apc); From 3f090039995f609a8e236f37980dee5712dded61 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:36 +0100 Subject: [PATCH 569/590] veth: fix peer NETDEV_XDP_ACT_NDO_XMIT after GRO is toggled while down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A veth device advertises NETDEV_XDP_ACT_NDO_XMIT only if its peer has an XDP program attached or GRO enabled, that is, only if the peer will have NAPI to receive the frames. veth_set_features() updates the peer's flag when GRO is toggled, but returns early if the device is down, and veth_open() only refreshes the flags of the device being opened. Toggling GRO while the device is down therefore leaves the peer's flag stale after the device comes up. If GRO was enabled while down, the device comes up with NAPI but the peer does not advertise NDO_XMIT, and devmap rejects redirects to the peer with -EOPNOTSUPP. If GRO was disabled while down, the device comes up without NAPI but the peer still advertises NDO_XMIT, so redirects are accepted and then dropped in veth_xdp_xmit() with -ENXIO. Commit 7a6102aa6df0 ("veth: Update XDP feature set when bringing up device") made veth_open() refresh the device's own flags. Refresh the peer's flags there too. The peer's flag depends on this device's XDP program and GRO setting, not on whether the peer is up, so it is correct to set it even if the peer is down. Fixes: 8267fc71abb2 ("veth: take into account peer device for NETDEV_XDP_ACT_NDO_XMIT xdp_features flag") Signed-off-by: Tianyi Gao Reviewed-by: Toke Høiland-Jørgensen Acked-by: Jesper Dangaard Brouer Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20261006173241.65945-2-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- drivers/net/veth.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 71227d0389aa..643b97dc5245 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1415,6 +1415,7 @@ static int veth_open(struct net_device *dev) } veth_set_xdp_features(dev); + veth_set_xdp_features(peer); return 0; } From c017800af5c71ebb2e6209eebf5c60c19850ffe7 Mon Sep 17 00:00:00 2001 From: Tianyi Gao Date: Tue, 6 Oct 2026 18:32:37 +0100 Subject: [PATCH 570/590] selftests: net: veth: test peer ndo-xmit after GRO toggle while down Test that toggling GRO on a veth device while it is down updates the peer's ndo-xmit XDP feature once the device comes up, for both GRO on and GRO off. xdp-features is only visible through netlink, so read it with the ynl CLI, as double_udp_encap.sh does. Skip the checks if the CLI is not found, as in an installed kselftest tree. Signed-off-by: Tianyi Gao Link: https://patch.msgid.link/20261006173241.65945-3-tianyi@cloudflare.com Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/veth.sh | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tools/testing/selftests/net/veth.sh b/tools/testing/selftests/net/veth.sh index 9709dd067c72..8b867e0675a7 100755 --- a/tools/testing/selftests/net/veth.sh +++ b/tools/testing/selftests/net/veth.sh @@ -9,6 +9,8 @@ readonly DST=1 readonly DST_NAT=100 readonly NS_SRC=$BASE$SRC readonly NS_DST=$BASE$DST +# shellcheck disable=SC2155 # prefer RO variable over return value from cmd +readonly YNL="$(dirname "$(readlink -f "$0")")/../../../net/ynl/pyynl/cli.py" # "baremetal" network used for raw UDP traffic readonly BM_NET_V4=192.168.1. @@ -74,6 +76,37 @@ chk_tso_flag() { __chk_flag "$1" $2 $3 tcp-segmentation-offload } +chk_xdp_feature() { + local msg="$1" + local target=$2 + local expected=$3 + local feature=$4 + local ifindex + local out + local st + local flag + + ifindex=`ip netns exec $BASE$target cat /sys/class/net/veth$target/ifindex` + out=`ip netns exec $BASE$target "$YNL" --family netdev --do dev-get \ + --output-json --json "{\"ifindex\": $ifindex}" 2>&1` + st=$? + + printf "%-60s" "$msg" + if [ $st -ne 0 ]; then + echo " fail - ynl cli error: $out" + ret=1 + return + fi + + flag=`echo "$out" | grep -c "\"$feature\""` + if [ "$flag" = "$expected" ]; then + echo " ok " + else + echo " fail - expected $expected found $flag" + ret=1 + fi +} + chk_channels() { local msg="$1" local target=$2 @@ -222,6 +255,7 @@ fi [ $CPUS -lt 2 ] && echo "Only one CPU available, some tests will be skipped" [ $STRESS -gt 0 -a $CPUS -lt 3 ] && echo " stress test will be skipped, too" +[ ! -f "$YNL" ] && echo "ynl cli not found, ndo-xmit tests will be skipped" create_ns chk_gro_flag "default - gro flag" $SRC off @@ -283,6 +317,18 @@ chk_gro_flag " - peer gro flag" $SRC off chk_tso_flag " - tso flag" $SRC on chk_tso_flag " - peer tso flag" $DST on ip -n $NS_DST link set dev veth$DST up +if [ -f "$YNL" ]; then + chk_xdp_feature " - peer ndo-xmit" $SRC 1 ndo-xmit + # make sure the peer flag is set before disabling gro while down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip netns exec $NS_DST ethtool -K veth$DST gro on + chk_xdp_feature " - peer ndo-xmit re-armed" $SRC 1 ndo-xmit + ip -n $NS_DST link set dev veth$DST down + ip netns exec $NS_DST ethtool -K veth$DST gro off + ip -n $NS_DST link set dev veth$DST up + chk_xdp_feature " - peer ndo-xmit cleared" $SRC 0 ndo-xmit + ip netns exec $NS_DST ethtool -K veth$DST gro on +fi ip netns exec $NS_SRC ethtool -K veth$SRC tx-udp-segmentation off ip netns exec $NS_DST ethtool -K veth$DST rx-udp-gro-forwarding on chk_gro " - aggregation with TSO off" 1 From b056ca4d3742d0769f91137bb1ce3215428398f7 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Tue, 6 Oct 2026 13:58:20 +0300 Subject: [PATCH 571/590] net/mlx5e: Order ICOSQ cc update after CQ doorbell mlx5e_poll_ico_cq() requires sq->cc to be updated only after mlx5_cqwq_update_db_record(), otherwise a CQ overrun may occur. The current implementation updates sq->cc before the CQ doorbell record, violating this ordering requirement. Update the CQ doorbell record first and use dma_wmb() before updating sq->cc. This ensures that the CQ space is released to the device before the corresponding ICOSQ consumer index is updated by software. Fixes: fd9b4be8002c ("net/mlx5e: RX, Support multiple outstanding UMR posts") Signed-off-by: Li RongQing Reviewed-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261006105820.257208-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_rx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c index 7bd0606a5253..903af3b3e779 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c @@ -908,10 +908,12 @@ int mlx5e_poll_ico_cq(struct mlx5e_cq *cq) } while (!last_wqe); } while ((++i < MLX5E_TX_CQ_POLL_BUDGET) && (cqe = mlx5_cqwq_get_cqe(&cq->wq))); - sq->cc = sqcc; - mlx5_cqwq_update_db_record(&cq->wq); + /* ensure cq space is freed before enabling more cqes */ + dma_wmb(); + + sq->cc = sqcc; return i; } From fc13ba44da197a186f2f4d77c5b7d73d0f99f053 Mon Sep 17 00:00:00 2001 From: Sebastien Royen Date: Wed, 7 Oct 2026 10:05:57 +0200 Subject: [PATCH 572/590] net: dsa: microchip: fix KSZ8765 fiber detection The KSZ8765 is similar to the KSZ8795 but has two fiber ports. KSZ8_PORT_STATUS_0 is used to detect fiber mode. It is currently defined as 0x08, which is the Global Control 6 MIB Control register and has bit 7 defined as Flush Counter. Set KSZ8_PORT_STATUS_0 to 0x18, which is the Port 1 Status 0 register where bit 7 is the Fiber Mode bit. This issue was discovered when upgrading an embedded device from kernel 5.10 to 6.16. The device was using a KSZ8765 device tree configuration and the corresponding hardware, but during boot the kernel incorrectly detected it as a KSZ8795. The relevant boot messages were: ksz-switch spi0.0: found switch: KSZ8795, rev 0 ksz-switch spi0.0: Device tree specifies chip KSZ8765 but found KSZ8795, please fix it! Cc: stable@vger.kernel.org Fixes: 91a98917a883 ("net: dsa: microchip: move switch chip_id detection to ksz_common") Reviewed-by: Nicolai Buchwitz Signed-off-by: Sebastien Royen Link: https://patch.msgid.link/20261007080557.15719-1-sebastien.royen@armadeus.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/microchip/ksz_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index cbe98494578c..f6104f714a8d 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -853,7 +853,7 @@ static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port) #define KSZ88_FAMILY_ID 0x88 #define KSZ8895_FAMILY_ID 0x95 -#define KSZ8_PORT_STATUS_0 0x08 +#define KSZ8_PORT_STATUS_0 0x18 #define KSZ8_PORT_FIBER_MODE BIT(7) #define SW_CHIP_ID_M GENMASK(7, 4) From 7ea07afb230f1342ab0f9365edd0aae137f425d3 Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:28 +0200 Subject: [PATCH 573/590] mlxsw: spectrum_flower: Fix port range register leak in tmplt_create() mlxsw_sp_flower_tmplt_create() parses a flow_cls_offload template into a stack-local struct mlxsw_sp_acl_rule_info purely to compute rulei.values.elusage. Parsing can acquire port range registers via mlxsw_sp_flower_parse_ports_range(), but since this rulei never goes through mlxsw_sp_acl_rulei_destroy(), those registers were never released, including through chain template deletion. Factor out of mlxsw_sp_acl_rulei_destroy() the code to actually release the necessary resources and call from mlxsw_sp_flower_tmplt_create() to plug the leak. The issue was found during a review of Wentao Liang's patch referenced below. Fixes: fe22f7410527 ("mlxsw: spectrum_flower: Add ability to match on port ranges") Reported-by: Wentao Liang Closes: https://lore.kernel.org/netdev/20260917113236.2149095-1-vulab@iscas.ac.cn/ Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/95339cf970ce78e1a74aa86ffcc104ecf7c7762c.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlxsw/spectrum.h | 2 ++ drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c | 10 ++++++++-- drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c | 9 +++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h index b03ff9e044f9..10af08ce72ef 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum.h +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum.h @@ -989,6 +989,8 @@ void mlxsw_sp_acl_ruleset_prio_get(struct mlxsw_sp_acl_ruleset *ruleset, struct mlxsw_sp_acl_rule_info * mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, struct mlxsw_afa_block *afa_block); +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei); void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_acl_rule_info *rulei); int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei); diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c index cb232accb296..f7be4f3a1518 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c @@ -340,8 +340,8 @@ mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl, return ERR_PTR(err); } -void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, - struct mlxsw_sp_acl_rule_info *rulei) +void mlxsw_sp_acl_rulei_free(struct mlxsw_sp *mlxsw_sp, + const struct mlxsw_sp_acl_rule_info *rulei) { if (rulei->action_created) mlxsw_afa_block_destroy(rulei->act_block); @@ -351,6 +351,12 @@ void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, if (rulei->dst_port_range_reg_valid) mlxsw_sp_port_range_reg_put(mlxsw_sp, rulei->dst_port_range_reg_index); +} + +void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp *mlxsw_sp, + struct mlxsw_sp_acl_rule_info *rulei) +{ + mlxsw_sp_acl_rulei_free(mlxsw_sp, rulei); kfree(rulei); } diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c index 353fd9ca89a6..5e4632cb792d 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c @@ -862,14 +862,19 @@ int mlxsw_sp_flower_tmplt_create(struct mlxsw_sp *mlxsw_sp, memset(&rulei, 0, sizeof(rulei)); err = mlxsw_sp_flower_parse(mlxsw_sp, block, &rulei, f); if (err) - return err; + goto out; + ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, block, f->common.chain_index, MLXSW_SP_ACL_PROFILE_FLOWER, &rulei.values.elusage); + err = PTR_ERR_OR_ZERO(ruleset); + +out: + mlxsw_sp_acl_rulei_free(mlxsw_sp, &rulei); /* keep the reference to the ruleset */ - return PTR_ERR_OR_ZERO(ruleset); + return err; } void mlxsw_sp_flower_tmplt_destroy(struct mlxsw_sp *mlxsw_sp, From 71198b59df56a9a0115115091cc1aad2a4e3c74d Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Tue, 6 Oct 2026 16:21:29 +0200 Subject: [PATCH 574/590] selftests: mlxsw: Test port range occupancy on template create Add a test that creates a tc chain template matching on both source and destination port ranges and verifies via devlink-resource occupancy that this does not leak port range registers, neither while the template exists nor after it is deleted. Reviewed-by: Ido Schimmel Signed-off-by: Petr Machata Reviewed-by: Jacob Keller Link: https://patch.msgid.link/e7b37b80adb7ac8b0ef20a22d7654a6656c1c545.1791294384.git.petrm@nvidia.com Signed-off-by: Jakub Kicinski --- .../drivers/net/mlxsw/port_range_occ.sh | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh index b1f0781f6b25..c7eba3f15066 100755 --- a/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh +++ b/tools/testing/selftests/drivers/net/mlxsw/port_range_occ.sh @@ -9,6 +9,7 @@ lib_dir=$(dirname $0)/../../../net/forwarding ALL_TESTS=" port_range_occ_test + port_range_occ_tmplt_test " NUM_NETIFS=2 source $lib_dir/lib.sh @@ -101,6 +102,31 @@ port_range_occ_test() log_test "port range occupancy" } +port_range_occ_tmplt_test() +{ + local occ + + RET=0 + occ=$(port_range_occ_get) + + # Addition of a chain template should not impact occupancy. + tc chain add dev "$swp1" ingress chain 1 protocol ip \ + flower ip_proto udp src_port 501-600 dst_port 701-800 + check_err $? "Could not add chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + # And just for completeness sake, neither should deletion. + tc chain del dev "$swp1" ingress chain 1 + check_err $? "Could not delete chain template" + + (( occ == $(port_range_occ_get) )) + check_err $? "Got occupancy $(port_range_occ_get), expected $occ" + + log_test "port range occupancy after template create/destroy" +} + trap cleanup EXIT setup_prepare From 2b82e16d6084cbc651e3c902198f1945408ee1a5 Mon Sep 17 00:00:00 2001 From: Daniel Machon Date: Wed, 7 Oct 2026 17:03:15 +0200 Subject: [PATCH 575/590] net: sparx5: free the matchall entry on destroy sparx5_tc_matchall_replace() allocates a struct sparx5_mall_entry for every offloaded matchall filter and adds it to sparx5->mall_entries. sparx5_tc_matchall_destroy() removes the entry from the list, but never frees it, so the entry of every deleted mirror and goto matchall filter is leaked. Free the entry after unlinking it. The leak was discovered by an AI code review agent, and reproduced with kmemleak on a lan969x EV board (EV23X71A) by repeatedly adding and deleting matchall mirror and goto filters. With the fix, kmemleak no longer reports the leak. Cc: stable@vger.kernel.org Fixes: 1ede4acf045c ("net: sparx5: add bookkeeping code for matchall rules") Signed-off-by: Daniel Machon Link: https://patch.msgid.link/20261007-sparx5-matchall-kfree-net-v1-1-c8918685b337@microchip.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c index 702257979462..146d7f28e8f3 100644 --- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_matchall.c @@ -160,6 +160,7 @@ static int sparx5_tc_matchall_destroy(struct net_device *ndev, } list_del(&entry->list); + kfree(entry); return err; } From ab9414ed70bd783e902a85c350620dee269bcb2b Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:29:34 +0000 Subject: [PATCH 576/590] net: skbuff: don't leave stale bytes in skb_copy_and_csum_bits() When skb_copy_and_csum_bits() reaches unreadable frags it returns 0 after copying only the linear part, and the rest of the caller's buffer is left as it was. The callers copy into a buffer that is about to go out on the wire: an ICMP error quoting the offending packet, or a driver's TX bounce buffer in skb_copy_and_csum_dev(). Neither buffer is zeroed beforehand, so whatever was in memory there gets sent. Zero the part of the buffer we didn't fill. The checksum usually won't match the data any more, so the receiver will usually drop the packet, but either way it no longer carries anything it shouldn't. Only zero for a positive @len, a negative one from a broken caller must not turn into a huge memset(). Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Mina Almasry Link: https://patch.msgid.link/20261007-b4-skb-copy-csum-stale-bytes-v1-1-adbbde033fb3@toxicpanda.com Signed-off-by: Jakub Kicinski --- net/core/skbuff.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 4aea06d5167d..41beaf625421 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3633,8 +3633,12 @@ __wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, pos = copy; } - if (!skb_frags_readable(skb)) + if (!skb_frags_readable(skb)) { + /* Don't hand the caller a buffer with stale bytes in it. */ + if (len > 0) + memset(to, 0, len); return 0; + } for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; From 089e58805c452e52179482b1025a8e309a57f801 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:55:40 +0000 Subject: [PATCH 577/590] xen/netfront: drop RX packets with a short Ethernet header handle_incoming_queue() pulls pull_to bytes into the head before calling eth_type_trans(). pull_to is the length of the first RX slot, capped at RX_COPY_THRESHOLD, and that length comes from the backend. Nothing checks it against ETH_HLEN. If the first slot is shorter than ETH_HLEN and more slots follow, the head ends up shorter than an Ethernet header while skb->len is longer, and eth_type_trans() BUG()s in __skb_pull(). If the whole packet is shorter than ETH_HLEN, eth_type_trans() reads the header past the end of the data instead. Pull at least ETH_HLEN, and drop the packet if that fails, which also drops packets too short to hold an Ethernet header. This also checks the return value of the pull, which was ignored. Fixes: 0d160211965b ("xen: add virtual network device driver") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Link: https://patch.msgid.link/20261007-b4-xen-netfront-short-head-v1-1-12d7113a7e4e@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index 2ed673649c48..d269457e839e 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1234,8 +1234,16 @@ static int handle_incoming_queue(struct netfront_queue *queue, while ((skb = __skb_dequeue(rxq)) != NULL) { int pull_to = NETFRONT_SKB_CB(skb)->pull_to; - if (pull_to > skb_headlen(skb)) - __pskb_pull_tail(skb, pull_to - skb_headlen(skb)); + /* pull_to comes from the first slot's length, which the + * backend controls. Make sure the head holds at least an + * Ethernet header for eth_type_trans(). + */ + if (!pskb_may_pull(skb, max(pull_to, ETH_HLEN))) { + kfree_skb(skb); + packets_dropped++; + queue->info->netdev->stats.rx_errors++; + continue; + } /* Ethernet work: Delayed to here as it peeks the header. */ skb->protocol = eth_type_trans(skb, queue->info->netdev); From 513e23857a3a64fa73a1deb36a385eecd4ad7690 Mon Sep 17 00:00:00 2001 From: Willem de Bruijn Date: Wed, 7 Oct 2026 13:57:30 -0400 Subject: [PATCH 578/590] net/packet: call packet_parse_headers after virtio_net_hdr_to_skb SOCK_RAW packet sockets incorrectly drop VLAN-tagged GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM. packet_parse_headers() sets skb->protocol to VLAN but advances skb->network_header past the VLAN tags. virtio_net_hdr_to_skb() then flow dissects these packets, which parses the network header as a VLAN tag, and fails. Call packet_parse_headers() after virtio_net_hdr_to_skb() instead. This matches tun_get_user() and tap_get_user() and thus simplifies overall complexity. Commit 01fdecc0480d ("net: packet: fix wrong transport_header when sending VLAN-tagged frame") fixed the same issue for the transport header probe in packet_parse_headers(). That probe is now skipped if virtio_net_hdr_to_skb() already set the transport header, avoiding a redundant dissection. Implementation details: - Do not set skb->protocol to the RX wildcard ETH_P_ALL. SOCK_RAW then derives it from the link layer header, as before. SOCK_DGRAM now leaves it 0, or derives it from gso_type for GSO with NEEDS_CSUM. - Keep virtio_net_hdr_set_proto() last, so that the link layer protocol takes priority over gso_type. Reported-by: Junnan Zhang Closes: https://lore.kernel.org/netdev/20260821085722.24036-1-zhangjn_dev@163.com/ Fixes: dfed913e8b55 ("net/af_packet: add VLAN support for AF_PACKET SOCK_RAW GSO") Signed-off-by: Willem de Bruijn Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20261007175816.3138556-1-willemdebruijn.kernel@gmail.com Signed-off-by: Jakub Kicinski --- net/packet/af_packet.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 6ff16eef24f4..52ae3d875e97 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -1943,8 +1943,7 @@ static void packet_parse_headers(struct sk_buff *skb, struct socket *sock) /* On TX skb->data is the L2 header; anchor it for all socket types. */ skb_reset_mac_header(skb); - if ((!skb->protocol || skb->protocol == htons(ETH_P_ALL)) && - sock->type == SOCK_RAW) + if (!skb->protocol && sock->type == SOCK_RAW) skb->protocol = dev_parse_header_protocol(skb); skb_probe_transport_header(skb); @@ -2614,7 +2613,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, struct page *page; int err; - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; @@ -2678,8 +2678,6 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, if (unlikely(!skb->len)) return -EINVAL; - packet_parse_headers(skb, sock); - return tp_len; } @@ -2919,9 +2917,13 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) tp_len = -EINVAL; goto tpacket_error; } - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, po->sk.sk_socket); + + if (has_vnet_hdr) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); if (unlikely(!uarg)) { if (likely(len_sum > 0)) @@ -3122,7 +3124,8 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) goto out_free; } - skb->protocol = proto; + if (proto != htons(ETH_P_ALL)) + skb->protocol = proto; skb->dev = dev; skb->priority = sockc.priority; skb->mark = sockc.mark; @@ -3131,16 +3134,18 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len) if (unlikely(extra_len == 4)) skb->no_fcs = 1; - packet_parse_headers(skb, sock); - if (vnet_hdr_sz) { err = virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le()); if (err) goto out_free; len += vnet_hdr_sz; - virtio_net_hdr_set_proto(skb, &vnet_hdr); } + packet_parse_headers(skb, sock); + + if (vnet_hdr_sz) + virtio_net_hdr_set_proto(skb, &vnet_hdr); + err = packet_xmit(po, skb); if (unlikely(err != 0)) { From 93ccaf1c26e2133396173b76a071e59024daa622 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Wed, 7 Oct 2026 17:57:32 +0000 Subject: [PATCH 579/590] xen/netfront: don't leak the skb when xennet_fill_frags() fails When a response chain has more slots than fit in the skb's frags, xennet_fill_frags() returns an error and xennet_poll() jumps to its error path. That path moves what's left on tmpq to errq to be freed, but the skb being filled was already dequeued from tmpq, so it's never freed. Each chain that overflows leaks the skb and the pages attached to it as frags, and the backend decides how many slots it sends. Put the skb back on tmpq before taking the error path, like the xennet_set_skb_gso() failure just above it does. Fixes: ad4f15dc2c70 ("xen/netfront: don't bug in case of too many frags") Cc: stable@vger.kernel.org Signed-off-by: Josef Bacik Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20261007-b4-xen-netfront-fill-frags-leak-v1-1-a8a01ff9cd52@toxicpanda.com Signed-off-by: Jakub Kicinski --- drivers/net/xen-netfront.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c index d269457e839e..fdcb91042f29 100644 --- a/drivers/net/xen-netfront.c +++ b/drivers/net/xen-netfront.c @@ -1346,8 +1346,10 @@ static int xennet_poll(struct napi_struct *napi, int budget) skb->data_len = rx->status; skb->len += rx->status; - if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) + if (unlikely(xennet_fill_frags(queue, skb, &tmpq))) { + __skb_queue_head(&tmpq, skb); goto err; + } if (rx->flags & XEN_NETRXF_csum_blank) skb->ip_summed = CHECKSUM_PARTIAL; From f8c8bd159a9bf626dd4921b7746ff481fce8c758 Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Wed, 7 Oct 2026 20:33:59 +0000 Subject: [PATCH 580/590] ptp: ocp: fix PCIe delay estimation calculation The commit in fixes introduced a high cap for delayas U64_MAX value while ktime_t is actually s64. This is wrong cap as it becomes negative value and any comparison to a real delay will fail to update delay value. Use KTIME_MAX constant as correct max cap for PCIe delay. The issue was hit in production (a negative value is observed): # cat /sys/class/timecard/ocp0/ts_window_adjust -3 Fixes: aa05fe67bcd64 ("ptp: ocp: Improve PCIe delay estimation") Signed-off-by: Vadim Fedorenko Reviewed-by: Daniel Machon Link: https://patch.msgid.link/20261007203359.417270-1-vadim.fedorenko@linux.dev Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_ocp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..a8cba2f64b98 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1825,7 +1825,7 @@ ptp_ocp_watchdog(struct timer_list *t) static void ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) { - ktime_t start, end, delay = U64_MAX; + ktime_t start, end, delay = KTIME_MAX; u32 ctrl; int i; From 6785011f8b16abf40b1e9d8b2e332232b3e68822 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 7 Oct 2026 18:42:49 -0400 Subject: [PATCH 581/590] ipv4: validate checksum_start before completing checksum If a packet with bad checksum metadata gets into the ipv4 stack, skb_checksum_help can corrupt the network header and cause a bunch of mischief. This was discovered and reported by Paulos, and has been reporoduced by others independently since. We really shouldn't allow such packets in, but as a defence in depth measure, let's also check before we complete the checksum. A more complete validation at input is forthcoming, but needs more work. Cc: stable@vger.kernel.org Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Fixes: bfd5f4a3d605 ("packet: Add GSO/csum offload support.") Reported-by: Paulos Yibelo Closes: https://lore.kernel.org/netdev/20260922030310.8684-2-habte.yibelo@gmail.com/ Signed-off-by: Michael S. Tsirkin Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/0a012b4923e189c4c593ef4f471e5ff0edbe9030.1791412497.git.mst@redhat.com Signed-off-by: Jakub Kicinski --- include/net/ip.h | 7 +++++++ net/bridge/netfilter/nf_conntrack_bridge.c | 10 +++++++--- net/ipv4/ip_output.c | 10 +++++++--- net/netfilter/nfnetlink_queue.c | 7 +++++++ net/netfilter/xt_CHECKSUM.c | 6 +++++- net/xfrm/xfrm_output.c | 16 +++++++++++----- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/net/ip.h b/include/net/ip.h index 6f602df72ee6..d07c2c573024 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -74,6 +74,13 @@ static inline unsigned int ip_hdrlen(const struct sk_buff *skb) return ip_hdr(skb)->ihl * 4; } +static inline int ip_check_csum_start(const struct sk_buff *skb) +{ + if (unlikely(skb->csum_start < skb->network_header + ip_hdrlen(skb))) + return -EINVAL; + return 0; +} + struct ipcm_cookie { struct sockcm_cookie sockc; __be32 addr; diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c index 7ecb8a26bfa3..b5444335b86f 100644 --- a/net/bridge/netfilter/nf_conntrack_bridge.c +++ b/net/bridge/netfilter/nf_conntrack_bridge.c @@ -39,9 +39,13 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto blackhole; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto blackhole; + } iph = ip_hdr(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index a24cc8ee11d3..eaa6fabda347 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -771,9 +771,13 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, int err = 0; /* for offloaded checksums cleanup checksum before fragmentation */ - if (skb->ip_summed == CHECKSUM_PARTIAL && - (err = skb_checksum_help(skb))) - goto fail; + if (skb->ip_summed == CHECKSUM_PARTIAL) { + err = ip_check_csum_start(skb); + if (!err) + err = skb_checksum_help(skb); + if (err) + goto fail; + } /* * Point into the IP datagram header. diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index a3bc00280051..477fc632657f 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -674,6 +675,12 @@ static int nfqnl_put_bridge(struct nf_queue_entry *entry, struct sk_buff *skb) static int nf_queue_checksum_help(struct sk_buff *entskb) { + if (entskb->protocol == htons(ETH_P_IP)) { + if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) || + ip_check_csum_start(entskb)) + return -EINVAL; + } + if (skb_csum_is_sctp(entskb)) return skb_crc32c_csum_help(entskb); diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c index 9d99f5a3d176..1fb0f8118404 100644 --- a/net/netfilter/xt_CHECKSUM.c +++ b/net/netfilter/xt_CHECKSUM.c @@ -15,6 +15,7 @@ #include #include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael S. Tsirkin "); @@ -25,8 +26,11 @@ MODULE_ALIAS("ip6t_CHECKSUM"); static unsigned int checksum_tg(struct sk_buff *skb, const struct xt_action_param *par) { - if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) + if (skb->ip_summed == CHECKSUM_PARTIAL && !skb_is_gso(skb)) { + if (xt_family(par) == NFPROTO_IPV4 && ip_check_csum_start(skb)) + return NF_DROP; skb_checksum_help(skb); + } return XT_CONTINUE; } diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c index e305ba32e356..f1f612cd7b43 100644 --- a/net/xfrm/xfrm_output.c +++ b/net/xfrm/xfrm_output.c @@ -823,16 +823,22 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb) } if (skb->ip_summed == CHECKSUM_PARTIAL) { - err = skb_checksum_help(skb); - if (err) { - XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); - kfree_skb(skb); - return err; + if (skb->protocol == htons(ETH_P_IP)) { + err = ip_check_csum_start(skb); + if (err) + goto error; } + err = skb_checksum_help(skb); + if (err) + goto error; } out: return xfrm_output2(net, sk, skb); +error: + XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR); + kfree_skb(skb); + return err; } EXPORT_SYMBOL_GPL(xfrm_output); From e02a5b6588161673ef75a4bc5cdfb62bd493374d Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:22 +0900 Subject: [PATCH 582/590] ipv4: do not warn on route notification size race Commit 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") added asynchronous route notifications for hardware flag changes. fib_alias_hw_flags_set() sizes the skb with fib_nlmsg_size() and later fills it with fib_dump_info() while holding only RCU. With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. fib_dump_info() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 680aea08e78c ("net: ipv4: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261004082043.GB92032@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/b7e203fc5867651cb67ef84aa03e082f0f76008e.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv4/fib_trie.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index acb1e4385914..248514dce0cd 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -1079,8 +1079,6 @@ void fib_alias_hw_flags_set(struct net *net, const struct fib_rt_info *fri) err = fib_dump_info(skb, 0, 0, RTM_NEWROUTE, fri, 0); if (err < 0) { - /* -EMSGSIZE implies BUG in fib_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From c97426ec649d99596d8969e1a052c16b14b7b485 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 8 Oct 2026 14:29:23 +0900 Subject: [PATCH 583/590] ipv6: do not warn on route notification size race fib6_info_hw_flags_set() sizes the skb with rt6_nlmsg_size() and later fills it with rt6_fill_node(). With nexthop compatibility mode enabled, a concurrent replacement can grow the group between these independent snapshots. rt6_fill_node() can then legitimately return -EMSGSIZE, so the warning does not prove a sizing bug. Remove the warning. The existing error path still frees the skb and reports the error to listeners. Fixes: 907eea486888 ("net: ipv6: Emit notification when fib hardware flags are changed") Reported-by: Ido Schimmel Link: https://lore.kernel.org/netdev/20261007114003.GA1011260@shredder/ Suggested-by: Ido Schimmel Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/4012604727e5ba80b69ccd4d08796511397ddaef.1791423190.git.4ncienth@gmail.com Signed-off-by: Jakub Kicinski --- net/ipv6/route.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 153ce16628c1..a5e955e56e79 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6509,8 +6509,6 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i, err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0, 0, 0, RT_DEL_REASON_UNSPEC); if (err < 0) { - /* -EMSGSIZE implies BUG in rt6_nlmsg_size() */ - WARN_ON(err == -EMSGSIZE); kfree_skb(skb); goto errout; } From 64a6b36b0e1855288080bc5f89769f27d6dc03c8 Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Thu, 8 Oct 2026 16:44:44 +0800 Subject: [PATCH 584/590] net/smc: protect clcsock lifetime in smc_getname smc_getname() dereferences smc->clcsock without holding clcsock_release_lock. Link-group termination can release the CLC socket through smc_close_active_abort() while the SMC socket is still open, for example after shutdown(SHUT_WR). A getsockname() caller can load smc->clcsock, then the termination worker can clear the pointer and call sock_release() before the caller accesses clcsock->ops or invokes getname(). This causes a use-after-free; if the worker clears the pointer before the load, it causes a NULL dereference. The syscall's file reference keeps the SMC socket alive but does not prevent asynchronous release of its CLC socket. KASAN reported the following with test-only timing instrumentation: BUG: KASAN: slab-use-after-free in smc_getname+0x19e/0x1b0 Read of size 8 at addr ffff888109abb4e0 by task poc/103 Call Trace: smc_getname+0x19e/0x1b0 do_getsockname+0xe5/0x170 __sys_getsockname+0x8c/0x100 Allocated by task 95: sock_alloc_inode+0x1e/0x280 sock_alloc+0x3d/0x240 __sock_create+0x7e/0x430 smc_create+0x121/0x240 Freed by task 0: kmem_cache_free+0xcc/0x340 rcu_core+0x50a/0x1850 Last potentially related work creation: evict+0x446/0x6c0 smc_clcsock_release+0xa8/0xd0 smc_close_active_abort+0x26a/0x3a0 __smc_lgr_terminate.part.0+0x137/0x2e0 Hold clcsock_release_lock across the pointer check and the getname() callback to serialize with smc_clcsock_release(). Return -EBADF if the CLC socket has already been released, preserving the existing peer state check and the callback's return value otherwise. Fixes: b03faa1fafc8 ("net/smc: postpone release of clcsock") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Reviewed-by: Mahanta Jambigi Reviewed-by: Dust Li Link: https://patch.msgid.link/20261008084444.787449-1-nicoyip.dev@gmail.com Signed-off-by: Jakub Kicinski --- net/smc/af_smc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index e9f93b3ab435..8b3ee70f8433 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -2784,6 +2784,7 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, int peer) { struct smc_sock *smc; + int rc = -EBADF; if (peer && (sock->sk->sk_state != SMC_ACTIVE) && (sock->sk->sk_state != SMC_APPCLOSEWAIT1)) @@ -2791,7 +2792,11 @@ int smc_getname(struct socket *sock, struct sockaddr *addr, smc = smc_sk(sock->sk); - return smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_lock(&smc->clcsock_release_lock); + if (smc->clcsock) + rc = smc->clcsock->ops->getname(smc->clcsock, addr, peer); + mutex_unlock(&smc->clcsock_release_lock); + return rc; } int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) From 3c6a4b11330fe424557538a98fca9f0a4c5bf313 Mon Sep 17 00:00:00 2001 From: Fernando Fernandez Mancera Date: Thu, 8 Oct 2026 12:41:35 +0200 Subject: [PATCH 585/590] net: openvswitch: validate transport header presence in set_ipv6_addr When executing IPv6 address rewrite actions on IPv6 fragments, set_ipv6_addr() calls update_ipv6_checksum(). If parse_ipv6hdr() processes a non-first IPv6 fragment, it sets key->ip.proto to NEXTHDR_FRAGMENT and returns early without calling skb_set_transport_header(). update_ipv6_checksum() unconditionally evaluates skb_transport_offset() on entry before checking l4_proto. Because skb->transport_header is uninitialized, this triggers a warning under CONFIG_DEBUG_NET=y although it is completely harmless. Fix this by returning early in update_ipv6_checksum() if l4_proto is NEXTHDR_FRAGMENT. This avoids reading the uninitialized transport offset for fragments while preserving the debug warning for any other protocol where the transport header is unexpectedly missing. See the syzbot trace: !skb_transport_header_was_set(skb) WARNING: ./include/linux/skbuff.h:3075 at skb_transport_header include/linux/skbuff.h:3075 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at skb_transport_offset include/linux/skbuff.h:3250 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at update_ipv6_checksum net/openvswitch/actions.c:361 [inline], CPU#1: syz-executor463/5635 WARNING: ./include/linux/skbuff.h:3075 at set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399, CPU#1: syz-executor463/5635 [...] RIP: 0010:skb_transport_header include/linux/skbuff.h:3075 [inline] RIP: 0010:skb_transport_offset include/linux/skbuff.h:3250 [inline] RIP: 0010:update_ipv6_checksum net/openvswitch/actions.c:361 [inline] RIP: 0010:set_ipv6_addr+0x462/0x660 net/openvswitch/actions.c:399 [...] Call Trace: set_ipv6 net/openvswitch/actions.c:531 [inline] do_execute_actions+0x557e/0x8600 net/openvswitch/actions.c:1366 ovs_execute_actions+0xde/0x520 net/openvswitch/actions.c:1592 ovs_packet_cmd_execute+0xb4f/0xf10 net/openvswitch/datapath.c:705 genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114 genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209 netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2572 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218 netlink_unicast+0x7bd/0x940 net/netlink/af_netlink.c:1361 netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1916 sock_sendmsg_nosec+0x14e/0x190 net/socket.c:800 Reported-by: syzbot+4cc63fcfb3845e149969@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4cc63fcfb3845e149969 Fixes: 3fdbd1ce11e5 ("openvswitch: add ipv6 'set' action") Signed-off-by: Fernando Fernandez Mancera Reviewed-by: Ilya Maximets Link: https://patch.msgid.link/20261008104135.4722-1-fmancera@suse.de Signed-off-by: Jakub Kicinski --- net/openvswitch/actions.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index dc5ff859f114..27152bca8a8c 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -358,7 +358,12 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh, static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto, __be32 addr[4], const __be32 new_addr[4]) { - int transport_len = skb->len - skb_transport_offset(skb); + int transport_len; + + if (l4_proto == NEXTHDR_FRAGMENT) + return; + + transport_len = skb->len - skb_transport_offset(skb); if (l4_proto == NEXTHDR_TCP) { if (likely(transport_len >= sizeof(struct tcphdr))) From 65ab9de4bdd4c05d8277cfc20ae0b9f5aff60897 Mon Sep 17 00:00:00 2001 From: Ramses de Norre Date: Thu, 8 Oct 2026 14:59:15 +0200 Subject: [PATCH 586/590] wireguard: queueing: preserve tstamp_type when encapsulating packet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sending traffic through a wireguard tunnel on a host using the fq qdisc fills the log with: fq: likely mono tstamp with tstamp_type 0 An skb carries a timestamp in skb->tstamp and, separately, a skb->tstamp_type field recording which clock that timestamp came from. The two have to agree. When wireguard encapsulates a packet it calls wg_reset_packet(), which clears the fields that must not leak from the inner packet into the tunnel packet. It does so in two steps: skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); skb_scrub_packet() deliberately keeps skb->tstamp when it holds a monotonic timestamp: that value is the time the packet is scheduled to be sent, and the qdisc still needs it. The memset then zeroes skb->tstamp_type, because that field sits inside the headers group while skb->tstamp does not. The packet therefore leaves wireguard carrying a monotonic timestamp labelled as a realtime one. Nothing noticed until commit c4f796c4f16b ("net_sched: sch_fq: convert skb->tstamp if not monotonic"): fq used to assume every timestamp was monotonic. It now consults tstamp_type, spots the mismatch, warns, and falls back to treating the value as monotonic. Pacing still ends up correct, so the log spam is the actual problem. Save tstamp_type before the memset and restore it when encapsulating, next to the hash fields that are already carried over this way. When decapsulating it stays zeroed, which is right: an incoming packet's timestamp is a realtime receive timestamp. Fixes: d98d58a00261 ("net: Set skb->mono_delivery_time and clear it after sch_handle_ingress()") Signed-off-by: Ramses de Norre Reviewed-by: Toke Høiland-Jørgensen Reviewed-by: Eric Dumazet Cc: stable@vger.kernel.org Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-3-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/queueing.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireguard/queueing.h b/drivers/net/wireguard/queueing.h index 79b6d70de236..663b19b78395 100644 --- a/drivers/net/wireguard/queueing.h +++ b/drivers/net/wireguard/queueing.h @@ -78,12 +78,14 @@ static inline void wg_reset_packet(struct sk_buff *skb, bool encapsulating) u8 l4_hash = skb->l4_hash; u8 sw_hash = skb->sw_hash; u32 hash = skb->hash; + u8 tstamp_type = skb->tstamp_type; skb_scrub_packet(skb, true); memset(&skb->headers, 0, sizeof(skb->headers)); if (encapsulating) { skb->l4_hash = l4_hash; skb->sw_hash = sw_hash; skb->hash = hash; + skb->tstamp_type = tstamp_type; } skb->queue_mapping = 0; skb->nohdr = 0; From d0305f8d90029556260c6824b61c493c424eba9e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Thu, 8 Oct 2026 14:59:16 +0200 Subject: [PATCH 587/590] wireguard: noise: reject response consumption after intermediate initiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two threads begin processing the identical response message, received twice. The first thread, A, runs. While it's running, the second one, B, gets partway through, and during that slow calculation, or even while blocking on down_write(), A completes and then also a handshake initiation that's already been queued up runs in thread C, which itself takes that same down_write(). The handshake initiation creation succeeds, and sets the state back to waiting-for-response, and calls up_write(), at which point thread B resumes, because either its finished its calculations or was finally allowed to acquire down_write(). Thread B then copies the state back to the peer, and begins a new session, using that state, which is the same session as the one made in thread A. Thread A Thread B Thread C down_read() sA = handshake->state memcpy(cA, handshake->crypto) up_read() if (sA != 1) goto fail slow_crypto(cA) down_read() sB = handshake->state memcpy(cB, handshake->crypto) up_read() if (sB != 1) goto fail slow_crypto(cB) down_write() if (sA != handshake->state) goto fail memcpy(handshake->crypto, cA) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() down_write() slow_crypto(handshake->crypto) handshake->state = 1 up_write() down_write() if (sB != handshake->state) goto fail memcpy(handshake->crypto, cB) handshake->state = 2 up_write() down_write() if (handshake->state != 2) goto fail derive_session(handshake->crypto) up_write() This seems basically impossible to hit in a meaningful way in practice, but ensure that it absolutely cannot happen by comparing the ephemeral private key that's on the stack with the latest one that the peer's handshake state has. Cc: stable@vger.kernel.org Fixes: e7096c131e51 ("net: WireGuard secure network tunnel") Reported-by: Jérémy Jean Signed-off-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261008130124.724119-4-Jason@zx2c4.com Signed-off-by: Jakub Kicinski --- drivers/net/wireguard/noise.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireguard/noise.c b/drivers/net/wireguard/noise.c index 9c0a09bf6c95..ec2513d6dfab 100644 --- a/drivers/net/wireguard/noise.c +++ b/drivers/net/wireguard/noise.c @@ -784,10 +784,11 @@ wg_noise_handshake_consume_response(struct message_handshake_response *src, /* Success! Copy everything to peer */ down_write(&handshake->lock); - /* It's important to check that the state is still the same, while we - * have an exclusive lock. + /* Check that the state is the same and that this is still the + * initiation we started with, while we have an exclusive lock. */ - if (handshake->state != state) { + if (handshake->state != state || + crypto_memneq(handshake->ephemeral_private, ephemeral_private, NOISE_PUBLIC_KEY_LEN)) { up_write(&handshake->lock); goto fail; } From 7c24a4e87e219072e5e106f6791146cbf3b056b3 Mon Sep 17 00:00:00 2001 From: Ilia Gavrilov Date: Thu, 8 Oct 2026 14:41:17 +0000 Subject: [PATCH 588/590] vsock: Fix memory leak in vmci_transport_recv_dgram_cb() During the closure of a datagram socket, the vmci_transport_recv_dgram_cb() function may be called, which will add the packets to the socket's backlog; then, after the receive queue is cleared, the __release_sock() function will move the packet back from the socket's backlog to the receive queue, which will lead to a memory leak. sock_close __sock_release __vsock_release // take ownership by user-space lock_sock_nested sock_set_flag(sk, SOCK_DEAD) vmci_transport_release vmci_dispatch_dgs vmci_datagram_invoke_guest_handler vmci_transport_recv_dgram_cb sk_receive_skb if (!sock_owned_by_user(sk)) [...] (!) else if sk_add_backlog() [...] (!) skb_queue_purge(&sk->sk_receive_queue) release_sock __release_sock // move all packets from the backlog // to the receive queue (!) sk_backlog_rcv vsock_queue_rcv_skb [...] __sock_queue_rcv_skb if (!sock_flag(sk, SOCK_DEAD)) // Since the socket is "dead", the function will not // be called sk->sk_data_ready(sk) sock_release_ownership(sk); sock_put Add a receive queue cleanup in the socket destructor to fix this. syzkaller report: unreferenced object 0xffff8880117bfb80 (size 240): comm "irq/56-vmw_vmci", pid 205, jiffies 4295064448 (age 73.992s) hex dump (first 32 bytes): 78 dc 26 1e 80 88 ff ff 78 dc 26 1e 80 88 ff ff x.&.....x.&..... 00 00 00 00 00 00 00 00 c0 da 26 1e 80 88 ff ff ..........&..... backtrace: [] __alloc_skb+0x287/0x330 net/core/skbuff.c:505 [] vmci_transport_recv_dgram_cb+0xbd/0x210 [vmw_vsock_vmci_transport] [] vmci_datagram_invoke_guest_handler+0x366/0x450 [vmw_vmci] [] vmci_dispatch_dgs+0x235/0x490 [vmw_vmci] [] vmci_interrupt+0x55/0x290 [vmw_vmci] [] irq_thread_fn+0x8b/0x1a0 kernel/irq/manage.c:1205 [] irq_thread+0x28e/0x530 kernel/irq/manage.c:1314 [] kthread+0x2e6/0x3a0 kernel/kthread.c:376 [] ret_from_fork+0x22/0x30 arch/x86/entry/entry_64.S:295 Found by InfoTeCS on behalf of Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: d021c344051a ("VSOCK: Introduce VM Sockets") Reviewed-by: Stefano Garzarella Signed-off-by: Ilia Gavrilov Link: https://patch.msgid.link/20261008144046.90853-1-Ilia.Gavrilov@infotecs.ru Signed-off-by: Jakub Kicinski --- net/vmw_vsock/af_vsock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 9b71479a2b29..533c733618df 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1048,6 +1048,8 @@ static void vsock_sk_destruct(struct sock *sk) { struct vsock_sock *vsk = vsock_sk(sk); + /* Socket buffers may remain in the queue in VMCI datagram mode */ + __skb_queue_purge(&sk->sk_receive_queue); /* Flush MSG_ZEROCOPY leftovers. */ __skb_queue_purge(&sk->sk_error_queue); From 6b48ed85ae0ca37c7403ef5d512be75975844f2f Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:32 +0200 Subject: [PATCH 589/590] net: macb: check TX ring before modifying skb macb_pad_and_fcs() replaces or extends the skb before the ring space check. On NETDEV_TX_BUSY the stack requeues an skb that is already freed or grown. Check the ring first, using the padded length for the descriptor count. Nonlinear skbs always take the copy path so the count can assume a linear skb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-1-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 76 ++++++++++++++---------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 8e5c034dc3a4..6082e63009a5 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2421,8 +2421,15 @@ static inline int macb_clear_csum(struct sk_buff *skb) return 0; } +static bool macb_needs_sw_fcs(struct sk_buff *skb, struct net_device *netdev) +{ + return netdev->features & NETIF_F_HW_CSUM && + skb->ip_summed != CHECKSUM_PARTIAL && + !skb_shinfo(skb)->gso_size && !ptp_one_step_sync(skb); +} + /* Returns a negative errno, or the FCS bytes appended (0 or ETH_FCS_LEN). */ -static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) +static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) { bool cloned = skb_cloned(*skb) || skb_header_cloned(*skb) || skb_is_nonlinear(*skb); @@ -2431,18 +2438,15 @@ static int macb_pad_and_fcs(struct sk_buff **skb, struct net_device *netdev) struct sk_buff *nskb; u32 fcs; - if (!(netdev->features & NETIF_F_HW_CSUM) || - !((*skb)->ip_summed != CHECKSUM_PARTIAL) || - skb_shinfo(*skb)->gso_size || ptp_one_step_sync(*skb)) + if (!add_fcs) return 0; if (padlen <= 0) { - /* FCS could be appeded to tailroom. */ - if (tailroom >= ETH_FCS_LEN) + /* FCS could be appended to tailroom. */ + if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) goto add_fcs; - /* No room for FCS, need to reallocate skb. */ - else - padlen = ETH_FCS_LEN; + /* Reallocate with room for the FCS. */ + padlen = ETH_FCS_LEN; } else { /* Add room for FCS. */ padlen += ETH_FCS_LEN; @@ -2481,27 +2485,15 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, unsigned int desc_cnt, nr_frags, frag_size, f; struct macb_queue *queue = &bp->queues[q]; netdev_tx_t ret = NETDEV_TX_OK; - unsigned int hdrlen; + unsigned int hdrlen, tx_len; + bool add_fcs, is_lso; unsigned long flags; int fcs_len; - bool is_lso; - - if (macb_clear_csum(skb)) { - dev_kfree_skb_any(skb); - return ret; - } - - fcs_len = macb_pad_and_fcs(&skb, netdev); - if (fcs_len < 0) { - dev_kfree_skb_any(skb); - return ret; - } - - if (macb_dma_ptp(bp) && - (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + add_fcs = macb_needs_sw_fcs(skb, netdev); is_lso = (skb_shinfo(skb)->gso_size != 0); + tx_len = add_fcs ? max_t(unsigned int, skb->len, ETH_ZLEN) + + ETH_FCS_LEN : skb->len; if (is_lso) { /* length of headers */ @@ -2515,8 +2507,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, /* if this is required, would need to copy to single buffer */ return NETDEV_TX_BUSY; } - } else + } else if (add_fcs) { + hdrlen = umin(tx_len, bp->max_tx_length); + } else { hdrlen = umin(skb_headlen(skb), bp->max_tx_length); + } #if defined(DEBUG) && defined(VERBOSE_DEBUG) netdev_vdbg(bp->netdev, @@ -2531,12 +2526,18 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, * socket buffer: skb fragments of jumbo frames may need to be * split into many buffer descriptors. */ - if (is_lso && (skb_headlen(skb) > hdrlen)) + if (add_fcs) { + /* macb_pad_and_fcs() linearizes the skb before adding the FCS. */ + desc_cnt = DIV_ROUND_UP(tx_len, bp->max_tx_length); + nr_frags = 0; + } else if (is_lso && (skb_headlen(skb) > hdrlen)) { /* extra header descriptor if also payload in first buffer */ desc_cnt = DIV_ROUND_UP((skb_headlen(skb) - hdrlen), bp->max_tx_length) + 1; - else + nr_frags = skb_shinfo(skb)->nr_frags; + } else { desc_cnt = DIV_ROUND_UP(skb_headlen(skb), bp->max_tx_length); - nr_frags = skb_shinfo(skb)->nr_frags; + nr_frags = skb_shinfo(skb)->nr_frags; + } for (f = 0; f < nr_frags; f++) { frag_size = skb_frag_size(&skb_shinfo(skb)->frags[f]); desc_cnt += DIV_ROUND_UP(frag_size, bp->max_tx_length); @@ -2554,6 +2555,21 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, goto unlock; } + if (macb_clear_csum(skb)) { + dev_kfree_skb_any(skb); + goto unlock; + } + + fcs_len = macb_pad_and_fcs(&skb, add_fcs); + if (fcs_len < 0) { + dev_kfree_skb_any(skb); + goto unlock; + } + + if (macb_dma_ptp(bp) && + (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + /* Map socket buffer for DMA transfer */ if (macb_tx_map(bp, queue, skb, hdrlen, fcs_len)) { dev_kfree_skb_any(skb); From 9151d6c42799105e109c8b54dbaad91ce0e17c47 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 6 Oct 2026 07:41:33 +0200 Subject: [PATCH 590/590] net: macb: copy shared skbs before appending the FCS macb_pad_and_fcs() appends the FCS in place when the skb has tailroom. A shared skb, as pktgen sends in clone_skb mode, grows by one FCS per transmit. BQL then completes more bytes than were queued and dql_completed() hits its BUG_ON. On a Raspberry Pi CM5 (RP1 GEM) pktgen with clone_skb 1000 burst 32 at 60 bytes kills the box within seconds. Copy shared skbs before appending the FCS. Clearing IFF_TX_SKB_SHARING would also fix it but makes pktgen refuse clone_skb on macb. Fixes: 653e92a9175e ("net: macb: add support for padding and fcs computation") Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261006-nb-macb-shared-skb-net-v1-2-a80641479041@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/cadence/macb_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index 6082e63009a5..261a7e87520a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2435,6 +2435,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) skb_is_nonlinear(*skb); int padlen = ETH_ZLEN - (*skb)->len; int tailroom = skb_tailroom(*skb); + bool shared = skb_shared(*skb); struct sk_buff *nskb; u32 fcs; @@ -2443,7 +2444,8 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) if (padlen <= 0) { /* FCS could be appended to tailroom. */ - if (!skb_is_nonlinear(*skb) && tailroom >= ETH_FCS_LEN) + if (!shared && !skb_is_nonlinear(*skb) && + tailroom >= ETH_FCS_LEN) goto add_fcs; /* Reallocate with room for the FCS. */ padlen = ETH_FCS_LEN; @@ -2452,7 +2454,7 @@ static int macb_pad_and_fcs(struct sk_buff **skb, bool add_fcs) padlen += ETH_FCS_LEN; } - if (cloned || tailroom < padlen) { + if (shared || cloned || tailroom < padlen) { nskb = skb_copy_expand(*skb, 0, padlen, GFP_ATOMIC); if (!nskb) return -ENOMEM;