From b560d414239232c6ed7205d3795d3f588034d69b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Fri, 10 Apr 2026 09:09:35 +0200 Subject: pinctrl: mediatek: moore: implement gpio_chip::get_direction() If the gpio_chip::get_direction() callback is not implemented by the GPIO controller driver, GPIOLIB emits a warning. Implement get_direction() for the GPIO part of pinctrl-moore. Fixes: 471e998c0e31 ("gpiolib: remove redundant callback check") Fixes: e623c4303ed1 ("gpiolib: sanitize the return value of gpio_chip::get_direction()") Reported-by: Frank Wunderlich Closes: https://lore.kernel.org/all/20260409132724.126258-1-linux@fw-web.de/ Signed-off-by: Bartosz Golaszewski Tested-By: Frank Wunderlich Signed-off-by: Linus Walleij --- drivers/pinctrl/mediatek/pinctrl-moore.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/pinctrl/mediatek/pinctrl-moore.c b/drivers/pinctrl/mediatek/pinctrl-moore.c index 70f608347a5f..071ba849e532 100644 --- a/drivers/pinctrl/mediatek/pinctrl-moore.c +++ b/drivers/pinctrl/mediatek/pinctrl-moore.c @@ -520,6 +520,23 @@ static int mtk_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, return pinctrl_gpio_direction_output(chip, gpio); } +static int mtk_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct mtk_pinctrl *hw = gpiochip_get_data(chip); + const struct mtk_pin_desc *desc; + int ret, dir; + + desc = (const struct mtk_pin_desc *)&hw->soc->pins[offset]; + if (!desc->name) + return -ENOTSUPP; + + ret = mtk_hw_get_value(hw, desc, PINCTRL_PIN_REG_DIR, &dir); + if (ret) + return ret; + + return dir ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + static int mtk_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) { struct mtk_pinctrl *hw = gpiochip_get_data(chip); @@ -566,6 +583,7 @@ static int mtk_build_gpiochip(struct mtk_pinctrl *hw) chip->parent = hw->dev; chip->request = gpiochip_generic_request; chip->free = gpiochip_generic_free; + chip->get_direction = mtk_gpio_get_direction; chip->direction_input = pinctrl_gpio_direction_input; chip->direction_output = mtk_gpio_direction_output; chip->get = mtk_gpio_get; -- cgit v1.2.3 From b51d33ea8a164bb5f0eec8ad817fa9730ac2b577 Mon Sep 17 00:00:00 2001 From: Til Kaiser Date: Mon, 13 Apr 2026 15:52:34 +0200 Subject: pinctrl: qcom: ipq4019: mark gpio as a GPIO pin function The qcom pinctrl core supports marking functions that represent GPIO mode via PINCTRL_GPIO_PINFUNCTION(), so that strict pinmuxing does not reject GPIO requests for pins that are muxed to the GPIO function. ipq4019 still describes its gpio function with QCA_PIN_FUNCTION(gpio), so it is not treated as a GPIO pin function. As a result, GPIO consumers can still conflict with pinctrl states that select the "gpio" function. Add a QCA_GPIO_PIN_FUNCTION() helper and use it for the ipq4019 gpio function, matching how the msm-based qcom drivers handle this. This allows ipq4019 to keep the GPIO-related pin configuration in DTS without tripping over strict pinmux ownership checks. Fixes: cc85cb96e2e4 ("pinctrl: qcom: make the pinmuxing strict") Signed-off-by: Til Kaiser Reviewed-by: Dmitry Baryshkov Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-ipq4019.c | 2 +- drivers/pinctrl/qcom/pinctrl-msm.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pinctrl/qcom/pinctrl-ipq4019.c b/drivers/pinctrl/qcom/pinctrl-ipq4019.c index c5f0decc3eb3..05fdd73b951e 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq4019.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq4019.c @@ -479,7 +479,7 @@ static const struct pinfunction ipq4019_functions[] = { QCA_PIN_FUNCTION(blsp_uart0), QCA_PIN_FUNCTION(blsp_uart1), QCA_PIN_FUNCTION(chip_rst), - QCA_PIN_FUNCTION(gpio), + QCA_GPIO_PIN_FUNCTION(gpio), QCA_PIN_FUNCTION(i2s_rx), QCA_PIN_FUNCTION(i2s_spdif_in), QCA_PIN_FUNCTION(i2s_spdif_out), diff --git a/drivers/pinctrl/qcom/pinctrl-msm.h b/drivers/pinctrl/qcom/pinctrl-msm.h index a4af279f748a..4fbff61de6bb 100644 --- a/drivers/pinctrl/qcom/pinctrl-msm.h +++ b/drivers/pinctrl/qcom/pinctrl-msm.h @@ -39,6 +39,11 @@ struct pinctrl_pin_desc; fname##_groups, \ ARRAY_SIZE(fname##_groups)) +#define QCA_GPIO_PIN_FUNCTION(fname) \ + [qca_mux_##fname] = PINCTRL_GPIO_PINFUNCTION(#fname, \ + fname##_groups, \ + ARRAY_SIZE(fname##_groups)) + /** * struct msm_pingroup - Qualcomm pingroup definition * @grp: Generic data of the pin group (name and pins) -- cgit v1.2.3 From 6dba9b7268cc50166bce47608670192fd874e363 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 28 Mar 2026 09:05:45 +0000 Subject: pinctrl: renesas: rzg2l: Fix incorrect PUPD register offset for high pins during suspend/resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When saving/restoring pull-up/down register state during suspend/resume, the second PUPD register access was incorrectly using the same base offset as the first, effectively reading/writing the same register twice instead of the adjacent one. Add the correct + 4 byte offset to the second RZG2L_PCTRL_REG_ACCESS32 call so that pupd[1][port] is properly saved and restored from the next 32-bit register in the PUPD register pair, covering pins 4–7 of ports with 4 or more pins. Fixes: b2bd65fbb617 ("pinctrl: renesas: rzg2l: Add suspend/resume support for pull up/down") Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260328090548.84124-1-biju.das.jz@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/pinctrl/renesas/pinctrl-rzg2l.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pinctrl/renesas/pinctrl-rzg2l.c b/drivers/pinctrl/renesas/pinctrl-rzg2l.c index 561e6018fd89..68b94c748f53 100644 --- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c +++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c @@ -3049,7 +3049,7 @@ static void rzg2l_pinctrl_pm_setup_regs(struct rzg2l_pinctrl *pctrl, bool suspen RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + PUPD(off), cache->pupd[0][port]); if (pincnt >= 4) { - RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + PUPD(off), + RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + PUPD(off) + 4, cache->pupd[1][port]); } } -- cgit v1.2.3 From c88ab9407986836820848128ce1f90f2fa49da95 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 13 Apr 2026 19:24:51 +0100 Subject: pinctrl: renesas: rzg2l: Fix SMT register cache handling Store SMT register cache per bank instead of using a single array. On RZ/V2H(P), RZ/V2N, and RZ/G3E, the SMT register is split across two 32-bit registers: bits 0/8/16/24 control pins 0-3, while pins 4-7 are controlled by the corresponding bits in the next register. The previous implementation cached only a single SMT register, leading to incomplete save/restore of SMT state. Convert cache->smt to a per-bank array and allocate storage for both halves. Update suspend/resume handling to save and restore both SMT registers when present. Fixes: 837afa592c623 ("pinctrl: renesas: rzg2l: Add suspend/resume support for Schmitt control registers") Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260413182456.811543-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Geert Uytterhoeven --- drivers/pinctrl/renesas/pinctrl-rzg2l.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/renesas/pinctrl-rzg2l.c b/drivers/pinctrl/renesas/pinctrl-rzg2l.c index 68b94c748f53..1c6b115e65d8 100644 --- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c +++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c @@ -335,7 +335,7 @@ struct rzg2l_pinctrl_reg_cache { u32 *iolh[2]; u32 *ien[2]; u32 *pupd[2]; - u32 *smt; + u32 *smt[2]; u8 sd_ch[2]; u8 eth_poc[2]; u8 oen; @@ -2737,10 +2737,6 @@ static int rzg2l_pinctrl_reg_cache_alloc(struct rzg2l_pinctrl *pctrl) if (!cache->pfc) return -ENOMEM; - cache->smt = devm_kcalloc(pctrl->dev, nports, sizeof(*cache->smt), GFP_KERNEL); - if (!cache->smt) - return -ENOMEM; - for (u8 i = 0; i < 2; i++) { u32 n_dedicated_pins = pctrl->data->n_dedicated_pins; @@ -2759,6 +2755,11 @@ static int rzg2l_pinctrl_reg_cache_alloc(struct rzg2l_pinctrl *pctrl) if (!cache->pupd[i]) return -ENOMEM; + cache->smt[i] = devm_kcalloc(pctrl->dev, nports, sizeof(*cache->smt[i]), + GFP_KERNEL); + if (!cache->smt[i]) + return -ENOMEM; + /* Allocate dedicated cache. */ dedicated_cache->iolh[i] = devm_kcalloc(pctrl->dev, n_dedicated_pins, sizeof(*dedicated_cache->iolh[i]), @@ -3066,8 +3067,14 @@ static void rzg2l_pinctrl_pm_setup_regs(struct rzg2l_pinctrl *pctrl, bool suspen } } - if (has_smt) - RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + SMT(off), cache->smt[port]); + if (has_smt) { + RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + SMT(off), + cache->smt[0][port]); + if (pincnt >= 4) { + RZG2L_PCTRL_REG_ACCESS32(suspend, pctrl->base + SMT(off) + 4, + cache->smt[1][port]); + } + } } } -- cgit v1.2.3 From d743c1ba6c66a7dcb2bfd93fd36b7185e8a4766b Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Thu, 23 Apr 2026 04:51:45 +0000 Subject: pinctrl: qcom: eliza: Fix QDSS trace clock/control pingroup names Fix a few typos for these in their respective pingroups, the groups already exist they just weren't referenced. Signed-off-by: Alexander Koskovich Fixes: 6f26989e15fb ("pinctrl: qcom: Add Eliza pinctrl driver") Reviewed-by: Konrad Dybcio Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-eliza.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/qcom/pinctrl-eliza.c b/drivers/pinctrl/qcom/pinctrl-eliza.c index c1f756cbcdeb..dd8c04046b18 100644 --- a/drivers/pinctrl/qcom/pinctrl-eliza.c +++ b/drivers/pinctrl/qcom/pinctrl-eliza.c @@ -1340,7 +1340,7 @@ static const struct msm_pingroup eliza_groups[] = { [51] = PINGROUP(51, _, _, _, _, _, _, _, _, _, _, _), [52] = PINGROUP(52, qup1_se2, pcie1_clk_req_n, qup1_se2, ddr_bist_complete, qdss_gpio_tracedata, _, vsense_trigger_mirnat, _, _, _, _), [53] = PINGROUP(53, qup1_se2, qup1_se2, gcc_gp1, ddr_bist_stop, _, qdss_gpio_tracedata, _, _, _, _, _), - [54] = PINGROUP(54, qup1_se2, qup1_se6, qdss_gpio_tracedata, gnss_adc1, atest_usb, ddr_pxi0, _, _, _, _, _), + [54] = PINGROUP(54, qup1_se2, qup1_se6, qdss_gpio_traceclk, gnss_adc1, atest_usb, ddr_pxi0, _, _, _, _, _), [55] = PINGROUP(55, qup1_se2, dp0_hot, qup1_se6, _, gnss_adc0, atest_usb, ddr_pxi0, _, _, _, _), [56] = PINGROUP(56, usb0_hs, tsense_pwm1, tsense_pwm2, tsense_pwm3, tsense_pwm4, _, _, _, _, _, _), [57] = PINGROUP(57, sd_write_protect, _, _, _, _, _, _, _, _, _, _), @@ -1358,7 +1358,7 @@ static const struct msm_pingroup eliza_groups[] = { [69] = PINGROUP(69, cam_mclk, audio_ext_mclk0, resout_gpio, prng_rosc1, _, _, _, _, _, _, _), [70] = PINGROUP(70, cci_i2c_sda, tmess_prng2, _, phase_flag, atest_char, _, _, _, _, _, _), [71] = PINGROUP(71, cci_i2c_scl, tmess_prng3, _, phase_flag, atest_char, _, _, _, _, _, _), - [72] = PINGROUP(72, cci_i2c_sda, tmess_prng1, qdss_gpio_tracedata, atest_char, _, _, _, _, _, _, _), + [72] = PINGROUP(72, cci_i2c_sda, tmess_prng1, qdss_gpio_tracectl, atest_char, _, _, _, _, _, _, _), [73] = PINGROUP(73, cci_i2c_scl, tmess_prng0, qdss_cti, atest_char, _, _, _, _, _, _, _), [74] = PINGROUP(74, cci_i2c_sda, prng_rosc3, qdss_cti, atest_char, _, _, _, _, _, _, _), [75] = PINGROUP(75, cci_i2c_scl, _, phase_flag, _, _, _, _, _, _, _, _), @@ -1430,10 +1430,10 @@ static const struct msm_pingroup eliza_groups[] = { [141] = PINGROUP(141, _, _, _, _, _, _, _, _, _, _, egpio), [142] = PINGROUP(142, _, _, _, _, _, _, _, _, _, _, egpio), [143] = PINGROUP(143, _, _, _, _, _, _, _, _, _, _, egpio), - [144] = PINGROUP(144, _, qdss_gpio_tracedata, _, _, _, _, _, _, _, _, egpio), + [144] = PINGROUP(144, _, qdss_gpio_tracectl, _, _, _, _, _, _, _, _, egpio), [145] = PINGROUP(145, qdss_gpio_tracedata, _, _, _, _, _, _, _, _, _, egpio), [146] = PINGROUP(146, _, qdss_gpio_tracedata, _, _, _, _, _, _, _, _, egpio), - [147] = PINGROUP(147, ddr_bist_fail, _, qdss_gpio_tracedata, _, _, _, _, _, _, _, egpio), + [147] = PINGROUP(147, ddr_bist_fail, _, qdss_gpio_traceclk, _, _, _, _, _, _, _, egpio), [148] = PINGROUP(148, _, _, _, _, _, _, _, _, _, _, egpio), [149] = PINGROUP(149, _, _, _, _, _, _, _, _, _, _, egpio), [150] = PINGROUP(150, _, _, _, _, _, _, _, _, _, _, egpio), -- cgit v1.2.3 From e72ce029810390eb987a036fb2c8a5da9a23b685 Mon Sep 17 00:00:00 2001 From: Xianwei Zhao Date: Wed, 22 Apr 2026 11:44:13 +0000 Subject: pinctrl: meson: amlogic-a4: fix deadlock issue Accessing the pinconf-pins sysfs node may deadlock. pinconf_pins_show() holds pctldev->mutex, and the platform driver calls pinctrl_find_gpio_range_from_pin(), which tries to acquire the same mutex again, leading to a deadlock. Use pinctrl_find_gpio_range_from_pin_nolock() to fix this issue. Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") Signed-off-by: Xianwei Zhao Reviewed-by: Neil Armstrong Signed-off-by: Linus Walleij --- drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c index e2293a872dcb..35d27626a336 100644 --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c @@ -292,7 +292,7 @@ static int aml_calc_reg_and_bit(struct pinctrl_gpio_range *range, static int aml_pinconf_get_pull(struct aml_pinctrl *info, unsigned int pin) { struct pinctrl_gpio_range *range = - pinctrl_find_gpio_range_from_pin(info->pctl, pin); + pinctrl_find_gpio_range_from_pin_nolock(info->pctl, pin); struct aml_gpio_bank *bank = gpio_chip_to_bank(range->gc); unsigned int reg, bit, val; int ret, conf; @@ -326,7 +326,7 @@ static int aml_pinconf_get_drive_strength(struct aml_pinctrl *info, u16 *drive_strength_ua) { struct pinctrl_gpio_range *range = - pinctrl_find_gpio_range_from_pin(info->pctl, pin); + pinctrl_find_gpio_range_from_pin_nolock(info->pctl, pin); struct aml_gpio_bank *bank = gpio_chip_to_bank(range->gc); unsigned int reg, bit; unsigned int val; @@ -365,7 +365,7 @@ static int aml_pinconf_get_gpio_bit(struct aml_pinctrl *info, unsigned int reg_type) { struct pinctrl_gpio_range *range = - pinctrl_find_gpio_range_from_pin(info->pctl, pin); + pinctrl_find_gpio_range_from_pin_nolock(info->pctl, pin); struct aml_gpio_bank *bank = gpio_chip_to_bank(range->gc); unsigned int reg, bit, val; int ret; -- cgit v1.2.3 From 9d69033ad967b6e09b1e5b30d1a32c6c4876465d Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 23 Apr 2026 16:55:24 +0530 Subject: pinctrl: qcom: Fix GPIO to PDC wake irq map for qcs615 PDC interrupts 122-125 were meant for ibi_i3c wakeup but qcs615 do not support i3c. GPIOs 39,51,88 and 89 are also connected to different PDC pin to support non-ibi wakeup. Update the wakeirq map to reflect same. Fixes: b698f36a9d40 ("pinctrl: qcom: add the tlmm driver for QCS615 platform") Signed-off-by: Maulik Shah Signed-off-by: Navya Malempati Reviewed-by: Konrad Dybcio Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-qcs615.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/qcom/pinctrl-qcs615.c b/drivers/pinctrl/qcom/pinctrl-qcs615.c index 0ed4332d989e..f066b3a576f7 100644 --- a/drivers/pinctrl/qcom/pinctrl-qcs615.c +++ b/drivers/pinctrl/qcom/pinctrl-qcs615.c @@ -1040,11 +1040,11 @@ static const struct msm_pingroup qcs615_groups[] = { static const struct msm_gpio_wakeirq_map qcs615_pdc_map[] = { { 1, 45 }, { 3, 31 }, { 7, 55 }, { 9, 110 }, { 11, 34 }, { 13, 33 }, { 14, 35 }, { 17, 46 }, { 19, 48 }, { 21, 83 }, - { 22, 36 }, { 26, 38 }, { 35, 37 }, { 39, 125 }, { 41, 47 }, - { 47, 49 }, { 48, 51 }, { 50, 52 }, { 51, 123 }, { 55, 56 }, + { 22, 36 }, { 26, 38 }, { 35, 37 }, { 39, 118 }, { 41, 47 }, + { 47, 49 }, { 48, 51 }, { 50, 52 }, { 51, 116 }, { 55, 56 }, { 56, 57 }, { 57, 58 }, { 60, 60 }, { 71, 54 }, { 80, 73 }, { 81, 64 }, { 82, 50 }, { 83, 65 }, { 84, 92 }, { 85, 99 }, - { 86, 67 }, { 87, 84 }, { 88, 124 }, { 89, 122 }, { 90, 69 }, + { 86, 67 }, { 87, 84 }, { 88, 117 }, { 89, 115 }, { 90, 69 }, { 92, 88 }, { 93, 75 }, { 94, 91 }, { 95, 72 }, { 96, 82 }, { 97, 74 }, { 98, 95 }, { 99, 94 }, { 100, 100 }, { 101, 40 }, { 102, 93 }, { 103, 77 }, { 104, 78 }, { 105, 96 }, { 107, 97 }, -- cgit v1.2.3 From 0a5e695095c557d2380131b613dea4e8d90371be Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:25 +0100 Subject: firmware: arm_ffa: Check for NULL FF-A ID table while driver registration The bus match callback assumes that every FF-A driver provides an id_table and dereferences it unconditionally. Enforce that contract at registration time so a buggy client driver cannot crash the bus during match. Fixes: 92743071464f ("firmware: arm_ffa: Ensure drivers provide a probe function") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-1-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/bus.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/bus.c b/drivers/firmware/arm_ffa/bus.c index 9576862d89c4..601c3418e0d9 100644 --- a/drivers/firmware/arm_ffa/bus.c +++ b/drivers/firmware/arm_ffa/bus.c @@ -26,6 +26,8 @@ static int ffa_device_match(struct device *dev, const struct device_driver *drv) id_table = to_ffa_driver(drv)->id_table; ffa_dev = to_ffa_dev(dev); + if (!id_table) + return 0; while (!uuid_is_null(&id_table->uuid)) { /* @@ -123,7 +125,7 @@ int ffa_driver_register(struct ffa_driver *driver, struct module *owner, { int ret; - if (!driver->probe) + if (!driver->probe || !driver->id_table) return -EINVAL; driver->driver.bus = &ffa_bus_type; -- cgit v1.2.3 From 09527e2c534911619d7e098729711100290bc3e1 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:26 +0100 Subject: firmware: arm_ffa: Skip free_pages on RX buffer alloc failure If the RX buffer allocation fails in ffa_init(), the error path jumps to free_pages even though no buffer has been allocated yet. Route that case directly to free_drv_info so the cleanup path is only used after at least one RX/TX buffer allocation has succeeded. Fixes: 3bbfe9871005 ("firmware: arm_ffa: Add initial Arm FFA driver support") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-2-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index eb2782848283..e6a051b20cb7 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2067,7 +2067,7 @@ static int __init ffa_init(void) drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL); if (!drv_info->rx_buffer) { ret = -ENOMEM; - goto free_pages; + goto free_drv_info; } drv_info->tx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL); -- cgit v1.2.3 From 9b5597af8bc51c25342ab11896532644b181d302 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:27 +0100 Subject: firmware: arm_ffa: Avoid collapsing NPI work from different CPUs Notification pending interrupts are registered as per-CPU IRQs, but the driver queues all NPI handling through a single shared work_struct. That allows queue_work_on() calls from different CPUs to collapse onto a single pending work item even though the work function uses the CPU it runs on to fetch and handle per-CPU notifications. Move notif_pcpu_work into the per-CPU ffa_pcpu_irq state and initialize one work item per CPU. This keeps NPI handling independent per CPU and avoids losing notifications when multiple CPUs queue work concurrently. Link: https://patch.msgid.link/20260428-ffa_fixes-v2-3-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index e6a051b20cb7..4e66c7325a4e 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -87,6 +87,7 @@ static inline int ffa_to_linux_errno(int errno) struct ffa_pcpu_irq { struct ffa_drv_info *info; + struct work_struct notif_pcpu_work; }; struct ffa_drv_info { @@ -106,7 +107,6 @@ struct ffa_drv_info { unsigned int cpuhp_state; struct ffa_pcpu_irq __percpu *irq_pcpu; struct workqueue_struct *notif_pcpu_wq; - struct work_struct notif_pcpu_work; struct work_struct sched_recv_irq_work; struct xarray partition_info; DECLARE_HASHTABLE(notifier_hash, ilog2(FFA_MAX_NOTIFICATIONS)); @@ -1539,8 +1539,9 @@ ffa_self_notif_handle(u16 vcpu, bool is_per_vcpu, void *cb_data) static void notif_pcpu_irq_work_fn(struct work_struct *work) { - struct ffa_drv_info *info = container_of(work, struct ffa_drv_info, + struct ffa_pcpu_irq *pcpu = container_of(work, struct ffa_pcpu_irq, notif_pcpu_work); + struct ffa_drv_info *info = pcpu->info; ffa_self_notif_handle(smp_processor_id(), true, info); } @@ -1811,7 +1812,7 @@ static irqreturn_t notif_pend_irq_handler(int irq, void *irq_data) struct ffa_drv_info *info = pcpu->info; queue_work_on(smp_processor_id(), info->notif_pcpu_wq, - &info->notif_pcpu_work); + &pcpu->notif_pcpu_work); return IRQ_HANDLED; } @@ -1928,8 +1929,11 @@ static int ffa_init_pcpu_irq(void) if (!irq_pcpu) return -ENOMEM; - for_each_present_cpu(cpu) + for_each_present_cpu(cpu) { per_cpu_ptr(irq_pcpu, cpu)->info = drv_info; + INIT_WORK(&per_cpu_ptr(irq_pcpu, cpu)->notif_pcpu_work, + notif_pcpu_irq_work_fn); + } drv_info->irq_pcpu = irq_pcpu; @@ -1958,7 +1962,6 @@ static int ffa_init_pcpu_irq(void) } INIT_WORK(&drv_info->sched_recv_irq_work, ffa_sched_recv_irq_work_fn); - INIT_WORK(&drv_info->notif_pcpu_work, notif_pcpu_irq_work_fn); drv_info->notif_pcpu_wq = create_workqueue("ffa_pcpu_irq_notification"); if (!drv_info->notif_pcpu_wq) return -EINVAL; -- cgit v1.2.3 From 9985d5357ed93af0d1933969c247e966957730e1 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:28 +0100 Subject: firmware: arm_ffa: Fix per-vcpu self notifications handling in workqueue Per-vcpu notification handling already runs from a per-cpu work item on the target cpu. Routing that path back through smp_call_function_single() re-enters the call-function IPI path and executes the notification handler with interrupts disabled. That makes the framework path unsafe, since it takes a mutex, allocates memory with GFP_KERNEL, and invokes client callbacks. Handle per-vcpu self notifications directly from the existing per-cpu work item instead. This keeps the per-vcpu path in task context and avoids the extra IPI hop entirely. Fixes: 3a3e2b83e805 ("firmware: arm_ffa: Avoid queuing work when running on the worker queue") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-4-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 4e66c7325a4e..2241e851f7ae 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1543,7 +1543,7 @@ static void notif_pcpu_irq_work_fn(struct work_struct *work) notif_pcpu_work); struct ffa_drv_info *info = pcpu->info; - ffa_self_notif_handle(smp_processor_id(), true, info); + notif_get_and_handle(info); } static const struct ffa_info_ops ffa_drv_info_ops = { -- cgit v1.2.3 From 6d3daa9b8d313f42d52e75590310f26a29b61b44 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:29 +0100 Subject: firmware: arm_ffa: Unregister bus notifier on teardown for FF-A v1.0 For FF-A v1.0 the driver registers a bus notifier to backfill UUID matching, but the notifier was never unregistered on cleanup paths. Track the registration state and unregister it during teardown and early partition-setup failure. Fixes: 9dd15934f60d ("firmware: arm_ffa: Move the FF-A v1.0 NULL UUID workaround to bus notifier") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-5-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 2241e851f7ae..a122814eb6d7 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -101,6 +101,7 @@ struct ffa_drv_info { bool mem_ops_native; bool msg_direct_req2_supp; bool bitmap_created; + bool bus_notifier_registered; bool notif_enabled; unsigned int sched_recv_irq; unsigned int notif_pend_irq; @@ -1630,6 +1631,15 @@ static struct notifier_block ffa_bus_nb = { .notifier_call = ffa_bus_notifier, }; +static void ffa_bus_notifier_unregister(void) +{ + if (!drv_info->bus_notifier_registered) + return; + + bus_unregister_notifier(&ffa_bus_type, &ffa_bus_nb); + drv_info->bus_notifier_registered = false; +} + static int ffa_xa_add_partition_info(struct ffa_device *dev) { struct ffa_dev_part_info *info; @@ -1713,6 +1723,8 @@ static void ffa_partitions_cleanup(void) struct list_head *phead; unsigned long idx; + ffa_bus_notifier_unregister(); + /* Clean up/free all registered devices */ ffa_devices_unregister(); @@ -1740,11 +1752,14 @@ static int ffa_setup_partitions(void) ret = bus_register_notifier(&ffa_bus_type, &ffa_bus_nb); if (ret) pr_err("Failed to register FF-A bus notifiers\n"); + else + drv_info->bus_notifier_registered = true; } count = ffa_partition_probe(&uuid_null, &pbuf); if (count <= 0) { pr_info("%s: No partitions found, error %d\n", __func__, count); + ffa_bus_notifier_unregister(); return -EINVAL; } -- cgit v1.2.3 From fd672888cccd6b855154efe0ac78e7ce3e8ab088 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 27 Apr 2026 14:35:19 +0800 Subject: phy: qcom: edp: Unify generic DP/eDP swing and pre-emphasis tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current eDP and DP swing/pre-emphasis tables do not match the HPG requirements for the supported platforms, correct the table accordingly. The generic tables which can be shared as follows: DP mode: -sa8775p/sc7280/sc8280xp/x1e80100 -glymur -sc8180x eDP mode(low vdiff): -glymur/sa8775p/sc8280xp/x1e80100 -sc7280 -sc8180x The proper tables for SC8180X and SC7280 will be added in a later patch, since they need separate table. Cc: stable@vger.kernel.org Fixes: f199223cb490 ("phy: qcom: Introduce new eDP PHY driver") Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Yongxing Mou Link: https://patch.msgid.link/20260427-edp_phy-v5-1-3bb876824475@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-edp.c | 41 +++++++++---------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c index 7372de05a0b8..2af3fd63832f 100644 --- a/drivers/phy/qualcomm/phy-qcom-edp.c +++ b/drivers/phy/qualcomm/phy-qcom-edp.c @@ -116,17 +116,17 @@ struct qcom_edp { }; static const u8 dp_swing_hbr_rbr[4][4] = { - { 0x08, 0x0f, 0x16, 0x1f }, + { 0x07, 0x0f, 0x16, 0x1f }, { 0x11, 0x1e, 0x1f, 0xff }, { 0x16, 0x1f, 0xff, 0xff }, { 0x1f, 0xff, 0xff, 0xff } }; static const u8 dp_pre_emp_hbr_rbr[4][4] = { - { 0x00, 0x0d, 0x14, 0x1a }, + { 0x00, 0x0e, 0x15, 0x1a }, { 0x00, 0x0e, 0x15, 0xff }, { 0x00, 0x0e, 0xff, 0xff }, - { 0x03, 0xff, 0xff, 0xff } + { 0x04, 0xff, 0xff, 0xff } }; static const u8 dp_swing_hbr2_hbr3[4][4] = { @@ -158,7 +158,7 @@ static const u8 edp_swing_hbr_rbr[4][4] = { }; static const u8 edp_pre_emp_hbr_rbr[4][4] = { - { 0x05, 0x12, 0x17, 0x1d }, + { 0x05, 0x11, 0x17, 0x1d }, { 0x05, 0x11, 0x18, 0xff }, { 0x06, 0x11, 0xff, 0xff }, { 0x00, 0xff, 0xff, 0xff } @@ -172,10 +172,10 @@ static const u8 edp_swing_hbr2_hbr3[4][4] = { }; static const u8 edp_pre_emp_hbr2_hbr3[4][4] = { - { 0x08, 0x11, 0x17, 0x1b }, - { 0x00, 0x0c, 0x13, 0xff }, - { 0x05, 0x10, 0xff, 0xff }, - { 0x00, 0xff, 0xff, 0xff } + { 0x0c, 0x15, 0x19, 0x1e }, + { 0x0b, 0x15, 0x19, 0xff }, + { 0x0e, 0x14, 0xff, 0xff }, + { 0x0d, 0xff, 0xff, 0xff } }; static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg = { @@ -193,27 +193,6 @@ static const u8 edp_phy_vco_div_cfg_v4[4] = { 0x01, 0x01, 0x02, 0x00, }; -static const u8 edp_pre_emp_hbr_rbr_v5[4][4] = { - { 0x05, 0x11, 0x17, 0x1d }, - { 0x05, 0x11, 0x18, 0xff }, - { 0x06, 0x11, 0xff, 0xff }, - { 0x00, 0xff, 0xff, 0xff } -}; - -static const u8 edp_pre_emp_hbr2_hbr3_v5[4][4] = { - { 0x0c, 0x15, 0x19, 0x1e }, - { 0x0b, 0x15, 0x19, 0xff }, - { 0x0e, 0x14, 0xff, 0xff }, - { 0x0d, 0xff, 0xff, 0xff } -}; - -static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg_v5 = { - .swing_hbr_rbr = &edp_swing_hbr_rbr, - .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3, - .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr_v5, - .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3_v5, -}; - static const u8 edp_phy_aux_cfg_v5[DP_AUX_CFG_SIZE] = { 0x00, 0x13, 0xa4, 0x00, 0x0a, 0x26, 0x0a, 0x03, 0x37, 0x03, 0x02, 0x02, 0x00, }; @@ -564,7 +543,7 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = { .is_edp = false, .aux_cfg = edp_phy_aux_cfg_v5, .vco_div_cfg = edp_phy_vco_div_cfg_v4, - .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v5, + .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v4, }; @@ -945,7 +924,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = { static struct qcom_edp_phy_cfg glymur_phy_cfg = { .aux_cfg = edp_phy_aux_cfg_v8, .vco_div_cfg = edp_phy_vco_div_cfg_v8, - .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v5, + .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v8, }; -- cgit v1.2.3 From 3011c365a329cf2db6d55e8d684550dc88350436 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 27 Apr 2026 14:35:20 +0800 Subject: phy: qcom: edp: Add eDP/DP mode switch support The eDP PHY supports both eDP/DP modes, each requiring a different swing/pre-emphasis table. However, the driver currently uses a fixed static table for eDP programming rather than selecting the appropriate table based on the current mode. Add separate tables for eDP and DP modes, and select the appropriate table dynamically based on the current mode. Glymur's DP mode table differs from the other platforms, add a dedicated table for it. This also fixes the table mismatch for X1E80100 (eDP) and SA8775P (DP). Cc: stable@vger.kernel.org Fixes: 3f12bf16213c ("phy: qcom: edp: Add support for eDP PHY on SA8775P") Reviewed-by: Konrad Dybcio Signed-off-by: Yongxing Mou Link: https://patch.msgid.link/20260427-edp_phy-v5-2-3bb876824475@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-edp.c | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c index 2af3fd63832f..3266026cfe37 100644 --- a/drivers/phy/qualcomm/phy-qcom-edp.c +++ b/drivers/phy/qualcomm/phy-qcom-edp.c @@ -87,7 +87,8 @@ struct qcom_edp_phy_cfg { bool is_edp; const u8 *aux_cfg; const u8 *vco_div_cfg; - const struct qcom_edp_swing_pre_emph_cfg *swing_pre_emph_cfg; + const struct qcom_edp_swing_pre_emph_cfg *dp_swing_pre_emph_cfg; + const struct qcom_edp_swing_pre_emph_cfg *edp_swing_pre_emph_cfg; const struct phy_ver_ops *ver_ops; }; @@ -150,6 +151,20 @@ static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg = { .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3, }; +static const u8 dp_pre_emp_hbr_rbr_v8[4][4] = { + { 0x00, 0x0e, 0x15, 0x1a }, + { 0x00, 0x0e, 0x15, 0xff }, + { 0x00, 0x0e, 0xff, 0xff }, + { 0x00, 0xff, 0xff, 0xff } +}; + +static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg_v8 = { + .swing_hbr_rbr = &dp_swing_hbr_rbr, + .swing_hbr3_hbr2 = &dp_swing_hbr2_hbr3, + .pre_emphasis_hbr_rbr = &dp_pre_emp_hbr_rbr_v8, + .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3, +}; + static const u8 edp_swing_hbr_rbr[4][4] = { { 0x07, 0x0f, 0x16, 0x1f }, { 0x0d, 0x16, 0x1e, 0xff }, @@ -246,7 +261,7 @@ static int qcom_edp_phy_init(struct phy *phy) * when more information becomes available about why this is * even needed. */ - if (edp->cfg->swing_pre_emph_cfg && !edp->is_edp) + if (edp->cfg->dp_swing_pre_emph_cfg && !edp->is_edp) aux_cfg[8] = 0xb7; writel(0xfc, edp->edp + DP_PHY_MODE); @@ -270,7 +285,7 @@ out_disable_supplies: static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configure_opts_dp *dp_opts) { - const struct qcom_edp_swing_pre_emph_cfg *cfg = edp->cfg->swing_pre_emph_cfg; + const struct qcom_edp_swing_pre_emph_cfg *cfg; unsigned int v_level = 0; unsigned int p_level = 0; u8 ldo_config; @@ -278,12 +293,14 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur u8 emph; int i; + if (edp->is_edp) + cfg = edp->cfg->edp_swing_pre_emph_cfg; + else + cfg = edp->cfg->dp_swing_pre_emph_cfg; + if (!cfg) return 0; - if (edp->is_edp) - cfg = &edp_phy_swing_pre_emph_cfg; - for (i = 0; i < dp_opts->lanes; i++) { v_level = max(v_level, dp_opts->voltage[i]); p_level = max(p_level, dp_opts->pre[i]); @@ -543,7 +560,8 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = { .is_edp = false, .aux_cfg = edp_phy_aux_cfg_v5, .vco_div_cfg = edp_phy_vco_div_cfg_v4, - .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v4, }; @@ -556,7 +574,8 @@ static const struct qcom_edp_phy_cfg sc7280_dp_phy_cfg = { static const struct qcom_edp_phy_cfg sc8280xp_dp_phy_cfg = { .aux_cfg = edp_phy_aux_cfg_v4, .vco_div_cfg = edp_phy_vco_div_cfg_v4, - .swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v4, }; @@ -564,7 +583,8 @@ static const struct qcom_edp_phy_cfg sc8280xp_edp_phy_cfg = { .is_edp = true, .aux_cfg = edp_phy_aux_cfg_v4, .vco_div_cfg = edp_phy_vco_div_cfg_v4, - .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v4, }; @@ -745,7 +765,8 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v6 = { static struct qcom_edp_phy_cfg x1e80100_phy_cfg = { .aux_cfg = edp_phy_aux_cfg_v4, .vco_div_cfg = edp_phy_vco_div_cfg_v4, - .swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v6, }; @@ -924,7 +945,8 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = { static struct qcom_edp_phy_cfg glymur_phy_cfg = { .aux_cfg = edp_phy_aux_cfg_v8, .vco_div_cfg = edp_phy_vco_div_cfg_v8, - .swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg_v8, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg, .ver_ops = &qcom_edp_phy_ops_v8, }; @@ -942,7 +964,7 @@ static int qcom_edp_phy_power_on(struct phy *phy) if (ret) return ret; - if (edp->cfg->swing_pre_emph_cfg && !edp->is_edp) + if (edp->cfg->edp_swing_pre_emph_cfg && !edp->is_edp) ldo_config = 0x1; writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); -- cgit v1.2.3 From 3d22594d6f842814b7718600486fe3ce9453abf0 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 27 Apr 2026 14:35:21 +0800 Subject: phy: qcom: edp: Add SC7280/SC8180X swing/pre-emphasis tables SC7280 and SC8180X previously shared the same cfg because they did not use swing/pre-emphasis tables. Add the corresponding tables for these platforms. Since they have different PHY sub-versions, their eDP/DP mode tables also differ, so move SC8180X to its own cfg instead of reusing the SC7280 one. Signed-off-by: Yongxing Mou Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260427-edp_phy-v5-3-3bb876824475@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-edp.c | 84 +++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c index 3266026cfe37..3e613b374032 100644 --- a/drivers/phy/qualcomm/phy-qcom-edp.c +++ b/drivers/phy/qualcomm/phy-qcom-edp.c @@ -165,6 +165,33 @@ static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg_v8 = { .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3, }; +static const u8 dp_swing_hbr2_hbr3_v2[4][4] = { + { 0x27, 0x2f, 0x36, 0xff }, + { 0x31, 0x3e, 0x3f, 0xff }, + { 0x3a, 0x3f, 0xff, 0xff }, + { 0xff, 0xff, 0xff, 0xff } +}; + +static const u8 dp_pre_emp_hbr2_hbr3_v2[4][4] = { + { 0x20, 0x2e, 0x35, 0xff }, + { 0x20, 0x2e, 0x35, 0xff }, + { 0x20, 0x2e, 0xff, 0xff }, + { 0xff, 0xff, 0xff, 0xff } +}; + +static const struct qcom_edp_swing_pre_emph_cfg dp_phy_swing_pre_emph_cfg_v2 = { + /* + * NOTE: The HPG does not specify a separate swing_hbr_rbr table. + * Reuse the HBR2/HBR3 table for now. + * + * TODO: Update this once the HPG explicitly defines RBR/HBR swing values. + */ + .swing_hbr_rbr = &dp_swing_hbr2_hbr3_v2, + .swing_hbr3_hbr2 = &dp_swing_hbr2_hbr3_v2, + .pre_emphasis_hbr_rbr = &dp_pre_emp_hbr2_hbr3_v2, + .pre_emphasis_hbr3_hbr2 = &dp_pre_emp_hbr2_hbr3_v2, +}; + static const u8 edp_swing_hbr_rbr[4][4] = { { 0x07, 0x0f, 0x16, 0x1f }, { 0x0d, 0x16, 0x1e, 0xff }, @@ -208,6 +235,48 @@ static const u8 edp_phy_vco_div_cfg_v4[4] = { 0x01, 0x01, 0x02, 0x00, }; +static const u8 edp_pre_emp_hbr_rbr_v2[4][4] = { + { 0x05, 0x12, 0x17, 0x1d }, + { 0x05, 0x11, 0x18, 0xff }, + { 0x06, 0x11, 0xff, 0xff }, + { 0x00, 0xff, 0xff, 0xff } +}; + +static const u8 edp_pre_emp_hbr2_hbr3_v2[4][4] = { + { 0x0c, 0x15, 0x19, 0x1e }, + { 0x08, 0x15, 0x19, 0xff }, + { 0x0e, 0x14, 0xff, 0xff }, + { 0x0d, 0xff, 0xff, 0xff } +}; + +static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg_v2 = { + .swing_hbr_rbr = &edp_swing_hbr_rbr, + .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3, + .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr_v2, + .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3_v2, +}; + +static const u8 edp_swing_hbr2_hbr3_v3[4][4] = { + { 0x06, 0x11, 0x16, 0x1b }, + { 0x0b, 0x19, 0x1f, 0xff }, + { 0x18, 0x1f, 0xff, 0xff }, + { 0x1f, 0xff, 0xff, 0xff } +}; + +static const u8 edp_pre_emp_hbr2_hbr3_v3[4][4] = { + { 0x0c, 0x15, 0x19, 0x1e }, + { 0x09, 0x14, 0x19, 0xff }, + { 0x0f, 0x14, 0xff, 0xff }, + { 0x0d, 0xff, 0xff, 0xff } +}; + +static const struct qcom_edp_swing_pre_emph_cfg edp_phy_swing_pre_emph_cfg_v3 = { + .swing_hbr_rbr = &edp_swing_hbr_rbr, + .swing_hbr3_hbr2 = &edp_swing_hbr2_hbr3_v3, + .pre_emphasis_hbr_rbr = &edp_pre_emp_hbr_rbr, + .pre_emphasis_hbr3_hbr2 = &edp_pre_emp_hbr2_hbr3_v3, +}; + static const u8 edp_phy_aux_cfg_v5[DP_AUX_CFG_SIZE] = { 0x00, 0x13, 0xa4, 0x00, 0x0a, 0x26, 0x0a, 0x03, 0x37, 0x03, 0x02, 0x02, 0x00, }; @@ -298,9 +367,6 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur else cfg = edp->cfg->dp_swing_pre_emph_cfg; - if (!cfg) - return 0; - for (i = 0; i < dp_opts->lanes; i++) { v_level = max(v_level, dp_opts->voltage[i]); p_level = max(p_level, dp_opts->pre[i]); @@ -568,6 +634,16 @@ static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = { static const struct qcom_edp_phy_cfg sc7280_dp_phy_cfg = { .aux_cfg = edp_phy_aux_cfg_v4, .vco_div_cfg = edp_phy_vco_div_cfg_v4, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v3, + .ver_ops = &qcom_edp_phy_ops_v4, +}; + +static const struct qcom_edp_phy_cfg sc8180x_dp_phy_cfg = { + .aux_cfg = edp_phy_aux_cfg_v4, + .vco_div_cfg = edp_phy_vco_div_cfg_v4, + .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg_v2, + .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v2, .ver_ops = &qcom_edp_phy_ops_v4, }; @@ -1348,7 +1424,7 @@ static const struct of_device_id qcom_edp_phy_match_table[] = { { .compatible = "qcom,glymur-dp-phy", .data = &glymur_phy_cfg, }, { .compatible = "qcom,sa8775p-edp-phy", .data = &sa8775p_dp_phy_cfg, }, { .compatible = "qcom,sc7280-edp-phy", .data = &sc7280_dp_phy_cfg, }, - { .compatible = "qcom,sc8180x-edp-phy", .data = &sc7280_dp_phy_cfg, }, + { .compatible = "qcom,sc8180x-edp-phy", .data = &sc8180x_dp_phy_cfg, }, { .compatible = "qcom,sc8280xp-dp-phy", .data = &sc8280xp_dp_phy_cfg, }, { .compatible = "qcom,sc8280xp-edp-phy", .data = &sc8280xp_edp_phy_cfg, }, { .compatible = "qcom,x1e80100-dp-phy", .data = &x1e80100_phy_cfg, }, -- cgit v1.2.3 From bf237a9fcbbf9d658522f7315ffc04bf2d49be42 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 27 Apr 2026 14:35:22 +0800 Subject: phy: qcom: edp: Fix AUX_CFG8 programming for DP mode AUX_CFG8 depends on whether the PHY is operating in eDP or DP mode, not the selected swing/pre-emphasis table. All supported platforms already have the proper tables, so remove the unnecessary check. Cc: stable@vger.kernel.org Fixes: 6078b8ce070c ("phy: qcom: edp: Add set_mode op for configuring eDP/DP submode") Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Yongxing Mou Link: https://patch.msgid.link/20260427-edp_phy-v5-4-3bb876824475@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-edp.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c index 3e613b374032..3a848f18a8d6 100644 --- a/drivers/phy/qualcomm/phy-qcom-edp.c +++ b/drivers/phy/qualcomm/phy-qcom-edp.c @@ -325,12 +325,7 @@ static int qcom_edp_phy_init(struct phy *phy) DP_PHY_PD_CTL_PLL_PWRDN | DP_PHY_PD_CTL_DP_CLAMP_EN, edp->edp + DP_PHY_PD_CTL); - /* - * TODO: Re-work the conditions around setting the cfg8 value - * when more information becomes available about why this is - * even needed. - */ - if (edp->cfg->dp_swing_pre_emph_cfg && !edp->is_edp) + if (!edp->is_edp) aux_cfg[8] = 0xb7; writel(0xfc, edp->edp + DP_PHY_MODE); -- cgit v1.2.3 From 519a228ee40d1be3453d1da339b4577c3785e333 Mon Sep 17 00:00:00 2001 From: Yongxing Mou Date: Mon, 27 Apr 2026 14:35:23 +0800 Subject: phy: qcom: edp: Add PHY-specific LDO config for eDP low vdiff For eDP low vdiff, the LDO setting depends on the PHY version rather than being a simple 0x0 or 0x1 value. Introduce a PHY callback to program the correct LDO setting according to the HPG. Since SC7280/SC8180X uses different LDO settings from SA8775P/SC8280XP, introduce qcom_edp_phy_ops_v3 to keep the LDO setting correct. Cc: stable@vger.kernel.org Fixes: f199223cb490 ("phy: qcom: Introduce new eDP PHY driver") Signed-off-by: Yongxing Mou Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Tested-by: Konrad Dybcio # SC8280XP X13s Link: https://patch.msgid.link/20260427-edp_phy-v5-5-3bb876824475@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-edp.c | 88 ++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-edp.c b/drivers/phy/qualcomm/phy-qcom-edp.c index 3a848f18a8d6..a3c893f72908 100644 --- a/drivers/phy/qualcomm/phy-qcom-edp.c +++ b/drivers/phy/qualcomm/phy-qcom-edp.c @@ -81,6 +81,7 @@ struct phy_ver_ops { int (*com_clk_fwd_cfg)(const struct qcom_edp *edp); int (*com_configure_pll)(const struct qcom_edp *edp); int (*com_configure_ssc)(const struct qcom_edp *edp); + int (*com_ldo_config)(const struct qcom_edp *edp); }; struct qcom_edp_phy_cfg { @@ -352,7 +353,7 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur const struct qcom_edp_swing_pre_emph_cfg *cfg; unsigned int v_level = 0; unsigned int p_level = 0; - u8 ldo_config; + int ret; u8 swing; u8 emph; int i; @@ -378,13 +379,13 @@ static int qcom_edp_set_voltages(struct qcom_edp *edp, const struct phy_configur if (swing == 0xff || emph == 0xff) return -EINVAL; - ldo_config = edp->is_edp ? 0x0 : 0x1; + ret = edp->cfg->ver_ops->com_ldo_config(edp); + if (ret) + return ret; - writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); writel(swing, edp->tx0 + TXn_TX_DRV_LVL); writel(emph, edp->tx0 + TXn_TX_EMP_POST1_LVL); - writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG); writel(swing, edp->tx1 + TXn_TX_DRV_LVL); writel(emph, edp->tx1 + TXn_TX_EMP_POST1_LVL); @@ -608,6 +609,52 @@ static int qcom_edp_com_configure_pll_v4(const struct qcom_edp *edp) return 0; } +static int qcom_edp_ldo_config_v3(const struct qcom_edp *edp) +{ + const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; + u32 ldo_config; + + if (!edp->is_edp) + ldo_config = 0x0; + else if (dp_opts->link_rate <= 2700) + ldo_config = 0x81; + else + ldo_config = 0x41; + + writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); + writel(dp_opts->lanes > 2 ? ldo_config : 0x00, edp->tx1 + TXn_LDO_CONFIG); + + return 0; +} + +static int qcom_edp_ldo_config_v4(const struct qcom_edp *edp) +{ + const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; + u32 ldo_config; + + if (!edp->is_edp) + ldo_config = 0x0; + else if (dp_opts->link_rate <= 2700) + ldo_config = 0xc1; + else + ldo_config = 0x81; + + writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); + writel(dp_opts->lanes > 2 ? ldo_config : 0x00, edp->tx1 + TXn_LDO_CONFIG); + + return 0; +} + +static const struct phy_ver_ops qcom_edp_phy_ops_v3 = { + .com_power_on = qcom_edp_phy_power_on_v4, + .com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v4, + .com_bias_en_clkbuflr = qcom_edp_com_bias_en_clkbuflr_v4, + .com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v4, + .com_configure_pll = qcom_edp_com_configure_pll_v4, + .com_configure_ssc = qcom_edp_com_configure_ssc_v4, + .com_ldo_config = qcom_edp_ldo_config_v3, +}; + static const struct phy_ver_ops qcom_edp_phy_ops_v4 = { .com_power_on = qcom_edp_phy_power_on_v4, .com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v4, @@ -615,6 +662,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v4 = { .com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v4, .com_configure_pll = qcom_edp_com_configure_pll_v4, .com_configure_ssc = qcom_edp_com_configure_ssc_v4, + .com_ldo_config = qcom_edp_ldo_config_v4, }; static const struct qcom_edp_phy_cfg sa8775p_dp_phy_cfg = { @@ -631,7 +679,7 @@ static const struct qcom_edp_phy_cfg sc7280_dp_phy_cfg = { .vco_div_cfg = edp_phy_vco_div_cfg_v4, .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg, .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v3, - .ver_ops = &qcom_edp_phy_ops_v4, + .ver_ops = &qcom_edp_phy_ops_v3, }; static const struct qcom_edp_phy_cfg sc8180x_dp_phy_cfg = { @@ -639,7 +687,7 @@ static const struct qcom_edp_phy_cfg sc8180x_dp_phy_cfg = { .vco_div_cfg = edp_phy_vco_div_cfg_v4, .dp_swing_pre_emph_cfg = &dp_phy_swing_pre_emph_cfg_v2, .edp_swing_pre_emph_cfg = &edp_phy_swing_pre_emph_cfg_v2, - .ver_ops = &qcom_edp_phy_ops_v4, + .ver_ops = &qcom_edp_phy_ops_v3, }; static const struct qcom_edp_phy_cfg sc8280xp_dp_phy_cfg = { @@ -824,6 +872,24 @@ static int qcom_edp_com_configure_pll_v6(const struct qcom_edp *edp) return 0; } +static int qcom_edp_ldo_config_v6(const struct qcom_edp *edp) +{ + const struct phy_configure_opts_dp *dp_opts = &edp->dp_opts; + u32 ldo_config; + + if (!edp->is_edp) + ldo_config = 0x0; + else if (dp_opts->link_rate <= 2700) + ldo_config = 0x51; + else + ldo_config = 0x91; + + writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); + writel(dp_opts->lanes > 2 ? ldo_config : 0x00, edp->tx1 + TXn_LDO_CONFIG); + + return 0; +} + static const struct phy_ver_ops qcom_edp_phy_ops_v6 = { .com_power_on = qcom_edp_phy_power_on_v6, .com_resetsm_cntrl = qcom_edp_phy_com_resetsm_cntrl_v6, @@ -831,6 +897,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v6 = { .com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v4, .com_configure_pll = qcom_edp_com_configure_pll_v6, .com_configure_ssc = qcom_edp_com_configure_ssc_v6, + .com_ldo_config = qcom_edp_ldo_config_v6, }; static struct qcom_edp_phy_cfg x1e80100_phy_cfg = { @@ -1011,6 +1078,7 @@ static const struct phy_ver_ops qcom_edp_phy_ops_v8 = { .com_clk_fwd_cfg = qcom_edp_com_clk_fwd_cfg_v8, .com_configure_pll = qcom_edp_com_configure_pll_v8, .com_configure_ssc = qcom_edp_com_configure_ssc_v8, + .com_ldo_config = qcom_edp_ldo_config_v6, }; static struct qcom_edp_phy_cfg glymur_phy_cfg = { @@ -1026,7 +1094,6 @@ static int qcom_edp_phy_power_on(struct phy *phy) const struct qcom_edp *edp = phy_get_drvdata(phy); u32 bias0_en, drvr0_en, bias1_en, drvr1_en; unsigned long pixel_freq; - u8 ldo_config = 0x0; int ret; u32 val; u8 cfg1; @@ -1035,11 +1102,10 @@ static int qcom_edp_phy_power_on(struct phy *phy) if (ret) return ret; - if (edp->cfg->edp_swing_pre_emph_cfg && !edp->is_edp) - ldo_config = 0x1; + ret = edp->cfg->ver_ops->com_ldo_config(edp); + if (ret) + return ret; - writel(ldo_config, edp->tx0 + TXn_LDO_CONFIG); - writel(ldo_config, edp->tx1 + TXn_LDO_CONFIG); writel(0x00, edp->tx0 + TXn_LANE_MODE_1); writel(0x00, edp->tx1 + TXn_LANE_MODE_1); -- cgit v1.2.3 From 646ebdd3105809d84ed04aa9e92e47e89cc44502 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 10 Apr 2026 23:03:09 +0200 Subject: media: rc: ttusbir: fix inverted error logic We have to report ENOMEM if no buffer is allocated. Typo dropped a "!". Restore it. Fixes: 50acaad3d202 ("media: rc: ttusbir: respect DMA coherency rules") Cc: stable@vger.kernel.org Signed-off-by: Oliver Neukum Signed-off-by: Sean Young Signed-off-by: Hans Verkuil --- drivers/media/rc/ttusbir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/media/rc/ttusbir.c b/drivers/media/rc/ttusbir.c index 3848ad3a6b85..db2f6698a6c0 100644 --- a/drivers/media/rc/ttusbir.c +++ b/drivers/media/rc/ttusbir.c @@ -191,7 +191,7 @@ static int ttusbir_probe(struct usb_interface *intf, tt = kzalloc_obj(*tt); buffer = kzalloc(5, GFP_KERNEL); rc = rc_allocate_device(RC_DRIVER_IR_RAW); - if (!tt || !rc || buffer) { + if (!tt || !rc || !buffer) { ret = -ENOMEM; goto out; } -- cgit v1.2.3 From 52ac35b8a151446481496404af3a8e5e889b3c5a Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Tue, 28 Apr 2026 17:44:58 +0530 Subject: pinctrl: qcom: Fix wakeirq map by removing disconnected irqs for sm8150 PDC interrupts 122-125 were meant for ibi_i3c wakeup but sm8150 do not support i3c. GPIOs 39,51,88 and 144 are also connected to different PDC pin and already reflected in the wake irq map. Remove the unsupported wakeup interrupts from the map. Fixes: 90337380c809 ("pinctrl: qcom: sm8150: Specify PDC map") Reviewed-by: Konrad Dybcio Signed-off-by: Maulik Shah Signed-off-by: Navya Malempati Signed-off-by: Linus Walleij --- drivers/pinctrl/qcom/pinctrl-sm8150.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/qcom/pinctrl-sm8150.c b/drivers/pinctrl/qcom/pinctrl-sm8150.c index 0767261f5149..12713671243c 100644 --- a/drivers/pinctrl/qcom/pinctrl-sm8150.c +++ b/drivers/pinctrl/qcom/pinctrl-sm8150.c @@ -1493,18 +1493,18 @@ static const struct msm_gpio_wakeirq_map sm8150_pdc_map[] = { { 3, 31 }, { 5, 32 }, { 8, 33 }, { 9, 34 }, { 10, 100 }, { 12, 104 }, { 24, 37 }, { 26, 38 }, { 27, 41 }, { 28, 42 }, { 30, 39 }, { 36, 43 }, { 37, 44 }, { 38, 30 }, { 39, 118 }, - { 39, 125 }, { 41, 47 }, { 42, 48 }, { 46, 50 }, { 47, 49 }, - { 48, 51 }, { 49, 53 }, { 50, 52 }, { 51, 116 }, { 51, 123 }, + { 41, 47 }, { 42, 48 }, { 46, 50 }, { 47, 49 }, + { 48, 51 }, { 49, 53 }, { 50, 52 }, { 51, 116 }, { 53, 54 }, { 54, 55 }, { 55, 56 }, { 56, 57 }, { 58, 58 }, { 60, 60 }, { 61, 61 }, { 68, 62 }, { 70, 63 }, { 76, 71 }, { 77, 66 }, { 81, 64 }, { 83, 65 }, { 86, 67 }, { 87, 84 }, - { 88, 117 }, { 88, 124 }, { 90, 69 }, { 91, 70 }, { 93, 75 }, + { 88, 117 }, { 90, 69 }, { 91, 70 }, { 93, 75 }, { 95, 72 }, { 96, 73 }, { 97, 74 }, { 101, 40 }, { 103, 77 }, { 104, 78 }, { 108, 79 }, { 112, 80 }, { 113, 81 }, { 114, 82 }, { 117, 85 }, { 118, 101 }, { 119, 87 }, { 120, 88 }, { 121, 89 }, { 122, 90 }, { 123, 91 }, { 124, 92 }, { 125, 93 }, { 129, 94 }, { 132, 105 }, { 133, 83 }, { 134, 36 }, { 136, 97 }, { 142, 103 }, - { 144, 115 }, { 144, 122 }, { 147, 102 }, { 150, 107 }, + { 144, 115 }, { 147, 102 }, { 150, 107 }, { 152, 108 }, { 153, 109 } }; -- cgit v1.2.3 From 3974ea1938406f9bfa7c1f48d4e43533f447bb08 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:30 +0100 Subject: firmware: arm_ffa: Bound PARTITION_INFO_GET_REGS copies The register-based PARTITION_INFO_GET path trusted the firmware-provided indices when copying partition descriptors into the caller buffer. Reject inconsistent counts or index progressions so the copy loop cannot write past the allocated array. Fixes: ba85c644ac8d ("firmware: arm_ffa: Add support for FFA_PARTITION_INFO_GET_REGS") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-6-8595ae450034@kernel.org (fixed cur_idx when exactly one descriptor in the first fragment) Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index a122814eb6d7..33b417e78684 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -323,6 +323,12 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, #define PART_INFO_ID_MASK GENMASK(15, 0) #define PART_INFO_EXEC_CXT_MASK GENMASK(31, 16) #define PART_INFO_PROPS_MASK GENMASK(63, 32) +#define FFA_PART_INFO_GET_REGS_FIRST_REG 3 +#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC 3 +#define FFA_PART_INFO_GET_REGS_MAX_DESC \ + (((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \ + FFA_PART_INFO_GET_REGS_FIRST_REG) / \ + FFA_PART_INFO_GET_REGS_REGS_PER_DESC) #define PART_INFO_ID(x) ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x)))) #define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x)))) #define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x)))) @@ -330,15 +336,13 @@ static int __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, struct ffa_partition_info *buffer, int num_parts) { - u16 buf_sz, start_idx, cur_idx, count = 0, prev_idx = 0, tag = 0; + u16 buf_sz, start_idx = 0, cur_idx, count = 0, tag = 0; struct ffa_partition_info *buf = buffer; ffa_value_t partition_info; do { __le64 *regs; - int idx; - - start_idx = prev_idx ? prev_idx + 1 : 0; + int idx, nr_desc, buf_idx; invoke_ffa_fn((ffa_value_t){ .a0 = FFA_PARTITION_INFO_GET_REGS, @@ -354,15 +358,28 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, count = PARTITION_COUNT(partition_info.a2); if (!buffer || !num_parts) /* count only */ return count; + if (count > num_parts) + return -EINVAL; cur_idx = CURRENT_INDEX(partition_info.a2); + if (cur_idx < start_idx || cur_idx >= count) + return -EINVAL; + + nr_desc = cur_idx - start_idx + 1; + if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC) + return -EINVAL; + + buf_idx = buf - buffer; + if (buf_idx + nr_desc > num_parts) + return -EINVAL; + tag = UUID_INFO_TAG(partition_info.a2); buf_sz = PARTITION_INFO_SZ(partition_info.a2); if (buf_sz > sizeof(*buffer)) buf_sz = sizeof(*buffer); regs = (void *)&partition_info.a3; - for (idx = 0; idx < cur_idx - start_idx + 1; idx++, buf++) { + for (idx = 0; idx < nr_desc; idx++, buf++) { union { uuid_t uuid; u64 regs[2]; @@ -380,7 +397,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3, uuid_copy(&buf->uuid, &uuid_regs.uuid); regs += 3; } - prev_idx = cur_idx; + start_idx = cur_idx + 1; } while (cur_idx < (count - 1)); -- cgit v1.2.3 From 2af18f8e36b277730527cacc2256b1332f56aa28 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:31 +0100 Subject: firmware: arm_ffa: Keep framework RX release under lock The framework notification handler drops rx_lock before issuing FFA_RX_RELEASE, leaving a window where another RX-buffer user can start a new FF-A transaction before ownership has actually been returned to firmware. Move the FFA_RX_RELEASE calls so they execute while rx_lock is still held on both the kmemdup() failure path and the normal success path. While doing that, switch the handler to scoped_guard() to keep the critical section explicit. Fixes: 285a5ea0f542 ("firmware: arm_ffa: Add support for handling framework notifications") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-7-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 33b417e78684..d1e70866a425 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1492,25 +1492,22 @@ static void handle_fwk_notif_callbacks(u32 bitmap) if (!(bitmap & FRAMEWORK_NOTIFY_RX_BUFFER_FULL)) return; - mutex_lock(&drv_info->rx_lock); + scoped_guard(mutex, &drv_info->rx_lock) { + msg = drv_info->rx_buffer; + buf = kmemdup((void *)msg + msg->offset, msg->size, GFP_KERNEL); + if (!buf) { + ffa_rx_release(); + return; + } - msg = drv_info->rx_buffer; - buf = kmemdup((void *)msg + msg->offset, msg->size, GFP_KERNEL); - if (!buf) { - mutex_unlock(&drv_info->rx_lock); - return; + target = SENDER_ID(msg->send_recv_id); + if (msg->offset >= sizeof(*msg)) + uuid_copy(&uuid, &msg->uuid); + else + uuid_copy(&uuid, &uuid_null); + ffa_rx_release(); } - target = SENDER_ID(msg->send_recv_id); - if (msg->offset >= sizeof(*msg)) - uuid_copy(&uuid, &msg->uuid); - else - uuid_copy(&uuid, &uuid_null); - - mutex_unlock(&drv_info->rx_lock); - - ffa_rx_release(); - read_lock(&drv_info->notify_lock); cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target, &uuid); read_unlock(&drv_info->notify_lock); -- cgit v1.2.3 From 4a1cc9e96b311d2609a6f963a5e35bd4ae730d97 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:32 +0100 Subject: firmware: arm_ffa: Validate framework notification message layout Framework notifications carry an indirect message in the shared RX buffer. Validate the reported offset and size before using them, reject zero-length payloads, and ensure that any non-header payload starts at the UUID field rather than in the middle of the message header. Use the validated offset and size values for both kmemdup() and the UUID parsing path so malformed firmware data cannot drive an out-of-bounds read or an oversized allocation. Fixes: 285a5ea0f542 ("firmware: arm_ffa: Add support for handling framework notifications") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-8-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index d1e70866a425..7287423faceb 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1487,21 +1487,35 @@ static void handle_fwk_notif_callbacks(u32 bitmap) int notify_id = 0, target; struct ffa_indirect_msg_hdr *msg; struct notifier_cb_info *cb_info = NULL; + size_t min_offset = offsetof(struct ffa_indirect_msg_hdr, uuid); /* Only one framework notification defined and supported for now */ if (!(bitmap & FRAMEWORK_NOTIFY_RX_BUFFER_FULL)) return; scoped_guard(mutex, &drv_info->rx_lock) { + u32 offset, size; + msg = drv_info->rx_buffer; - buf = kmemdup((void *)msg + msg->offset, msg->size, GFP_KERNEL); + offset = msg->offset; + size = msg->size; + + if (!size || (offset != min_offset && offset < sizeof(*msg)) || + offset > drv_info->rxtx_bufsz || + size > drv_info->rxtx_bufsz - offset) { + pr_err("invalid framework notification message\n"); + ffa_rx_release(); + return; + } + + buf = kmemdup((void *)msg + offset, size, GFP_KERNEL); if (!buf) { ffa_rx_release(); return; } target = SENDER_ID(msg->send_recv_id); - if (msg->offset >= sizeof(*msg)) + if (offset >= sizeof(*msg)) uuid_copy(&uuid, &msg->uuid); else uuid_copy(&uuid, &uuid_null); -- cgit v1.2.3 From 0399e3f872ca3d78044bb715a73ea645806d2c7b Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:33 +0100 Subject: firmware: arm_ffa: Align RxTx buffer size before mapping Commit 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP") advertises PAGE_ALIGN(rxtx_bufsz) to firmware when mapping the buffers but the driver continues to stores the minimum FF-A buffer size in drv_info->rxtx_bufsz which is used elsewhere in the driver. Align the size before storing it so that the allocation, validation and FFA_RXTX_MAP all use the same buffer size. Fixes: 83210251fd70 ("firmware: arm_ffa: Use the correct buffer size during RXTX_MAP") Cc: Sebastian Ene Link: https://sashiko.dev/#/patchset/20260402113939.930221-1-sebastianene@google.com Reviewed-by: Sebastian Ene Link: https://patch.msgid.link/20260428-ffa_fixes-v2-9-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 7287423faceb..66ed98e32bd6 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -2109,6 +2109,7 @@ static int __init ffa_init(void) rxtx_bufsz = SZ_4K; } + rxtx_bufsz = PAGE_ALIGN(rxtx_bufsz); drv_info->rxtx_bufsz = rxtx_bufsz; drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL); if (!drv_info->rx_buffer) { @@ -2124,7 +2125,7 @@ static int __init ffa_init(void) ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer), virt_to_phys(drv_info->rx_buffer), - PAGE_ALIGN(rxtx_bufsz) / FFA_PAGE_SIZE); + rxtx_bufsz / FFA_PAGE_SIZE); if (ret) { pr_err("failed to register FFA RxTx buffers\n"); goto free_pages; -- cgit v1.2.3 From 38290b180a4d5746baed796d49f88d56d2f336cd Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:34 +0100 Subject: firmware: arm_ffa: Snapshot notifier callbacks under lock Both notification handlers currently look up a notifier callback under notify_lock, drop the lock, and then dereference the returned notifier entry. A concurrent unregister can delete and free that entry in the gap, leaving the handler to dereference stale memory. Copy the callback pointer and callback data while notify_lock is still held and invoke the callback only after the lock is dropped. This keeps the existing callback execution model while removing the use-after-free window in both the framework and non-framework notification paths. Fixes: 285a5ea0f542 ("firmware: arm_ffa: Add support for handling framework notifications") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-10-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 66ed98e32bd6..98ead7ed28ca 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1463,20 +1463,25 @@ static int ffa_notify_send(struct ffa_device *dev, int notify_id, static void handle_notif_callbacks(u64 bitmap, enum notify_type type) { + ffa_notifier_cb cb; + void *cb_data; int notify_id; - struct notifier_cb_info *cb_info = NULL; for (notify_id = 0; notify_id <= FFA_MAX_NOTIFICATIONS && bitmap; notify_id++, bitmap >>= 1) { if (!(bitmap & 1)) continue; - read_lock(&drv_info->notify_lock); - cb_info = notifier_hnode_get_by_type(notify_id, type); - read_unlock(&drv_info->notify_lock); + scoped_guard(read_lock, &drv_info->notify_lock) { + struct notifier_cb_info *cb_info; + + cb_info = notifier_hnode_get_by_type(notify_id, type); + cb = cb_info ? cb_info->cb : NULL; + cb_data = cb_info ? cb_info->cb_data : NULL; + } - if (cb_info && cb_info->cb) - cb_info->cb(notify_id, cb_info->cb_data); + if (cb) + cb(notify_id, cb_data); } } @@ -1484,9 +1489,10 @@ static void handle_fwk_notif_callbacks(u32 bitmap) { void *buf; uuid_t uuid; + void *fwk_cb_data; int notify_id = 0, target; + ffa_fwk_notifier_cb fwk_cb; struct ffa_indirect_msg_hdr *msg; - struct notifier_cb_info *cb_info = NULL; size_t min_offset = offsetof(struct ffa_indirect_msg_hdr, uuid); /* Only one framework notification defined and supported for now */ @@ -1522,12 +1528,17 @@ static void handle_fwk_notif_callbacks(u32 bitmap) ffa_rx_release(); } - read_lock(&drv_info->notify_lock); - cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target, &uuid); - read_unlock(&drv_info->notify_lock); + scoped_guard(read_lock, &drv_info->notify_lock) { + struct notifier_cb_info *cb_info; + + cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target, + &uuid); + fwk_cb = cb_info ? cb_info->fwk_cb : NULL; + fwk_cb_data = cb_info ? cb_info->cb_data : NULL; + } - if (cb_info && cb_info->fwk_cb) - cb_info->fwk_cb(notify_id, cb_info->cb_data, buf); + if (fwk_cb) + fwk_cb(notify_id, fwk_cb_data, buf); kfree(buf); } -- cgit v1.2.3 From a6848a50404eefb6f0b131c21881a2d8d21b31a9 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 28 Apr 2026 19:33:35 +0100 Subject: firmware: arm_ffa: Fix sched-recv callback partition lookup ffa_sched_recv_cb_update() used list_for_each_entry_safe() to search for a matching partition and then tested the iterator against NULL. That is not a valid end-of-list check for circular lists and can fall through with an invalid pointer. Use a normal iterator and detect the not-found case correctly before touching the partition state. Fixes: be61da938576 ("firmware: arm_ffa: Allow multiple UUIDs per partition to register SRI callback") Link: https://patch.msgid.link/20260428-ffa_fixes-v2-11-8595ae450034@kernel.org Signed-off-by: Sudeep Holla --- drivers/firmware/arm_ffa/driver.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index 98ead7ed28ca..b9f17fda7243 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -1207,7 +1207,7 @@ static int ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback, void *cb_data, bool is_registration) { - struct ffa_dev_part_info *partition = NULL, *tmp; + struct ffa_dev_part_info *partition = NULL; struct list_head *phead; bool cb_valid; @@ -1220,11 +1220,11 @@ ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback, return -EINVAL; } - list_for_each_entry_safe(partition, tmp, phead, node) + list_for_each_entry(partition, phead, node) if (partition->dev == dev) break; - if (!partition) { + if (&partition->node == phead) { pr_err("%s: No such partition ID 0x%x\n", __func__, dev->vm_id); return -EINVAL; } -- cgit v1.2.3 From 7602c0ec0bbfd3985d49f4f0cad281c1414008c9 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 31 Dec 2025 21:51:26 +0530 Subject: firmware: psci: Set pm_set_resume/suspend_via_firmware() for SYSTEM_SUSPEND PSCI specification defines the SYSTEM_SUSPEND feature which enables the firmware to implement the suspend to RAM (S2RAM) functionality by transitioning the system to a deeper low power state. When the system enters such state, the power to the peripheral devices might be removed. So the respective device drivers must prepare for the possible removal of the power by performing actions such as shutting down or resetting the device in their system suspend callbacks. The Linux PM framework allows the platform drivers to convey this info to device drivers by calling the pm_set_suspend_via_firmware() and pm_set_resume_via_firmware() APIs. Hence, if the PSCI firmware supports SYSTEM_SUSPEND feature, call the above mentioned APIs in the psci_system_suspend_begin() and psci_system_suspend_enter() callbacks. Signed-off-by: Konrad Dybcio Reviewed-by: Sudeep Holla [mani: reworded the description to be more elaborative] Signed-off-by: Manivannan Sadhasivam Tested-by: Jon Hunter Acked-by: Jon Hunter Acked-by: Lorenzo Pieralisi Signed-off-by: Arnd Bergmann --- drivers/firmware/psci/psci.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c index 38ca190d4a22..e73bae6cb23a 100644 --- a/drivers/firmware/psci/psci.c +++ b/drivers/firmware/psci/psci.c @@ -539,12 +539,22 @@ static int psci_system_suspend(unsigned long unused) static int psci_system_suspend_enter(suspend_state_t state) { + pm_set_resume_via_firmware(); + return cpu_suspend(0, psci_system_suspend); } +static int psci_system_suspend_begin(suspend_state_t state) +{ + pm_set_suspend_via_firmware(); + + return 0; +} + static const struct platform_suspend_ops psci_suspend_ops = { .valid = suspend_valid_only_mem, .enter = psci_system_suspend_enter, + .begin = psci_system_suspend_begin, }; static void __init psci_init_system_reset2(void) -- cgit v1.2.3 From 9228169d2ae055ed09a163887fc59a710a5eb73b Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:43 +0000 Subject: cpufreq/amd-pstate: Grab "amd_pstate_driver_lock" when toggling dynamic_epp Concurrently changing driver mode and dynamic_epp with: echo passive > /sys/devices/system/cpu/amd_pstate/status& echo disable > /sys/devices/system/cpu/amd_pstate/dynamic_epp& hits the WARN_ON_ONCE() in static_key_disable_cpuslocked() and hangs the system since both sysfs writes are trying to do amd_pstate_change_driver_mode() without any synchronization. Grab the "amd_pstate_driver_lock" mutex when modifying "dynamic_epp" to prevent the two paths from racing with each other. Add a lockdep assertion for "amd_pstate_driver_lock" in amd_pstate_change_driver_mode() to formalize the dependency. Since "cppc_mode" is stable under "amd_pstate_driver_lock", only reload the driver when in "AMD_PSTATE_ACTIVE" mode and reject all writes when in passive or guided mode, or if the driver is not loaded, since only active mode operates on EPP. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-2-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 453084c67327..5e2d28c73294 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1707,6 +1707,8 @@ static int amd_pstate_change_driver_mode(int mode) { int ret; + lockdep_assert_held(&amd_pstate_driver_lock); + ret = amd_pstate_unregister_driver(0); if (ret) return ret; @@ -1821,6 +1823,13 @@ static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, if (ret) return ret; + guard(mutex)(&amd_pstate_driver_lock); + + if (cppc_state != AMD_PSTATE_ACTIVE) { + pr_debug("dynamic_epp can only be toggled in active mode\n"); + return -EINVAL; + } + if (dynamic_epp == enabled) return -EINVAL; -- cgit v1.2.3 From 87d2a8dec0f02b200eb3527da0ab11ba4d4e7deb Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:44 +0000 Subject: cpufreq/amd-pstate: Return -ENOMEM on failure to allocate profile_name Failure to allocate profile name will return -EINVAL from platform_profile_register() while in fact, it is a failure to allocate memory for the profile_name string. Return -ENOMEM when kasprintf() fails to allocate profile_name string. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-3-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 5e2d28c73294..72514be2f30f 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1291,6 +1291,8 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) return ret; cpudata->profile_name = kasprintf(GFP_KERNEL, "amd-pstate-epp-cpu%d", cpudata->cpu); + if (!cpudata->profile_name) + return -ENOMEM; cpudata->ppdev = platform_profile_register(get_cpu_device(policy->cpu), cpudata->profile_name, -- cgit v1.2.3 From c5eed6ddc757e477f52b3d99bfde9e59975c72ca Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:45 +0000 Subject: cpufreq/amd-pstate: Allow writes to dynamic_epp when state isn't modified Writing the current "dynamic_epp" state to sysfs fails with -EINVAL even though the desired result was achieved. Allow writes to "dynamic_epp" that does not modify the state. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-4-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 72514be2f30f..462ddad7bc79 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1832,8 +1832,9 @@ static ssize_t dynamic_epp_store(struct device *a, struct device_attribute *b, return -EINVAL; } + /* Nothing to do */ if (dynamic_epp == enabled) - return -EINVAL; + return count; /* reinitialize with desired dynamic EPP value */ dynamic_epp = enabled; -- cgit v1.2.3 From f3acf7ff113007557538b278ccb0e4ab7ae513ea Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:46 +0000 Subject: cpufreq/amd-pstate: Reorder notifier unregistration and floor perf reset An active power supply notifier can race with amd_pstate_epp_cpu_exit() trying to reset the floor perf and can overwrite the floor perf set in MSR_AMD_CPPC_REQ. Unregister the notifier before setting the floor perf to prevent the rare race. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-5-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 462ddad7bc79..175925762a93 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1982,12 +1982,13 @@ static void amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy) if (cpudata) { union perf_cached perf = READ_ONCE(cpudata->perf); + if (cpudata->dynamic_epp) + amd_pstate_clear_dynamic_epp(policy); + /* Reset CPPC_REQ MSR to the BIOS value */ amd_pstate_update_perf(policy, perf.bios_min_perf, 0U, 0U, 0U, false); amd_pstate_set_floor_perf(policy, cpudata->bios_floor_perf); - if (cpudata->dynamic_epp) - amd_pstate_clear_dynamic_epp(policy); kfree(cpudata); policy->driver_data = NULL; } -- cgit v1.2.3 From caa822d312be54e3fe1a3b52c887e0888e149c12 Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:47 +0000 Subject: cpufreq/amd-pstate: Use "epp_default_dc" as default when dynamic_epp is disabled If "dynamic_epp" is disabled, the driver initialization and the default EPP selection from sysfs currently sets the EPP based on the power supply state of the system at that time but there is no power supply callbacks registered to toggle it when the power supply state changes. This can lead to faster battery drain on platforms that start off while being plugged to the wall but later move to battery power since the EPP stays at AMD_CPPC_EPP_PERFORMANCE. Use "epp_default_dc" as the default EPP selection when dynamic_epp is disabled, restoring older behavior. On servers, this defaults to AMD_CPPC_EPP_PERFORMANCE and on other platforms, it defaults to AMD_CPPC_EPP_BALANCE_PERFORMANCE. Fixes: e30ca6dd5345 ("cpufreq/amd-pstate: Add dynamic energy performance preference") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-6-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 175925762a93..9eb9c3f4e809 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1429,7 +1429,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, if (ret) epp = epp_values[ret]; else - epp = amd_pstate_get_balanced_epp(policy); + epp = cpudata->epp_default_dc; } if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) { @@ -1954,7 +1954,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) if (dynamic_epp) ret = amd_pstate_set_dynamic_epp(policy); else - ret = amd_pstate_set_epp(policy, amd_pstate_get_balanced_epp(policy)); + ret = amd_pstate_set_epp(policy, cpudata->epp_default_dc); if (ret) goto free_cpudata1; -- cgit v1.2.3 From f9f16835d4dc46113c0a72625ffbf61f1aa95e5c Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Fri, 8 May 2026 05:17:48 +0000 Subject: cpufreq/amd-pstate-ut: Drop policy reference before driver switch Recent changes to the EPP unit test tries to perform a driver switch with a cpufreq_policy reference held when the driver is loaded into anything but the active mode which leads to a circular dependency and the unit test hanging indefinitely. Drop the reference before driver switch and grab it back once the driver mode is stabilized for the test. The EPP writes are only possible with CPUFREQ_POLICY_POWERSAVE policy. Temporarily switch the cpudata->policy (while holding the write end of the policy->rwsem) to CPUFREQ_POLICY_POWERSAVE and restore the original policy once tests are done. To ensure the final EPP is correct in case the driver started with CPUFREQ_POLICY_PERFORMANCE, EPP performance is tested last. The __free() based cleanup for cpufreq_policy is lost in the process. Reported-by: Kalpana Shetty Fixes: 7e173bc310d2b ("cpufreq/amd-pstate-ut: Add a unit test for raw EPP") Reviewed-by: Mario Limonciello Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260508051748.10484-7-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate-ut.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index aa8a464fab47..13a23dac477d 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -274,20 +274,21 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) static int amd_pstate_ut_epp(u32 index) { - struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; - char *buf __free(cleanup_page) = NULL; static const char * const epp_strings[] = { - "performance", - "balance_performance", - "balance_power", "power", + "balance_power", + "balance_performance", + "performance", }; - struct amd_cpudata *cpudata; + char *buf __free(cleanup_page) = NULL; + struct cpufreq_policy *policy = NULL; enum amd_pstate_mode orig_mode; + struct amd_cpudata *cpudata; + unsigned long orig_policy; bool orig_dynamic_epp; int ret, cpu = 0; - int i; u16 epp; + int i; policy = cpufreq_cpu_get(cpu); if (!policy) @@ -297,6 +298,10 @@ static int amd_pstate_ut_epp(u32 index) orig_mode = amd_pstate_get_status(); orig_dynamic_epp = cpudata->dynamic_epp; + /* Drop reference before potential driver change. */ + cpufreq_cpu_put(policy); + policy = NULL; + /* disable dynamic EPP before running test */ if (cpudata->dynamic_epp) { pr_debug("Dynamic EPP is enabled, disabling it\n"); @@ -311,6 +316,17 @@ static int amd_pstate_ut_epp(u32 index) if (ret) goto out; + policy = cpufreq_cpu_get(cpu); + if (!policy) { + ret = -ENODEV; + goto out; + } + + down_write(&policy->rwsem); + cpudata = policy->driver_data; + orig_policy = cpudata->policy; + cpudata->policy = CPUFREQ_POLICY_POWERSAVE; + for (epp = 0; epp <= U8_MAX; epp++) { u8 val; @@ -358,6 +374,12 @@ static int amd_pstate_ut_epp(u32 index) ret = 0; out: + if (policy) { + cpudata->policy = orig_policy; + up_write(&policy->rwsem); + cpufreq_cpu_put(policy); + } + if (orig_dynamic_epp) { int ret2; -- cgit v1.2.3 From 91ddf6f722084383fb05be731c0107814b055c0c Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sat, 21 Mar 2026 15:42:32 +0100 Subject: phy: marvell: mvebu-a3700-utmi: fix incorrect USB2_PHY_CTRL register access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mvebu_a3700_utmi_phy_power_off() function tries to modify the USB2_PHY_CTRL register by using the IO address of the PHY IP block along with the readl/writel IO accessors. However, the register exist in the USB miscellaneous register space, and as such it must be accessed via regmap like it is done in the mvebu_a3700_utmi_phy_power_on() function. Change the code to use regmap_update_bits() for modífying the register to fix this. Fixes: cc8b7a0ae866 ("phy: add A3700 UTMI PHY driver") Signed-off-by: Gabor Juhos Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260321-a3700-utmi-fix-usb2_phy_ctrl-access-v1-1-6005ff4b5058@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/marvell/phy-mvebu-a3700-utmi.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/marvell/phy-mvebu-a3700-utmi.c b/drivers/phy/marvell/phy-mvebu-a3700-utmi.c index 04f4fb4bed70..f882bc57649c 100644 --- a/drivers/phy/marvell/phy-mvebu-a3700-utmi.c +++ b/drivers/phy/marvell/phy-mvebu-a3700-utmi.c @@ -168,9 +168,8 @@ static int mvebu_a3700_utmi_phy_power_off(struct phy *phy) u32 reg; /* Disable PHY pull-up and enable USB2 suspend */ - reg = readl(utmi->regs + USB2_PHY_CTRL(usb32)); - reg &= ~(RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32)); - writel(reg, utmi->regs + USB2_PHY_CTRL(usb32)); + regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), + RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32), 0); /* Power down OTG module */ if (usb32) { -- cgit v1.2.3 From da110228b54f2e2143d97ea7151e0dc22e539d67 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Mon, 4 May 2026 11:33:05 +0800 Subject: phy: tegra: xusb: Fix per-pad high-speed termination calibration The existing code reads a single hs_term_range_adj value from bit field [10:7] of FUSE_SKU_CALIB_0 and applies it to all USB2 pads uniformly. However, on SoCs that support per-pad termination, each pad has its own hs_term_range_adj field: pad 0 in FUSE_SKU_CALIB_0[10:7], and pads 1-3 in FUSE_USB_CALIB_EXT_0 at bit offsets [8:5], [12:9], and [16:13] respectively. Fix the calibration by reading per-pad values from the appropriate fuse registers. For SoCs that do not support per-pad termination, replicate pad 0's value to all pads to maintain existing behavior. Add a has_per_pad_term flag to the SoC data to indicate whether per-pad termination values are available in FUSE_USB_CALIB_EXT_0. Fixes: 1ef535c6ba8e ("phy: tegra: xusb: Add Tegra194 support") Cc: stable@vger.kernel.org Signed-off-by: Wayne Chang Signed-off-by: Wei-Cheng Chen Reviewed-by: Jon Hunter Tested-by: Jon Hunter Link: https://patch.msgid.link/20260504033305.2283145-1-weichengc@nvidia.com Signed-off-by: Vinod Koul --- drivers/phy/tegra/xusb-tegra186.c | 33 ++++++++++++++++++++++++++------- drivers/phy/tegra/xusb.h | 1 + 2 files changed, 27 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 1ddf11265974..60156aea2707 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -20,8 +20,8 @@ /* FUSE USB_CALIB registers */ #define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0) #define HS_CURR_LEVEL_PAD_MASK 0x3f -#define HS_TERM_RANGE_ADJ_SHIFT 7 -#define HS_TERM_RANGE_ADJ_MASK 0xf +#define HS_TERM_RANGE_ADJ_PADX_SHIFT(x) ((x) ? (5 + (x - 1) * 4) : 7) +#define HS_TERM_RANGE_ADJ_PAD_MASK 0xf #define HS_SQUELCH_SHIFT 29 #define HS_SQUELCH_MASK 0x7 @@ -253,7 +253,7 @@ struct tegra_xusb_fuse_calibration { u32 *hs_curr_level; u32 hs_squelch; - u32 hs_term_range_adj; + u32 *hs_term_range_adj; u32 rpd_ctrl; }; @@ -930,7 +930,7 @@ static int tegra186_utmi_phy_power_on(struct phy *phy) value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); value &= ~TERM_RANGE_ADJ(~0); - value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj); + value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj[index]); value &= ~RPD_CTRL(~0); value |= RPD_CTRL(priv->calib.rpd_ctrl); padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); @@ -1464,17 +1464,23 @@ static const char * const tegra186_usb3_functions[] = { static int tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) { + const struct tegra_xusb_padctl_soc *soc = padctl->base.soc; struct device *dev = padctl->base.dev; unsigned int i, count; u32 value, *level; + u32 *hs_term_range_adj; int err; - count = padctl->base.soc->ports.usb2.count; + count = soc->ports.usb2.count; level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL); if (!level) return -ENOMEM; + hs_term_range_adj = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL); + if (!hs_term_range_adj) + return -ENOMEM; + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); if (err) return dev_err_probe(dev, err, @@ -1490,8 +1496,8 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) & HS_SQUELCH_MASK; - padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) & - HS_TERM_RANGE_ADJ_MASK; + hs_term_range_adj[0] = (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(0)) & + HS_TERM_RANGE_ADJ_PAD_MASK; err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value); if (err) { @@ -1503,6 +1509,17 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl) padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK; + for (i = 1; i < count; i++) { + if (soc->has_per_pad_term) + hs_term_range_adj[i] = + (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(i)) & + HS_TERM_RANGE_ADJ_PAD_MASK; + else + hs_term_range_adj[i] = hs_term_range_adj[0]; + } + + padctl->calib.hs_term_range_adj = hs_term_range_adj; + return 0; } @@ -1708,6 +1725,7 @@ const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = { .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), .supports_gen2 = true, .poll_trk_completed = true, + .has_per_pad_term = true, }; EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc); @@ -1732,6 +1750,7 @@ const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = { .trk_hw_mode = false, .trk_update_on_idle = true, .supports_lp_cfg_en = true, + .has_per_pad_term = true, }; EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc); #endif diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index cd277d0ed9e1..77609e54de66 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -435,6 +435,7 @@ struct tegra_xusb_padctl_soc { bool trk_hw_mode; bool trk_update_on_idle; bool supports_lp_cfg_en; + bool has_per_pad_term; }; struct tegra_xusb_padctl { -- cgit v1.2.3 From 5a759b120e31aa3ed914d98b51eb1755235250f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Lebiedzi=C5=84ski?= Date: Mon, 6 Apr 2026 15:56:27 +0200 Subject: phy: exynos5-usbdrd: fix USB 2.0 HS PHY tuning values for Exynos7870 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing PHYPARAM0 tuning values for Exynos7870 are incorrect, causing the USB 2.0 PHY to fail high-speed negotiation and fall back to full-speed (12Mbps) operation. Fix TXVREFTUNE (transmitter voltage reference) from 14 to 3, TXRESTUNE (transmitter impedance) from 3 to 2, and SQRXTUNE (squelch threshold) from 6 to 5. Also explicitly set TXPREEMPPULSETUNE to 0, which was previously missing from the tuning table despite being included in the register mask. All values are derived from the vendor kernel for the Samsung Galaxy A6 (SM-A600FN), as no public hardware documentation is available for the Exynos7870 USB DRD PHY. With these corrections, the PHY successfully negotiates high-speed (480Mbps) operation. Fixes: 588d5d20ca8d ("phy: exynos5-usbdrd: add exynos7870 USBDRD support") Cc: stable@vger.kernel.org Tested-by: Kaustabh Chakraborty Reviewed-by: Krzysztof Kozlowski Signed-off-by: Łukasz Lebiedziński Link: https://patch.msgid.link/20260406135627.234835-1-kernel@lvkasz.us Signed-off-by: Vinod Koul --- drivers/phy/samsung/phy-exynos5-usbdrd.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c index 5a181cb4597e..8711a3b62c8e 100644 --- a/drivers/phy/samsung/phy-exynos5-usbdrd.c +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -1958,13 +1958,14 @@ const struct exynos5_usbdrd_phy_tuning exynos7870_tunes_utmi_postinit[] = { PHYPARAM0_TXPREEMPAMPTUNE | PHYPARAM0_TXHSXVTUNE | PHYPARAM0_TXFSLSTUNE | PHYPARAM0_SQRXTUNE | PHYPARAM0_OTGTUNE | PHYPARAM0_COMPDISTUNE), - (FIELD_PREP_CONST(PHYPARAM0_TXVREFTUNE, 14) | + (FIELD_PREP_CONST(PHYPARAM0_TXVREFTUNE, 3) | FIELD_PREP_CONST(PHYPARAM0_TXRISETUNE, 1) | - FIELD_PREP_CONST(PHYPARAM0_TXRESTUNE, 3) | + FIELD_PREP_CONST(PHYPARAM0_TXRESTUNE, 2) | + FIELD_PREP_CONST(PHYPARAM0_TXPREEMPPULSETUNE, 0) | FIELD_PREP_CONST(PHYPARAM0_TXPREEMPAMPTUNE, 0) | FIELD_PREP_CONST(PHYPARAM0_TXHSXVTUNE, 0) | FIELD_PREP_CONST(PHYPARAM0_TXFSLSTUNE, 3) | - FIELD_PREP_CONST(PHYPARAM0_SQRXTUNE, 6) | + FIELD_PREP_CONST(PHYPARAM0_SQRXTUNE, 5) | FIELD_PREP_CONST(PHYPARAM0_OTGTUNE, 2) | FIELD_PREP_CONST(PHYPARAM0_COMPDISTUNE, 3))), PHY_TUNING_ENTRY_LAST -- cgit v1.2.3 From 80305760d7a55b884fb9023c490b75568d1ea0b1 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Wed, 15 Apr 2026 16:18:51 +0530 Subject: phy: qcom-qmp-ufs: Fix kaanapali PHY PLL lock failure after SM8650 G4 fix Commit 81af9e40e2e4 ("phy: qcom: qmp-ufs: Fix SM8650 PCS table for Gear 4") moved QPHY_V6_PCS_UFS_PLL_CNTL register configuration from the shared sm8650_ufsphy_g5_pcs table to the SM8650-specific sm8650_ufsphy_pcs base table to fix Gear 4 operation on SM8650. However, this change inadvertently broke kaanapali and SM8750 SoCs which also rely on the shared sm8650_ufsphy_g5_pcs table for Gear 5 configuration but use their own sm8750_ufsphy_pcs base table. After the change, kaanapali PHYs are left without the required PLL_CNTL = 0x33 setting, causing the PHY PLL to remain at its hardware reset default value, preventing PLL lock and resulting in DME_LINKSTARTUP timeouts. Fix this by adding the missing QPHY_V6_PCS_UFS_PLL_CNTL = 0x33 entry to the sm8750_ufsphy_pcs table, mirroring what the original commit already did for sm8650_ufsphy_pcs. Cc: stable@vger.kernel.org # v6.19.12 Fixes: 81af9e40e2e4 ("phy: qcom: qmp-ufs: Fix SM8650 PCS table for Gear 4") Signed-off-by: Nitin Rawat Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260415104851.2763238-1-nitin.rawat@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-ufs.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c b/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c index 771bc7c2ab50..b87314c8379d 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c @@ -1112,6 +1112,7 @@ static const struct qmp_phy_init_tbl sm8750_ufsphy_pcs[] = { QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_MULTI_LANE_CTRL1, 0x02), QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_TX_MID_TERM_CTRL1, 0x43), QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_PCS_CTRL1, 0x40), + QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_PLL_CNTL, 0x33), QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_TX_LARGE_AMP_DRV_LVL, 0x0f), QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_RX_SIGDET_CTRL2, 0x68), QMP_PHY_INIT_CFG(QPHY_V6_PCS_UFS_TX_POST_EMP_LVL_S4, 0x0e), -- cgit v1.2.3 From c2cd08e8f150738515c8df415ad7ecfa3d38124a Mon Sep 17 00:00:00 2001 From: Yulin Lu Date: Mon, 13 Apr 2026 15:00:33 +0800 Subject: phy: eswin: Fix incorrect error check in probe() devm_ioremap() returns NULL on failure, not an ERR_PTR. Using IS_ERR() to check the return value is incorrect. Fix this by checking for NULL and returning -ENOMEM. Fixes: 67ee9ccaa34a ("phy: eswin: Create eswin directory and add EIC7700 SATA PHY driver") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-phy/adjNbuWoc1B-3Ok1@stanley.mountain/ Signed-off-by: Yulin Lu Link: https://patch.msgid.link/20260413070033.128-1-luyulin@eswincomputing.com Signed-off-by: Vinod Koul --- drivers/phy/eswin/phy-eic7700-sata.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/eswin/phy-eic7700-sata.c b/drivers/phy/eswin/phy-eic7700-sata.c index c33653d48daa..76774b9e391b 100644 --- a/drivers/phy/eswin/phy-eic7700-sata.c +++ b/drivers/phy/eswin/phy-eic7700-sata.c @@ -216,8 +216,8 @@ static int eic7700_sata_phy_probe(struct platform_device *pdev) return -ENOENT; regs = devm_ioremap(dev, res->start, resource_size(res)); - if (IS_ERR(regs)) - return PTR_ERR(regs); + if (!regs) + return -ENOMEM; sata_phy->regmap = devm_regmap_init_mmio (dev, regs, &eic7700_sata_phy_regmap_config); -- cgit v1.2.3 From a4058c09dd6e28ec33316fd6eb45ddae4cab1f31 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Thu, 26 Mar 2026 00:23:58 +0800 Subject: phy: spacemit: Remove incorrect clk_disable() in spacemit_usb2phy_init() When clk_enable() fails, the clock was never enabled. Calling clk_disable() in this error path is incorrect. Remove the spurious clk_disable() call from the error handling in spacemit_usb2phy_init(). Fixes: fe4bc1a08638 ("phy: spacemit: support K1 USB2.0 PHY controller") Signed-off-by: Felix Gu Reviewed-by: Ze Huang Link: https://patch.msgid.link/20260326-k1-usb3-v1-1-0c2b6adf5185@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/spacemit/phy-k1-usb2.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/phy/spacemit/phy-k1-usb2.c b/drivers/phy/spacemit/phy-k1-usb2.c index 9215d0b223b2..e8c1e26428a9 100644 --- a/drivers/phy/spacemit/phy-k1-usb2.c +++ b/drivers/phy/spacemit/phy-k1-usb2.c @@ -97,7 +97,6 @@ static int spacemit_usb2phy_init(struct phy *phy) ret = clk_enable(sphy->clk); if (ret) { dev_err(&phy->dev, "failed to enable clock\n"); - clk_disable(sphy->clk); return ret; } -- cgit v1.2.3 From cbdbfba9e8907bea923874d05d6a35ff429a5544 Mon Sep 17 00:00:00 2001 From: Ihor Matushchak Date: Fri, 8 May 2026 10:49:33 +0200 Subject: regulator: Kconfig: fix a typo in help Fixes a typo in Kconfig, 'protectorvia' -> 'protector via'. Signed-off-by: Ihor Matushchak Link: https://patch.msgid.link/20260508084933.4076-1-ihor.matushchak@foobox.net Signed-off-by: Mark Brown --- drivers/regulator/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d71dac9436e3..78076ac6eac4 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -757,7 +757,7 @@ config REGULATOR_MAX20086 select REGMAP_I2C help This driver controls a Maxim MAX20086-MAX20089 camera power - protectorvia I2C bus. The regulator has 2 or 4 outputs depending on + protector via I2C bus. The regulator has 2 or 4 outputs depending on the device model. This driver is only capable to turn on/off them. config REGULATOR_MAX20411 -- cgit v1.2.3 From ea34567db0a6b3a7ce78ba421592344315c8f90e Mon Sep 17 00:00:00 2001 From: Peter Oberparleiter Date: Thu, 7 May 2026 16:27:08 +0200 Subject: s390/cio: Restore GFP_DMA for CHSC allocation Re-add GFP_DMA when allocating memory for CHSC control blocks. On some supported machines, CHSC cannot access memory outside the DMA zone, causing CHSC command failures. Cc: stable@vger.kernel.org Fixes: a3a64a4def8d ("s390/cio: remove unneeded DMA zone allocation") Signed-off-by: Peter Oberparleiter Reviewed-by: Heiko Carstens Signed-off-by: Alexander Gordeev --- drivers/s390/cio/chsc.c | 4 ++-- drivers/s390/cio/chsc_sch.c | 20 ++++++++++---------- drivers/s390/cio/scm.c | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index fbb58edd6274..9689f722c863 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -1142,8 +1142,8 @@ int __init chsc_init(void) { int ret; - sei_page = (void *)get_zeroed_page(GFP_KERNEL); - chsc_page = (void *)get_zeroed_page(GFP_KERNEL); + sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sei_page || !chsc_page) { ret = -ENOMEM; goto out_err; diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c index 73413417a2ce..b6cb8bb8bcc4 100644 --- a/drivers/s390/cio/chsc_sch.c +++ b/drivers/s390/cio/chsc_sch.c @@ -292,7 +292,7 @@ static int chsc_ioctl_start(void __user *user_area) if (!css_general_characteristics.dynio) /* It makes no sense to try. */ return -EOPNOTSUPP; - chsc_area = (void *)get_zeroed_page(GFP_KERNEL); + chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); if (!chsc_area) return -ENOMEM; request = kzalloc_obj(*request); @@ -340,7 +340,7 @@ static int chsc_ioctl_on_close_set(void __user *user_area) ret = -ENOMEM; goto out_unlock; } - on_close_chsc_area = (void *)get_zeroed_page(GFP_KERNEL); + on_close_chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); if (!on_close_chsc_area) { ret = -ENOMEM; goto out_free_request; @@ -392,7 +392,7 @@ static int chsc_ioctl_start_sync(void __user *user_area) struct chsc_sync_area *chsc_area; int ret, ccode; - chsc_area = (void *)get_zeroed_page(GFP_KERNEL); + chsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!chsc_area) return -ENOMEM; if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) { @@ -438,7 +438,7 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *scpcd_area; - scpcd_area = (void *)get_zeroed_page(GFP_KERNEL); + scpcd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!scpcd_area) return -ENOMEM; cd = kzalloc_obj(*cd); @@ -500,7 +500,7 @@ static int chsc_ioctl_info_cu(void __user *user_cd) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *scucd_area; - scucd_area = (void *)get_zeroed_page(GFP_KERNEL); + scucd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!scucd_area) return -ENOMEM; cd = kzalloc_obj(*cd); @@ -563,7 +563,7 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *sscud_area; - sscud_area = (void *)get_zeroed_page(GFP_KERNEL); + sscud_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sscud_area) return -ENOMEM; cud = kzalloc_obj(*cud); @@ -625,7 +625,7 @@ static int chsc_ioctl_conf_info(void __user *user_ci) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *sci_area; - sci_area = (void *)get_zeroed_page(GFP_KERNEL); + sci_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sci_area) return -ENOMEM; ci = kzalloc_obj(*ci); @@ -696,7 +696,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) u32 res; } __attribute__ ((packed)) *cssids_parm; - sccl_area = (void *)get_zeroed_page(GFP_KERNEL); + sccl_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sccl_area) return -ENOMEM; ccl = kzalloc_obj(*ccl); @@ -756,7 +756,7 @@ static int chsc_ioctl_chpd(void __user *user_chpd) int ret; chpd = kzalloc_obj(*chpd); - scpd_area = (void *)get_zeroed_page(GFP_KERNEL); + scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!scpd_area || !chpd) { ret = -ENOMEM; goto out_free; @@ -796,7 +796,7 @@ static int chsc_ioctl_dcal(void __user *user_dcal) u8 data[PAGE_SIZE - 36]; } __attribute__ ((packed)) *sdcal_area; - sdcal_area = (void *)get_zeroed_page(GFP_KERNEL); + sdcal_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sdcal_area) return -ENOMEM; dcal = kzalloc_obj(*dcal); diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c index d13ed1011c03..171212a6d2d9 100644 --- a/drivers/s390/cio/scm.c +++ b/drivers/s390/cio/scm.c @@ -229,7 +229,7 @@ int scm_update_information(void) size_t num; int ret; - scm_info = (void *)__get_free_page(GFP_KERNEL); + scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); if (!scm_info) return -ENOMEM; -- cgit v1.2.3 From 63d2059cd665c4e5e4ca6cfbc58c6f171e7939b1 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 5 May 2026 12:09:02 -0400 Subject: pinctrl: imx1: Allow parsing DT without function nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old format to define pinctrl settings for imx in DT has two hierarchy levels. The first level are function device nodes. The second level are pingroups which contain a property fsl,pins. The original ntention was to define all pin functions in a single dtsi file and just reference the correct ones in the board files. The commit ("5fcdf6a7ed95e pinctrl: imx: Allow parsing DT without function nodes") already make moden i.MX chip support flatten layout. Make legacy chipes (more than 15 years) support this flatten layout also. Fixes: e948cbdc41d6f ("ARM: dts: imx: remove redundant intermediate node in pinmux hierarchy") Tested-by: Sébastien Szymanski Signed-off-by: Frank Li Signed-off-by: Linus Walleij --- drivers/pinctrl/freescale/pinctrl-imx1-core.c | 48 +++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/pinctrl/freescale/pinctrl-imx1-core.c b/drivers/pinctrl/freescale/pinctrl-imx1-core.c index b36c8a1461b7..b7bd4ef9c0db 100644 --- a/drivers/pinctrl/freescale/pinctrl-imx1-core.c +++ b/drivers/pinctrl/freescale/pinctrl-imx1-core.c @@ -540,10 +540,34 @@ static int imx1_pinctrl_parse_functions(struct device_node *np, return 0; } +/* + * Check if the DT contains pins in the direct child nodes. This indicates the + * newer DT format to store pins. This function returns true if the first found + * fsl,pins property is in a child of np. Otherwise false is returned. + */ +static bool imx1_pinctrl_dt_is_flat_functions(struct device_node *np) +{ + struct device_node *function_np; + struct device_node *pinctrl_np; + + for_each_child_of_node(np, function_np) { + if (of_property_present(function_np, "fsl,pins")) + return true; + + for_each_child_of_node(function_np, pinctrl_np) { + if (of_property_present(pinctrl_np, "fsl,pins")) + return false; + } + } + + return true; +} + static int imx1_pinctrl_parse_dt(struct platform_device *pdev, struct imx1_pinctrl *pctl, struct imx1_pinctrl_soc_info *info) { struct device_node *np = pdev->dev.of_node; + bool flat_funcs; int ret; u32 nfuncs = 0; u32 ngroups = 0; @@ -552,9 +576,15 @@ static int imx1_pinctrl_parse_dt(struct platform_device *pdev, if (!np) return -ENODEV; - for_each_child_of_node_scoped(np, child) { - ++nfuncs; - ngroups += of_get_child_count(child); + flat_funcs = imx1_pinctrl_dt_is_flat_functions(np); + if (flat_funcs) { + nfuncs = 1; + ngroups = of_get_child_count(np); + } else { + for_each_child_of_node_scoped(np, child) { + ++nfuncs; + ngroups += of_get_child_count(child); + } } if (!nfuncs) { @@ -574,10 +604,14 @@ static int imx1_pinctrl_parse_dt(struct platform_device *pdev, if (!info->functions || !info->groups) return -ENOMEM; - for_each_child_of_node_scoped(np, child) { - ret = imx1_pinctrl_parse_functions(child, info, ifunc++); - if (ret == -ENOMEM) - return -ENOMEM; + if (flat_funcs) { + imx1_pinctrl_parse_functions(np, info, 0); + } else { + for_each_child_of_node_scoped(np, child) { + ret = imx1_pinctrl_parse_functions(child, info, ifunc++); + if (ret == -ENOMEM) + return -ENOMEM; + } } return 0; -- cgit v1.2.3 From 5e121a81667a83e9a01d62b429e340f5a4a84abc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 12 May 2026 09:48:49 +0200 Subject: spi: ep93xx: fix error pointer deref after DMA setup failure The driver falls back to PIO mode if DMA setup fails during probe. Make sure to the clear the DMA channel pointers on setup failure to avoid dereferencing an error pointer on later probe errors or driver unbind. This issue was flagged by Sashiko when reviewing a devres allocation conversion patch. Fixes: e79e7c2df627 ("spi: ep93xx: add DT support for Cirrus EP93xx") Link: https://sashiko.dev/#/patchset/20260429091333.165363-1-johan%40kernel.org?part=10 Cc: stable@vger.kernel.org # 6.12 Cc: Nikita Shubin Signed-off-by: Johan Hovold Acked-by: Nikita Shubin Link: https://patch.msgid.link/20260512074849.915143-1-johan@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-ep93xx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/spi/spi-ep93xx.c b/drivers/spi/spi-ep93xx.c index db50018050e5..f716c9607be4 100644 --- a/drivers/spi/spi-ep93xx.c +++ b/drivers/spi/spi-ep93xx.c @@ -582,12 +582,14 @@ static int ep93xx_spi_setup_dma(struct device *dev, struct ep93xx_spi *espi) espi->dma_rx = dma_request_chan(dev, "rx"); if (IS_ERR(espi->dma_rx)) { ret = dev_err_probe(dev, PTR_ERR(espi->dma_rx), "rx DMA setup failed"); + espi->dma_rx = NULL; goto fail_free_page; } espi->dma_tx = dma_request_chan(dev, "tx"); if (IS_ERR(espi->dma_tx)) { ret = dev_err_probe(dev, PTR_ERR(espi->dma_tx), "tx DMA setup failed"); + espi->dma_tx = NULL; goto fail_release_rx; } -- cgit v1.2.3 From 55dda532bbc261aef495e403c8900c5e2ab5fa34 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Wed, 6 May 2026 15:42:38 +0200 Subject: wifi: ath11k: fix error path leaks in some WMI WOW calls Fix two instances where we used to directly return the result of ath11k_wmi_cmd_send(...). Because we did not check the return value, we also did not free the skb in the error path. Fixes: 79802b13a492 ("ath11k: implement WoW enable and wakeup commands") Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260506134240.2284016-2-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 40747fba3b0c..024c2aad9fb4 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -9332,6 +9332,7 @@ int ath11k_wmi_wow_host_wakeup_ind(struct ath11k *ar) struct wmi_wow_host_wakeup_ind *cmd; struct sk_buff *skb; size_t len; + int ret; len = sizeof(*cmd); skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len); @@ -9345,14 +9346,20 @@ int ath11k_wmi_wow_host_wakeup_ind(struct ath11k *ar) ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "tlv wow host wakeup ind\n"); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_HOSTWAKEUP_FROM_SLEEP_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_HOSTWAKEUP_FROM_SLEEP_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_WOW_HOSTWAKEUP_FROM_SLEEP_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_wow_enable(struct ath11k *ar) { struct wmi_wow_enable_cmd *cmd; struct sk_buff *skb; - int len; + int ret, len; len = sizeof(*cmd); skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len); @@ -9367,7 +9374,13 @@ int ath11k_wmi_wow_enable(struct ath11k *ar) cmd->pause_iface_config = WOW_IFACE_PAUSE_ENABLED; ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "tlv wow enable\n"); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ENABLE_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ENABLE_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_WOW_ENABLE_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_scan_prob_req_oui(struct ath11k *ar, -- cgit v1.2.3 From ebad0b48996fd4919c36bbcb07289d37d046de74 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Wed, 6 May 2026 15:42:39 +0200 Subject: wifi: ath11k: fix error path leaks in some WMI calls This is the same pattern that was previously identified as problematic: direct 'return ath11k_wmi_cmd_send(...)' will leak the skb in the error path if it is not explicitly handled. Fixes: c417b247ba04 ("ath11k: implement hardware data filter") Fixes: 9cbd7fc9be82 ("ath11k: support MAC address randomization in scan") Fixes: ba9177fcef21 ("ath11k: Add basic WoW functionalities") Fixes: fec4b898f369 ("ath11k: Add WoW net-detect functionality") Fixes: c3c36bfe998b ("ath11k: support ARP and NS offload") Fixes: a16d9b50cfba ("ath11k: support GTK rekey offload") Fixes: 652f69ed9c1b ("ath11k: Add support for SAR") Fixes: 0f84a156aa3b ("ath11k: Handle keepalive during WoWLAN suspend and resume") Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260506134240.2284016-3-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/wmi.c | 112 +++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index 024c2aad9fb4..dca6e011cc40 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -9299,7 +9299,7 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id, { struct wmi_hw_data_filter_cmd *cmd; struct sk_buff *skb; - int len; + int ret, len; len = sizeof(*cmd); skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len); @@ -9324,7 +9324,13 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id, "hw data filter enable %d filter_bitmap 0x%x\n", enable, filter_bitmap); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_HW_DATA_FILTER_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_wow_host_wakeup_ind(struct ath11k *ar) @@ -9389,7 +9395,7 @@ int ath11k_wmi_scan_prob_req_oui(struct ath11k *ar, struct sk_buff *skb; struct wmi_scan_prob_req_oui_cmd *cmd; u32 prob_req_oui; - int len; + int ret, len; prob_req_oui = (((u32)mac_addr[0]) << 16) | (((u32)mac_addr[1]) << 8) | mac_addr[2]; @@ -9408,7 +9414,13 @@ int ath11k_wmi_scan_prob_req_oui(struct ath11k *ar, ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "scan prob req oui %d\n", prob_req_oui); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_SCAN_PROB_REQ_OUI_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_SCAN_PROB_REQ_OUI_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_SCAN_PROB_REQ_OUI_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_wow_add_wakeup_event(struct ath11k *ar, u32 vdev_id, @@ -9418,6 +9430,7 @@ int ath11k_wmi_wow_add_wakeup_event(struct ath11k *ar, u32 vdev_id, struct wmi_wow_add_del_event_cmd *cmd; struct sk_buff *skb; size_t len; + int ret; len = sizeof(*cmd); skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len); @@ -9435,7 +9448,13 @@ int ath11k_wmi_wow_add_wakeup_event(struct ath11k *ar, u32 vdev_id, ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "tlv wow add wakeup event %s enable %d vdev_id %d\n", wow_wakeup_event(event), enable, vdev_id); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ENABLE_DISABLE_WAKE_EVENT_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ENABLE_DISABLE_WAKE_EVENT_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_WOW_ENABLE_DISABLE_WAKE_EVENT_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_wow_add_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id, @@ -9448,6 +9467,7 @@ int ath11k_wmi_wow_add_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id, struct sk_buff *skb; u8 *ptr; size_t len; + int ret; len = sizeof(*cmd) + sizeof(*tlv) + /* array struct */ @@ -9540,7 +9560,13 @@ int ath11k_wmi_wow_add_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id, ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "tlv wow add pattern vdev_id %d pattern_id %d pattern_offset %d\n", vdev_id, pattern_id, pattern_offset); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ADD_WAKE_PATTERN_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_ADD_WAKE_PATTERN_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_WOW_ADD_WAKE_PATTERN_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_wow_del_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id) @@ -9548,6 +9574,7 @@ int ath11k_wmi_wow_del_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id) struct wmi_wow_del_pattern_cmd *cmd; struct sk_buff *skb; size_t len; + int ret; len = sizeof(*cmd); skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len); @@ -9566,7 +9593,13 @@ int ath11k_wmi_wow_del_pattern(struct ath11k *ar, u32 vdev_id, u32 pattern_id) ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "tlv wow del pattern vdev_id %d pattern_id %d\n", vdev_id, pattern_id); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_DEL_WAKE_PATTERN_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_WOW_DEL_WAKE_PATTERN_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_WOW_DEL_WAKE_PATTERN_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } static struct sk_buff * @@ -9710,6 +9743,7 @@ int ath11k_wmi_wow_config_pno(struct ath11k *ar, u32 vdev_id, struct wmi_pno_scan_req *pno_scan) { struct sk_buff *skb; + int ret; if (pno_scan->enable) skb = ath11k_wmi_op_gen_config_pno_start(ar, vdev_id, pno_scan); @@ -9719,7 +9753,13 @@ int ath11k_wmi_wow_config_pno(struct ath11k *ar, u32 vdev_id, if (IS_ERR_OR_NULL(skb)) return -ENOMEM; - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_NETWORK_LIST_OFFLOAD_CONFIG_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_NETWORK_LIST_OFFLOAD_CONFIG_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_NETWORK_LIST_OFFLOAD_CONFIG_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } static void ath11k_wmi_fill_ns_offload(struct ath11k *ar, @@ -9837,6 +9877,7 @@ int ath11k_wmi_arp_ns_offload(struct ath11k *ar, u8 *buf_ptr; size_t len; u8 ns_cnt, ns_ext_tuples = 0; + int ret; offload = &arvif->arp_ns_offload; ns_cnt = offload->ipv6_count; @@ -9875,7 +9916,13 @@ int ath11k_wmi_arp_ns_offload(struct ath11k *ar, if (ns_ext_tuples) ath11k_wmi_fill_ns_offload(ar, offload, &buf_ptr, enable, 1); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_SET_ARP_NS_OFFLOAD_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_SET_ARP_NS_OFFLOAD_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_SET_ARP_NS_OFFLOAD_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_gtk_rekey_offload(struct ath11k *ar, @@ -9883,7 +9930,7 @@ int ath11k_wmi_gtk_rekey_offload(struct ath11k *ar, { struct wmi_gtk_rekey_offload_cmd *cmd; struct ath11k_rekey_data *rekey_data = &arvif->rekey_data; - int len; + int ret, len; struct sk_buff *skb; __le64 replay_ctr; @@ -9917,14 +9964,20 @@ int ath11k_wmi_gtk_rekey_offload(struct ath11k *ar, ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "offload gtk rekey vdev: %d %d\n", arvif->vdev_id, enable); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_GTK_OFFLOAD_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_GTK_OFFLOAD_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_GTK_OFFLOAD_CMDID offload\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_gtk_rekey_getinfo(struct ath11k *ar, struct ath11k_vif *arvif) { struct wmi_gtk_rekey_offload_cmd *cmd; - int len; + int ret, len; struct sk_buff *skb; len = sizeof(*cmd); @@ -9941,7 +9994,13 @@ int ath11k_wmi_gtk_rekey_getinfo(struct ath11k *ar, ath11k_dbg(ar->ab, ATH11K_DBG_WMI, "get gtk rekey vdev_id: %d\n", arvif->vdev_id); - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_GTK_OFFLOAD_CMDID); + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_GTK_OFFLOAD_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_GTK_OFFLOAD_CMDID getinfo\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_pdev_set_bios_sar_table_param(struct ath11k *ar, const u8 *sar_val) @@ -9951,6 +10010,7 @@ int ath11k_wmi_pdev_set_bios_sar_table_param(struct ath11k *ar, const u8 *sar_va struct sk_buff *skb; u8 *buf_ptr; u32 len, sar_len_aligned, rsvd_len_aligned; + int ret; sar_len_aligned = roundup(BIOS_SAR_TABLE_LEN, sizeof(u32)); rsvd_len_aligned = roundup(BIOS_SAR_RSVD1_LEN, sizeof(u32)); @@ -9981,7 +10041,13 @@ int ath11k_wmi_pdev_set_bios_sar_table_param(struct ath11k *ar, const u8 *sar_va tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) | FIELD_PREP(WMI_TLV_LEN, rsvd_len_aligned); - return ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_SET_BIOS_SAR_TABLE_CMDID); + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_SET_BIOS_SAR_TABLE_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_PDEV_SET_BIOS_SAR_TABLE_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_pdev_set_bios_geo_table_param(struct ath11k *ar) @@ -9992,6 +10058,7 @@ int ath11k_wmi_pdev_set_bios_geo_table_param(struct ath11k *ar) struct sk_buff *skb; u8 *buf_ptr; u32 len, rsvd_len_aligned; + int ret; rsvd_len_aligned = roundup(BIOS_SAR_RSVD2_LEN, sizeof(u32)); len = sizeof(*cmd) + TLV_HDR_SIZE + rsvd_len_aligned; @@ -10011,7 +10078,13 @@ int ath11k_wmi_pdev_set_bios_geo_table_param(struct ath11k *ar) tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_BYTE) | FIELD_PREP(WMI_TLV_LEN, rsvd_len_aligned); - return ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_SET_BIOS_GEO_TABLE_CMDID); + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_SET_BIOS_GEO_TABLE_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_PDEV_SET_BIOS_GEO_TABLE_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } int ath11k_wmi_sta_keepalive(struct ath11k *ar, @@ -10022,6 +10095,7 @@ int ath11k_wmi_sta_keepalive(struct ath11k *ar, struct wmi_sta_keepalive_arp_resp *arp; struct sk_buff *skb; size_t len; + int ret; len = sizeof(*cmd) + sizeof(*arp); skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, len); @@ -10053,7 +10127,13 @@ int ath11k_wmi_sta_keepalive(struct ath11k *ar, "sta keepalive vdev %d enabled %d method %d interval %d\n", arg->vdev_id, arg->enabled, arg->method, arg->interval); - return ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID); + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID); + if (ret) { + ath11k_warn(ar->ab, "failed to send WMI_STA_KEEPALIVE_CMDID\n"); + dev_kfree_skb(skb); + } + + return ret; } bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar) -- cgit v1.2.3 From 7320d6eb861e9913193a7801834c661381756a79 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Wed, 6 May 2026 15:42:40 +0200 Subject: wifi: ath11k: fix error path leak in ath11k_tm_cmd_wmi_ftm() This is similar to what was fixed by previous patches. We have a call to ath11k_wmi_cmd_send() which does check the return value, but forgot to free the related skb on error. Fixes: b43310e44edc ("wifi: ath11k: factory test mode support") Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260506134240.2284016-4-nico.escande@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/testmode.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/testmode.c b/drivers/net/wireless/ath/ath11k/testmode.c index a9751ea2a0b7..c72eed358f6d 100644 --- a/drivers/net/wireless/ath/ath11k/testmode.c +++ b/drivers/net/wireless/ath/ath11k/testmode.c @@ -457,6 +457,7 @@ static int ath11k_tm_cmd_wmi_ftm(struct ath11k *ar, struct nlattr *tb[]) ret = ath11k_wmi_cmd_send(wmi, skb, cmd_id); if (ret) { ath11k_warn(ar->ab, "failed to send wmi ftm command: %d\n", ret); + dev_kfree_skb(skb); goto out; } -- cgit v1.2.3 From 54a5b38e4396530e5b2f12b54d3844e860ab6784 Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Tue, 28 Apr 2026 14:17:37 +0800 Subject: wifi: ath10k: skip WMI and beacon transmission when device is wedged In ath10k_wmi_cmd_send(), the current code detects ATH10K_STATE_WEDGED and sets ret to -ESHUTDOWN, but still proceeds to transmit pending beacons and calls ath10k_wmi_cmd_send_nowait(). This can lead to incorrect behavior, as WMI commands and beacons are still sent after the device has been marked as wedged, and the original -ESHUTDOWN return value may be overwritten by the result of the send path. The wedged state indicates the hardware is already unreliable, and no further interaction with firmware is expected or meaningful in this state. Fix this by skipping beacon transmission and the WMI send path entirely once ATH10K_STATE_WEDGED is detected, ensuring consistent return values and avoiding unnecessary firmware interaction. Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1 Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189 Fixes: c256a94d1b1b ("wifi: ath10k: shutdown driver when hardware is unreliable") Signed-off-by: Kang Yang Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260428061737.37-1-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath10k/wmi.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c index 0bdb38edd915..e57588c19c80 100644 --- a/drivers/net/wireless/ath/ath10k/wmi.c +++ b/drivers/net/wireless/ath/ath10k/wmi.c @@ -3,7 +3,6 @@ * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. - * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ @@ -1947,15 +1946,15 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id) ret = -ESHUTDOWN; ath10k_dbg(ar, ATH10K_DBG_WMI, "drop wmi command %d, hardware is wedged\n", cmd_id); - } - /* try to send pending beacons first. they take priority */ - ath10k_wmi_tx_beacons_nowait(ar); + } else { + /* try to send pending beacons first. they take priority */ + ath10k_wmi_tx_beacons_nowait(ar); - ret = ath10k_wmi_cmd_send_nowait(ar, skb, cmd_id); - - if (ret && test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) - ret = -ESHUTDOWN; + ret = ath10k_wmi_cmd_send_nowait(ar, skb, cmd_id); + if (ret && test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + ret = -ESHUTDOWN; + } (ret != -EAGAIN); }), 3 * HZ); -- cgit v1.2.3 From 3812a9e84265a5cdd90d29fe8d97a023e91fb945 Mon Sep 17 00:00:00 2001 From: Hardik Prakash Date: Tue, 12 May 2026 13:01:38 +0530 Subject: pinctrl-amd: enable IRQ for WACF2200 touchscreen on Lenovo Yoga 7 14AGP11 On Lenovo Yoga 7 14AGP11 (83TD), the WACF2200 touchscreen controller is wired via I2C2 (AMDI0010:02) with its interrupt on GPIO pin 157 (confirmed via ACPI _CRS GpioInt decode). After amd_gpio_irq_init() clears all GPIO interrupts at boot, pin 157 is never re-enabled, preventing the touchscreen from signalling the driver. Windows keeps GPIO 157 INTERRUPT_ENABLE (bit 11) and INTERRUPT_MASK (bit 12) set after initialisation. Add a DMI quirk to restore these bits after amd_gpio_irq_init() on this hardware. Assisted-by: Claude:claude-sonnet-4-6 Assisted-by: GPT-Codex:gpt-5.2-codex Signed-off-by: Hardik Prakash Acked-by: Andy Shevchenko Signed-off-by: Linus Walleij --- drivers/pinctrl/pinctrl-amd.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'drivers') diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c index e3128b0045d2..64315b0edf2a 100644 --- a/drivers/pinctrl/pinctrl-amd.c +++ b/drivers/pinctrl/pinctrl-amd.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,39 @@ static struct amd_gpio *pinctrl_dev; #endif +static const struct dmi_system_id amd_gpio_quirk_yoga7_14agp11[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83TD"), + DMI_MATCH(DMI_BOARD_NAME, "LNVNB161216"), + }, + }, + { } +}; + +static void amd_gpio_apply_quirks(struct amd_gpio *gpio_dev) +{ + const unsigned int pin = 157; /* WACF2200 GpioInt per ACPI _CRS */ + unsigned long flags; + u32 reg; + + if (!dmi_check_system(amd_gpio_quirk_yoga7_14agp11)) + return; + if (pin >= gpio_dev->gc.ngpio) + return; + + raw_spin_lock_irqsave(&gpio_dev->lock, flags); + reg = readl(gpio_dev->base + pin * 4); + reg |= BIT(INTERRUPT_ENABLE_OFF) | BIT(INTERRUPT_MASK_OFF); + writel(reg, gpio_dev->base + pin * 4); + raw_spin_unlock_irqrestore(&gpio_dev->lock, flags); + + dev_info(&gpio_dev->pdev->dev, + "Enabled IRQ for GPIO %u (Yoga 7 14AGP11 touchscreen)\n", + pin); +} + static int amd_gpio_get_direction(struct gpio_chip *gc, unsigned offset) { unsigned long flags; @@ -1219,6 +1253,7 @@ static int amd_gpio_probe(struct platform_device *pdev) /* Disable and mask interrupts */ amd_gpio_irq_init(gpio_dev); + amd_gpio_apply_quirks(gpio_dev); girq = &gpio_dev->gc.irq; gpio_irq_chip_set_chip(girq, &amd_gpio_irqchip); -- cgit v1.2.3 From 933430f1709b089a0bf0b23ef0f047014ef899e7 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Tue, 14 Apr 2026 17:14:30 +0200 Subject: drm/msm/dpu: fix UV scanlines calculation for YUV UBWC formats The UV scanlines is calculated with (height + 1) / 2 unlike the Y scanlines, add back the correct scanlines calculation for UBWC YUV formats. Fixes: 2f3ff6ab8f5c ("drm/msm/dpu: use standard functions in _dpu_format_populate_plane_sizes_ubwc()") Fixes: ada4a19ed21c ("drm/msm/dpu: rewrite _dpu_format_populate_plane_sizes_ubwc()") Signed-off-by: Neil Armstrong Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/718309/ Link: https://lore.kernel.org/r/20260414-topic-sm8x50-msm-dpu1-formats-qc10c-v1-1-0b62325b9030@linaro.org Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c index 6e8883dbfad4..590922c4f69b 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c @@ -61,7 +61,7 @@ static int _dpu_format_populate_plane_sizes_ubwc( bool meta = MSM_FORMAT_IS_UBWC(fmt); if (MSM_FORMAT_IS_YUV(fmt)) { - unsigned int stride, sclines; + unsigned int stride, y_sclines, uv_sclines; unsigned int y_tile_width, y_tile_height; unsigned int y_meta_stride, y_meta_scanlines; unsigned int uv_meta_stride, uv_meta_scanlines; @@ -77,23 +77,25 @@ static int _dpu_format_populate_plane_sizes_ubwc( y_tile_width = 32; } - sclines = round_up(fb->height, 16); + y_sclines = round_up(fb->height, 16); + uv_sclines = round_up((fb->height+1)>>1, 16); y_tile_height = 4; } else { stride = round_up(fb->width, 128); y_tile_width = 32; - sclines = round_up(fb->height, 32); + y_sclines = round_up(fb->height, 32); + uv_sclines = round_up((fb->height+1)>>1, 32); y_tile_height = 8; } layout->plane_pitch[0] = stride; layout->plane_size[0] = round_up(layout->plane_pitch[0] * - sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + y_sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); layout->plane_pitch[1] = stride; layout->plane_size[1] = round_up(layout->plane_pitch[1] * - sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + uv_sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); if (!meta) return 0; -- cgit v1.2.3 From d03279f0d9fdbe6f6761f191a76093c395930018 Mon Sep 17 00:00:00 2001 From: Mahadevan P Date: Tue, 28 Apr 2026 17:14:25 +0530 Subject: drm/msm/dpu: Fix Kaanapali CWB register configuration The Kaanapali DPU catalog defines kaanapali_cwb[] with the correct CWB base addresses for this platform (0x169200, 0x169600, 0x16a200, 0x16a600), but the dpu_kaanapali_cfg struct was mistakenly pointing to sm8650_cwb instead. The SM8650 CWB blocks sit at completely different offsets (0x66200, 0x66600, 0x7E200, 0x7E600), so using them on Kaanapali would program CWB registers at wrong addresses, corrupting unrelated hardware blocks and breaking writeback capture. Fix this by pointing .cwb to the correct kaanapali_cwb array. Fixes: 83fe2cd56b1d ("drm/msm/dpu: Add support for Kaanapali DPU") Signed-off-by: Mahadevan P Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/721444/ Link: https://lore.kernel.org/r/20260428-kaanapali_cwb-v1-1-51fdb2c65498@oss.qualcomm.com Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h index b7b06e45b529..06da1583fb1e 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h +++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_13_0_kaanapali.h @@ -480,7 +480,7 @@ const struct dpu_mdss_cfg dpu_kaanapali_cfg = { .wb_count = ARRAY_SIZE(kaanapali_wb), .wb = kaanapali_wb, .cwb_count = ARRAY_SIZE(kaanapali_cwb), - .cwb = sm8650_cwb, + .cwb = kaanapali_cwb, .intf_count = ARRAY_SIZE(kaanapali_intf), .intf = kaanapali_intf, .vbif = &sm8650_vbif, -- cgit v1.2.3 From 5b49a46baa853b26dbefa65c6c75dd9ff69f63d4 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 28 Apr 2026 20:21:38 +0300 Subject: drm/msm/dsi: don't dump registers past the mapped region On DSI 6G platforms the IO address space is internally adjusted by io_offset. Later this adjusted address might be used for memory dumping. However the size that is used for memory dumping isn't adjusted to account for the io_offset, leading to the potential access to the unmapped region. Lower ctrl_size by the io_offset value to prevent access past the mapped area. msm_disp_snapshot_add_block+0x1d4/0x3c8 [msm] (P) msm_dsi_host_snapshot+0x4c/0x78 [msm] msm_dsi_snapshot+0x28/0x50 [msm] msm_disp_snapshot_capture_state+0x74/0x140 [msm] msm_disp_snapshot_state_sync+0x60/0x90 [msm] _msm_disp_snapshot_work+0x30/0x90 [msm] kthread_worker_fn+0xdc/0x460 kthread+0x120/0x140 Fixes: bac2c6a62ed9 ("drm/msm: get rid of msm_iomap_size") Signed-off-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Patchwork: https://patchwork.freedesktop.org/patch/721747/ Link: https://lore.kernel.org/r/20260428-msm-fix-dsi-dump-v1-1-5d4cb5ccfac7@oss.qualcomm.com --- drivers/gpu/drm/msm/dsi/dsi_host.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index 565d425f88b8..982abaaac00d 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -2033,6 +2033,7 @@ int msm_dsi_host_init(struct msm_dsi *msm_dsi) /* fixup base address by io offset */ msm_host->ctrl_base += cfg->io_offset; + msm_host->ctrl_size -= cfg->io_offset; ret = devm_regulator_bulk_get_const(&pdev->dev, cfg->num_regulators, cfg->regulator_data, -- cgit v1.2.3 From c0c70a11365cba7fba25a77463582bcec0f7846e Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Tue, 5 May 2026 03:24:58 +0300 Subject: drm/msm/dpu: don't mix devm and drmm functions Mixing devm and drmm functions will result in a use-after-free on msm driver teardown if userspace keeps a reference on the drm device: The WB connector data will be destroyed because of the use of devm_kzalloc()), while the usersoace still can try interacting with the WB connector (which uses drmm_ functions). Change dpu_writeback_init() to use drmm_. Fixes: 0b37ac63fc9d ("drm/msm/dpu: use drmm_writeback_connector_init()") Reported-by: Christophe JAILLET Closes: https://lore.kernel.org/r/78c764b8-44cf-4db5-88e7-807a85954518@wanadoo.fr Signed-off-by: Dmitry Baryshkov Reviewed-by: John.Harrison@Igalia.com Patchwork: https://patchwork.freedesktop.org/patch/722656/ Link: https://lore.kernel.org/r/20260505-wb-drop-encoder-v5-1-42567b7c7af2@oss.qualcomm.com --- drivers/gpu/drm/msm/disp/dpu1/dpu_writeback.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_writeback.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_writeback.c index 7545c0293efb..6f2370c9dd98 100644 --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_writeback.c +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_writeback.c @@ -5,6 +5,7 @@ #include #include +#include #include "dpu_writeback.h" @@ -125,7 +126,7 @@ int dpu_writeback_init(struct drm_device *dev, struct drm_encoder *enc, struct dpu_wb_connector *dpu_wb_conn; int rc = 0; - dpu_wb_conn = devm_kzalloc(dev->dev, sizeof(*dpu_wb_conn), GFP_KERNEL); + dpu_wb_conn = drmm_kzalloc(dev, sizeof(*dpu_wb_conn), GFP_KERNEL); if (!dpu_wb_conn) return -ENOMEM; -- cgit v1.2.3 From 0b28000b64f40dd29a730507aa0447231960cfb8 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Thu, 7 May 2026 20:50:10 +0800 Subject: RDMA/nldev: Add mutual exclusion in nldev_dellink() We must serialize calls to nldev_dellink() or risk a crash as syzbot reported: KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] Call Trace: udp_tunnel_sock_release+0x6d/0x80 net/ipv4/udp_tunnel_core.c:197 rxe_release_udp_tunnel drivers/infiniband/sw/rxe/rxe_net.c:294 [inline] rxe_sock_put drivers/infiniband/sw/rxe/rxe_net.c:639 [inline] rxe_net_del+0xfb/0x290 drivers/infiniband/sw/rxe/rxe_net.c:660 rxe_dellink+0x15/0x20 drivers/infiniband/sw/rxe/rxe.c:254 Fixes: a60e3f3d6fba ("RDMA/nldev: Add dellink function pointer") Reported-by: syzbot+d8f76778263ab65c2b21@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d8f76778263ab65c2b21 Tested-by: syzbot+d8f76778263ab65c2b21@syzkaller.appspotmail.com Signed-off-by: Edward Adam Davis Link: https://patch.msgid.link/tencent_611BEB4B141B1A2526BAA3BBB2335F9E9108@qq.com Reviewed-by: Zhu Yanjun Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/nldev.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/infiniband/core/nldev.c b/drivers/infiniband/core/nldev.c index 96c745d5bac4..5aaba2b9746b 100644 --- a/drivers/infiniband/core/nldev.c +++ b/drivers/infiniband/core/nldev.c @@ -51,6 +51,7 @@ * a controlled QKEY. */ static bool privileged_qkey; +static DEFINE_MUTEX(nldev_dellink_mutex); typedef int (*res_fill_func_t)(struct sk_buff*, bool, struct rdma_restrack_entry*, uint32_t); @@ -1846,7 +1847,9 @@ static int nldev_dellink(struct sk_buff *skb, struct nlmsghdr *nlh, * implicitly scoped to the driver supporting dynamic link deletion like RXE. */ if (device->link_ops && device->link_ops->dellink) { + mutex_lock(&nldev_dellink_mutex); err = device->link_ops->dellink(device); + mutex_unlock(&nldev_dellink_mutex); if (err) return err; } -- cgit v1.2.3 From f6b079629becfa977f9c51fe53ad2e6dcc55ef44 Mon Sep 17 00:00:00 2001 From: Lord Ulf Henrik Holmberg Date: Sat, 9 May 2026 10:40:11 +0200 Subject: RDMA/bnxt_re: zero shared page before exposing to userspace bnxt_re_alloc_ucontext() allocates uctx->shpg via __get_free_page(GFP_KERNEL). The buddy allocator does not zero pages without __GFP_ZERO, so the page contains stale kernel data from whatever object most recently freed it. The page is then mapped into userspace via vm_insert_page() under BNXT_RE_MMAP_SH_PAGE in bnxt_re_mmap(). The driver only ever writes 4 bytes (a u32 AVID) at offset BNXT_RE_AVID_OFFT (0x10) inside bnxt_re_create_ah(); the remaining 4092 bytes of the page are exposed to userspace unsanitised, leaking kernel memory contents. Any user with access to /dev/infiniband/uverbsX on a host with a bnxt_re device (typically rdma group membership) can read this data via a single mmap() at pgoff 0 after IB_USER_VERBS_CMD_GET_CONTEXT. Other shared pages in the same file already use get_zeroed_page() correctly: drivers/infiniband/hw/bnxt_re/ib_verbs.c srq->uctx_srq_page = (void *)get_zeroed_page(GFP_KERNEL); cq->uctx_cq_page = (void *)get_zeroed_page(GFP_KERNEL); uctx->shpg is the only outlier. Bring it in line with the existing convention by switching to get_zeroed_page(). Fixes: 1ac5a4047975 ("RDMA/bnxt_re: Add bnxt_re RoCE driver") Signed-off-by: Lord Ulf Henrik Holmberg Link: https://patch.msgid.link/20260509084011.11971-1-pomzm67@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/bnxt_re/ib_verbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/infiniband/hw/bnxt_re/ib_verbs.c b/drivers/infiniband/hw/bnxt_re/ib_verbs.c index 7ed294516b7e..365ec2767d25 100644 --- a/drivers/infiniband/hw/bnxt_re/ib_verbs.c +++ b/drivers/infiniband/hw/bnxt_re/ib_verbs.c @@ -4638,7 +4638,7 @@ int bnxt_re_alloc_ucontext(struct ib_ucontext *ctx, struct ib_udata *udata) uctx->rdev = rdev; - uctx->shpg = (void *)__get_free_page(GFP_KERNEL); + uctx->shpg = (void *)get_zeroed_page(GFP_KERNEL); if (!uctx->shpg) { rc = -ENOMEM; goto fail; -- cgit v1.2.3 From 0e7c710478b3089cdfe8669347f77b163e836c4f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 21:20:30 +0200 Subject: cpufreq: intel_pstate: Use correct scaling factor on Raptor Lake-E Raptor Lake-E has the same processor ID as Raptor Lake-S, so there is an entry in intel_hybrid_scaling_factor[] for it. It does not contain E-cores though and hybrid_get_cpu_type() returns 0 for its P-cores, so they get the default "core" scaling factor. However, the original Raptor Lake scaling factor for P-cores still needs to be used for mapping the HWP performance levels of the P-cores in Raptor Lake-E to frequency, as though they were part of a real hybrid system. To address this, update hwp_get_cpu_scaling() to return hybrid_scaling_factor, which is the P-core scaling factor retrieved from intel_hybrid_scaling_factor[], for all CPUs that are not enumerated as E-cores. Fixes: 9b18d536b124 ("cpufreq: intel_pstate: Use CPPC to get scaling factors") Link: https://lore.kernel.org/all/20260511235328.2018458-1-srinivas.pandruvada@linux.intel.com/ Reported-by: Henry Tseng Closes: https://lore.kernel.org/linux-pm/20260508063032.3248602-1-henrytseng@qnap.com/ Signed-off-by: Rafael J. Wysocki Cc: All applicable Link: https://patch.msgid.link/4523296.ejJDZkT8p0@rafael.j.wysocki --- drivers/cpufreq/intel_pstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 1292da53e5fc..978e2b604ac8 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -2279,7 +2279,7 @@ static int hwp_get_cpu_scaling(int cpu) * Return the hybrid scaling factor for P-cores and use the * default core scaling for E-cores. */ - if (hybrid_get_cpu_type(cpu) == INTEL_CPU_TYPE_CORE) + if (hybrid_get_cpu_type(cpu) != INTEL_CPU_TYPE_ATOM) return hybrid_scaling_factor; return core_get_scaling(); -- cgit v1.2.3 From 85cffdb21bc1a928f4c89f88dcdf42a460b04e70 Mon Sep 17 00:00:00 2001 From: Henry Tseng Date: Wed, 13 May 2026 18:28:46 +0800 Subject: cpufreq: intel_pstate: Use HYBRID_SCALING_FACTOR_ADL for Bartlett Lake Bartlett Lake P-core only SKUs (e.g. Intel Core 9 273PE) do not report X86_FEATURE_HYBRID_CPU and are not in intel_hybrid_scaling_factor[]. In hwp_get_cpu_scaling(), the non-hybrid fallback then applies core_get_scaling() (100000), producing cpuinfo_max_freq values that exceed the documented Max Turbo Frequency: intel_pstate: CPU0: PERF_CTL turbo = 57 intel_pstate: CPU0: HWP_CAP guaranteed = 30 intel_pstate: CPU0: HWP_CAP highest = 70 intel_pstate: CPU0: HWP-to-frequency scaling factor: 100000 intel_pstate: set_policy cpuinfo.max 7000000 policy->max 7000000 ... intel_pstate: CPU12: PERF_CTL turbo = 57 intel_pstate: CPU12: HWP_CAP guaranteed = 30 intel_pstate: CPU12: HWP_CAP highest = 73 intel_pstate: CPU12: HWP-to-frequency scaling factor: 100000 intel_pstate: set_policy cpuinfo.max 7300000 policy->max 7300000 ... Per the Intel datasheet [1], the Intel Core 9 273PE specifies: Performance-cores: 12 Efficient-cores: 0 Max Turbo Frequency: 5.7 GHz Intel Thermal Velocity Boost Frequency: 5.7 GHz Intel Turbo Boost Max Technology 3.0 Frequency: 5.6 GHz Performance-core Max Turbo Frequency: 5.4 GHz Performance-core Base Frequency: 2.3 GHz Bartlett Lake P-cores are Raptor Cove cores, per commit d466304c4322 ("x86/cpu: Add CPU model number for Bartlett Lake CPUs with Raptor Cove cores"). The Alder Lake and Raptor Lake P-core entries in intel_hybrid_scaling_factor[] use HYBRID_SCALING_FACTOR_ADL (78741). The same factor applies to Bartlett Lake. Add Bartlett Lake to intel_hybrid_scaling_factor[] with HYBRID_SCALING_FACTOR_ADL so HWP performance levels map to the correct CPU frequencies matching the datasheet's Max Turbo Frequency. Link: https://www.intel.com/content/www/us/en/products/sku/245717/intel-core-9-processor-273pe-36m-cache-up-to-5-70-ghz/specifications.html [1] Signed-off-by: Henry Tseng [ rjw: Changelog tweak ] Link: https://patch.msgid.link/20260513102847.75179-1-henrytseng@qnap.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/intel_pstate.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 978e2b604ac8..1f093e346430 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -3734,6 +3734,7 @@ static const struct x86_cpu_id intel_hybrid_scaling_factor[] = { X86_MATCH_VFM(INTEL_RAPTORLAKE, HYBRID_SCALING_FACTOR_ADL), X86_MATCH_VFM(INTEL_RAPTORLAKE_P, HYBRID_SCALING_FACTOR_ADL), X86_MATCH_VFM(INTEL_RAPTORLAKE_S, HYBRID_SCALING_FACTOR_ADL), + X86_MATCH_VFM(INTEL_BARTLETTLAKE, HYBRID_SCALING_FACTOR_ADL), X86_MATCH_VFM(INTEL_METEORLAKE_L, HYBRID_SCALING_FACTOR_MTL), X86_MATCH_VFM(INTEL_LUNARLAKE_M, HYBRID_SCALING_FACTOR_LNL), {} -- cgit v1.2.3 From 46e351e84853dda726072bb3d38ba7bd63e7532b Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Sat, 14 Mar 2026 04:14:50 +0000 Subject: drm/msm: Fix GMEM_BASE for A650 Commit dc220915ddb2 ("drm/msm: Fix GMEM_BASE for gen8") changed the GMEM_BASE check from adreno_is_a650_family() & adreno_is_a740_family() to family >= ADRENO_6XX_GEN4. This inadvertently excluded A650 (ADRENO_6XX_GEN3), causing it to report an incorrect GMEM_BASE which results in severe rendering corruption. Update check to also include ADRENO_6XX_GEN3 to fix A650. Fixes: dc220915ddb2 ("drm/msm: Fix GMEM_BASE for gen8") Signed-off-by: Alexander Koskovich Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/711880/ Message-ID: <20260314-fix-gmem-base-a650-v1-1-3308f60cf74c@pm.me> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index 66f80f2d12f9..a812a4590cc0 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -376,7 +376,7 @@ int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, *value = adreno_gpu->info->gmem; return 0; case MSM_PARAM_GMEM_BASE: - if (adreno_gpu->info->family >= ADRENO_6XX_GEN4) + if (adreno_gpu->info->family >= ADRENO_6XX_GEN3) *value = 0; else *value = 0x100000; -- cgit v1.2.3 From e64bca63647db1d5518198d6c5ca2dbcc66b182b Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sat, 24 Jan 2026 00:37:38 +0800 Subject: drm/msm/adreno: Fix a reference leak in a6xx_gpu_init() In a6xx_gpu_init(), node is obtained via of_parse_phandle(). While there was a manual of_node_put() at the end of the common path, several early error returns would bypass this call, resulting in a reference leak. Fix this by using the __free(device_node) cleanup handler to release the reference when the variable goes out of scope. Fixes: 5a903a44a984 ("drm/msm/a6xx: Introduce GMU wrapper support") Signed-off-by: Felix Gu Patchwork: https://patchwork.freedesktop.org/patch/700661/ Message-ID: <20260124-a6xx_gpu-v1-1-fa0c8b2dcfb1@gmail.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 615509c8917e..1e455391fbac 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -2621,7 +2621,6 @@ static struct msm_gpu *a6xx_gpu_init(struct drm_device *dev) struct platform_device *pdev = priv->gpu_pdev; struct adreno_platform_config *config = pdev->dev.platform_data; const struct adreno_info *info = config->info; - struct device_node *node; struct a6xx_gpu *a6xx_gpu; struct adreno_gpu *adreno_gpu; struct msm_gpu *gpu; @@ -2643,7 +2642,8 @@ static struct msm_gpu *a6xx_gpu_init(struct drm_device *dev) adreno_gpu->registers = NULL; /* Check if there is a GMU phandle and set it up */ - node = of_parse_phandle(pdev->dev.of_node, "qcom,gmu", 0); + struct device_node *node __free(device_node) = + of_parse_phandle(pdev->dev.of_node, "qcom,gmu", 0); /* FIXME: How do we gracefully handle this? */ BUG_ON(!node); @@ -2690,7 +2690,6 @@ static struct msm_gpu *a6xx_gpu_init(struct drm_device *dev) ret = a6xx_gmu_wrapper_init(a6xx_gpu, node); else ret = a6xx_gmu_init(a6xx_gpu, node); - of_node_put(node); if (ret) { a6xx_destroy(&(a6xx_gpu->base.base)); return ERR_PTR(ret); -- cgit v1.2.3 From 2b4abf879360ea00a9e2b46d2d15dcdbc0687eed Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 11 Apr 2026 17:59:15 +0300 Subject: drm/msm/adreno: fix userspace-triggered crash on a2xx-a4xx Before a5xx Adreno driver will not try fetching UBWC params (because those generations didn't support UBWC anyway), however it's still possible to query UBWC-related params from the userspace, triggering possible NULL pointer dereference. Check for UBWC config in adreno_get_param() and return sane defaults if there is none. Fixes: a452510aad53 ("drm/msm/adreno: Switch to the common UBWC config struct") Signed-off-by: Dmitry Baryshkov Reviewed-by: Rob Clark Patchwork: https://patchwork.freedesktop.org/patch/717778/ Message-ID: <20260411-adreno-fix-ubwc-v3-1-4983156f3f80@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/adreno_gpu.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/adreno_gpu.c b/drivers/gpu/drm/msm/adreno/adreno_gpu.c index a812a4590cc0..03f96a1154e1 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_gpu.c +++ b/drivers/gpu/drm/msm/adreno/adreno_gpu.c @@ -424,15 +424,21 @@ int adreno_get_param(struct msm_gpu *gpu, struct msm_context *ctx, *value = vm->mm_range; return 0; case MSM_PARAM_HIGHEST_BANK_BIT: + if (!adreno_gpu->ubwc_config) + return UERR(ENOENT, drm, "no UBWC on this platform"); *value = adreno_gpu->ubwc_config->highest_bank_bit; return 0; case MSM_PARAM_RAYTRACING: *value = adreno_gpu->has_ray_tracing; return 0; case MSM_PARAM_UBWC_SWIZZLE: + if (!adreno_gpu->ubwc_config) + return UERR(ENOENT, drm, "no UBWC on this platform"); *value = adreno_gpu->ubwc_config->ubwc_swizzle; return 0; case MSM_PARAM_MACROTILE_MODE: + if (!adreno_gpu->ubwc_config) + return UERR(ENOENT, drm, "no UBWC on this platform"); *value = adreno_gpu->ubwc_config->macrotile_mode; return 0; case MSM_PARAM_UCHE_TRAP_BASE: -- cgit v1.2.3 From 7a529ff48b99011c946e6d8addd071c06d3ccdae Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 11 Apr 2026 08:03:12 -0700 Subject: drm/msm/a6xx: Restore sysprof_active This got lost in the shuffle somehow when moving the vfunc table to catalogue. Fixes inhibiting IFPC when userspace is collecting perfcntr data. Fixes: 491fadb2b818 ("drm/msm/adreno: Move adreno_gpu_func to catalogue") Signed-off-by: Rob Clark Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/717780/ Message-ID: <20260411150312.257937-1-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c index 1e455391fbac..99712f58e792 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c @@ -2739,6 +2739,7 @@ const struct adreno_gpu_funcs a6xx_gpu_funcs = { .create_private_vm = a6xx_create_private_vm, .get_rptr = a6xx_get_rptr, .progress = a6xx_progress, + .sysprof_setup = a6xx_gmu_sysprof_setup, }, .init = a6xx_gpu_init, .get_timestamp = a6xx_gmu_get_timestamp, @@ -2807,6 +2808,7 @@ const struct adreno_gpu_funcs a7xx_gpu_funcs = { .create_private_vm = a6xx_create_private_vm, .get_rptr = a6xx_get_rptr, .progress = a6xx_progress, + .sysprof_setup = a6xx_gmu_sysprof_setup, }, .init = a6xx_gpu_init, .get_timestamp = a6xx_gmu_get_timestamp, -- cgit v1.2.3 From 78d79c614aaa172ae1ddaea19a3885a9ff3ba857 Mon Sep 17 00:00:00 2001 From: Rob Clark Date: Sat, 18 Apr 2026 08:08:47 -0700 Subject: drm/msm: Correct modparam description Preemption is enabled for gen8 as well. Signed-off-by: Rob Clark Reviewed-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/719256/ Message-ID: <20260418150847.157246-1-robin.clark@oss.qualcomm.com> --- drivers/gpu/drm/msm/adreno/adreno_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c index 4edfe80c5be7..fc38331ce640 100644 --- a/drivers/gpu/drm/msm/adreno/adreno_device.c +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c @@ -17,7 +17,7 @@ MODULE_PARM_DESC(snapshot_debugbus, "Include debugbus sections in GPU devcoredum module_param_named(snapshot_debugbus, snapshot_debugbus, bool, 0600); int enable_preemption = -1; -MODULE_PARM_DESC(enable_preemption, "Enable preemption (A7xx only) (1=on , 0=disable, -1=auto (default))"); +MODULE_PARM_DESC(enable_preemption, "Enable preemption (A7xx+ only) (1=on , 0=disable, -1=auto (default))"); module_param(enable_preemption, int, 0600); bool disable_acd; -- cgit v1.2.3 From 55e0f0d1c1a4ee1e46da7da4d443eb3044fb3851 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Tue, 21 Apr 2026 13:02:38 +0900 Subject: drm/msm: Fix iommu_map_sgtable() return value check and avoid WARN Commit "iommu: return full error code from iommu_map_sg[_atomic]()" changed iommu_map_sgtable() to return an ssize_t and negative values in error cases, rather than a size_t and a zero. Store the return value in the appropriate type and in case of error, return it rather than WARNing. Fixes: ad8f36e4b6b1 ("iommu: return full error code from iommu_map_sg[_atomic]()") Signed-off-by: Mikko Perttunen Patchwork: https://patchwork.freedesktop.org/patch/719685/ Message-ID: <20260421-iommu_map_sgtable-return-v1-3-fb484c07d2a1@nvidia.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_iommu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/msm_iommu.c b/drivers/gpu/drm/msm/msm_iommu.c index 271baf4dc4e8..895d03b59da6 100644 --- a/drivers/gpu/drm/msm/msm_iommu.c +++ b/drivers/gpu/drm/msm/msm_iommu.c @@ -677,7 +677,7 @@ static int msm_iommu_map(struct msm_mmu *mmu, uint64_t iova, int prot) { struct msm_iommu *iommu = to_msm_iommu(mmu); - size_t ret; + ssize_t ret; WARN_ON(off != 0); @@ -686,7 +686,8 @@ static int msm_iommu_map(struct msm_mmu *mmu, uint64_t iova, iova |= GENMASK_ULL(63, 49); ret = iommu_map_sgtable(iommu->domain, iova, sgt, prot); - WARN_ON(!ret); + if (ret < 0) + return ret; return (ret == len) ? 0 : -EINVAL; } -- cgit v1.2.3 From b5c7a7f452b885bfbe102bd3a057a5f496802f8b Mon Sep 17 00:00:00 2001 From: Chen Ni Date: Tue, 28 Apr 2026 15:35:58 +0800 Subject: drm/msm/a6xx: Check kzalloc return in a8xx_hfi_send_perf_table Check the return value of kzalloc() to prevent a NULL pointer dereference on allocation failure. Fixes: 06cfbca0e1c6 ("drm/msm/a6xx: Share dependency vote table with GMU") Signed-off-by: Chen Ni Reviewed-by: Dmitry Baryshkov Reviewed-by: Akhil P Oommen Patchwork: https://patchwork.freedesktop.org/patch/721342/ Message-ID: <20260428073558.1234238-1-nichen@iscas.ac.cn> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/adreno/a6xx_hfi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/adreno/a6xx_hfi.c b/drivers/gpu/drm/msm/adreno/a6xx_hfi.c index 487c2736f2b3..186a73c0b99c 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_hfi.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_hfi.c @@ -289,6 +289,8 @@ static int a8xx_hfi_send_perf_table(struct a6xx_gmu *gmu) (gmu->nr_gpu_freqs * num_gx_votes * sizeof(gmu->gx_arc_votes[0])) + (gmu->nr_gmu_freqs * num_cx_votes * sizeof(gmu->cx_arc_votes[0])); tbl = kzalloc(size, GFP_KERNEL); + if (!tbl) + return -ENOMEM; tbl->type = HFI_TABLE_GPU_PERF; /* First fill GX votes */ -- cgit v1.2.3 From 50030d63b4d3deda4cbea85bb43cfc1785267621 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 13 May 2026 17:04:48 +0200 Subject: driver core: platform: remove software node on release() If we pass a software node to a newly created device using struct platform_device_info, it will not be removed when the device is released. This may happen when a module creating the device is removed or on failure in platform_device_add(). When we try to reuse that software node in a subsequent call to platform_device_register_full(), it will fail with -EBUSY. Provide a wrapper around the existing platform_device_release() that additionally calls device_remove_software_node() and use it to replace the former if we end up adding a software node. While at it: check all three possible situations in which two software nodes for a single platform device can be created/assigned in platform_device_register_full() and bail-out early. Fixes: 0fc434bc2c45 ("driver core: platform: allow attaching software nodes when creating devices") Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260513-swnode-remove-on-dev-unreg-v6-1-f9c58939df27@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/base/platform.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 75b4698d0e58..a19dd22deef2 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -606,6 +606,12 @@ static void platform_device_release(struct device *dev) kfree(pa); } +static void platform_device_release_full(struct device *dev) +{ + device_remove_software_node(dev); + platform_device_release(dev); +} + /** * platform_device_alloc - create a platform device * @name: base name of the device we're adding @@ -848,7 +854,13 @@ struct platform_device *platform_device_register_full(const struct platform_devi int ret; struct platform_device *pdev; - if (pdevinfo->swnode && pdevinfo->properties) + /* + * Only one software node per device is allowed. Make sure we don't + * accept or create two. + */ + if ((pdevinfo->swnode && pdevinfo->properties) || + (pdevinfo->swnode && is_software_node(pdevinfo->fwnode)) || + (pdevinfo->properties && is_software_node(pdevinfo->fwnode))) return ERR_PTR(-EINVAL); pdev = platform_device_alloc(pdevinfo->name, pdevinfo->id); @@ -878,6 +890,8 @@ struct platform_device *platform_device_register_full(const struct platform_devi ret = device_add_software_node(&pdev->dev, pdevinfo->swnode); if (ret) goto err; + + pdev->dev.release = platform_device_release_full; } else if (pdevinfo->properties) { ret = device_create_managed_software_node(&pdev->dev, pdevinfo->properties, NULL); -- cgit v1.2.3 From 57cf4e8d6a57dc2ef5810f4852a23ba4c71b74bb Mon Sep 17 00:00:00 2001 From: Saurav Sachidanand Date: Thu, 7 May 2026 22:11:44 +0000 Subject: i2c: tegra: fix pm_runtime leak on mutex_lock failure If tegra_i2c_mutex_lock() fails, the function returns without calling pm_runtime_put(), leaking the runtime PM reference acquired by the preceding pm_runtime_get_sync(). This prevents the device from ever entering runtime suspend. Add the missing pm_runtime_put() before returning on lock failure. Fixes: 6077cfd716fb ("i2c: tegra: Add support for SW mutex register") Signed-off-by: Saurav Sachidanand Cc: # v7.0+ Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260507221145.62183-2-sauravsc@amazon.com --- drivers/i2c/busses/i2c-tegra.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 9fd5ade774a0..c24b8de0a9c7 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -1666,8 +1666,10 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], } ret = tegra_i2c_mutex_lock(i2c_dev); - if (ret) + if (ret) { + pm_runtime_put(i2c_dev->dev); return ret; + } for (i = 0; i < num; i++) { enum msg_end_type end_type = MSG_END_STOP; -- cgit v1.2.3 From 30792d12842901f5276f466a960962d5bfa15cc8 Mon Sep 17 00:00:00 2001 From: Saurav Sachidanand Date: Thu, 7 May 2026 22:11:45 +0000 Subject: i2c: tegra: make tegra_i2c_mutex_unlock() return void tegra_i2c_mutex_unlock() returning an error that overwrites the transfer result causes silent loss of I2C transfer errors. If the transfer failed but the unlock succeeded, the error was lost and the function incorrectly reported success. Rather than propagating the unlock error (which is not actionable by the caller - the I2C message may have been sent regardless), convert the function to return void and WARN on the unexpected condition. If the unlock fails, subsequent lock attempts will fail anyway, making the error visible on the next transfer. Fixes: 6077cfd716fb ("i2c: tegra: Add support for SW mutex register") Signed-off-by: Saurav Sachidanand Cc: # v7.0+ Reviewed-by: Jon Hunter Acked-by: Thierry Reding Signed-off-by: Andi Shyti Link: https://lore.kernel.org/r/20260507221145.62183-3-sauravsc@amazon.com --- drivers/i2c/busses/i2c-tegra.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index c24b8de0a9c7..479a1667e88d 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -589,25 +589,22 @@ static int tegra_i2c_mutex_lock(struct tegra_i2c_dev *i2c_dev) return ret; } -static int tegra_i2c_mutex_unlock(struct tegra_i2c_dev *i2c_dev) +static void tegra_i2c_mutex_unlock(struct tegra_i2c_dev *i2c_dev) { unsigned int reg = i2c_dev->hw->regs->sw_mutex; u32 val, id; if (!i2c_dev->hw->has_mutex) - return 0; + return; val = readl(i2c_dev->base + reg); id = FIELD_GET(I2C_SW_MUTEX_GRANT, val); - if (id && id != I2C_SW_MUTEX_ID_CCPLEX) { - dev_warn(i2c_dev->dev, "unable to unlock mutex, mutex is owned by: %u\n", id); - return -EPERM; - } + if (WARN(id && id != I2C_SW_MUTEX_ID_CCPLEX, + "unable to unlock mutex, mutex is owned by: %u\n", id)) + return; writel(0, i2c_dev->base + reg); - - return 0; } static void tegra_i2c_mask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) @@ -1700,7 +1697,7 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], break; } - ret = tegra_i2c_mutex_unlock(i2c_dev); + tegra_i2c_mutex_unlock(i2c_dev); pm_runtime_put(i2c_dev->dev); return ret ?: i; -- cgit v1.2.3 From 03a2cc1756a0570f887d624cd6c535ea0cbd4951 Mon Sep 17 00:00:00 2001 From: Muchun Song Date: Tue, 28 Apr 2026 16:52:18 +0800 Subject: drivers/base/memory: fix memory block reference leak in poison accounting memblk_nr_poison_inc() and memblk_nr_poison_sub() look up a memory block via find_memory_block_by_id(), which acquires a reference to the memory block device. Both helpers use the returned memory block without dropping that reference, leaking the device reference on each successful lookup. Drop the reference after updating nr_hwpoison. Link: https://lore.kernel.org/20260428085219.1316047-3-songmuchun@bytedance.com Fixes: 5033091de814 ("mm/hwpoison: introduce per-memory_block hwpoison counter") Signed-off-by: Muchun Song Reviewed-by: Miaohe Lin Acked-by: Oscar Salvador Acked-by: David Hildenbrand (Arm) Cc: Danilo Krummrich Cc: Greg Kroah-Hartman Cc: "Huang, Ying" Cc: Naoya Horiguchi Cc: "Rafael J. Wysocki" Cc: Vishal Verma Cc: Signed-off-by: Andrew Morton --- drivers/base/memory.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index f806a683b767..6981b55d582a 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -1230,8 +1230,10 @@ void memblk_nr_poison_inc(unsigned long pfn) const unsigned long block_id = pfn_to_block_id(pfn); struct memory_block *mem = find_memory_block_by_id(block_id); - if (mem) + if (mem) { atomic_long_inc(&mem->nr_hwpoison); + put_device(&mem->dev); + } } void memblk_nr_poison_sub(unsigned long pfn, long i) @@ -1239,8 +1241,10 @@ void memblk_nr_poison_sub(unsigned long pfn, long i) const unsigned long block_id = pfn_to_block_id(pfn); struct memory_block *mem = find_memory_block_by_id(block_id); - if (mem) + if (mem) { atomic_long_sub(i, &mem->nr_hwpoison); + put_device(&mem->dev); + } } static unsigned long memblk_nr_poison(struct memory_block *mem) -- cgit v1.2.3 From e4e9b7b38d5db2cc6a8770bc0596bb8b36b92b1f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 12 May 2026 17:19:47 -0500 Subject: cpufreq/amd-pstate: Drop Kconfig option for dynamic EPP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are some performance issues being identified by dynamic EPP and we don't want to have distributions turning it on by default exposing them to users at this time. Drop the kconfig option, and require an explicit opt in from kernel command line or runtime sysfs option to turn it on. Reported-by: Viktor Jägersküpper Closes: https://lore.kernel.org/linux-pm/14a87c99-785c-4b16-bfce-35ecbf053448@freenet.de/ Reported-by: Stuart Meckle Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221473 Signed-off-by: Mario Limonciello Reviewed-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260512221947.1652988-1-mario.limonciello@amd.com (fix sysfs file path) Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/Kconfig.x86 | 12 ------------ drivers/cpufreq/amd-pstate.c | 4 ---- 2 files changed, 16 deletions(-) (limited to 'drivers') diff --git a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86 index 027e6ea2e038..a9093cd5e5d1 100644 --- a/drivers/cpufreq/Kconfig.x86 +++ b/drivers/cpufreq/Kconfig.x86 @@ -70,18 +70,6 @@ config X86_AMD_PSTATE_DEFAULT_MODE For details, take a look at: . -config X86_AMD_PSTATE_DYNAMIC_EPP - bool "AMD Processor P-State dynamic EPP support" - depends on X86_AMD_PSTATE - default n - help - Allow the kernel to dynamically change the energy performance - value from events like ACPI platform profile and AC adapter plug - events. - - This feature can also be changed at runtime, this configuration - option only sets the kernel default value behavior. - config X86_AMD_PSTATE_UT tristate "selftest for AMD Processor P-State driver" depends on X86 && ACPI_PROCESSOR diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 9eb9c3f4e809..62b5d995281d 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -87,11 +87,7 @@ static struct cpufreq_driver amd_pstate_driver; static struct cpufreq_driver amd_pstate_epp_driver; static int cppc_state = AMD_PSTATE_UNDEFINED; static bool amd_pstate_prefcore = true; -#ifdef CONFIG_X86_AMD_PSTATE_DYNAMIC_EPP -static bool dynamic_epp = CONFIG_X86_AMD_PSTATE_DYNAMIC_EPP; -#else static bool dynamic_epp; -#endif static struct quirk_entry *quirks; /* -- cgit v1.2.3 From e3ac0d9f1a205f33a43fba3b79ef74d2f604c78b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 24 Apr 2026 22:24:29 +0300 Subject: Bluetooth: btmtk: accept too short WMT FUNC_CTRL events MT7925 (USB ID 0e8d:e025) on fw version 20260106153314 sends WMT FUNC_CTRL events that are missing the status field. Prior to commit 006b9943b982 ("Bluetooth: btmtk: validate WMT event SKB length before struct access") the status was read from out-of-bounds of SKB data, which usually would result to success with BTMTK_WMT_ON_UNDONE, although I don't know the intent here. The bounds check added in that commit returns with error instead, producing "Bluetooth: hci0: Failed to send wmt func ctrl (-22)" and makes the device unusable. Fix the regression by interpreting too short packet as status BTMTK_WMT_ON_UNDONE, which makes the device work normally again. Fixes: 634a4408c061 ("Bluetooth: btmtk: validate WMT event SKB length before struct access") Signed-off-by: Pauli Virtanen Tested-by: Mikhail Gavrilov # MT7922 (0489:e0e2) Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index f70c1b0f8990..a29f72216c34 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -719,8 +719,8 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev, case BTMTK_WMT_FUNC_CTRL: if (!skb_pull_data(data->evt_skb, sizeof(wmt_evt_funcc->status))) { - err = -EINVAL; - goto err_free_skb; + status = BTMTK_WMT_ON_UNDONE; + break; } wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt; -- cgit v1.2.3 From 375ba7484132662a4a8c7547d088fb6275c00282 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Mon, 11 May 2026 21:58:37 +0800 Subject: Bluetooth: hci_qca: Convert timeout from jiffies to ms Since the timer uses jiffies as its unit rather than ms, the timeout value must be converted from ms to jiffies when configuring the timer. Otherwise, the intended 8s timeout is incorrectly set to approximately 33s. To improve readability, embed msecs_to_jiffies() directly in the macro definitions and drop the _MS suffix from macros that now yield jiffies values: MEMDUMP_TIMEOUT, FW_DOWNLOAD_TIMEOUT, IBS_DISABLE_SSR_TIMEOUT, CMD_TRANS_TIMEOUT, and IBS_BTSOC_TX_IDLE_TIMEOUT. IBS_WAKE_RETRANS_TIMEOUT_MS and IBS_HOST_TX_IDLE_TIMEOUT_MS are intentionally left unchanged. Their values are stored in the struct fields wake_retrans and tx_idle_delay, which hold ms values at runtime and can be modified via debugfs. The msecs_to_jiffies() conversion happens at each call site against the field value, so it cannot be embedded in the macro. Wake timer depends on commit c347ca17d62a Cc: stable@vger.kernel.org Fixes: d841502c79e3 ("Bluetooth: hci_qca: Collect controller memory dump during SSR") Reviewed-by: Paul Menzel Acked-by: Bartosz Golaszewski Signed-off-by: Shuai Zhang Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_qca.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index cd1834246b47..ed280399bf47 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -48,13 +48,12 @@ #define HCI_MAX_IBS_SIZE 10 #define IBS_WAKE_RETRANS_TIMEOUT_MS 100 -#define IBS_BTSOC_TX_IDLE_TIMEOUT_MS 200 +#define IBS_BTSOC_TX_IDLE_TIMEOUT msecs_to_jiffies(200) #define IBS_HOST_TX_IDLE_TIMEOUT_MS 2000 -#define CMD_TRANS_TIMEOUT_MS 100 -#define MEMDUMP_TIMEOUT_MS 8000 -#define IBS_DISABLE_SSR_TIMEOUT_MS \ - (MEMDUMP_TIMEOUT_MS + FW_DOWNLOAD_TIMEOUT_MS) -#define FW_DOWNLOAD_TIMEOUT_MS 3000 +#define CMD_TRANS_TIMEOUT msecs_to_jiffies(100) +#define MEMDUMP_TIMEOUT msecs_to_jiffies(8000) +#define FW_DOWNLOAD_TIMEOUT msecs_to_jiffies(3000) +#define IBS_DISABLE_SSR_TIMEOUT (MEMDUMP_TIMEOUT + FW_DOWNLOAD_TIMEOUT) /* susclk rate */ #define SUSCLK_RATE_32KHZ 32768 @@ -1096,7 +1095,7 @@ static void qca_controller_memdump(struct work_struct *work) queue_delayed_work(qca->workqueue, &qca->ctrl_memdump_timeout, - msecs_to_jiffies(MEMDUMP_TIMEOUT_MS)); + MEMDUMP_TIMEOUT); skb_pull(skb, sizeof(qca_memdump->ram_dump_size)); qca_memdump->current_seq_no = 0; qca_memdump->received_dump = 0; @@ -1369,7 +1368,7 @@ static int qca_set_baudrate(struct hci_dev *hdev, uint8_t baudrate) if (hu->serdev) serdev_device_wait_until_sent(hu->serdev, - msecs_to_jiffies(CMD_TRANS_TIMEOUT_MS)); + CMD_TRANS_TIMEOUT); /* Give the controller time to process the request */ switch (qca_soc_type(hu)) { @@ -1401,8 +1400,8 @@ static inline void host_set_baudrate(struct hci_uart *hu, unsigned int speed) static int qca_send_power_pulse(struct hci_uart *hu, bool on) { + int timeout = CMD_TRANS_TIMEOUT; int ret; - int timeout = msecs_to_jiffies(CMD_TRANS_TIMEOUT_MS); u8 cmd = on ? QCA_WCN3990_POWERON_PULSE : QCA_WCN3990_POWEROFF_PULSE; /* These power pulses are single byte command which are sent @@ -1607,7 +1606,7 @@ static void qca_wait_for_dump_collection(struct hci_dev *hdev) struct qca_data *qca = hu->priv; wait_on_bit_timeout(&qca->flags, QCA_MEMDUMP_COLLECTION, - TASK_UNINTERRUPTIBLE, MEMDUMP_TIMEOUT_MS); + TASK_UNINTERRUPTIBLE, MEMDUMP_TIMEOUT); clear_bit(QCA_MEMDUMP_COLLECTION, &qca->flags); } @@ -2591,7 +2590,7 @@ static void qca_serdev_remove(struct serdev_device *serdev) static void qca_serdev_shutdown(struct serdev_device *serdev) { int ret; - int timeout = msecs_to_jiffies(CMD_TRANS_TIMEOUT_MS); + int timeout = CMD_TRANS_TIMEOUT; struct qca_serdev *qcadev = serdev_device_get_drvdata(serdev); struct hci_uart *hu = &qcadev->serdev_hu; struct hci_dev *hdev = hu->hdev; @@ -2648,7 +2647,7 @@ static int __maybe_unused qca_suspend(struct device *dev) bool tx_pending = false; int ret = 0; u8 cmd; - u32 wait_timeout = 0; + unsigned long wait_timeout = 0; set_bit(QCA_SUSPENDING, &qca->flags); @@ -2669,15 +2668,15 @@ static int __maybe_unused qca_suspend(struct device *dev) if (test_bit(QCA_IBS_DISABLED, &qca->flags) || test_bit(QCA_SSR_TRIGGERED, &qca->flags)) { wait_timeout = test_bit(QCA_SSR_TRIGGERED, &qca->flags) ? - IBS_DISABLE_SSR_TIMEOUT_MS : - FW_DOWNLOAD_TIMEOUT_MS; + IBS_DISABLE_SSR_TIMEOUT : + FW_DOWNLOAD_TIMEOUT; /* QCA_IBS_DISABLED flag is set to true, During FW download * and during memory dump collection. It is reset to false, * After FW download complete. */ wait_on_bit_timeout(&qca->flags, QCA_IBS_DISABLED, - TASK_UNINTERRUPTIBLE, msecs_to_jiffies(wait_timeout)); + TASK_UNINTERRUPTIBLE, wait_timeout); if (test_bit(QCA_IBS_DISABLED, &qca->flags)) { bt_dev_err(hu->hdev, "SSR or FW download time out"); @@ -2729,7 +2728,7 @@ static int __maybe_unused qca_suspend(struct device *dev) if (tx_pending) { serdev_device_wait_until_sent(hu->serdev, - msecs_to_jiffies(CMD_TRANS_TIMEOUT_MS)); + CMD_TRANS_TIMEOUT); serial_clock_vote(HCI_IBS_TX_VOTE_CLOCK_OFF, hu); } @@ -2738,7 +2737,7 @@ static int __maybe_unused qca_suspend(struct device *dev) */ ret = wait_event_interruptible_timeout(qca->suspend_wait_q, qca->rx_ibs_state == HCI_IBS_RX_ASLEEP, - msecs_to_jiffies(IBS_BTSOC_TX_IDLE_TIMEOUT_MS)); + IBS_BTSOC_TX_IDLE_TIMEOUT); if (ret == 0) { ret = -ETIMEDOUT; goto error; -- cgit v1.2.3 From 775d8d7ad02aa345e1588424a6a8b9ae49fb9012 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 13 May 2026 11:55:20 +0100 Subject: ovpn: tcp - use cached peer pointer in ovpn_tcp_close() ovpn_tcp_close() loads the ovpn_socket via rcu_dereference_sk_user_data() under rcu_read_lock(), takes a reference on sock->peer, caches the peer pointer in a local, and drops the read lock. It then passes sock->peer (rather than the cached local) to ovpn_peer_del(), re-dereferencing the ovpn_socket after the RCU read section has ended. Unlike ovpn_tcp_sendmsg(), which uses the same "load under RCU, use after unlock" pattern but is protected by lock_sock() held across the function, ovpn_tcp_close() runs without the socket lock: inet_release() invokes sk_prot->close() without taking lock_sock first. ovpn_socket_release() can therefore complete its kref_put -> detach -> synchronize_rcu -> kfree(sock) sequence concurrently, in the window after ovpn_tcp_close() drops rcu_read_lock() but before it dereferences sock->peer. The synchronize_rcu() in ovpn_socket_release() protects readers that use the dereferenced pointer inside the RCU read section, not those that escape the pointer to a local and use it afterwards. A reproducer follows the pattern of commit 94560267d6c4 ("ovpn: tcp - don't deref NULL sk_socket member after tcp_close()"): trigger a peer removal (keepalive expiration or netlink OVPN_CMD_DEL_PEER) at the same moment userspace closes the TCP fd. That commit fixed the detach-side of the same race window; this one fixes the close-side at a different victim. Tighten the entry block to read sock->peer exactly once into the cached peer local, and route all subsequent uses (the hold check, the ovpn_peer_del() call, and the prot->close() invocation) through that local. sock->peer is only ever written once in ovpn_socket_new() under lock_sock(), before rcu_assign_sk_user_data() publishes the ovpn_socket, and is never reassigned afterwards - but the previous multi-read pattern made that invariant implicit rather than explicit. The same multi-read shape exists in ovpn_tcp_recvmsg(), ovpn_tcp_sendmsg(), ovpn_tcp_data_ready() and ovpn_tcp_write_space(); those will be cleaned up via a dedicated helper in a follow-up net-next series. Fixes: 11851cbd60ea ("ovpn: implement TCP transport") Reviewed-by: Sabrina Dubroca Assisted-by: Claude:claude-opus-4-7 Signed-off-by: David Carlier Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/tcp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index 65054cc84be5..82809b016f0a 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -581,14 +581,19 @@ static void ovpn_tcp_close(struct sock *sk, long timeout) rcu_read_lock(); sock = rcu_dereference_sk_user_data(sk); - if (!sock || !sock->peer || !ovpn_peer_hold(sock->peer)) { + if (!sock) { rcu_read_unlock(); return; } + peer = sock->peer; + if (!peer || !ovpn_peer_hold(peer)) { + rcu_read_unlock(); + return; + } rcu_read_unlock(); - ovpn_peer_del(sock->peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); + ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); peer->tcp.sk_cb.prot->close(sk, timeout); ovpn_peer_put(peer); } -- cgit v1.2.3 From 1fef6614673ff0846d30acdeeaf3cf98bb5f6116 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 13 May 2026 11:55:21 +0100 Subject: ovpn: respect peer refcount in CMD_NEW_PEER error path ovpn_nl_peer_new_doit()'s error path calls ovpn_peer_release() directly rather than ovpn_peer_put(), bypassing the kref. The accompanying comment ("peer was not yet hashed, thus it is not used in any context") holds for UDP but not for TCP. For UDP, the ovpn_socket union uses the .ovpn arm and never points back at a peer; UDP encap_recv looks up peers via the not-yet-populated hashtables, so the new peer is unreachable until ovpn_peer_add() publishes it. For TCP, ovpn_socket_new() sets ovpn_sock->peer and ovpn_tcp_socket_attach() publishes ovpn_sock via rcu_assign_sk_user_data(). From that moment until ovpn_socket_release() detaches in the error path, the TCP fd is fully wired: userspace recvmsg / sendmsg / close / poll on the fd, as well as the strparser-driven ovpn_tcp_rcv() path, can reach the peer through sk_user_data -> ovpn_sock->peer and bump its refcount via ovpn_peer_hold(). ovpn_tcp_socket_wait_finish() (called inside ovpn_socket_release()) drains strparser and the tx work, but does not synchronize with userspace syscall callers that already hold a peer reference. If ovpn_nl_peer_modify() or ovpn_peer_add() returns an error while such a caller is in flight - notably an ovpn_tcp_recvmsg() blocked in __skb_recv_datagram() on peer->tcp.user_queue - the direct ovpn_peer_release() destroys the peer while the caller still holds the reference, and the eventual ovpn_peer_put() from that caller operates on freed memory. Replace the direct destructor call with ovpn_peer_put() so the kref correctly defers destruction until the last reference is dropped. In the common case where no concurrent user is present, behaviour is unchanged: the kref hits zero immediately and ovpn_peer_release_kref() runs the same destructor. With this conversion ovpn_peer_release() has no callers outside peer.c - ovpn_peer_release_kref() in the same translation unit is the only remaining user - so make it static and drop its declaration from peer.h. Fixes: 11851cbd60ea ("ovpn: implement TCP transport") Reviewed-by: Sabrina Dubroca Assisted-by: Claude:claude-opus-4-7 Signed-off-by: David Carlier Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 8 +++++--- drivers/net/ovpn/peer.c | 2 +- drivers/net/ovpn/peer.h | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 291e2e5bb450..4c66c1ec497e 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -462,10 +462,12 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) sock_release: ovpn_socket_release(peer); peer_release: - /* release right away because peer was not yet hashed, thus it is not - * used in any context + /* For UDP, the peer is unreachable until added to the hashtables, so + * dropping the initial reference is enough. For TCP, the peer may be + * concurrently reachable via sk_user_data->peer until + * ovpn_socket_release() detaches; rely on the refcount. */ - ovpn_peer_release(peer); + ovpn_peer_put(peer); return ret; } diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c02dfab51a6e..fb10d1fea940 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -354,7 +354,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) * ovpn_peer_release - release peer private members * @peer: the peer to release */ -void ovpn_peer_release(struct ovpn_peer *peer) +static void ovpn_peer_release(struct ovpn_peer *peer) { ovpn_crypto_state_release(&peer->crypto); spin_lock_bh(&peer->lock); diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 328401570cba..86c8cffada6d 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -127,7 +127,6 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer) return kref_get_unless_zero(&peer->refcount); } -void ovpn_peer_release(struct ovpn_peer *peer); void ovpn_peer_release_kref(struct kref *kref); /** -- cgit v1.2.3 From 982422b11e6f95f766a8cd2c2b1cbdb77e234a61 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Tue, 17 Mar 2026 14:47:56 +0100 Subject: ovpn: fix race between deleting interface and adding new peer While deleting an existing ovpn interface, there is a very narrow window where adding a new peer via netlink may cause the netdevice to hang and prevent its unregistration. It may happen during ovpn_dellink(), when all existing peers are freed and the device is queued for deregistration, but a CMD_PEER_NEW message comes in adding a new peer that takes again a reference to the netdev. At this point there is no way to release the device because we are under the assumption that all peers were already released. Fix the race condition by releasing all peers in ndo_uninit(), when the netdevice has already been removed from the netdev list. Also ovpn_peer_add() has now an extra check that forces the function to bail out if the device reg_state is not REGISTERED. This way any incoming CMD_PEER_NEW racing with the interface deletion routine will simply stop before adding the peer. Note that the above check happens while holding the netdev_lock to prevent racing netdev state changes. ovpn_dellink() is now empty and can be removed. Reported-by: Hyunwoo Kim Closes: https://lore.kernel.org/netdev/aaVgJ16edTfQkYbx@v4bel/ Suggested-by: Sabrina Dubroca Fixes: 80747caef33d ("ovpn: introduce the ovpn_peer object") Reviewed-by: Sabrina Dubroca Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.c | 12 ++---------- drivers/net/ovpn/peer.c | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 2e0420febda0..9993c1dfe471 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -92,6 +92,8 @@ static void ovpn_net_uninit(struct net_device *dev) { struct ovpn_priv *ovpn = netdev_priv(dev); + disable_delayed_work_sync(&ovpn->keepalive_work); + ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); gro_cells_destroy(&ovpn->gro_cells); } @@ -208,15 +210,6 @@ static int ovpn_newlink(struct net_device *dev, return register_netdevice(dev); } -static void ovpn_dellink(struct net_device *dev, struct list_head *head) -{ - struct ovpn_priv *ovpn = netdev_priv(dev); - - cancel_delayed_work_sync(&ovpn->keepalive_work); - ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); - unregister_netdevice_queue(dev, head); -} - static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev) { struct ovpn_priv *ovpn = netdev_priv(dev); @@ -235,7 +228,6 @@ static struct rtnl_link_ops ovpn_link_ops = { .policy = ovpn_policy, .maxtype = IFLA_OVPN_MAX, .newlink = ovpn_newlink, - .dellink = ovpn_dellink, .fill_info = ovpn_fill_info, }; diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index fb10d1fea940..a09d61296425 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -1034,14 +1034,29 @@ static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer) */ int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer) { + int ret = -ENODEV; + + /* Prevent adding new peers while destroying the ovpn interface. + * Failing to do so would end up holding the device reference + * endlessly hostage of the new peer object with no chance of + * release.. + */ + netdev_lock(ovpn->dev); + if (ovpn->dev->reg_state != NETREG_REGISTERED) + goto out; + switch (ovpn->mode) { case OVPN_MODE_MP: - return ovpn_peer_add_mp(ovpn, peer); + ret = ovpn_peer_add_mp(ovpn, peer); + break; case OVPN_MODE_P2P: - return ovpn_peer_add_p2p(ovpn, peer); + ret = ovpn_peer_add_p2p(ovpn, peer); + break; } +out: + netdev_unlock(ovpn->dev); - return -EOPNOTSUPP; + return ret; } /** -- cgit v1.2.3 From 1854082fe0ddb81bc93d1f8e8a00554217fd09d1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 8 May 2026 21:19:58 +0100 Subject: phy: apple: atc: Fix typec switch/mux leak on unbind atcphy_probe_switch() and atcphy_probe_mux() discard the pointers returned by typec_switch_register() and typec_mux_register(). The platform driver has no .remove callback, so when the driver unbinds (e.g. via sysfs unbind) neither typec_switch_unregister() nor typec_mux_unregister() is called. The framework reference taken in typec_switch_register() (device_initialize() + device_add() in drivers/usb/typec/mux.c) is therefore never dropped and the typec_switch_dev / typec_mux_dev objects stay live forever, with their sysfs entries under the typec_mux class also left behind. A subsequent rebind cannot recreate them with the same fwnode-derived name. Save the registered handles and unregister them through devm_add_action_or_reset() so framework registration is torn down in step with the driver's other devm-managed state. While here, drop struct apple_atcphy::sw and ::mux: they were declared with the consumer-side types (typec_switch *, typec_mux *) instead of the provider-side types and were never assigned. Scope of the fix ================ This patch fixes the registration leak only. It does not close the use-after-free window that arises when a consumer that obtained a reference via fwnode_typec_switch_get() / fwnode_typec_mux_get() outlives the provider unbind: such consumers keep the underlying typec_switch_dev / typec_mux_dev alive past device_unregister(), and a later typec_switch_set() / typec_mux_set() still invokes the registered atcphy_sw_set() / atcphy_mux_set(), which dereferences the freed apple_atcphy through typec_{switch,mux}_get_drvdata(). On Apple Silicon the relevant consumers are the typec port and the cd321x controller registered by drivers/usb/typec/tipd/core.c. Cable plug / orientation events and alt-mode transitions trigger the .set callbacks via: tps6598x_interrupt() drivers/usb/typec/tipd/core.c tps6598x_handle_plug_event() tps6598x_connect()/_disconnect() typec_set_orientation() drivers/usb/typec/class.c typec_switch_set(port->sw) drivers/usb/typec/mux.c atcphy_sw_set() drivers/phy/apple/atc.c cd321x_update_work() drivers/usb/typec/tipd/core.c cd321x_typec_update_mode() typec_mux_set(cd321x->mux) drivers/usb/typec/mux.c atcphy_mux_set() drivers/phy/apple/atc.c Closing that window requires framework support for invalidating consumer-held references on provider unbind. The same consumer-survives-provider pattern has been discussed for the PHY framework [1] and is out of scope here. [1] https://lore.kernel.org/linux-phy/aZejMSJ9qqRWb2pX@google.com/ Fixes: 8e98ca1e74db ("phy: apple: Add Apple Type-C PHY") Signed-off-by: David Carlier Reviewed-by: Vladimir Oltean Tested-by: Joshua Peisach Link: https://lkml.kernel.org/r/6ec1ed08328340db42655287afd5fa4067316b11.camel@perches.com Link: https://patch.msgid.link/20260508201958.30060-1-devnexen@gmail.com Signed-off-by: Vinod Koul --- drivers/phy/apple/atc.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index e9d106f135c5..4156fabad742 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -628,9 +628,6 @@ struct apple_atcphy { struct reset_controller_dev rcdev; - struct typec_switch *sw; - struct typec_mux *mux; - struct mutex lock; }; @@ -2066,15 +2063,25 @@ static int atcphy_sw_set(struct typec_switch_dev *sw, enum typec_orientation ori return 0; } +static void atcphy_typec_switch_unregister(void *data) +{ + typec_switch_unregister(data); +} + static int atcphy_probe_switch(struct apple_atcphy *atcphy) { + struct typec_switch_dev *sw; struct typec_switch_desc sw_desc = { .drvdata = atcphy, .fwnode = atcphy->dev->fwnode, .set = atcphy_sw_set, }; - return PTR_ERR_OR_ZERO(typec_switch_register(atcphy->dev, &sw_desc)); + sw = typec_switch_register(atcphy->dev, &sw_desc); + if (IS_ERR(sw)) + return PTR_ERR(sw); + + return devm_add_action_or_reset(atcphy->dev, atcphy_typec_switch_unregister, sw); } static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) @@ -2146,15 +2153,25 @@ static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *sta return atcphy_configure(atcphy, target_mode); } +static void atcphy_typec_mux_unregister(void *data) +{ + typec_mux_unregister(data); +} + static int atcphy_probe_mux(struct apple_atcphy *atcphy) { + struct typec_mux_dev *mux; struct typec_mux_desc mux_desc = { .drvdata = atcphy, .fwnode = atcphy->dev->fwnode, .set = atcphy_mux_set, }; - return PTR_ERR_OR_ZERO(typec_mux_register(atcphy->dev, &mux_desc)); + mux = typec_mux_register(atcphy->dev, &mux_desc); + if (IS_ERR(mux)) + return PTR_ERR(mux); + + return devm_add_action_or_reset(atcphy->dev, atcphy_typec_mux_unregister, mux); } static int atcphy_load_tunables(struct apple_atcphy *atcphy) -- cgit v1.2.3 From 0ce1bc9e46ecabe84772bb561e373c0d9876d6f2 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 13 May 2026 13:53:24 -0400 Subject: RDMA/siw: Reject MPA FPDU length underflow before signed receive math A malicious connected siw peer can send an iWARP FPDU whose MPA length field (c_hdr->mpa_len, 16 bit big-endian, peer-controlled) is smaller than the fixed DDP/RDMAP header for the announced opcode. Soft-iWARP parses the full header in siw_get_hdr() based on iwarp_pktinfo[opcode] .hdr_len, but never compares mpa_len against that header length. siw_tcp_rx_data() then derives srx->fpdu_part_rem = be16_to_cpu(mpa_len) - fpdu_part_rcvd + MPA_HDR_SIZE; where fpdu_part_rcvd equals iwarp_pktinfo[opcode].hdr_len at this point. For a tagged WRITE (hdr_len 16, MPA_HDR_SIZE 2) the smallest on-wire mpa_len of 0 yields fpdu_part_rem = -14, and any mpa_len below hdr_len - MPA_HDR_SIZE underflows to a negative int. The signed value then flows into siw_proc_write()/siw_proc_rresp() as bytes = min(srx->fpdu_part_rem, srx->skb_new); is handed to siw_check_mem() as an int len (whose interval check addr + len > mem->va + mem->len is satisfied for a valid base when len is negative), and reaches siw_rx_data() -> siw_rx_kva() / siw_rx_umem() -> skb_copy_bits() as a signed copy length. The header copy branch in skb_copy_bits() promotes that to size_t, producing a multi-gigabyte read. KASAN under a KUnit harness that drives the real kernel TCP receive path -- a loopback AF_INET socketpair, the malformed FPDU written via kernel_sendmsg, sk_data_ready firing in softirq, tcp_read_sock dispatching to siw_tcp_rx_data -- reports: BUG: KASAN: use-after-free in skb_copy_bits+0x284/0x480 Read of size 4294967295 at addr ffff888... Call Trace: skb_copy_bits siw_rx_kva siw_rx_data siw_check_mem siw_proc_write siw_tcp_rx_data __tcp_read_sock siw_qp_llp_data_ready tcp_data_ready tcp_data_queue Add the missing invariant at the earliest point where the peer header is fully assembled. iwarp_pktinfo[*].hdr_len - MPA_HDR_SIZE is exactly the value the siw transmitter uses as the minimum mpa_len for each opcode (drivers/infiniband/sw/siw/siw_qp.c:33), so this matches the protocol contract. Out-of-range FPDUs terminate the connection with TERM_ERROR_LAYER_LLP / LLP_ETYPE_MPA / LLP_ECODE_FPDU_START -- which is RFC 5044 Section 8 error code 3 ("Marker and ULPDU Length fields do not agree on the start of an FPDU"), the correct framing-error class for this inconsistency. Fixes: 8b6a361b8c48 ("rdma/siw: receive path") Link: https://patch.msgid.link/r/20260513175325.2042630-2-michael.bommarito@gmail.com Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 Acked-by: Bernard Metzler Signed-off-by: Jason Gunthorpe --- drivers/infiniband/sw/siw/siw_qp_rx.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/infiniband/sw/siw/siw_qp_rx.c b/drivers/infiniband/sw/siw/siw_qp_rx.c index e8a88b378d51..34d03584160c 100644 --- a/drivers/infiniband/sw/siw/siw_qp_rx.c +++ b/drivers/infiniband/sw/siw/siw_qp_rx.c @@ -1081,6 +1081,21 @@ static int siw_get_hdr(struct siw_rx_stream *srx) return -EAGAIN; } + /* + * Peer-controlled mpa_len must not underflow srx->fpdu_part_rem + * in siw_tcp_rx_data(); a negative value flows as a signed copy + * length into siw_check_mem() and skb_copy_bits(). + */ + if (unlikely(be16_to_cpu(c_hdr->mpa_len) + MPA_HDR_SIZE < + iwarp_pktinfo[opcode].hdr_len)) { + pr_warn_ratelimited("siw: short mpa_len %u for opcode %u (hdr_len %u)\n", + be16_to_cpu(c_hdr->mpa_len), opcode, + iwarp_pktinfo[opcode].hdr_len); + siw_init_terminate(rx_qp(srx), TERM_ERROR_LAYER_LLP, + LLP_ETYPE_MPA, LLP_ECODE_FPDU_START, 0); + return -EINVAL; + } + /* * DDP/RDMAP header receive completed. Check if the current * DDP segment starts a new RDMAP message or continues a previously -- cgit v1.2.3 From 0c0dddc07d272a8d25922e48041e8e4d2434df7e Mon Sep 17 00:00:00 2001 From: Ralf Lici Date: Wed, 13 May 2026 15:26:10 +0200 Subject: ovpn: disable BHs when updating device stats ovpn updates dev->dstats from both process and softirq contexts. In particular, TCP paths may run from socket callbacks, workqueues or strparser work, while UDP receive and ovpn's ndo_start_xmit path may update the same per-device dstats from BH context. Add ovpn device drop-stat helpers that disable BHs around dev_dstats_rx_dropped() and dev_dstats_tx_dropped(), and use them for drop accounting. The successful RX dev_dstats_rx_add() update is already covered by the BH-disabled section around gro_cells_receive(). For the successful TCP TX dev_dstats_tx_add() update, replace the existing preempt-disabled section with a BH-disabled one. Fixes: 11851cbd60ea ("ovpn: implement TCP transport") Signed-off-by: Ralf Lici Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/io.c | 12 ++++++------ drivers/net/ovpn/stats.h | 16 ++++++++++++++++ drivers/net/ovpn/tcp.c | 10 +++++----- drivers/net/ovpn/udp.c | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 22c555dd962e..a6b777a9c2d9 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -201,7 +201,7 @@ void ovpn_decrypt_post(void *data, int ret) skb = NULL; drop: if (unlikely(skb)) - dev_dstats_rx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_rx_dropped(peer->ovpn->dev); kfree_skb(skb); drop_nocount: if (likely(peer)) @@ -225,7 +225,7 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n", netdev_name(peer->ovpn->dev), peer->id, key_id); - dev_dstats_rx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_rx_dropped(peer->ovpn->dev); kfree_skb(skb); ovpn_peer_put(peer); return; @@ -301,7 +301,7 @@ err_unlock: rcu_read_unlock(); err: if (unlikely(skb)) - dev_dstats_tx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_tx_dropped(peer->ovpn->dev); if (likely(peer)) ovpn_peer_put(peer); if (likely(ks)) @@ -343,7 +343,7 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, */ skb_list_walk_safe(skb, curr, next) { if (unlikely(!ovpn_encrypt_one(peer, curr))) { - dev_dstats_tx_dropped(ovpn->dev); + ovpn_dev_dstats_tx_dropped(ovpn->dev); kfree_skb(curr); } } @@ -414,7 +414,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) if (unlikely(!curr)) { net_err_ratelimited("%s: skb_share_check failed for payload packet\n", netdev_name(dev)); - dev_dstats_tx_dropped(ovpn->dev); + ovpn_dev_dstats_tx_dropped(ovpn->dev); continue; } @@ -440,7 +440,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) drop: ovpn_peer_put(peer); drop_no_peer: - dev_dstats_tx_dropped(ovpn->dev); + ovpn_dev_dstats_tx_dropped(ovpn->dev); skb_tx_error(skb); kfree_skb_list(skb); return NETDEV_TX_OK; diff --git a/drivers/net/ovpn/stats.h b/drivers/net/ovpn/stats.h index 53433d8b6c33..3a45b97c0056 100644 --- a/drivers/net/ovpn/stats.h +++ b/drivers/net/ovpn/stats.h @@ -11,6 +11,8 @@ #ifndef _NET_OVPN_OVPNSTATS_H_ #define _NET_OVPN_OVPNSTATS_H_ +#include + /* one stat */ struct ovpn_peer_stat { atomic64_t bytes; @@ -44,4 +46,18 @@ static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats, ovpn_peer_stats_increment(&stats->tx, n); } +static inline void ovpn_dev_dstats_tx_dropped(struct net_device *dev) +{ + local_bh_disable(); + dev_dstats_tx_dropped(dev); + local_bh_enable(); +} + +static inline void ovpn_dev_dstats_rx_dropped(struct net_device *dev) +{ + local_bh_disable(); + dev_dstats_rx_dropped(dev); + local_bh_enable(); +} + #endif /* _NET_OVPN_OVPNSTATS_H_ */ diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index 82809b016f0a..433bd07a4f1b 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -152,7 +152,7 @@ err: if (WARN_ON(!ovpn_peer_hold(peer))) goto err_nopeer; schedule_work(&peer->tcp.defer_del_work); - dev_dstats_rx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_rx_dropped(peer->ovpn->dev); err_nopeer: kfree_skb(skb); } @@ -298,9 +298,9 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk) } while (peer->tcp.out_msg.len > 0); if (!peer->tcp.out_msg.len) { - preempt_disable(); + local_bh_disable(); dev_dstats_tx_add(peer->ovpn->dev, skb->len); - preempt_enable(); + local_bh_enable(); } kfree_skb(peer->tcp.out_msg.skb); @@ -331,7 +331,7 @@ static void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sock *sk, ovpn_tcp_send_sock(peer, sk); if (peer->tcp.out_msg.skb) { - dev_dstats_tx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_tx_dropped(peer->ovpn->dev); kfree_skb(skb); return; } @@ -353,7 +353,7 @@ void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct sock *sk, if (sock_owned_by_user(sk)) { if (skb_queue_len(&peer->tcp.out_queue) >= READ_ONCE(net_hotdata.max_backlog)) { - dev_dstats_tx_dropped(peer->ovpn->dev); + ovpn_dev_dstats_tx_dropped(peer->ovpn->dev); kfree_skb(skb); goto unlock; } diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 059e896b4a2f..8811aa9eedeb 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -125,7 +125,7 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) return 0; drop: - dev_dstats_rx_dropped(ovpn->dev); + ovpn_dev_dstats_rx_dropped(ovpn->dev); drop_noovpn: kfree_skb(skb); return 0; -- cgit v1.2.3 From 7d260c5d2d89eb2c8c528d54b576b3aae3e20231 Mon Sep 17 00:00:00 2001 From: Matt Fleming Date: Wed, 13 May 2026 12:22:26 +0100 Subject: net/mlx5e: Fix use-after-free in mlx5e_tx_reporter_timeout_recover mlx5e_tx_reporter_timeout_recover() accesses sq->netdev after mlx5e_safe_reopen_channels() has torn down and freed the channel (and its embedded SQs). Replace the three sq->netdev references with priv->netdev which is safe because priv outlives channel teardown. The netdev_err() call already used priv->netdev for this reason; make the trylock/unlock and health_channel_eq_recover calls consistent. This fixes the following KASAN splat: BUG: KASAN: use-after-free in mlx5e_tx_reporter_timeout_recover+0x1dd/0x360 [mlx5_core] Read of size 8 at addr ffff889860ed0b28 by task kworker/u113:2/5277 Call Trace: mlx5e_tx_reporter_timeout_recover+0x1dd/0x360 [mlx5_core] devlink_health_reporter_recover+0xa2/0x150 devlink_health_report+0x254/0x7c0 mlx5e_reporter_tx_timeout+0x297/0x380 [mlx5_core] mlx5e_tx_timeout_work+0x109/0x170 [mlx5_core] process_one_work+0x677/0xf20 worker_thread+0x51f/0xd90 kthread+0x3a5/0x810 ret_from_fork+0x208/0x400 ret_from_fork_asm+0x1a/0x30 Fixes: 83ac0304a2d7 ("net/mlx5e: Fix deadlocks between devlink and netdev instance locks") Cc: stable@vger.kernel.org Reviewed-by: Cosmin Ratiu Reviewed-by: Tariq Toukan Signed-off-by: Matt Fleming Link: https://patch.msgid.link/20260513112226.140512-1-matt@readmodwrite.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c index afdeb1b3d425..8409ae73768f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c @@ -160,13 +160,13 @@ static int mlx5e_tx_reporter_timeout_recover(void *ctx) * channels are being closed for other reason and this work is not * relevant anymore. */ - while (!netdev_trylock(sq->netdev)) { + while (!netdev_trylock(priv->netdev)) { if (!test_bit(MLX5E_STATE_CHANNELS_ACTIVE, &priv->state)) return 0; msleep(20); } - err = mlx5e_health_channel_eq_recover(sq->netdev, eq, sq->cq.ch_stats); + err = mlx5e_health_channel_eq_recover(priv->netdev, eq, sq->cq.ch_stats); if (!err) { to_ctx->status = 0; /* this sq recovered */ goto out; @@ -186,7 +186,7 @@ static int mlx5e_tx_reporter_timeout_recover(void *ctx) "mlx5e_safe_reopen_channels failed recovering from a tx_timeout, err(%d).\n", err); out: - netdev_unlock(sq->netdev); + netdev_unlock(priv->netdev); return err; } -- cgit v1.2.3 From e8fb3de2a8effcaf62bec2c56b93d8bb480371d1 Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 13 May 2026 23:13:20 +0800 Subject: octeontx2-pf: fix double free in rvu_rep_rsrc_init() rvu_rep_rsrc_init() allocates queue memory before calling otx2_init_hw_resources(). When hardware resource setup fails, otx2_init_hw_resources() already unwinds the partially initialized SQ, CQ, and aura state before returning an error. The representor error path then calls otx2_free_hw_resources() again and can free the same resources a second time. Fix this by splitting the cleanup labels so that a failure from otx2_init_hw_resources() only releases queue memory. Keep the otx2_free_hw_resources() call for failures that happen after hardware resource initialization completed successfully. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc3. Runtime validation was not performed because reproducing this path requires OcteonTX2 representor hardware. Fixes: 3937b7308d4f ("octeontx2-pf: Create representor netdev") Cc: stable@vger.kernel.org # v6.13+ Signed-off-by: Zilin Guan Signed-off-by: Dawei Feng Reviewed-by: Geetha sowjanya Link: https://patch.msgid.link/20260513151320.213260-1-dawei.feng@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/nic/rep.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c index 94f155ffb17f..0f5d5642d3f7 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/rep.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/rep.c @@ -609,7 +609,7 @@ static int rvu_rep_rsrc_init(struct otx2_nic *priv) err = otx2_init_hw_resources(priv); if (err) - goto err_free_rsrc; + goto err_free_mem; /* Set maximum frame size allowed in HW */ err = otx2_hw_set_mtu(priv, priv->hw.max_mtu); @@ -621,6 +621,7 @@ static int rvu_rep_rsrc_init(struct otx2_nic *priv) err_free_rsrc: otx2_free_hw_resources(priv); +err_free_mem: otx2_free_queue_mem(qset); return err; } -- cgit v1.2.3 From c9d08c8c4c5006d71b3c3c3c0dc41ebc46931951 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Wed, 13 May 2026 09:27:37 +0300 Subject: net/mlx5e: Don't leak RSS context in case of error If mlx5e_rx_res_rss_set_rxfh() fails during mlx5e_create_rxfh_context(), the RSS context is not cleaned up. This leaves a stale entry in 'res->rss[rss_idx]' that occupies a context slot. Destroy the RSS context before returning the error. Fixes: 6c2509d44636 ("net/mlx5e: Add error flow for ethtool -X command") Signed-off-by: Gal Pressman Reviewed-by: Nimrod Oren Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260513062737.333259-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index bb61e2179078..99a0034b9b20 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -1574,8 +1574,11 @@ static int mlx5e_create_rxfh_context(struct net_device *dev, rxfh->indir, rxfh->key, hfunc == ETH_RSS_HASH_NO_CHANGE ? NULL : &hfunc, rxfh->input_xfrm == RXH_XFRM_NO_CHANGE ? NULL : &symmetric); - if (err) + if (err) { + WARN_ON(mlx5e_rx_res_rss_destroy(priv->rx_res, + rxfh->rss_context)); goto unlock; + } mlx5e_rx_res_rss_get_rxfh(priv->rx_res, rxfh->rss_context, ethtool_rxfh_context_indir(ctx), -- cgit v1.2.3 From 8d0a5af8b1ba598e7340761729801624e7a9330e Mon Sep 17 00:00:00 2001 From: Jeroen Massar Date: Wed, 13 May 2026 09:33:02 +0300 Subject: net/mlx5: Do not restore destination-less TC rules After IPsec policy/state TX rules are added, any TC flow rule, which forwards packets to uplink, is modified to forward to IPsec TX tables. As these tables are destroyed dynamically, whenever there is no reference to them, the destinations of this kind of rules must be restored to uplink, unless there is no destination for that rule. The flow rules FLOW_ACTION_ACCEPT, DROP, TRAP, GOTO and SAMPLE do not have a destination port, and thus out_count = 0. At cleanup time of the rules in mlx5_esw_ipsec_modify_flow_dests we call mlx5_eswitch_restore_ipsec_rule but as the above types do not have a destination we get an underflow of out_count, as the port is passed, which is esw_attr->out_count - 1. This change avoids calling mlx5_eswitch_restore_ipsec_rule when there are no output destinations and thus avoids the underflow. Fixes: d1569537a837 ("net/mlx5e: Modify and restore TC rules for IPSec TX rules") Signed-off-by: Jeroen Massar Reviewed-by: Jianbo Liu Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260513063302.333761-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec_fs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec_fs.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec_fs.c index 3cfe743610d3..ab50d2c734ed 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec_fs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec_fs.c @@ -142,7 +142,8 @@ static int mlx5_esw_ipsec_modify_flow_dests(struct mlx5_eswitch *esw, attr = flow->attr; esw_attr = attr->esw_attr; - if (esw_attr->out_count - esw_attr->split_count > 1) + if (!esw_attr->out_count || + esw_attr->out_count - esw_attr->split_count > 1) return 0; err = mlx5_eswitch_restore_ipsec_rule(esw, flow->rule[0], esw_attr, -- cgit v1.2.3 From c6df9a65cbb0fe7808a4b2872095f4c849b3196a Mon Sep 17 00:00:00 2001 From: Or Har-Toov Date: Wed, 13 May 2026 09:36:40 +0300 Subject: net/mlx5: Skip disabled vports when setting max TX speed When setting vports max TX speed during LAG activation or bond state changes, the code iterates over all eswitch vports. However, some vports may not be enabled yet. Skip vports that are not enabled to avoid sending FW commands for uninitialized vports. Save the LAG aggregated speed in the vport struct so it can be applied when the vport is enabled later. Fixes: 50f1d188c580 ("net/mlx5: Propagate LAG effective max_tx_speed to vports") Signed-off-by: Or Har-Toov Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260513063640.334132-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 21 +++++++++++++++++++++ drivers/net/ethernet/mellanox/mlx5/core/eswitch.h | 1 + drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c | 5 +++++ 3 files changed, 27 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index 123c96716a54..7c8311f41232 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -908,6 +908,24 @@ static void esw_vport_cleanup(struct mlx5_eswitch *esw, struct mlx5_vport *vport esw_vport_cleanup_acl(esw, vport); } +static void mlx5_esw_vport_set_max_tx_speed(struct mlx5_eswitch *esw, + struct mlx5_vport *vport) +{ + int ret; + + if (!MLX5_CAP_ESW(esw->dev, esw_vport_state_max_tx_speed)) + return; + + ret = mlx5_modify_vport_max_tx_speed(esw->dev, + MLX5_VPORT_STATE_OP_MOD_ESW_VPORT, + vport->vport, true, + vport->agg_max_tx_speed); + if (ret) + mlx5_core_dbg(esw->dev, + "Failed to set vport %d speed %d, err=%d\n", + vport->vport, vport->agg_max_tx_speed, ret); +} + int mlx5_esw_vport_enable(struct mlx5_eswitch *esw, struct mlx5_vport *vport, enum mlx5_eswitch_vport_event enabled_events) { @@ -948,6 +966,9 @@ int mlx5_esw_vport_enable(struct mlx5_eswitch *esw, struct mlx5_vport *vport, esw->enabled_vports++; esw_debug(esw->dev, "Enabled VPORT(%d)\n", vport_num); + + if (vport->agg_max_tx_speed) + mlx5_esw_vport_set_max_tx_speed(esw, vport); done: mutex_unlock(&esw->state_lock); return ret; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 5128f5020dae..e9cf7c592ce9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -247,6 +247,7 @@ struct mlx5_vport { enum mlx5_eswitch_vport_event enabled_events; int index; struct mlx5_devlink_port *dl_port; + u32 agg_max_tx_speed; }; struct mlx5_esw_indir_table; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index 449e4bd86c06..f8e70ac5a85b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1274,6 +1274,11 @@ static void mlx5_lag_modify_device_vports_speed(struct mlx5_core_dev *mdev, if (vport->vport == MLX5_VPORT_UPLINK) continue; + vport->agg_max_tx_speed = speed; + + if (!vport->enabled) + continue; + ret = mlx5_modify_vport_max_tx_speed(mdev, op_mod, vport->vport, true, speed); if (ret) -- cgit v1.2.3 From 5db89c99566fc4728cc92e941d8e1975711e24b5 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Wed, 13 May 2026 21:37:39 -0400 Subject: net: ifb: report ethtool stats over num_tx_queues ifb_dev_init() allocates dp->tx_private to dev->num_tx_queues entries via kzalloc_objs(*txp, dev->num_tx_queues). Both IFB per-queue RX and TX stats live in those entries: ifb_xmit() updates txp->rx_stats using the skb queue mapping, ifb_ri_tasklet() updates txp->tx_stats, and ifb_stats64() aggregates both over dev->num_tx_queues. The ethtool stats callbacks instead size and walk the per-queue stats with dev->real_num_rx_queues and dev->real_num_tx_queues. With an asymmetric device where the RX queue count exceeds the TX queue count, for example: ip link add name ifb10 numtxqueues 1 numrxqueues 8 type ifb ethtool -S ifb10 ifb_get_ethtool_stats() indexes past the tx_private allocation and copies adjacent slab data through ETHTOOL_GSTATS. Use dev->num_tx_queues consistently for the stats strings, the stats count, and the stats data walks. This reports one RX stats group and one TX stats group for each backing ifb_q_private entry, which is the queue set IFB can actually populate. Reproduced under UML+KASAN at v7.1-rc2: BUG: KASAN: slab-out-of-bounds in ifb_fill_stats_data+0x3c/0xae Read of size 8 at addr 0000000062dbd228 by task ethtool/36 ifb_fill_stats_data+0x3c/0xae ifb_get_ethtool_stats+0xc0/0x129 __dev_ethtool+0x1ca5/0x363c dev_ethtool+0x123/0x1b3 dev_ioctl+0x56c/0x744 sock_do_ioctl+0x15f/0x1b2 sock_ioctl+0x4d5/0x50a sys_ioctl+0xd8b/0xde9 With the patch applied, the same UML+KASAN repro is silent and ethtool -S ifb10 reports only the stats backed by the single allocated tx_private entry. Fixes: a21ee5b2fcb8 ("net: ifb: support ethtools stats") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260514013739.3549624-1-michael.bommarito@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ifb.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ifb.c b/drivers/net/ifb.c index 5407d2ed71b3..43aa1bfd41cf 100644 --- a/drivers/net/ifb.c +++ b/drivers/net/ifb.c @@ -211,12 +211,12 @@ static void ifb_get_strings(struct net_device *dev, u32 stringset, u8 *buf) switch (stringset) { case ETH_SS_STATS: - for (i = 0; i < dev->real_num_rx_queues; i++) + for (i = 0; i < dev->num_tx_queues; i++) for (j = 0; j < IFB_Q_STATS_LEN; j++) ethtool_sprintf(&p, "rx_queue_%u_%.18s", i, ifb_q_stats_desc[j].desc); - for (i = 0; i < dev->real_num_tx_queues; i++) + for (i = 0; i < dev->num_tx_queues; i++) for (j = 0; j < IFB_Q_STATS_LEN; j++) ethtool_sprintf(&p, "tx_queue_%u_%.18s", i, ifb_q_stats_desc[j].desc); @@ -229,8 +229,7 @@ static int ifb_get_sset_count(struct net_device *dev, int sset) { switch (sset) { case ETH_SS_STATS: - return IFB_Q_STATS_LEN * (dev->real_num_rx_queues + - dev->real_num_tx_queues); + return IFB_Q_STATS_LEN * dev->num_tx_queues * 2; default: return -EOPNOTSUPP; } @@ -262,12 +261,12 @@ static void ifb_get_ethtool_stats(struct net_device *dev, struct ifb_q_private *txp; int i; - for (i = 0; i < dev->real_num_rx_queues; i++) { + for (i = 0; i < dev->num_tx_queues; i++) { txp = dp->tx_private + i; ifb_fill_stats_data(&data, &txp->rx_stats); } - for (i = 0; i < dev->real_num_tx_queues; i++) { + for (i = 0; i < dev->num_tx_queues; i++) { txp = dp->tx_private + i; ifb_fill_stats_data(&data, &txp->tx_stats); } -- cgit v1.2.3 From 6ea68a8dc7d2711504d944811981a5304af7d7a9 Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Mon, 11 May 2026 12:53:17 -0500 Subject: scsi: sd: Fix return code handling in sd_spinup_disk() As found by smatch-ci, scsi_execute_cmd() can return negative or positve values so we should use a int instead of unsigned int. Fixes: b4d0c33a32c3 ("scsi: sd: Fix sshdr use in sd_spinup_disk") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-scsi/agFbI7E6JQwd3wGW@stanley.mountain/T/#u Signed-off-by: Mike Christie Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20260511175317.114007-1-michael.christie@oracle.com Signed-off-by: Martin K. Petersen --- drivers/scsi/sd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index adc3fa55ca2c..599e75f33334 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -2476,8 +2476,7 @@ sd_spinup_disk(struct scsi_disk *sdkp) { static const u8 cmd[10] = { TEST_UNIT_READY }; unsigned long spintime_expire = 0; - int spintime, sense_valid = 0; - unsigned int the_result; + int the_result, spintime, sense_valid = 0; struct scsi_sense_hdr sshdr; struct scsi_failure failure_defs[] = { /* Do not retry Medium Not Present */ -- cgit v1.2.3 From b52a8d52c3125ec9a93106ed816582368de34426 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Sun, 19 Apr 2026 17:04:20 -0400 Subject: scsi: isci: Fix use-after-free in device removal path The ISCI completion tasklet is initialized in isci_host_alloc() (drivers/scsi/isci/init.c:496) and scheduled from both MSI-X and legacy interrupt handlers (drivers/scsi/isci/host.c:223,613). isci_host_deinit() stops the controller and waits for stop completion, but it never kills completion_tasklet before teardown continues. A top-of-function tasklet_kill() is not sufficient here: interrupts are only disabled when isci_host_stop_complete() runs, so until wait_for_stop() returns the IRQ handlers can still requeue the tasklet. The tasklet callback also re-enables interrupts after draining completions, so killing the tasklet before the source is quiesced leaves the same race open. Once wait_for_stop() returns, no further IRQ-driven scheduling can occur. Kill completion_tasklet there so teardown cannot race a queued tasklet running on a dead ihost. On remove or unload, the stale callback can otherwise dereference ihost and touch ihost->smu_registers after the host lifetime ends. A UML + KASAN analogue reproduced the failure class both with no tasklet_kill() and with tasklet_kill() placed before source quiesce, and stayed clean once the kill happened after quiescing the scheduling source. This mirrors commit f6ab594672d4 ("scsi: aic94xx: fix use-after-free in device removal path"), but ISCI needs the kill after wait_for_stop(). Fixes: 6f231dda6808 ("isci: Intel(R) C600 Series Chipset Storage Control Unit Driver") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Assisted-by: Codex:gpt-5-4 Signed-off-by: Michael Bommarito Link: https://patch.msgid.link/20260419210420.2134639-1-michael.bommarito@gmail.com Signed-off-by: Martin K. Petersen --- drivers/scsi/isci/host.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/scsi/isci/host.c b/drivers/scsi/isci/host.c index 6d2f4c831df7..ff199bab5d1a 100644 --- a/drivers/scsi/isci/host.c +++ b/drivers/scsi/isci/host.c @@ -1252,6 +1252,9 @@ void isci_host_deinit(struct isci_host *ihost) wait_for_stop(ihost); + /* No further IRQ-driven scheduling can happen past wait_for_stop(). */ + tasklet_kill(&ihost->completion_tasklet); + /* phy stop is after controller stop to allow port and device to * go idle before shutting down the phys, but the expectation is * that i/o has been shut off well before we reach this -- cgit v1.2.3 From b71cb088b2e3427924a470fc43e7aedb8a40d2e3 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Fri, 24 Apr 2026 09:39:23 +0800 Subject: scsi: target: tcm_loop: Fix NULL ptr dereference The TCM_LOOP LUN creation process calls device_register() to create the device, which in turn invokes tcm_loop_driver_probe() registered with the TCM_LOOP bus to create and register the scsi_host. However, if the scsi_host memory allocation fails or scsi_add_host() fails, the device_register() process still returns success. Subsequently, when the user binds the LUN to a specific backend device, it accesses the NULL or freed scsi_host. Crash Call Trace: RIP: 0010:scsi_is_host_device+0x7/0x20 scsi_alloc_target+0x32/0x2c0 __scsi_add_device+0x41/0xf0 scsi_add_device+0xd/0x30 tcm_loop_port_link+0x25/0x50 [tcm_loop] target_fabric_port_link+0x9c/0xb0 [target_core_mod] ... This issue is fixed by: 1. Setting the tcm_loop_hba's scsi_host to NULL, if scsi_add_host() fails. 2. Checking the tcm_loop_hba's scsi_host after device_register(). 3. Checking the tcm_loop_hba's scsi_host in tcm_loop_driver_remove(). Fixes: 3703b2c5d041 ("[SCSI] tcm_loop: Add multi-fabric Linux/SCSI LLD fabric module") Signed-off-by: Guixin Liu Reviewed-by: Mike Christie Link: https://patch.msgid.link/20260424013923.25998-1-kanie@linux.alibaba.com Signed-off-by: Martin K. Petersen --- drivers/target/loopback/tcm_loop.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/target/loopback/tcm_loop.c b/drivers/target/loopback/tcm_loop.c index a25fd826b542..110297345751 100644 --- a/drivers/target/loopback/tcm_loop.c +++ b/drivers/target/loopback/tcm_loop.c @@ -393,6 +393,7 @@ static int tcm_loop_driver_probe(struct device *dev) if (error) { pr_err("%s: scsi_add_host failed\n", __func__); scsi_host_put(sh); + tl_hba->sh = NULL; return -ENODEV; } return 0; @@ -406,8 +407,10 @@ static void tcm_loop_driver_remove(struct device *dev) tl_hba = to_tcm_loop_hba(dev); sh = tl_hba->sh; - scsi_remove_host(sh); - scsi_host_put(sh); + if (sh) { + scsi_remove_host(sh); + scsi_host_put(sh); + } } static void tcm_loop_release_adapter(struct device *dev) @@ -436,6 +439,11 @@ static int tcm_loop_setup_hba_bus(struct tcm_loop_hba *tl_hba, int tcm_loop_host return -ENODEV; } + if (!tl_hba->sh) { + device_unregister(&tl_hba->dev); + return -ENODEV; + } + return 0; } -- cgit v1.2.3 From 3f9ed5f5aa9ecffd284218fffd6abc29617f51d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Tue, 28 Apr 2026 16:45:53 +0200 Subject: drm/msm: Don't use UTS_RELEASE directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UTS_RELEASE evaluates to a static string and changes quite easily (e.g. uncommitted changes in the source tree or new commits). So when checking if a patch introduces changes to the resulting binary each usage of UTS_RELEASE is source of annoyance. Instead of using UTS_RELEASE directly use init_utsname()->release which evaluates to the same string but with that a change of UTS_RELEASE doesn't affect msm_disp_snapshot_util.o or msm_gpu.o. Signed-off-by: Uwe Kleine-König (The Capable Hub) Patchwork: https://patchwork.freedesktop.org/patch/721948/ Message-ID: <20260428144553.1103785-2-u.kleine-koenig@baylibre.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c | 4 ++-- drivers/gpu/drm/msm/msm_gpu.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c b/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c index 19b470968f4d..636bcb65a2a4 100644 --- a/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c +++ b/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c @@ -5,7 +5,7 @@ #define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ -#include +#include #include "msm_disp_snapshot.h" @@ -79,7 +79,7 @@ void msm_disp_state_print(struct msm_disp_state *state, struct drm_printer *p) } drm_printf(p, "---\n"); - drm_printf(p, "kernel: " UTS_RELEASE "\n"); + drm_printf(p, "kernel: %s\n", init_utsname()->release); drm_printf(p, "module: " KBUILD_MODNAME "\n"); drm_printf(p, "dpu devcoredump\n"); drm_printf(p, "time: %ptSp\n", &state->time); diff --git a/drivers/gpu/drm/msm/msm_gpu.c b/drivers/gpu/drm/msm/msm_gpu.c index d901304fff6d..b0228ec42030 100644 --- a/drivers/gpu/drm/msm/msm_gpu.c +++ b/drivers/gpu/drm/msm/msm_gpu.c @@ -13,11 +13,11 @@ #include "msm_gpu_trace.h" //#include "adreno/adreno_gpu.h" -#include #include #include #include #include +#include /* * Power Management: @@ -196,7 +196,7 @@ static ssize_t msm_gpu_devcoredump_read(char *buffer, loff_t offset, p = drm_coredump_printer(&iter); drm_printf(&p, "---\n"); - drm_printf(&p, "kernel: " UTS_RELEASE "\n"); + drm_printf(&p, "kernel: %s\n", init_utsname()->release); drm_printf(&p, "module: " KBUILD_MODNAME "\n"); drm_printf(&p, "time: %ptSp\n", &state->time); if (state->comm) -- cgit v1.2.3 From 3392291fc509d8ad6e4ad90f15b0a193f721cbc9 Mon Sep 17 00:00:00 2001 From: Daniel J Blueman Date: Fri, 8 May 2026 14:57:21 +0800 Subject: drm/msm: Fix shrinker deadlock With PROVE_LOCKING on an Snapdragon X1 and VM reclaim pressure, we see: ====================================================== WARNING: possible circular locking dependency detected 7.0.0-debug+ #43 Tainted: G W ------------------------------------------------------ kswapd0/82 is trying to acquire lock: ffff800080ec3870 (reservation_ww_class_acquire){+.+.}-{0:0}, at: msm_gem_shrinker_scan+0x17c/0x400 [msm] but task is already holding lock: ffffc31709b263b8 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x88/0x988 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #2 (fs_reclaim){+.+.}-{0:0}: __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 fs_reclaim_acquire+0xd0/0xf0 dma_resv_lockdep+0x224/0x348 do_one_initcall+0x84/0x5d0 do_initcalls+0x194/0x1d8 kernel_init_freeable+0x128/0x180 kernel_init+0x2c/0x160 ret_from_fork+0x10/0x20 -> #1 (reservation_ww_class_mutex){+.+.}-{4:4}: __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 dma_resv_lockdep+0x1a8/0x348 do_one_initcall+0x84/0x5d0 do_initcalls+0x194/0x1d8 kernel_init_freeable+0x128/0x180 kernel_init+0x2c/0x160 ret_from_fork+0x10/0x20 -> #0 (reservation_ww_class_acquire){+.+.}-{0:0}: check_prev_add+0x114/0x790 validate_chain+0x594/0x6f0 __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 drm_gem_lru_scan+0x1ac/0x440 msm_gem_shrinker_scan+0x17c/0x400 [msm] do_shrink_slab+0x150/0x4a0 shrink_slab+0x144/0x460 shrink_one+0x9c/0x1b0 shrink_many+0x27c/0x5c0 shrink_node+0x344/0x550 balance_pgdat+0x2c0/0x988 kswapd+0x11c/0x318 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 other info that might help us debug this: Chain exists of: reservation_ww_class_acquire --> reservation_ww_class_mutex --> fs_reclaim Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(fs_reclaim); lock(reservation_ww_class_mutex); lock(fs_reclaim); lock(reservation_ww_class_acquire); *** DEADLOCK *** 1 lock held by kswapd0/82: #0: ffffc31709b263b8 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x88/0x988 stack backtrace: CPU: 4 UID: 0 PID: 82 Comm: kswapd0 Tainted: G W 7.0.0-debug+ #43 PREEMPT(full) Tainted: [W]=WARN Hardware name: LENOVO 21BX0016US/21BX0016US, BIOS N3HET94W (1.66 ) 09/15/2025 Call trace: show_stack+0x20/0x40 (C) dump_stack_lvl+0x9c/0xd0 dump_stack+0x18/0x30 print_circular_bug+0x114/0x120 check_noncircular+0x178/0x198 check_prev_add+0x114/0x790 validate_chain+0x594/0x6f0 __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 drm_gem_lru_scan+0x1ac/0x440 msm_gem_shrinker_scan+0x17c/0x400 [msm] do_shrink_slab+0x150/0x4a0 shrink_slab+0x144/0x460 shrink_one+0x9c/0x1b0 shrink_many+0x27c/0x5c0 shrink_node+0x344/0x550 balance_pgdat+0x2c0/0x988 kswapd+0x11c/0x318 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 kswapd0 holding fs_reclaim calls the MSM shrinker, which calls dma_resv_lock. This in turn acquires fs_reclaim. Fix this deadlock by using dma_resv_trylock() instead, dropping the subsequently unused passed wait-wound lock 'ticket'. Cc: stable@vger.kernel.org Signed-off-by: Daniel J Blueman Fixes: fe4952b5f27c ("drm/msm: Convert vm locking") Patchwork: https://patchwork.freedesktop.org/patch/723564/ Message-ID: <20260508065722.18785-1-daniel@quora.org> [rob: fixup compile errors, replace lockdep splat with something legible] Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/msm_gem_shrinker.c | 40 ++++++++++++++-------------------- 1 file changed, 16 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/msm_gem_shrinker.c b/drivers/gpu/drm/msm/msm_gem_shrinker.c index 31fa51a44f86..6e39e4e578bb 100644 --- a/drivers/gpu/drm/msm/msm_gem_shrinker.c +++ b/drivers/gpu/drm/msm/msm_gem_shrinker.c @@ -43,8 +43,7 @@ msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) } static bool -with_vm_locks(struct ww_acquire_ctx *ticket, - void (*fn)(struct drm_gem_object *obj), +with_vm_locks(void (*fn)(struct drm_gem_object *obj), struct drm_gem_object *obj) { /* @@ -52,7 +51,7 @@ with_vm_locks(struct ww_acquire_ctx *ticket, * success paths */ struct drm_gpuvm_bo *vm_bo, *last_locked = NULL; - int ret = 0; + bool locked = true; drm_gem_for_each_gpuvm_bo (vm_bo, obj) { struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm); @@ -60,23 +59,14 @@ with_vm_locks(struct ww_acquire_ctx *ticket, if (resv == obj->resv) continue; - ret = dma_resv_lock(resv, ticket); - - /* - * Since we already skip the case when the VM and obj - * share a resv (ie. _NO_SHARE objs), we don't expect - * to hit a double-locking scenario... which the lock - * unwinding cannot really cope with. - */ - WARN_ON(ret == -EALREADY); - /* - * Don't bother with slow-lock / backoff / retry sequence, - * if we can't get the lock just give up and move on to - * the next object. + * dma_resv_lock can't be used due to acquiring 'ticket' before the + * fs_reclaim lock, which is held in shrinker context */ - if (ret) + if (!dma_resv_trylock(resv)) { + locked = false; goto out_unlock; + } /* * Hold a ref to prevent the vm_bo from being freed @@ -108,11 +98,11 @@ out_unlock: } } - return ret == 0; + return locked; } static bool -purge(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) +purge(struct drm_gem_object *obj, struct ww_acquire_ctx *) { if (!is_purgeable(to_msm_bo(obj))) return false; @@ -120,11 +110,11 @@ purge(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) if (msm_gem_active(obj)) return false; - return with_vm_locks(ticket, msm_gem_purge, obj); + return with_vm_locks(msm_gem_purge, obj); } static bool -evict(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) +evict(struct drm_gem_object *obj, struct ww_acquire_ctx *) { if (is_unevictable(to_msm_bo(obj))) return false; @@ -132,7 +122,7 @@ evict(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket) if (msm_gem_active(obj)) return false; - return with_vm_locks(ticket, msm_gem_evict, obj); + return with_vm_locks(msm_gem_evict, obj); } static bool @@ -164,7 +154,6 @@ static unsigned long msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) { struct msm_drm_private *priv = shrinker->private_data; - struct ww_acquire_ctx ticket; struct { struct drm_gem_lru *lru; bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket); @@ -185,11 +174,14 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) for (unsigned i = 0; (nr > 0) && (i < ARRAY_SIZE(stages)); i++) { if (!stages[i].cond) continue; + /* + * 'ticket' not needed on trylock paths + */ stages[i].freed = drm_gem_lru_scan(stages[i].lru, nr, &stages[i].remaining, stages[i].shrink, - &ticket); + NULL); nr -= stages[i].freed; freed += stages[i].freed; remaining += stages[i].remaining; -- cgit v1.2.3 From ed831e7ea1a860bdbab3eadeb95f7f73e9d212df Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 6 May 2026 09:45:37 -0700 Subject: PCI: brcmstb: Assign pcie->gen from of_pci_get_max_link_speed() After commit 03f920936977 ("PCI: controller: Validate max-link-speed"), pcie->gen stopped being assigned and as a result the established PCIe link would stop supporting Gen3 speeds on 2712 since pcie->gen is used to populate LnkCntl2 and LnkCap in brcm_pcie_set_gen(). If the 'max-link-speed' property is not specified, or it exceeds Gen3, resort to the HW defaults. Link: https://github.com/raspberrypi/linux/issues/7343 Reported-by: Dom Cobley Reported-by: Phil Elwell Fixes: 03f920936977 ("PCI: controller: Validate max-link-speed") Signed-off-by: Florian Fainelli Signed-off-by: Bjorn Helgaas Reviewed-by: Hans Zhang <18255117159@163.com> Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260506164537.103196-1-florian.fainelli@broadcom.com --- drivers/pci/controller/pcie-brcmstb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 714bcab97b60..08a0e7091ced 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -2072,8 +2072,10 @@ static int brcm_pcie_probe(struct platform_device *pdev) return PTR_ERR(pcie->clk); ret = of_pci_get_max_link_speed(np); - if (pcie_get_link_speed(ret) == PCI_SPEED_UNKNOWN) + if (ret < 0 || ret > 3) pcie->gen = 0; + else + pcie->gen = ret; pcie->ssc = of_property_read_bool(np, "brcm,enable-ssc"); -- cgit v1.2.3 From cfd08f09723c5408eb3025b945fff08a99343911 Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Wed, 13 May 2026 15:45:18 +0300 Subject: IB/IPoIB: ndo_set_rx_mode_async conversion The commit in the fixes tag added a warning for devices that are netdev ops locked that they should be converted to .ndo_set_rx_mode_async. IPoIB for mlx5 is such a driver which was missed during the conversion because the flow is more complex: - mlx5 part of IPoIB device was converted to ops-lock in commit [1]. - ipoib_intf_init() then overrides netdev_ops with ipoib_netdev_ops_{pf,vf}, which still wired ndo_set_rx_mode to the legacy sync path -- tripping the new warning on every probe. So now we have the following splat: netdevice: ib0 (uninitialized): ops-locked drivers should use ndo_set_rx_mode_async WARNING: net/core/dev.c:11366 at register_netdevice+0x83c/0x21d0 ... register_netdev+0x1f/0x40 ipoib_add_one+0x35c/0x880 [ib_ipoib] This patch implements .ndo_set_rx_mode_async but it simply schedules the multicast restart task like before. This is done to maintain the assumption that this task and others [2] must run on the same order workqueue to avoid racing with themselves. The race between ipoib_mcast_join_task() and ipoib_mcast_restart_task() would be the most obvious example. [1] 8f7b00307bf1, "net/mlx5e: Convert mlx5 netdevs to instance locking") [2] ipoib_mcast_join_task, ipoib_mcast_restart_task, ipoib_mcast_carrier_on_task, ipoib_reap_ah, ipoib_reap_neigh Fixes: 3cbd22938877 ("net: warn ops-locked drivers still using ndo_set_rx_mode") Signed-off-by: Dragos Tatulea Reviewed-by: Cosmin Ratiu Acked-by: Jason Gunthorpe Link: https://patch.msgid.link/20260513124519.3357165-1-dtatulea@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/ulp/ipoib/ipoib_main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c index 402671567736..3e1e1e861739 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_main.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c @@ -1297,7 +1297,9 @@ static int ipoib_hard_header(struct sk_buff *skb, return IPOIB_HARD_LEN; } -static void ipoib_set_mcast_list(struct net_device *dev) +static void ipoib_set_rx_mode_async(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct ipoib_dev_priv *priv = ipoib_priv(dev); @@ -2160,7 +2162,7 @@ static const struct net_device_ops ipoib_netdev_ops_pf = { .ndo_fix_features = ipoib_fix_features, .ndo_start_xmit = ipoib_start_xmit, .ndo_tx_timeout = ipoib_timeout, - .ndo_set_rx_mode = ipoib_set_mcast_list, + .ndo_set_rx_mode_async = ipoib_set_rx_mode_async, .ndo_get_iflink = ipoib_get_iflink, .ndo_set_vf_link_state = ipoib_set_vf_link_state, .ndo_get_vf_config = ipoib_get_vf_config, @@ -2183,7 +2185,7 @@ static const struct net_device_ops ipoib_netdev_ops_vf = { .ndo_fix_features = ipoib_fix_features, .ndo_start_xmit = ipoib_start_xmit, .ndo_tx_timeout = ipoib_timeout, - .ndo_set_rx_mode = ipoib_set_mcast_list, + .ndo_set_rx_mode_async = ipoib_set_rx_mode_async, .ndo_get_iflink = ipoib_get_iflink, .ndo_get_stats64 = ipoib_get_stats, .ndo_eth_ioctl = ipoib_ioctl, -- cgit v1.2.3 From c0bf0a4f3f1f5f57aa83e1400ba4f56f0abfd542 Mon Sep 17 00:00:00 2001 From: Sam Daly Date: Wed, 13 May 2026 18:42:53 +0200 Subject: octeontx2-af: CGX: add bounds check to cgx_speed_mbps index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cgx_speed_mbps has 13 elements but RESP_LINKSTAT_SPEED can yield values 0-15. If it returns a value >= 13, this causes an out-of-bounds array access. Add a bounds check and default to speed 0 if the index is out of range. Fixes: 61071a871ea6 ("octeontx2-af: Forward CGX link notifications to PFs") Cc: Sunil Goutham Cc: Linu Cherian Cc: Geetha sowjanya Cc: hariprasad Cc: Subbaraya Sundeep Cc: Andrew Lunn Cc: stable Signed-off-by: Sam Daly Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/2026051352-refined-demise-e88d@gregkh Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/cgx.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c index 4f33a816bc7a..2e94d5105016 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/cgx.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/cgx.c @@ -1294,13 +1294,18 @@ static inline void link_status_user_format(u64 lstat, struct cgx_link_user_info *linfo, struct cgx *cgx, u8 lmac_id) { + unsigned int speed; + linfo->link_up = FIELD_GET(RESP_LINKSTAT_UP, lstat); linfo->full_duplex = FIELD_GET(RESP_LINKSTAT_FDUPLEX, lstat); - linfo->speed = cgx_speed_mbps[FIELD_GET(RESP_LINKSTAT_SPEED, lstat)]; linfo->an = FIELD_GET(RESP_LINKSTAT_AN, lstat); linfo->fec = FIELD_GET(RESP_LINKSTAT_FEC, lstat); linfo->lmac_type_id = FIELD_GET(RESP_LINKSTAT_LMAC_TYPE, lstat); + speed = FIELD_GET(RESP_LINKSTAT_SPEED, lstat); + linfo->speed = speed < ARRAY_SIZE(cgx_speed_mbps) ? + cgx_speed_mbps[speed] : 0; + if (linfo->lmac_type_id >= LMAC_MODE_MAX) { dev_err(&cgx->pdev->dev, "Unknown lmac_type_id %d reported by firmware on cgx port%d:%d", linfo->lmac_type_id, cgx->cgx_id, lmac_id); -- cgit v1.2.3 From 496ba79b9496b8b3747cbc764ebd33ee7325e806 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Sun, 10 May 2026 01:55:37 +0800 Subject: spi: mtk-snfi: Fix resource leak in mtk_snand_read_page_cache() When DMA read times out in mtk_snand_read_page_cache(), the original code erroneously jumped to cleanup label which skips DMA unmapping and ECC disable, causing a resource leak. Fixes: 764f1b748164 ("spi: add driver for MTK SPI NAND Flash Interface") Signed-off-by: Felix Gu Link: https://patch.msgid.link/20260510-snfi-v1-1-bc375cf1af8e@gmail.com Signed-off-by: Mark Brown --- drivers/spi/spi-mtk-snfi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/spi/spi-mtk-snfi.c b/drivers/spi/spi-mtk-snfi.c index e616e6800e92..6e96e50fedad 100644 --- a/drivers/spi/spi-mtk-snfi.c +++ b/drivers/spi/spi-mtk-snfi.c @@ -961,7 +961,7 @@ static int mtk_snand_read_page_cache(struct mtk_snand *snf, &snf->op_done, usecs_to_jiffies(SNFI_POLL_INTERVAL))) { dev_err(snf->dev, "DMA timed out for reading from cache.\n"); ret = -ETIMEDOUT; - goto cleanup; + goto cleanup2; } // Wait for BUS_SEC_CNTR returning expected value -- cgit v1.2.3 From 76824d2467feb1828b745d6add2541918d7be3da Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 16 May 2026 14:53:45 +0300 Subject: drm/msm/snapshot: fix dumping of the unaligned regions The snapshotting code internally aligns data segment to 16 bytes. This works fine for DPU code (where most of the regions are aligned), but fails for snapshotting of the DSI data (because DSI data region is shifted by 4 bytes). Fix the code by removing length alignment and by accurately printing last registers in the region. While reworking the code also fix the 16x memory overallocation in msm_disp_state_dump_regs(). Fixes: 98659487b845 ("drm/msm: add support to take dpu snapshot") Reported-by: Salendarsingh Gaud Signed-off-by: Dmitry Baryshkov Patchwork: https://patchwork.freedesktop.org/patch/725449/ Message-ID: <20260516-msm-fix-dsi-dump-2-v2-1-9e49fb2d240e@oss.qualcomm.com> Signed-off-by: Rob Clark --- drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c b/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c index 636bcb65a2a4..01a7019f5d67 100644 --- a/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c +++ b/drivers/gpu/drm/msm/disp/msm_disp_snapshot_util.c @@ -9,7 +9,7 @@ #include "msm_disp_snapshot.h" -static void msm_disp_state_dump_regs(u32 **reg, u32 aligned_len, void __iomem *base_addr) +static void msm_disp_state_dump_regs(u32 **reg, u32 len, void __iomem *base_addr) { u32 len_padded; u32 num_rows; @@ -19,11 +19,11 @@ static void msm_disp_state_dump_regs(u32 **reg, u32 aligned_len, void __iomem *b void __iomem *end_addr; int i; - len_padded = aligned_len * REG_DUMP_ALIGN; - num_rows = aligned_len / REG_DUMP_ALIGN; + len_padded = round_up(len, REG_DUMP_ALIGN); + num_rows = DIV_ROUND_UP(len, REG_DUMP_ALIGN); addr = base_addr; - end_addr = base_addr + aligned_len; + end_addr = base_addr + len; *reg = kvzalloc(len_padded, GFP_KERNEL); if (!*reg) @@ -48,8 +48,8 @@ static void msm_disp_state_dump_regs(u32 **reg, u32 aligned_len, void __iomem *b static void msm_disp_state_print_regs(const u32 *dump_addr, u32 len, void __iomem *base_addr, struct drm_printer *p) { + void __iomem *addr, *end_addr; int i; - void __iomem *addr; u32 num_rows; if (!dump_addr) { @@ -58,6 +58,7 @@ static void msm_disp_state_print_regs(const u32 *dump_addr, u32 len, } addr = base_addr; + end_addr = base_addr + len; num_rows = len / REG_DUMP_ALIGN; for (i = 0; i < num_rows; i++) { @@ -67,6 +68,17 @@ static void msm_disp_state_print_regs(const u32 *dump_addr, u32 len, dump_addr[i * 4 + 2], dump_addr[i * 4 + 3]); addr += REG_DUMP_ALIGN; } + + if (addr != end_addr) { + drm_printf(p, "0x%lx : %08x", + (unsigned long)(addr - base_addr), + dump_addr[i * 4]); + if (addr + 0x4 < end_addr) + drm_printf(p, " %08x", dump_addr[i * 4 + 1]); + if (addr + 0x8 < end_addr) + drm_printf(p, " %08x", dump_addr[i * 4 + 2]); + drm_printf(p, "\n"); + } } void msm_disp_state_print(struct msm_disp_state *state, struct drm_printer *p) @@ -185,7 +197,7 @@ void msm_disp_snapshot_add_block(struct msm_disp_state *disp_state, u32 len, va_end(va); INIT_LIST_HEAD(&new_blk->node); - new_blk->size = ALIGN(len, REG_DUMP_ALIGN); + new_blk->size = len; new_blk->base_addr = base_addr; msm_disp_state_dump_regs(&new_blk->state, new_blk->size, base_addr); -- cgit v1.2.3 From 92cee08dc4f00e77fd1317e4343c5d458b0abab7 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Sat, 4 Apr 2026 22:41:44 -0700 Subject: wifi: iwlwifi: mld: fix TSO segmentation explosion when AMSDU is disabled When the TLC notification disables AMSDU for a TID, the MLD driver sets max_tid_amsdu_len to the sentinel value 1. The TSO segmentation path in iwl_mld_tx_tso_segment() checks for zero but not for this sentinel, allowing it to reach the num_subframes calculation: num_subframes = (max_tid_amsdu_len + pad) / (subf_len + pad) = (1 + 2) / (1534 + 2) = 0 This zero propagates to iwl_tx_tso_segment() which sets: gso_size = num_subframes * mss = 0 Calling skb_gso_segment() with gso_size=0 creates over 32000 tiny segments from a single GSO skb. This floods the TX ring with ~1024 micro-frames (the rest are purged), creating a massive burst of TX completion events that can lead to memory corruption and a subsequent use-after-free in TCP's retransmit queue (refcount underflow in tcp_shifted_skb, NULL deref in tcp_rack_detect_loss). The MVM driver is immune because it checks mvmsta->amsdu_enabled before reaching the num_subframes calculation. The MLD driver has no equivalent bitmap check and relies solely on max_tid_amsdu_len, which does not catch the sentinel value. Fix this by detecting the sentinel value (max_tid_amsdu_len == 1) at the existing check and falling back to non-AMSDU TSO segmentation. Also add a WARN_ON_ONCE guard after the num_subframes division as defense-in-depth to catch any future code paths that produce zero through a different mechanism. Suggested-by: Miriam Rachel Korenblit Fixes: d1e879ec600f ("wifi: iwlwifi: add iwlmld sub-driver") Signed-off-by: Cole Leavitt Link: https://patch.msgid.link/20260405054145.1064152-3-cole@unwrap.rs Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/tx.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 546d09a38dab..094a28f75559 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -834,7 +834,7 @@ static int iwl_mld_tx_tso_segment(struct iwl_mld *mld, struct sk_buff *skb, return -EINVAL; max_tid_amsdu_len = sta->cur->max_tid_amsdu_len[tid]; - if (!max_tid_amsdu_len) + if (!max_tid_amsdu_len || max_tid_amsdu_len == 1) return iwl_tx_tso_segment(skb, 1, netdev_flags, mpdus_skbs); /* Sub frame header + SNAP + IP header + TCP header + MSS */ @@ -846,6 +846,9 @@ static int iwl_mld_tx_tso_segment(struct iwl_mld *mld, struct sk_buff *skb, */ num_subframes = (max_tid_amsdu_len + pad) / (subf_len + pad); + if (WARN_ON_ONCE(!num_subframes)) + return iwl_tx_tso_segment(skb, 1, netdev_flags, mpdus_skbs); + if (sta->max_amsdu_subframes && num_subframes > sta->max_amsdu_subframes) num_subframes = sta->max_amsdu_subframes; -- cgit v1.2.3 From 2becb38a3e217ef2b2f42fddd7db7a25905ec291 Mon Sep 17 00:00:00 2001 From: Sheroz Juraev Date: Sun, 15 Mar 2026 13:12:21 +0500 Subject: wifi: iwlwifi: mld: stop TX during firmware restart When iwlwifi firmware crashes (e.g., NMI_INTERRUPT_UNKNOWN on Intel BE201/Wi-Fi 7), iwl_mld_nic_error() sets mld->fw_status.in_hw_restart to true. However, iwl_mld_tx_from_txq() does not check this flag before dequeuing frames from mac80211 and pushing them to the transport layer. Since the firmware is dead, iwl_trans_tx() returns -EIO for each frame, which then gets freed immediately. Under high-throughput conditions (e.g., Tailscale UDP traffic or active SSH sessions), this creates a tight dequeue-send-fail-free loop that wastes CPU cycles and generates rapid skb allocation churn, leading to memory pressure from slab fragmentation. The RX path already has this guard (iwl_mld_rx_mpdu checks in_hw_restart at rx.c:1906), and so does the TXQ allocation worker (iwl_mld_add_txqs_wk at tx.c:156). Add the same guard to iwl_mld_tx_from_txq() to stop all TX during firmware restart. Frames left in mac80211's TXQs are naturally drained after restart completes, when queue reallocation triggers iwl_mld_tx_from_txq() via iwl_mld_add_txq_list(), or when new upper-layer traffic invokes wake_tx_queue. Tested on ASUS Zenbook 14 UX3405CA with Intel BE201 (Wi-Fi 7) on kernel 6.19.5 where the firmware crashes approximately every 10-15 minutes under Tailscale traffic. Fixes: d1e879ec600f ("wifi: iwlwifi: add iwlmld sub-driver") Cc: stable@vger.kernel.org Signed-off-by: Sheroz Juraev Link: https://patch.msgid.link/20260315081221.2678478-1-goodmartiandev@gmail.com Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/tx.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tx.c b/drivers/net/wireless/intel/iwlwifi/mld/tx.c index 094a28f75559..0bcb1ae69468 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tx.c @@ -973,6 +973,16 @@ void iwl_mld_tx_from_txq(struct iwl_mld *mld, struct ieee80211_txq *txq) struct sk_buff *skb = NULL; u8 zero_addr[ETH_ALEN] = {}; + /* + * Don't transmit during firmware restart. The firmware is dead, + * so iwl_trans_tx() would return -EIO for each frame. Avoid the + * overhead of dequeuing from mac80211 only to immediately free + * the skbs, and the potential memory pressure from rapid skb + * allocation churn during high-throughput restart scenarios. + */ + if (unlikely(mld->fw_status.in_hw_restart)) + return; + /* * No need for threads to be pending here, they can leave the first * taker all the work. -- cgit v1.2.3 From d733ed481fd20a8e7bfe5119c4e77761ba3f87ee Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Fri, 15 May 2026 15:14:56 +0300 Subject: wifi: iwlwifi: mld: don't dereference a pointer before NULL checking it In iwl_mld_remove_link, the link->fw_id is saved at the beginning of the function so we have it after we freed the link. But the link pointer can be NULL, and is not checked when the fw_id is stored. Fix it by simply freeing the link at the end of the function. fFixes: 0e66a39f4f0e ("wifi: iwlwifi: fix potential use after free in iwl_mld_remove_link()") Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260515151351.371f40fc6711.I6a82cfe9655564e9c5731af91c36493b26b1208e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/link.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/link.c b/drivers/net/wireless/intel/iwlwifi/mld/link.c index b66e84d2365f..be2cdf43c72e 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/link.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/link.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include "constants.h" @@ -504,7 +504,6 @@ void iwl_mld_remove_link(struct iwl_mld *mld, struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif); struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf); bool is_deflink = link == &mld_vif->deflink; - u8 fw_id = link->fw_id; if (WARN_ON(!link || link->active)) return; @@ -512,15 +511,15 @@ void iwl_mld_remove_link(struct iwl_mld *mld, iwl_mld_rm_link_from_fw(mld, bss_conf); /* Continue cleanup on failure */ - if (!is_deflink) - kfree_rcu(link, rcu_head); - RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL); - if (WARN_ON(fw_id >= mld->fw->ucode_capa.num_links)) + if (WARN_ON(link->fw_id >= mld->fw->ucode_capa.num_links)) return; - RCU_INIT_POINTER(mld->fw_id_to_bss_conf[fw_id], NULL); + RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL); + + if (!is_deflink) + kfree_rcu(link, rcu_head); } void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld, -- cgit v1.2.3 From fb84b5cbcaab3ca0f4e961d92a40ed7f3aac483b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:14:57 +0300 Subject: wifi: iwlwifi: mvm: fix driver-set TX rates on old devices On old devices such as 7265D, rates are still encoded in version 1 format, which doesn't use the CCK/OFDM rate index (0-3/0-7) but rather their PLCP value (e.g. 10 for 1 Mbps CCK rate.) While introducing v3 rates, I changed the driver from internally handling v1 rates and converting to v2, to internally handling v3 and converting to v1 or v2 according to the firmware. I accordingly changed the code in iwl_mvm_mac80211_idx_to_hwrate() to no longer have different values for different APIs. This was correct. However, I later reverted this part of the change, because it was reported that I had broken beacon rates, causing a FW assert/crash. This caused TX_CMD rates to be set incorrectly, potentially causing a warning when reported back from the device as having been used. Fix this (hopefully correctly now) by handling beacon rates in the TX_CMD that's embedded in the beacon template command separately. Restore iwl_mvm_mac80211_idx_to_hwrate() to return only the rate index, not PLCP value, fixing the real TX_CMD. Cc: stable@vger.kernel.org Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515151351.7407e293dff7.I4ea1a17f8fe99c933d3f3e30d077cf4246125c3e@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c | 27 +++++++++++++++-------- drivers/net/wireless/intel/iwlwifi/mvm/utils.c | 14 ++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c index c523c5e82d4a..8ffa72aca3cf 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac-ctxt.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -927,13 +927,18 @@ u8 iwl_mvm_mac_ctxt_get_lowest_rate(struct iwl_mvm *mvm, u16 iwl_mvm_mac_ctxt_get_beacon_flags(const struct iwl_fw *fw, u8 rate_idx) { - u16 flags = iwl_mvm_mac80211_idx_to_hwrate(fw, rate_idx); bool is_new_rate = iwl_fw_lookup_cmd_ver(fw, BEACON_TEMPLATE_CMD, 0) > 10; + u16 flags = 0; if (rate_idx <= IWL_LAST_CCK_RATE) flags |= is_new_rate ? IWL_MAC_BEACON_CCK : IWL_MAC_BEACON_CCK_V1; + if (iwl_fw_lookup_cmd_ver(fw, TX_CMD, 0) > 8) + flags |= iwl_mvm_mac80211_idx_to_hwrate(fw, rate_idx); + else + flags |= iwl_fw_rate_idx_to_plcp(rate_idx); + return flags; } @@ -962,6 +967,7 @@ static void iwl_mvm_mac_ctxt_set_tx(struct iwl_mvm *mvm, { struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); struct ieee80211_tx_info *info; + u32 rate_n_flags = 0; u8 rate; u32 tx_flags; @@ -981,18 +987,21 @@ static void iwl_mvm_mac_ctxt_set_tx(struct iwl_mvm *mvm, IWL_UCODE_TLV_CAPA_BEACON_ANT_SELECTION)) { iwl_mvm_toggle_tx_ant(mvm, &mvm->mgmt_last_antenna_idx); - tx_params->rate_n_flags = - cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) << - RATE_MCS_ANT_POS); + rate_n_flags |= BIT(mvm->mgmt_last_antenna_idx) << + RATE_MCS_ANT_POS; } rate = iwl_mvm_mac_ctxt_get_beacon_rate(mvm, info, vif); - tx_params->rate_n_flags |= - cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate)); - if (rate == IWL_FIRST_CCK_RATE) - tx_params->rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK_V1); + if (rate < IWL_FIRST_OFDM_RATE) + rate_n_flags |= RATE_MCS_MOD_TYPE_CCK; + else + rate_n_flags |= RATE_MCS_MOD_TYPE_LEGACY_OFDM; + + rate_n_flags |= iwl_mvm_mac80211_idx_to_hwrate(mvm->fw, rate); + tx_params->rate_n_flags = iwl_mvm_v3_rate_to_fw(rate_n_flags, + mvm->fw_rates_ver); } int iwl_mvm_mac_ctxt_send_beacon_cmd(struct iwl_mvm *mvm, diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c index 4a33a032c2a7..f052537e9567 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/utils.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2014 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -159,15 +159,9 @@ int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, u8 iwl_mvm_mac80211_idx_to_hwrate(const struct iwl_fw *fw, int rate_idx) { - if (iwl_fw_lookup_cmd_ver(fw, TX_CMD, 0) > 8) - /* In the new rate legacy rates are indexed: - * 0 - 3 for CCK and 0 - 7 for OFDM. - */ - return (rate_idx >= IWL_FIRST_OFDM_RATE ? - rate_idx - IWL_FIRST_OFDM_RATE : - rate_idx); - - return iwl_fw_rate_idx_to_plcp(rate_idx); + return rate_idx >= IWL_FIRST_OFDM_RATE ? + rate_idx - IWL_FIRST_OFDM_RATE : + rate_idx; } u8 iwl_mvm_mac80211_ac_to_ucode_ac(enum ieee80211_ac_numbers ac) -- cgit v1.2.3 From 25e416f148f3f948638ca7c6ff63fd842d9c07ad Mon Sep 17 00:00:00 2001 From: Moriya Itzchaki Date: Fri, 15 May 2026 15:14:58 +0300 Subject: wifi: iwlwifi: use correct function to read STEP_URM register CNVI_PMU_STEP_FLOW is a PRPH register, not a UMAC PRPH register. Use iwl_read_prph() instead of iwl_read_umac_prph() to read it correctly. Signed-off-by: Moriya Itzchaki Reviewed-by: Johannes Berg Link: https://patch.msgid.link/20260515151352.3a69fa2dbda7.I8d96635a9c06a835b05a10b6d66c8a9299676246@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c index a50e845cea42..64262bcca55d 100644 --- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c +++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans-gen2.c @@ -398,9 +398,9 @@ void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans) mutex_unlock(&trans_pcie->mutex); if (trans->mac_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) - trans->step_urm = !!(iwl_read_umac_prph(trans, - CNVI_PMU_STEP_FLOW) & - CNVI_PMU_STEP_FLOW_FORCE_URM); + trans->step_urm = !!(iwl_read_prph(trans, + CNVI_PMU_STEP_FLOW) & + CNVI_PMU_STEP_FLOW_FORCE_URM); } static bool iwl_pcie_set_ltr(struct iwl_trans *trans) -- cgit v1.2.3 From b753b3334bad7c4735b6e5face0c331d4be11dda Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 15:14:59 +0300 Subject: wifi: iwlwifi: mld: don't WARN on WoWLAN suspend w/o BSS vif Clearly, from a user perspective, it must be valid to configure WoWLAN (which can include network detection) and then suspend while not connected to a network, or even without an interface at all (WoWLAN config is handled on a per-wiphy basis). Since mac80211 doesn't distinguish these cases and simply calls the driver to suspend whenever WoWLAN is configured, the driver has to cleanly handle the case where it's called for WoWLAN but no (BSS) interface exists. Remove the WARN_ON(), move the print so it doesn't get done in this case, and keep returning 1 to disconnect everything. Signed-off-by: Johannes Berg Link: https://patch.msgid.link/20260515151352.0c55d1135409.I54f8be0e2aa28cfb1cb1dcf3b2d2d8fe75b4397b@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/d3.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c index ef98efc8fb1b..3a595a1c2e00 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c @@ -1930,12 +1930,12 @@ int iwl_mld_wowlan_suspend(struct iwl_mld *mld, struct cfg80211_wowlan *wowlan) if (WARN_ON(!wowlan)) return 1; - IWL_DEBUG_WOWLAN(mld, "Starting the wowlan suspend flow\n"); - bss_vif = iwl_mld_get_bss_vif(mld); - if (WARN_ON(!bss_vif)) + if (!bss_vif) return 1; + IWL_DEBUG_WOWLAN(mld, "Starting the wowlan suspend flow\n"); + if (!bss_vif->cfg.assoc) { int ret; /* If we're not associated, this must be netdetect */ -- cgit v1.2.3 From 734a4e051b9767f439137940095d63afbfed0745 Mon Sep 17 00:00:00 2001 From: Emmanuel Grumbach Date: Fri, 15 May 2026 15:15:00 +0300 Subject: wifi: iwlwifi: mld: disconnect only after 6 beacons without Rx After 4 missed beacons since last Rx, the firmware will send an NDP to the AP. If the NDP is ACK'ed, it'll reset the missed_beacons_since_last_rx counter. Disconnecting after 4 beacons doesn't give enough time to the firmware to send the NDP. Wait until we get 6 missed beacons since last Rx before disconnecting. Signed-off-by: Emmanuel Grumbach Link: https://patch.msgid.link/20260515151352.c4ed0d849f98.Iefa2e8be9edfc74683997eea60bb53c2002f31f0@changeid Signed-off-by: Miri Korenblit --- drivers/net/wireless/intel/iwlwifi/mld/constants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/constants.h b/drivers/net/wireless/intel/iwlwifi/mld/constants.h index e2a5eecc18c3..890abcab3837 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/constants.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/constants.h @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #ifndef __iwl_mld_constants_h__ #define __iwl_mld_constants_h__ -#define IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD 4 +#define IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD 6 #define IWL_MLD_MISSED_BEACONS_THRESHOLD 8 #define IWL_MLD_MISSED_BEACONS_THRESHOLD_LONG 19 #define IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS 5 -- cgit v1.2.3 From e02b5262fd288cc235f14e12233ea54e78c04611 Mon Sep 17 00:00:00 2001 From: Julien Chauveau Date: Tue, 24 Mar 2026 20:30:11 +0100 Subject: drm/bridge: it66121: acquire reset GPIO in probe The it66121_ctx structure has a gpio_reset field, and it66121_hw_reset() calls gpiod_set_value() on it. However, the GPIO descriptor is never acquired via devm_gpiod_get(), leaving gpio_reset as NULL throughout the driver lifetime. gpiod_set_value() silently returns when passed a NULL descriptor, so the hardware reset sequence in it66121_hw_reset() is a no-op. This leaves the chip in an undefined state at probe time, which can prevent it from responding on the I2C bus. The DT binding marks reset-gpios as a required property, so all compliant device trees provide this GPIO. Add the missing devm_gpiod_get() call after enabling power supplies and before the hardware reset, so the chip is properly reset with power applied. Fixes: 988156dc2fc9 ("drm: bridge: add it66121 driver") Cc: stable@vger.kernel.org Signed-off-by: Julien Chauveau Reviewed-by: Javier Martinez Canillas Tested-by: Javier Martinez Canillas Link: https://patch.msgid.link/20260324193011.16583-1-chauveau.julien@gmail.com Signed-off-by: Javier Martinez Canillas --- drivers/gpu/drm/bridge/ite-it66121.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index 9246e9c15a6e..ed21f09cd19a 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -1559,6 +1559,11 @@ static int it66121_probe(struct i2c_client *client) return ret; } + ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ctx->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(ctx->gpio_reset), + "Failed to get reset GPIO\n"); + it66121_hw_reset(ctx); ctx->regmap = devm_regmap_init_i2c(client, &it66121_regmap_config); -- cgit v1.2.3 From f9b2d3b703d13df50c630997dfdc25648e96db0d Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Mon, 18 May 2026 10:31:11 +0200 Subject: regulator: tps65219: fix irq_data.rdev not being assigned Commit 64a6b577490c ("regulator: tps65219: Remove debugging helper function") removed the tps65219_get_rdev_by_name() helper along with the irq_data.rdev assignment that depended on it. This left irq_data.rdev uninitialized for all IRQs, causing undefined behavior when regulator_notifier_call_chain() is called from the IRQ handler: Internal error: Oops: 0000000096000004 pc : regulator_notifier_call_chain lr : tps65219_regulator_irq_handler Call trace: regulator_notifier_call_chain tps65219_regulator_irq_handler handle_nested_irq regmap_irq_thread irq_thread_fn irq_thread kthread ret_from_fork Instead of restoring a dedicated lookup array, restructure the probe function to combine regulator registration with IRQ registration in the same loop. This way the rdev returned by devm_regulator_register() is naturally available for assigning to irq_data.rdev without any auxiliary data structure. Non-regulator IRQs (SENSOR, TIMEOUT) that don't correspond to any registered regulator are registered with rdev=NULL, and the IRQ handler is protected with a NULL check to avoid crashing. Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/all/aBDSTxALaOc-PD7X@gaggiata.pivistrello.it/ Reported-by: Francesco Dolcini Fixes: 64a6b577490c ("regulator: tps65219: Remove debugging helper function") Signed-off-by: Alexander Sverdlin Link: https://patch.msgid.link/20260518083113.2063368-1-alexander.sverdlin@siemens.com Signed-off-by: Mark Brown --- drivers/regulator/tps65219-regulator.c | 135 +++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 40 deletions(-) (limited to 'drivers') diff --git a/drivers/regulator/tps65219-regulator.c b/drivers/regulator/tps65219-regulator.c index d77ca486879f..324c3a33af8a 100644 --- a/drivers/regulator/tps65219-regulator.c +++ b/drivers/regulator/tps65219-regulator.c @@ -346,8 +346,9 @@ static irqreturn_t tps65219_regulator_irq_handler(int irq, void *data) return IRQ_HANDLED; } - regulator_notifier_call_chain(irq_data->rdev, - irq_data->type->event, NULL); + if (irq_data->rdev) + regulator_notifier_call_chain(irq_data->rdev, + irq_data->type->event, NULL); dev_err(irq_data->dev, "Error IRQ trap %s for %s\n", irq_data->type->event_name, irq_data->type->regulator_name); @@ -398,14 +399,65 @@ static struct tps65219_chip_data chip_info_table[] = { }, }; -static int tps65219_regulator_probe(struct platform_device *pdev) +static bool tps65219_is_regulator_name(const struct tps65219_chip_data *pmic, + const char *name) +{ + int i; + + for (i = 0; i < pmic->common_rdesc_size; i++) + if (!strcmp(pmic->common_rdesc[i].name, name)) + return true; + for (i = 0; i < pmic->rdesc_size; i++) + if (!strcmp(pmic->rdesc[i].name, name)) + return true; + return false; +} + +static int tps65219_register_irqs(struct platform_device *pdev, + struct tps65219 *tps, + struct regulator_dev *rdev, + struct tps65219_regulator_irq_type *irq_types, + int nirqs, + const char *regulator_name) { struct tps65219_regulator_irq_data *irq_data; + int i, irq, error; + + for (i = 0; i < nirqs; i++) { + if (strcmp(irq_types[i].regulator_name, regulator_name)) + continue; + + irq = platform_get_irq_byname(pdev, irq_types[i].irq_name); + if (irq < 0) + return -EINVAL; + + irq_data = devm_kmalloc(tps->dev, sizeof(*irq_data), GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + irq_data->dev = tps->dev; + irq_data->type = &irq_types[i]; + irq_data->rdev = rdev; + + error = devm_request_threaded_irq(tps->dev, irq, NULL, + tps65219_regulator_irq_handler, + IRQF_ONESHOT, + irq_types[i].irq_name, + irq_data); + if (error) + return dev_err_probe(tps->dev, error, + "Failed to request %s IRQ %d\n", + irq_types[i].irq_name, irq); + } + return 0; +} + +static int tps65219_regulator_probe(struct platform_device *pdev) +{ struct tps65219_regulator_irq_type *irq_type; struct tps65219_chip_data *pmic; struct regulator_dev *rdev; int error; - int irq; int i; struct tps65219 *tps = dev_get_drvdata(pdev->dev.parent); @@ -425,6 +477,19 @@ static int tps65219_regulator_probe(struct platform_device *pdev) return dev_err_probe(tps->dev, PTR_ERR(rdev), "Failed to register %s regulator\n", pmic->common_rdesc[i].name); + + error = tps65219_register_irqs(pdev, tps, rdev, + pmic->common_irq_types, + pmic->common_irq_size, + pmic->common_rdesc[i].name); + if (error) + return error; + error = tps65219_register_irqs(pdev, tps, rdev, + pmic->irq_types, + pmic->dev_irq_size, + pmic->common_rdesc[i].name); + if (error) + return error; } for (i = 0; i < pmic->rdesc_size; i++) { @@ -434,52 +499,42 @@ static int tps65219_regulator_probe(struct platform_device *pdev) return dev_err_probe(tps->dev, PTR_ERR(rdev), "Failed to register %s regulator\n", pmic->rdesc[i].name); + + error = tps65219_register_irqs(pdev, tps, rdev, + pmic->common_irq_types, + pmic->common_irq_size, + pmic->rdesc[i].name); + if (error) + return error; + error = tps65219_register_irqs(pdev, tps, rdev, + pmic->irq_types, + pmic->dev_irq_size, + pmic->rdesc[i].name); + if (error) + return error; } + /* Register non-regulator IRQs (TIMEOUT, SENSOR) with rdev=NULL */ for (i = 0; i < pmic->common_irq_size; ++i) { irq_type = &pmic->common_irq_types[i]; - irq = platform_get_irq_byname(pdev, irq_type->irq_name); - if (irq < 0) - return -EINVAL; - - irq_data = devm_kmalloc(tps->dev, sizeof(*irq_data), GFP_KERNEL); - if (!irq_data) - return -ENOMEM; - - irq_data->dev = tps->dev; - irq_data->type = irq_type; - error = devm_request_threaded_irq(tps->dev, irq, NULL, - tps65219_regulator_irq_handler, - IRQF_ONESHOT, - irq_type->irq_name, - irq_data); + if (tps65219_is_regulator_name(pmic, irq_type->regulator_name)) + continue; + error = tps65219_register_irqs(pdev, tps, NULL, + irq_type, 1, + irq_type->regulator_name); if (error) - return dev_err_probe(tps->dev, error, - "Failed to request %s IRQ %d\n", - irq_type->irq_name, irq); + return error; } for (i = 0; i < pmic->dev_irq_size; ++i) { irq_type = &pmic->irq_types[i]; - irq = platform_get_irq_byname(pdev, irq_type->irq_name); - if (irq < 0) - return -EINVAL; - - irq_data = devm_kmalloc(tps->dev, sizeof(*irq_data), GFP_KERNEL); - if (!irq_data) - return -ENOMEM; - - irq_data->dev = tps->dev; - irq_data->type = irq_type; - error = devm_request_threaded_irq(tps->dev, irq, NULL, - tps65219_regulator_irq_handler, - IRQF_ONESHOT, - irq_type->irq_name, - irq_data); + if (tps65219_is_regulator_name(pmic, irq_type->regulator_name)) + continue; + error = tps65219_register_irqs(pdev, tps, NULL, + irq_type, 1, + irq_type->regulator_name); if (error) - return dev_err_probe(tps->dev, error, - "Failed to request %s IRQ %d\n", - irq_type->irq_name, irq); + return error; } return 0; -- cgit v1.2.3 From 360190bd965f93794d5f5685a6de22ce6da2b672 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 14 May 2026 09:38:59 +0200 Subject: ata: libata-scsi: improve readability of ata_scsi_qc_issue() Improve readability of ata_scsi_qc_issue(). No functional changes. Tested-by: Tommy Kelly Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index f44612e269a4..f9ca5410e223 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1767,7 +1767,7 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) int ret; if (!ap->ops->qc_defer) - goto issue; + goto issue_qc; /* * If we already have a deferred qc, then rely on the SCSI layer to @@ -1786,38 +1786,37 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) break; case ATA_DEFER_LINK: ret = SCSI_MLQUEUE_DEVICE_BUSY; - break; + goto defer_qc; case ATA_DEFER_PORT: ret = SCSI_MLQUEUE_HOST_BUSY; - break; + goto defer_qc; default: WARN_ON_ONCE(1); ret = SCSI_MLQUEUE_HOST_BUSY; - break; + goto defer_qc; } - if (ret) { - /* - * We must defer this qc: if this is not an NCQ command, keep - * this qc as a deferred one and report to the SCSI layer that - * we issued it so that it is not requeued. The deferred qc will - * be issued with the port deferred_qc_work once all on-going - * commands complete. - */ - if (!ata_is_ncq(qc->tf.protocol)) { - ap->deferred_qc = qc; - return 0; - } +issue_qc: + ata_qc_issue(qc); + return 0; - /* Force a requeue of the command to defer its execution. */ - ata_qc_free(qc); - return ret; +defer_qc: + /* + * We must defer this qc: if this is not an NCQ command, keep + * this qc as a deferred one and report to the SCSI layer that + * we issued it so that it is not requeued. The deferred qc will + * be issued with the port deferred_qc_work once all on-going + * commands complete. + */ + if (!ata_is_ncq(qc->tf.protocol)) { + ap->deferred_qc = qc; + return 0; } -issue: - ata_qc_issue(qc); + /* Force a requeue of the command to defer its execution. */ + ata_qc_free(qc); - return 0; + return ret; } /** -- cgit v1.2.3 From ce4548807d2e4ae48fd0dbe38865467369877913 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 14 May 2026 09:39:00 +0200 Subject: ata: libata-scsi: do not use the deferred QC feature for ATA_DEFER_PORT The deferred QC feature was meant to handle mixed NCQ and non-NCQ commands, i.e. for return value ATA_DEFER_LINK. ATA_DEFER_PORT is returned by PATA drivers, but also certain SATA drivers like sata_mv and sata_sil24 that uses ap->excl_link to workaround hardware bugs in these HBAs. Regardless of the reason, using the deferred QC feature for ATA_DEFER_PORT is always wrong, and will break the ap->excl_link usage of the SATA drivers that rely on that feature. Modify ata_scsi_qc_issue() to only use the deferred QC feature when mixing NCQ and non-NCQ commands, i.e. ATA_DEFER_LINK. Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Tested-by: Tommy Kelly Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libata-scsi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index f9ca5410e223..f03b6326ad2d 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1789,11 +1789,11 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) goto defer_qc; case ATA_DEFER_PORT: ret = SCSI_MLQUEUE_HOST_BUSY; - goto defer_qc; + goto free_qc; default: WARN_ON_ONCE(1); ret = SCSI_MLQUEUE_HOST_BUSY; - goto defer_qc; + goto free_qc; } issue_qc: @@ -1813,6 +1813,7 @@ defer_qc: return 0; } +free_qc: /* Force a requeue of the command to defer its execution. */ ata_qc_free(qc); -- cgit v1.2.3 From f233124fb36cd57ef09f96d517a38ab4b902e15e Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 14 May 2026 09:39:01 +0200 Subject: ata: libata-scsi: do not use the deferred QC feature on PMPs with CBS When using Port Multipliers (PMPs) with Command-Based Switching (CBS), you can only issue commands to one link at a time. For PMPs with CBS, there is already code to handle commands being sent to different links in sata_pmp_qc_defer_cmd_switch() using ap->excl_link. sata_sil24 also makes use of ap->excl_link. A user on the list reported that commit 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") broke PMPs with CBS. The commit introduced code that stores a deferred qc in ap->deferred_qc, to later be issued via a workqueue. It turns out that this change is incompatible with the existing ap->excl_link handling used by PMPs with CBS. Thus, modify sata_pmp_qc_defer_cmd_switch() and sil24_qc_defer() to return ATA_DEFER_LINK_EXCL, and make sure that the deferred QC handling via workqueue is not used for this return value. This way, PMPs with CBS will work once again. Note that the starvation referenced in commit 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") can only happen on libsas ports, and libsas does not support Port Multipliers, thus there is no harm of reverting back to the previous way of deferring commands for PMPs with CBS. Non-libsas ports connected to anything but a PMP with CBS (e.g. a normal drive or a PMP with FBS) will continue using the deferred workqueue, since it does result in lower completion latencies for non-NCQ commands, even though the workqueue is not strictly needed to avoid starvation for non-libsas ports. If we want to modify the scope of the workqueue issuing to also handle PMPs with CBS, then we should ensure that we can save both NCQ and non-NCQ commands in ap->deferred_qc, while also removing the existing PMP CBS handling using ap->excl_link, such that we don't duplicate features. While at it, also add a comment explaining how the ap->excl_link mechanism works. Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Tested-by: Tommy Kelly Reported-by: Tommy Kelly Closes: https://lore.kernel.org/linux-ide/ce09cc21-a8e9-4845-b205-35411e22fba9@tkel.ly/ Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libata-pmp.c | 13 ++++++++++++- drivers/ata/libata-scsi.c | 8 ++++++++ drivers/ata/sata_sil24.c | 6 +++++- 3 files changed, 25 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/ata/libata-pmp.c b/drivers/ata/libata-pmp.c index e3adc008fed1..7e889534d73b 100644 --- a/drivers/ata/libata-pmp.c +++ b/drivers/ata/libata-pmp.c @@ -110,13 +110,24 @@ int sata_pmp_qc_defer_cmd_switch(struct ata_queued_cmd *qc) { struct ata_link *link = qc->dev->link; struct ata_port *ap = link->ap; + int ret; if (ap->excl_link == NULL || ap->excl_link == link) { if (ap->nr_active_links == 0 || ata_link_active(link)) { qc->flags |= ATA_QCFLAG_CLEAR_EXCL; - return ata_std_qc_defer(qc); + ret = ata_std_qc_defer(qc); + if (ret == ATA_DEFER_LINK) + return ATA_DEFER_LINK_EXCL; + return ret; } + /* + * Note: ap->excl_link contains the link that is next in line, + * i.e. implicit round robin. If there is only one link + * dispatching, ap->excl_link will be left unclaimed, allowing + * other links to set ap->excl_link, ensuring that the currently + * active link cannot queue any more. + */ ap->excl_link = link; } diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index f03b6326ad2d..ca29744c57f9 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1787,6 +1787,14 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) case ATA_DEFER_LINK: ret = SCSI_MLQUEUE_DEVICE_BUSY; goto defer_qc; + case ATA_DEFER_LINK_EXCL: + /* + * Drivers making use of ap->excl_link cannot store the QC in + * ap->deferred_qc, because the ap->excl_link handling is + * incompatible with the ap->deferred_qc workqueue handling. + */ + ret = SCSI_MLQUEUE_DEVICE_BUSY; + goto free_qc; case ATA_DEFER_PORT: ret = SCSI_MLQUEUE_HOST_BUSY; goto free_qc; diff --git a/drivers/ata/sata_sil24.c b/drivers/ata/sata_sil24.c index d642ece9f07a..57f1081b86db 100644 --- a/drivers/ata/sata_sil24.c +++ b/drivers/ata/sata_sil24.c @@ -789,6 +789,7 @@ static int sil24_qc_defer(struct ata_queued_cmd *qc) struct ata_link *link = qc->dev->link; struct ata_port *ap = link->ap; u8 prot = qc->tf.protocol; + int ret; /* * There is a bug in the chip: @@ -826,7 +827,10 @@ static int sil24_qc_defer(struct ata_queued_cmd *qc) qc->flags |= ATA_QCFLAG_CLEAR_EXCL; } - return ata_std_qc_defer(qc); + ret = ata_std_qc_defer(qc); + if (ret == ATA_DEFER_LINK) + return ATA_DEFER_LINK_EXCL; + return ret; } static enum ata_completion_errors sil24_qc_prep(struct ata_queued_cmd *qc) -- cgit v1.2.3 From 759e8756da00aa115d504a18155b1d1ee1cc12e8 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 14 May 2026 09:39:02 +0200 Subject: ata: libata-scsi: do not needlessly defer commands when using PMP with FBS The ACS specification does not allow a non-NCQ command to be issued while an NCQ command is outstanding. Commit 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") introduced a feature where a deferred non-NCQ command gets issued from a workqueue. The design stores a single non-NCQ command per port. However, when using Port Multipliers (PMPs), specifically PMPs that support FIS-Based Switching (FBS), non-NCQ and NCQ commands can be mixed on the same port, just not for the same link, see e.g. ata_std_qc_defer() which is, and always has operated on a per-link basis. Therefore, move the deferred_qc from struct ata_port to struct ata_link. This way, when using a PMP with FBS, we will not needlessly defer commands to all other links, just because one link issued a non-NCQ command while having an NCQ command outstanding. Only commands for that specific link will be deferred. This is in line with how PMPs with FBS worked before commit 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation"). Fixes: 0ea84089dbf6 ("ata: libata-scsi: avoid Non-NCQ command starvation") Tested-by: Tommy Kelly Reviewed-by: Damien Le Moal Signed-off-by: Niklas Cassel --- drivers/ata/libata-core.c | 9 ++++++--- drivers/ata/libata-eh.c | 8 ++++---- drivers/ata/libata-pmp.c | 5 ++++- drivers/ata/libata-scsi.c | 43 +++++++++++++++++++++++++------------------ 4 files changed, 39 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index e76d15411e2a..3d0027ec33c2 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -5584,6 +5584,7 @@ void ata_link_init(struct ata_port *ap, struct ata_link *link, int pmp) link->pmp = pmp; link->active_tag = ATA_TAG_POISON; link->hw_sata_spd_limit = UINT_MAX; + INIT_WORK(&link->deferred_qc_work, ata_scsi_deferred_qc_work); /* can't use iterator, ap isn't initialized yet */ for (i = 0; i < ATA_MAX_DEVICES; i++) { @@ -5666,7 +5667,6 @@ struct ata_port *ata_port_alloc(struct ata_host *host) mutex_init(&ap->scsi_scan_mutex); INIT_DELAYED_WORK(&ap->hotplug_task, ata_scsi_hotplug); INIT_DELAYED_WORK(&ap->scsi_rescan_task, ata_scsi_dev_rescan); - INIT_WORK(&ap->deferred_qc_work, ata_scsi_deferred_qc_work); INIT_LIST_HEAD(&ap->eh_done_q); init_waitqueue_head(&ap->eh_wait_q); init_completion(&ap->park_req_pending); @@ -6291,12 +6291,15 @@ static void ata_port_detach(struct ata_port *ap) /* It better be dead now and not have any remaining deferred qc. */ WARN_ON(!(ap->pflags & ATA_PFLAG_UNLOADED)); - WARN_ON(ap->deferred_qc); - cancel_work_sync(&ap->deferred_qc_work); cancel_delayed_work_sync(&ap->hotplug_task); cancel_delayed_work_sync(&ap->scsi_rescan_task); + ata_for_each_link(link, ap, PMP_FIRST) { + WARN_ON(link->deferred_qc); + cancel_work_sync(&link->deferred_qc_work); + } + /* Delete port multiplier link transport devices */ if (ap->pmp_link) { int i; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 9a4b67b90b17..d623eb32ed8b 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -651,11 +651,11 @@ int ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap, if (qc->scsicmd != scmd) continue; if ((qc->flags & ATA_QCFLAG_ACTIVE) || - qc == ap->deferred_qc) + qc == qc->dev->link->deferred_qc) break; } - if (i < ATA_MAX_QUEUE && qc == ap->deferred_qc) { + if (i < ATA_MAX_QUEUE && qc == qc->dev->link->deferred_qc) { /* * This is a deferred command that timed out while * waiting for the command queue to drain. Since the qc @@ -666,8 +666,8 @@ int ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap, * deferred qc work from issuing this qc. */ WARN_ON_ONCE(qc->flags & ATA_QCFLAG_ACTIVE); - ap->deferred_qc = NULL; - cancel_work(&ap->deferred_qc_work); + qc->dev->link->deferred_qc = NULL; + cancel_work(&qc->dev->link->deferred_qc_work); set_host_byte(scmd, DID_TIME_OUT); scsi_eh_finish_cmd(scmd, &ap->eh_done_q); } else if (i < ATA_MAX_QUEUE) { diff --git a/drivers/ata/libata-pmp.c b/drivers/ata/libata-pmp.c index 7e889534d73b..e8540931b4a1 100644 --- a/drivers/ata/libata-pmp.c +++ b/drivers/ata/libata-pmp.c @@ -582,8 +582,11 @@ static void sata_pmp_detach(struct ata_device *dev) if (ap->ops->pmp_detach) ap->ops->pmp_detach(ap); - ata_for_each_link(tlink, ap, EDGE) + ata_for_each_link(tlink, ap, EDGE) { + WARN_ON(tlink->deferred_qc); + cancel_work_sync(&tlink->deferred_qc_work); ata_eh_detach_dev(tlink->device); + } spin_lock_irqsave(ap->lock, flags); ap->nr_pmp_links = 0; diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index ca29744c57f9..d43207c6e467 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -1664,8 +1664,9 @@ static void ata_scsi_qc_done(struct ata_queued_cmd *qc, bool set_result, void ata_scsi_deferred_qc_work(struct work_struct *work) { - struct ata_port *ap = - container_of(work, struct ata_port, deferred_qc_work); + struct ata_link *link = + container_of(work, struct ata_link, deferred_qc_work); + struct ata_port *ap = link->ap; struct ata_queued_cmd *qc; unsigned long flags; @@ -1676,10 +1677,10 @@ void ata_scsi_deferred_qc_work(struct work_struct *work) * such case, we should not need any more deferring the qc, so warn if * qc_defer() says otherwise. */ - qc = ap->deferred_qc; + qc = link->deferred_qc; if (qc && !ata_port_eh_scheduled(ap)) { WARN_ON_ONCE(ap->ops->qc_defer(qc)); - ap->deferred_qc = NULL; + link->deferred_qc = NULL; ata_qc_issue(qc); } @@ -1688,7 +1689,7 @@ void ata_scsi_deferred_qc_work(struct work_struct *work) void ata_scsi_requeue_deferred_qc(struct ata_port *ap) { - struct ata_queued_cmd *qc = ap->deferred_qc; + struct ata_link *link; lockdep_assert_held(ap->lock); @@ -1697,16 +1698,21 @@ void ata_scsi_requeue_deferred_qc(struct ata_port *ap) * do not try to be smart about what to do with this deferred command * and simply requeue it by completing it with DID_REQUEUE. */ - if (qc) { - ap->deferred_qc = NULL; - cancel_work(&ap->deferred_qc_work); - ata_scsi_qc_done(qc, true, DID_REQUEUE << 16); + ata_for_each_link(link, ap, PMP_FIRST) { + struct ata_queued_cmd *qc = link->deferred_qc; + + if (qc) { + link->deferred_qc = NULL; + cancel_work(&link->deferred_qc_work); + ata_scsi_qc_done(qc, true, DID_REQUEUE << 16); + } } } -static void ata_scsi_schedule_deferred_qc(struct ata_port *ap) +static void ata_scsi_schedule_deferred_qc(struct ata_link *link) { - struct ata_queued_cmd *qc = ap->deferred_qc; + struct ata_queued_cmd *qc = link->deferred_qc; + struct ata_port *ap = link->ap; lockdep_assert_held(ap->lock); @@ -1723,12 +1729,12 @@ static void ata_scsi_schedule_deferred_qc(struct ata_port *ap) return; } if (!ap->ops->qc_defer(qc)) - queue_work(system_highpri_wq, &ap->deferred_qc_work); + queue_work(system_highpri_wq, &link->deferred_qc_work); } static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) { - struct ata_port *ap = qc->ap; + struct ata_link *link = qc->dev->link; struct scsi_cmnd *cmd = qc->scsicmd; u8 *cdb = cmd->cmnd; bool have_sense = qc->flags & ATA_QCFLAG_SENSE_VALID; @@ -1759,11 +1765,12 @@ static void ata_scsi_qc_complete(struct ata_queued_cmd *qc) ata_scsi_qc_done(qc, false, 0); - ata_scsi_schedule_deferred_qc(ap); + ata_scsi_schedule_deferred_qc(link); } static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) { + struct ata_link *link = qc->dev->link; int ret; if (!ap->ops->qc_defer) @@ -1774,7 +1781,7 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) * requeue and defer all incoming commands until the deferred qc is * processed, once all on-going commands complete. */ - if (ap->deferred_qc) { + if (link->deferred_qc) { ata_qc_free(qc); return SCSI_MLQUEUE_DEVICE_BUSY; } @@ -1790,8 +1797,8 @@ static int ata_scsi_qc_issue(struct ata_port *ap, struct ata_queued_cmd *qc) case ATA_DEFER_LINK_EXCL: /* * Drivers making use of ap->excl_link cannot store the QC in - * ap->deferred_qc, because the ap->excl_link handling is - * incompatible with the ap->deferred_qc workqueue handling. + * link->deferred_qc, because the ap->excl_link handling is + * incompatible with the link->deferred_qc workqueue handling. */ ret = SCSI_MLQUEUE_DEVICE_BUSY; goto free_qc; @@ -1817,7 +1824,7 @@ defer_qc: * commands complete. */ if (!ata_is_ncq(qc->tf.protocol)) { - ap->deferred_qc = qc; + link->deferred_qc = qc; return 0; } -- cgit v1.2.3 From 379e8f1ca5e919b130b40d8115d92a536e5f8d7a Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Mon, 18 May 2026 13:41:45 +0200 Subject: drm/gem: Make the GEM LRU lock part of drm_device Recently, a few races have been discovered in the GEM LRU logic, all of them caused by the fact the LRU lock is accessed through gem->lru->lock, and that very same lock also protects changes to gem->lru, leading to situations where gem->lru needs to first be accessed without the lock held, to then get the lru to access the lock through and finally take the lock and do the expected operation. Currently, the only driver making use of this API (MSM) declares a device-wide lock, and the user we're about to add (panthor) will do the same. There's no evidence that we will ever have a driver that wants different pools of LRUs protected by different locks under the same drm_device. So we're better off moving this lock to drm_device and always locking it through obj->dev->gem_lru_mutex, or directly through dev->gem_lru_mutex. If anyone ever needs more fine-grained locking, this can be revisited to pass some drm_gem_lru_pool object representing the pool of LRUs under a specific lock, but for now, the per-device lock seems to be enough. Fixes: e7c2af13f811 ("drm/gem: Add LRU/shrinker helper") Reported-by: Chia-I Wu Closes: https://gitlab.freedesktop.org/panfrost/linux/-/work_items/86 Reviewed-by: Rob Clark Reviewed-by: Liviu Dudau Reviewed-by: Steven Price Reviewed-by: Chia-I Wu Link: https://patch.msgid.link/20260518-panthor-shrinker-fixes-v4-1-1920234470d5@collabora.com Signed-off-by: Boris Brezillon --- drivers/gpu/drm/drm_drv.c | 2 ++ drivers/gpu/drm/drm_gem.c | 36 +++++++++++++++------------------- drivers/gpu/drm/msm/msm_drv.c | 11 +++++------ drivers/gpu/drm/msm/msm_drv.h | 7 ------- drivers/gpu/drm/msm/msm_gem.c | 33 +++++++++++++++---------------- drivers/gpu/drm/msm/msm_gem_shrinker.c | 4 ++-- drivers/gpu/drm/msm/msm_gem_submit.c | 6 +++--- drivers/gpu/drm/msm/msm_gem_vma.c | 12 ++++++------ drivers/gpu/drm/msm/msm_ringbuffer.c | 6 +++--- 9 files changed, 53 insertions(+), 64 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 985c283cf59f..675675480da4 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -697,6 +697,7 @@ static void drm_dev_init_release(struct drm_device *dev, void *res) mutex_destroy(&dev->master_mutex); mutex_destroy(&dev->clientlist_mutex); mutex_destroy(&dev->filelist_mutex); + mutex_destroy(&dev->gem_lru_mutex); } static int drm_dev_init(struct drm_device *dev, @@ -738,6 +739,7 @@ static int drm_dev_init(struct drm_device *dev, INIT_LIST_HEAD(&dev->vblank_event_list); spin_lock_init(&dev->event_lock); + mutex_init(&dev->gem_lru_mutex); mutex_init(&dev->filelist_mutex); mutex_init(&dev->clientlist_mutex); mutex_init(&dev->master_mutex); diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index d6424267260b..b95b015d2983 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -1541,12 +1541,10 @@ EXPORT_SYMBOL(drm_gem_unlock_reservations); * drm_gem_lru_init - initialize a LRU * * @lru: The LRU to initialize - * @lock: The lock protecting the LRU */ void -drm_gem_lru_init(struct drm_gem_lru *lru, struct mutex *lock) +drm_gem_lru_init(struct drm_gem_lru *lru) { - lru->lock = lock; lru->count = 0; INIT_LIST_HEAD(&lru->list); } @@ -1571,14 +1569,10 @@ drm_gem_lru_remove_locked(struct drm_gem_object *obj) void drm_gem_lru_remove(struct drm_gem_object *obj) { - struct drm_gem_lru *lru = obj->lru; - - if (!lru) - return; - - mutex_lock(lru->lock); - drm_gem_lru_remove_locked(obj); - mutex_unlock(lru->lock); + mutex_lock(&obj->dev->gem_lru_mutex); + if (obj->lru) + drm_gem_lru_remove_locked(obj); + mutex_unlock(&obj->dev->gem_lru_mutex); } EXPORT_SYMBOL(drm_gem_lru_remove); @@ -1593,7 +1587,7 @@ EXPORT_SYMBOL(drm_gem_lru_remove); void drm_gem_lru_move_tail_locked(struct drm_gem_lru *lru, struct drm_gem_object *obj) { - lockdep_assert_held_once(lru->lock); + lockdep_assert_held_once(&obj->dev->gem_lru_mutex); if (obj->lru) drm_gem_lru_remove_locked(obj); @@ -1617,9 +1611,9 @@ EXPORT_SYMBOL(drm_gem_lru_move_tail_locked); void drm_gem_lru_move_tail(struct drm_gem_lru *lru, struct drm_gem_object *obj) { - mutex_lock(lru->lock); + mutex_lock(&obj->dev->gem_lru_mutex); drm_gem_lru_move_tail_locked(lru, obj); - mutex_unlock(lru->lock); + mutex_unlock(&obj->dev->gem_lru_mutex); } EXPORT_SYMBOL(drm_gem_lru_move_tail); @@ -1633,6 +1627,7 @@ EXPORT_SYMBOL(drm_gem_lru_move_tail); * of the shrink callback to check for this (ie. dma_resv_test_signaled()) * or if necessary block until the buffer becomes idle. * + * @dev: DRM device the LRU belongs to * @lru: The LRU to scan * @nr_to_scan: The number of pages to try to reclaim * @remaining: The number of pages left to reclaim, should be initialized by caller @@ -1640,7 +1635,8 @@ EXPORT_SYMBOL(drm_gem_lru_move_tail); * @ticket: Optional ww_acquire_ctx context to use for locking */ unsigned long -drm_gem_lru_scan(struct drm_gem_lru *lru, +drm_gem_lru_scan(struct drm_device *dev, + struct drm_gem_lru *lru, unsigned int nr_to_scan, unsigned long *remaining, bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket), @@ -1650,9 +1646,9 @@ drm_gem_lru_scan(struct drm_gem_lru *lru, struct drm_gem_object *obj; unsigned freed = 0; - drm_gem_lru_init(&still_in_lru, lru->lock); + drm_gem_lru_init(&still_in_lru); - mutex_lock(lru->lock); + mutex_lock(&dev->gem_lru_mutex); while (freed < nr_to_scan) { obj = list_first_entry_or_null(&lru->list, typeof(*obj), lru_node); @@ -1675,7 +1671,7 @@ drm_gem_lru_scan(struct drm_gem_lru *lru, * rest of the loop body, to reduce contention with other * code paths that need the LRU lock */ - mutex_unlock(lru->lock); + mutex_unlock(&dev->gem_lru_mutex); if (ticket) ww_acquire_init(ticket, &reservation_ww_class); @@ -1709,7 +1705,7 @@ drm_gem_lru_scan(struct drm_gem_lru *lru, tail: drm_gem_object_put(obj); - mutex_lock(lru->lock); + mutex_lock(&dev->gem_lru_mutex); } /* @@ -1721,7 +1717,7 @@ tail: list_splice_tail(&still_in_lru.list, &lru->list); lru->count += still_in_lru.count; - mutex_unlock(lru->lock); + mutex_unlock(&dev->gem_lru_mutex); return freed; } diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index 195f40e331e5..cc2bcd14b1c2 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -128,11 +128,10 @@ static int msm_drm_init(struct device *dev, const struct drm_driver *drv, /* * Initialize the LRUs: */ - mutex_init(&priv->lru.lock); - drm_gem_lru_init(&priv->lru.unbacked, &priv->lru.lock); - drm_gem_lru_init(&priv->lru.pinned, &priv->lru.lock); - drm_gem_lru_init(&priv->lru.willneed, &priv->lru.lock); - drm_gem_lru_init(&priv->lru.dontneed, &priv->lru.lock); + drm_gem_lru_init(&priv->lru.unbacked); + drm_gem_lru_init(&priv->lru.pinned); + drm_gem_lru_init(&priv->lru.willneed); + drm_gem_lru_init(&priv->lru.dontneed); /* Initialize stall-on-fault */ spin_lock_init(&priv->fault_stall_lock); @@ -140,7 +139,7 @@ static int msm_drm_init(struct device *dev, const struct drm_driver *drv, /* Teach lockdep about lock ordering wrt. shrinker: */ fs_reclaim_acquire(GFP_KERNEL); - might_lock(&priv->lru.lock); + might_lock(&ddev->gem_lru_mutex); fs_reclaim_release(GFP_KERNEL); if (priv->kms_init) { diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h index 6d847d593f1a..617b3c4b42c0 100644 --- a/drivers/gpu/drm/msm/msm_drv.h +++ b/drivers/gpu/drm/msm/msm_drv.h @@ -150,13 +150,6 @@ struct msm_drm_private { * DONTNEED state (ie. can be purged) */ struct drm_gem_lru dontneed; - - /** - * lock: - * - * Protects manipulation of all of the LRUs. - */ - struct mutex lock; } lru; struct notifier_block vmap_notifier; diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c index 2cb3ab04f125..efd3d3c9a449 100644 --- a/drivers/gpu/drm/msm/msm_gem.c +++ b/drivers/gpu/drm/msm/msm_gem.c @@ -177,11 +177,11 @@ static void update_lru_locked(struct drm_gem_object *obj) static void update_lru(struct drm_gem_object *obj) { - struct msm_drm_private *priv = obj->dev->dev_private; + struct drm_device *dev = obj->dev; - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); update_lru_locked(obj); - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); } static struct page **get_pages(struct drm_gem_object *obj) @@ -292,11 +292,11 @@ void msm_gem_pin_obj_locked(struct drm_gem_object *obj) static void pin_obj_locked(struct drm_gem_object *obj) { - struct msm_drm_private *priv = obj->dev->dev_private; + struct drm_device *dev = obj->dev; - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); msm_gem_pin_obj_locked(obj); - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); } struct page **msm_gem_pin_pages_locked(struct drm_gem_object *obj) @@ -487,16 +487,16 @@ int msm_gem_pin_vma_locked(struct drm_gem_object *obj, struct drm_gpuva *vma) void msm_gem_unpin_locked(struct drm_gem_object *obj) { - struct msm_drm_private *priv = obj->dev->dev_private; + struct drm_device *dev = obj->dev; struct msm_gem_object *msm_obj = to_msm_bo(obj); msm_gem_assert_locked(obj); - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); msm_obj->pin_count--; GEM_WARN_ON(msm_obj->pin_count < 0); update_lru_locked(obj); - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); } /* Special unpin path for use in fence-signaling path, avoiding the need @@ -507,10 +507,10 @@ void msm_gem_unpin_locked(struct drm_gem_object *obj) */ void msm_gem_unpin_active(struct drm_gem_object *obj) { - struct msm_drm_private *priv = obj->dev->dev_private; + struct drm_device *dev = obj->dev; struct msm_gem_object *msm_obj = to_msm_bo(obj); - GEM_WARN_ON(!mutex_is_locked(&priv->lru.lock)); + GEM_WARN_ON(!mutex_is_locked(&dev->gem_lru_mutex)); msm_obj->pin_count--; GEM_WARN_ON(msm_obj->pin_count < 0); @@ -797,12 +797,12 @@ void msm_gem_put_vaddr(struct drm_gem_object *obj) */ int msm_gem_madvise(struct drm_gem_object *obj, unsigned madv) { - struct msm_drm_private *priv = obj->dev->dev_private; + struct drm_device *dev = obj->dev; struct msm_gem_object *msm_obj = to_msm_bo(obj); msm_gem_lock(obj); - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); if (msm_obj->madv != __MSM_MADV_PURGED) msm_obj->madv = madv; @@ -814,7 +814,7 @@ int msm_gem_madvise(struct drm_gem_object *obj, unsigned madv) */ update_lru_locked(obj); - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); msm_gem_unlock(obj); @@ -824,7 +824,6 @@ int msm_gem_madvise(struct drm_gem_object *obj, unsigned madv) void msm_gem_purge(struct drm_gem_object *obj) { struct drm_device *dev = obj->dev; - struct msm_drm_private *priv = obj->dev->dev_private; struct msm_gem_object *msm_obj = to_msm_bo(obj); msm_gem_assert_locked(obj); @@ -839,10 +838,10 @@ void msm_gem_purge(struct drm_gem_object *obj) put_pages(obj); - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); /* A one-way transition: */ msm_obj->madv = __MSM_MADV_PURGED; - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); drm_gem_free_mmap_offset(obj); diff --git a/drivers/gpu/drm/msm/msm_gem_shrinker.c b/drivers/gpu/drm/msm/msm_gem_shrinker.c index 31fa51a44f86..c07af9602fee 100644 --- a/drivers/gpu/drm/msm/msm_gem_shrinker.c +++ b/drivers/gpu/drm/msm/msm_gem_shrinker.c @@ -186,7 +186,7 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) if (!stages[i].cond) continue; stages[i].freed = - drm_gem_lru_scan(stages[i].lru, nr, + drm_gem_lru_scan(priv->dev, stages[i].lru, nr, &stages[i].remaining, stages[i].shrink, &ticket); @@ -255,7 +255,7 @@ msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr) unsigned long remaining = 0; for (idx = 0; lrus[idx] && unmapped < vmap_shrink_limit; idx++) { - unmapped += drm_gem_lru_scan(lrus[idx], + unmapped += drm_gem_lru_scan(priv->dev, lrus[idx], vmap_shrink_limit - unmapped, &remaining, vmap_shrink, diff --git a/drivers/gpu/drm/msm/msm_gem_submit.c b/drivers/gpu/drm/msm/msm_gem_submit.c index 26ea8a28be47..3c6bc90c3d48 100644 --- a/drivers/gpu/drm/msm/msm_gem_submit.c +++ b/drivers/gpu/drm/msm/msm_gem_submit.c @@ -352,7 +352,7 @@ static int submit_fence_sync(struct msm_gem_submit *submit) static int submit_pin_objects(struct msm_gem_submit *submit) { - struct msm_drm_private *priv = submit->dev->dev_private; + struct drm_device *dev = submit->dev; int i, ret = 0; for (i = 0; i < submit->nr_bos; i++) { @@ -381,11 +381,11 @@ static int submit_pin_objects(struct msm_gem_submit *submit) * get_pages() which could trigger reclaim.. and if we held the LRU lock * could trigger deadlock with the shrinker). */ - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); for (i = 0; i < submit->nr_bos; i++) { msm_gem_pin_obj_locked(submit->bos[i].obj); } - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); submit->bos_pinned = true; diff --git a/drivers/gpu/drm/msm/msm_gem_vma.c b/drivers/gpu/drm/msm/msm_gem_vma.c index 1a952b171ed7..c4cfe036066b 100644 --- a/drivers/gpu/drm/msm/msm_gem_vma.c +++ b/drivers/gpu/drm/msm/msm_gem_vma.c @@ -702,7 +702,7 @@ static struct dma_fence * msm_vma_job_run(struct drm_sched_job *_job) { struct msm_vm_bind_job *job = to_msm_vm_bind_job(_job); - struct msm_drm_private *priv = job->vm->drm->dev_private; + struct drm_device *dev = job->vm->drm; struct msm_gem_vm *vm = to_msm_vm(job->vm); struct drm_gem_object *obj; int ret = vm->unusable ? -EINVAL : 0; @@ -745,13 +745,13 @@ msm_vma_job_run(struct drm_sched_job *_job) if (ret) msm_gem_vm_unusable(job->vm); - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); job_foreach_bo (obj, job) { msm_gem_unpin_active(obj); } - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); /* VM_BIND ops are synchronous, so no fence to wait on: */ return NULL; @@ -1305,7 +1305,7 @@ vm_bind_job_pin_objects(struct msm_vm_bind_job *job) return PTR_ERR(pages); } - struct msm_drm_private *priv = job->vm->drm->dev_private; + struct drm_device *dev = job->vm->drm; /* * A second loop while holding the LRU lock (a) avoids acquiring/dropping @@ -1314,10 +1314,10 @@ vm_bind_job_pin_objects(struct msm_vm_bind_job *job) * get_pages() which could trigger reclaim.. and if we held the LRU lock * could trigger deadlock with the shrinker). */ - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); job_foreach_bo (obj, job) msm_gem_pin_obj_locked(obj); - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); job->bos_pinned = true; diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c index 30ddb5351e98..2d6b930b766e 100644 --- a/drivers/gpu/drm/msm/msm_ringbuffer.c +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c @@ -16,13 +16,13 @@ static struct dma_fence *msm_job_run(struct drm_sched_job *job) struct msm_gem_submit *submit = to_msm_submit(job); struct msm_fence_context *fctx = submit->ring->fctx; struct msm_gpu *gpu = submit->gpu; - struct msm_drm_private *priv = gpu->dev->dev_private; + struct drm_device *dev = gpu->dev; unsigned nr_cmds = submit->nr_cmds; int i; msm_fence_init(submit->hw_fence, fctx); - mutex_lock(&priv->lru.lock); + mutex_lock(&dev->gem_lru_mutex); for (i = 0; i < submit->nr_bos; i++) { struct drm_gem_object *obj = submit->bos[i].obj; @@ -32,7 +32,7 @@ static struct dma_fence *msm_job_run(struct drm_sched_job *job) submit->bos_pinned = false; - mutex_unlock(&priv->lru.lock); + mutex_unlock(&dev->gem_lru_mutex); /* TODO move submit path over to using a per-ring lock.. */ mutex_lock(&gpu->lock); -- cgit v1.2.3 From 6eb0168d091404bee11d1f0712515d77b8d01579 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Mon, 11 May 2026 19:28:37 +0200 Subject: drm/xe/memirq: Update interrupt handler logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To workaround some corner case hardware limitations, new programming note for the memory based interrupt handler suggests to assume that some status bytes, like GT_MI_USER_INTERRUPT and GUC_INTR_GUC2HOST, are always set. Update our interrupt handler to follow the new rules. Bspec: 53672 Fixes: a6581ebe7685 ("drm/xe/vf: Introduce Memory Based Interrupts Handler") Signed-off-by: Michal Wajdeczko Cc: Rodrigo Vivi Cc: Matthew Brost Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260511172838.2299-2-michal.wajdeczko@intel.com (cherry picked from commit 284f4cae4579eed9dd4406f18a6c1becc69f8931) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_memirq.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_memirq.c b/drivers/gpu/drm/xe/xe_memirq.c index 811e07136efb..579af47edc61 100644 --- a/drivers/gpu/drm/xe/xe_memirq.c +++ b/drivers/gpu/drm/xe/xe_memirq.c @@ -427,13 +427,25 @@ static bool memirq_received(struct xe_memirq *memirq, struct iosys_map *vector, return __memirq_received(memirq, vector, offset, name, true); } +static void memirq_assume_received(struct xe_memirq *memirq, const char *source, + u16 offset, const char *status) +{ + memirq_debug(memirq, "ASSUME %s %s(%u)\n", source, status, offset); +} + static void memirq_dispatch_engine(struct xe_memirq *memirq, struct iosys_map *status, struct xe_hw_engine *hwe) { memirq_debug(memirq, "STATUS %s %*ph\n", hwe->name, 16, status->vaddr); - if (memirq_received(memirq, status, ilog2(GT_MI_USER_INTERRUPT), hwe->name)) - xe_hw_engine_handle_irq(hwe, GT_MI_USER_INTERRUPT); + /* + * The programming note says to assume that GT_MI_USER_INTERRUPT is always + * set. Check and clear related status byte just for a debug. + */ + if (IS_ENABLED(CONFIG_DRM_XE_DEBUG_MEMIRQ) && + !memirq_received(memirq, status, ilog2(GT_MI_USER_INTERRUPT), hwe->name)) + memirq_assume_received(memirq, hwe->name, ilog2(GT_MI_USER_INTERRUPT), "USER"); + xe_hw_engine_handle_irq(hwe, GT_MI_USER_INTERRUPT); } static void memirq_dispatch_guc(struct xe_memirq *memirq, struct iosys_map *status, @@ -443,8 +455,14 @@ static void memirq_dispatch_guc(struct xe_memirq *memirq, struct iosys_map *stat memirq_debug(memirq, "STATUS %s %*ph\n", name, 16, status->vaddr); - if (memirq_received(memirq, status, ilog2(GUC_INTR_GUC2HOST), name)) - xe_guc_irq_handler(guc, GUC_INTR_GUC2HOST); + /* + * The programming note says to assume that GUC_INTR_GUC2HOST is always + * set. Check and clear related status byte just for a debug. + */ + if (IS_ENABLED(CONFIG_DRM_XE_DEBUG_MEMIRQ) && + !memirq_received(memirq, status, ilog2(GUC_INTR_GUC2HOST), name)) + memirq_assume_received(memirq, name, ilog2(GUC_INTR_GUC2HOST), "GUC2HOST"); + xe_guc_irq_handler(guc, GUC_INTR_GUC2HOST); /* * This is a software interrupt that must be cleared after it's consumed -- cgit v1.2.3 From d3ded53fab90996e7d94a39049e11962dd066725 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Mon, 11 May 2026 15:41:34 +0000 Subject: drm/xe/gsc: Fix double-free of managed BO in error path The error path in xe_gsc_init_post_hwconfig() explicitly frees a BO allocated with xe_managed_bo_create_pin_map() via xe_bo_unpin_map_no_vm(). Since the managed BO already has a devm cleanup action registered, this causes a double-free when devm unwinds during probe failure. Remove the explicit free and let devm handle it, consistent with all other xe_managed_bo_create_pin_map() callers. Fixes: 2e5d47fe7839 ("drm/xe/uc: Use managed bo for HuC and GSC objects") Reviewed-by: Daniele Ceraolo Spurio Assisted-by: Claude:claude-opus-4.6 Link: https://patch.msgid.link/20260511154134.223696-1-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit 71d61e3e299a17139e47f980a4d6f425b2c59bf7) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_gsc.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_gsc.c b/drivers/gpu/drm/xe/xe_gsc.c index 0d13e357fb43..aab59dc647fb 100644 --- a/drivers/gpu/drm/xe/xe_gsc.c +++ b/drivers/gpu/drm/xe/xe_gsc.c @@ -482,8 +482,7 @@ int xe_gsc_init_post_hwconfig(struct xe_gsc *gsc) EXEC_QUEUE_FLAG_PERMANENT, 0); if (IS_ERR(q)) { xe_gt_err(gt, "Failed to create queue for GSC submission\n"); - err = PTR_ERR(q); - goto out_bo; + return PTR_ERR(q); } wq = alloc_ordered_workqueue("gsc-ordered-wq", 0); @@ -506,8 +505,6 @@ int xe_gsc_init_post_hwconfig(struct xe_gsc *gsc) out_q: xe_exec_queue_put(q); -out_bo: - xe_bo_unpin_map_no_vm(bo); return err; } -- cgit v1.2.3 From 9bb2f1d7e6e58b8e434ddc2048c661bf87ccdf2a Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Thu, 14 May 2026 17:57:26 +0200 Subject: drm/xe/vf: Fix signature of print functions We have plugged-in existing VF print functions into our GT debugfs show helper as-is, but we missed that the helper expects functions to return int, while they were defined as void. This can lead to errors being reported when CFI is enabled. Fixes: 63d8cb8fe3dd ("drm/xe/vf: Expose SR-IOV VF attributes to GT debugfs") Signed-off-by: Michal Wajdeczko Cc: Mohanram Meenakshisundaram Reviewed-by: Shuicheng Lin Link: https://patch.msgid.link/20260514155726.7165-1-michal.wajdeczko@intel.com (cherry picked from commit 314e31c9a8a1c421ee4f7f755b9348aefbbca090) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_gt_sriov_vf.c | 24 ++++++++++++++++++------ drivers/gpu/drm/xe/xe_gt_sriov_vf.h | 6 +++--- 2 files changed, 21 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_vf.c b/drivers/gpu/drm/xe/xe_gt_sriov_vf.c index 8989c8e1be95..0cd9d77f3351 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_vf.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_vf.c @@ -1137,13 +1137,15 @@ void xe_gt_sriov_vf_write32(struct xe_gt *gt, struct xe_reg reg, u32 val) } /** - * xe_gt_sriov_vf_print_config - Print VF self config. + * xe_gt_sriov_vf_print_config() - Print VF self config. * @gt: the &xe_gt * @p: the &drm_printer * * This function is for VF use only. + * + * Return: always 0. */ -void xe_gt_sriov_vf_print_config(struct xe_gt *gt, struct drm_printer *p) +int xe_gt_sriov_vf_print_config(struct xe_gt *gt, struct drm_printer *p) { struct xe_gt_sriov_vf_selfconfig *config = >->sriov.vf.self_config; struct xe_device *xe = gt_to_xe(gt); @@ -1170,16 +1172,20 @@ void xe_gt_sriov_vf_print_config(struct xe_gt *gt, struct drm_printer *p) drm_printf(p, "GuC contexts:\t%u\n", config->num_ctxs); drm_printf(p, "GuC doorbells:\t%u\n", config->num_dbs); + + return 0; } /** - * xe_gt_sriov_vf_print_runtime - Print VF's runtime regs received from PF. + * xe_gt_sriov_vf_print_runtime() - Print VF's runtime regs received from PF. * @gt: the &xe_gt * @p: the &drm_printer * * This function is for VF use only. + * + * Return: always 0. */ -void xe_gt_sriov_vf_print_runtime(struct xe_gt *gt, struct drm_printer *p) +int xe_gt_sriov_vf_print_runtime(struct xe_gt *gt, struct drm_printer *p) { struct vf_runtime_reg *vf_regs = gt->sriov.vf.runtime.regs; unsigned int size = gt->sriov.vf.runtime.num_regs; @@ -1188,16 +1194,20 @@ void xe_gt_sriov_vf_print_runtime(struct xe_gt *gt, struct drm_printer *p) for (; size--; vf_regs++) drm_printf(p, "%#x = %#x\n", vf_regs->offset, vf_regs->value); + + return 0; } /** - * xe_gt_sriov_vf_print_version - Print VF ABI versions. + * xe_gt_sriov_vf_print_version() - Print VF ABI versions. * @gt: the &xe_gt * @p: the &drm_printer * * This function is for VF use only. + * + * Return: always 0. */ -void xe_gt_sriov_vf_print_version(struct xe_gt *gt, struct drm_printer *p) +int xe_gt_sriov_vf_print_version(struct xe_gt *gt, struct drm_printer *p) { struct xe_device *xe = gt_to_xe(gt); struct xe_uc_fw_version *guc_version = >->sriov.vf.guc_version; @@ -1227,6 +1237,8 @@ void xe_gt_sriov_vf_print_version(struct xe_gt *gt, struct drm_printer *p) GUC_RELAY_VERSION_LATEST_MAJOR, GUC_RELAY_VERSION_LATEST_MINOR); drm_printf(p, "\thandshake:\t%u.%u\n", pf_version->major, pf_version->minor); + + return 0; } static bool vf_post_migration_shutdown(struct xe_gt *gt) diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_vf.h b/drivers/gpu/drm/xe/xe_gt_sriov_vf.h index a6f7127521a5..79878f21b1da 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_vf.h +++ b/drivers/gpu/drm/xe/xe_gt_sriov_vf.h @@ -35,9 +35,9 @@ bool xe_gt_sriov_vf_sched_groups_enabled(struct xe_gt *gt); u32 xe_gt_sriov_vf_read32(struct xe_gt *gt, struct xe_reg reg); void xe_gt_sriov_vf_write32(struct xe_gt *gt, struct xe_reg reg, u32 val); -void xe_gt_sriov_vf_print_config(struct xe_gt *gt, struct drm_printer *p); -void xe_gt_sriov_vf_print_runtime(struct xe_gt *gt, struct drm_printer *p); -void xe_gt_sriov_vf_print_version(struct xe_gt *gt, struct drm_printer *p); +int xe_gt_sriov_vf_print_config(struct xe_gt *gt, struct drm_printer *p); +int xe_gt_sriov_vf_print_runtime(struct xe_gt *gt, struct drm_printer *p); +int xe_gt_sriov_vf_print_version(struct xe_gt *gt, struct drm_printer *p); int xe_gt_sriov_vf_wait_valid_ggtt(struct xe_gt *gt); int xe_vf_migration_fixups_complete_count(struct xe_gt *gt); -- cgit v1.2.3 From 96bf49b526e2d03a2b7f6e861925a08f46ed0d28 Mon Sep 17 00:00:00 2001 From: Mohanram Meenakshisundaram Date: Thu, 14 May 2026 23:19:18 +0530 Subject: drm/xe/pf: Fix CFI failure in debugfs access Reading debugfs file (/sys/kernel/debug/dri/0/gt*/pf/adverse_events) with CFI (Control Flow Integrity) enabled, the kernel panics at xe_gt_debugfs_simple_show+0x82/0xc0. xe_gt_debugfs_simple_show() declare a function pointer expecting int return type, but xe_gt_sriov_pf_monitor_print_events() is void return type, leading to CFI failure and kernel panic. [507620.973657] CFI failure at xe_gt_debugfs_simple_show+0x82/0xc0 [xe] (target: xe_gt_sriov_pf_monitor_print_events+0x0/0x130 [xe]; expected type: 0xd72c7139) Fix xe_gt_sriov_pf_monitor_print_events() function by updating to return an int type. Fixes: 1c99d3d3edab ("drm/xe/pf: Expose PF monitor details via debugfs") Signed-off-by: Mohanram Meenakshisundaram Reviewed-by: Michal Wajdeczko Signed-off-by: Michal Wajdeczko Link: https://patch.msgid.link/20260514174918.1556357-2-mohanram.meenakshisundaram@intel.com (cherry picked from commit ff1d386a8359746d9699ac30336e3b0684c68958) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.c | 6 +++++- drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.c index 7d532bded02a..a85ba4435378 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.c @@ -114,8 +114,10 @@ int xe_gt_sriov_pf_monitor_process_guc2pf(struct xe_gt *gt, const u32 *msg, u32 * VFs with no events are not printed. * * This function can only be called on PF. + * + * Return: always 0 */ -void xe_gt_sriov_pf_monitor_print_events(struct xe_gt *gt, struct drm_printer *p) +int xe_gt_sriov_pf_monitor_print_events(struct xe_gt *gt, struct drm_printer *p) { unsigned int n, total_vfs = xe_gt_sriov_pf_get_totalvfs(gt); const struct xe_gt_sriov_monitor *data; @@ -144,4 +146,6 @@ void xe_gt_sriov_pf_monitor_print_events(struct xe_gt *gt, struct drm_printer *p #undef __format #undef __value } + + return 0; } diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.h b/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.h index 7ca9351a271b..0b8f088d3a16 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.h +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_monitor.h @@ -13,7 +13,7 @@ struct drm_printer; struct xe_gt; void xe_gt_sriov_pf_monitor_flr(struct xe_gt *gt, u32 vfid); -void xe_gt_sriov_pf_monitor_print_events(struct xe_gt *gt, struct drm_printer *p); +int xe_gt_sriov_pf_monitor_print_events(struct xe_gt *gt, struct drm_printer *p); #ifdef CONFIG_PCI_IOV int xe_gt_sriov_pf_monitor_process_guc2pf(struct xe_gt *gt, const u32 *msg, u32 len); -- cgit v1.2.3 From 16be14eec5fdaea9477368856a85173246c41454 Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Thu, 14 May 2026 18:44:44 -0300 Subject: drm/xe: Define CACHE_MODE_1 as MCR register CACHE_MODE_1 is a MCR register for all platforms that currently use it in the Xe driver. Use XE_REG_MCR() when defining it. Fixes: 8cd7e9759766 ("drm/xe: Add missing DG2 lrc workarounds") Fixes: ff063430caa8 ("drm/xe/mtl: Add some initial MTL workarounds") Bspec: 66534, 67788 Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260514-rtp-mcr-check-v3-1-30dd47855fee@intel.com Signed-off-by: Gustavo Sousa (cherry picked from commit 8f765f0c054e0fb39980a76b4c899b027395929d) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/regs/xe_gt_regs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index 9c88ca3ce768..4399518c270e 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -152,7 +152,7 @@ #define XEHPG_INSTDONE_GEOM_SVGUNIT XE_REG_MCR(0x666c) -#define CACHE_MODE_1 XE_REG(0x7004, XE_REG_OPTION_MASKED) +#define CACHE_MODE_1 XE_REG_MCR(0x7004, XE_REG_OPTION_MASKED) #define MSAA_OPTIMIZATION_REDUC_DISABLE REG_BIT(11) #define COMMON_SLICE_CHICKEN1 XE_REG(0x7010, XE_REG_OPTION_MASKED) -- cgit v1.2.3 From a4660bd949733fd6ea621fdb50fabac2608155e9 Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Thu, 14 May 2026 18:44:45 -0300 Subject: drm/xe: Define and use MCR version of COMMON_SLICE_CHICKEN1 The register COMMON_SLICE_CHICKEN1 is a MCR register on Xe2. Let's make sure to define a MCR version of it and use it for the relevant IP versions. Use XEHP_ as prefix for the register name, since it is MCR as of Xe_HP. Fixes: a5d221924e13 ("drm/xe/xe2_hpg: Add set of workarounds") Fixes: 9f18b55b6d3f ("drm/xe/xe2: Add workaround 18033852989") Bspec: 66534, 71185 Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260514-rtp-mcr-check-v3-2-30dd47855fee@intel.com Signed-off-by: Gustavo Sousa (cherry picked from commit a672725fdbfc3ea430130039d677c7dc98d59df8) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/regs/xe_gt_regs.h | 1 + drivers/gpu/drm/xe/xe_wa.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index 4399518c270e..ed9aba5bf75e 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -156,6 +156,7 @@ #define MSAA_OPTIMIZATION_REDUC_DISABLE REG_BIT(11) #define COMMON_SLICE_CHICKEN1 XE_REG(0x7010, XE_REG_OPTION_MASKED) +#define XEHP_COMMON_SLICE_CHICKEN1 XE_REG_MCR(0x7010, XE_REG_OPTION_MASKED) #define DISABLE_BOTTOM_CLIP_RECTANGLE_TEST REG_BIT(14) #define HIZ_CHICKEN XE_REG(0x7018, XE_REG_OPTION_MASKED) diff --git a/drivers/gpu/drm/xe/xe_wa.c b/drivers/gpu/drm/xe/xe_wa.c index 4b1cbced06be..100569a62283 100644 --- a/drivers/gpu/drm/xe/xe_wa.c +++ b/drivers/gpu/drm/xe/xe_wa.c @@ -651,7 +651,7 @@ static const struct xe_rtp_entry_sr lrc_was[] = { }, { XE_RTP_NAME("18033852989"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2004), ENGINE_CLASS(RENDER)), - XE_RTP_ACTIONS(SET(COMMON_SLICE_CHICKEN1, DISABLE_BOTTOM_CLIP_RECTANGLE_TEST)) + XE_RTP_ACTIONS(SET(XEHP_COMMON_SLICE_CHICKEN1, DISABLE_BOTTOM_CLIP_RECTANGLE_TEST)) }, { XE_RTP_NAME("15016589081"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2004), ENGINE_CLASS(RENDER)), -- cgit v1.2.3 From 6df5678b6a94ac80e31e847074c4b30c21025b1f Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Thu, 14 May 2026 18:44:46 -0300 Subject: drm/xe: Define and use MCR version of COMMON_SLICE_CHICKEN4 The register COMMON_SLICE_CHICKEN4 is a MCR register on both Xe2 and Xe3. Let's make sure to define a MCR version of it and use it for the relevant IP versions. Use XEHP_ as prefix for the register name, since it is MCR as of Xe_HP. v2: - Also change for one entry in lrc_tunnings, which was caught by manual testing and add corresponging Fixes tag in commit message. (Gustavo) Fixes: 8d6f16f1f082 ("drm/xe: Extend Wa_22021007897 to Xe3 platforms") Fixes: e5c13e2c505b ("drm/xe/xe2hpg: Add Wa_22021007897") Fixes: 8ccf5f6b2295 ("drm/xe/tuning: Apply windower hardware filtering setting on Xe3 and Xe3p") Bspec: 66534, 71185, 74417 Reviewed-by: Matt Roper Link: https://patch.msgid.link/20260514-rtp-mcr-check-v3-3-30dd47855fee@intel.com Signed-off-by: Gustavo Sousa (cherry picked from commit 75f65f1a4c06da1d87f28570a9d4cdad28f13360) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/regs/xe_gt_regs.h | 1 + drivers/gpu/drm/xe/xe_tuning.c | 2 +- drivers/gpu/drm/xe/xe_wa.c | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index ed9aba5bf75e..353fe0bd49bf 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -179,6 +179,7 @@ #define XEHPG_SC_INSTDONE_EXTRA2 XE_REG_MCR(0x7108) #define COMMON_SLICE_CHICKEN4 XE_REG(0x7300, XE_REG_OPTION_MASKED) +#define XEHP_COMMON_SLICE_CHICKEN4 XE_REG_MCR(0x7300, XE_REG_OPTION_MASKED) #define SBE_PUSH_CONSTANT_BEHIND_FIX_ENABLE REG_BIT(12) #define DISABLE_TDC_LOAD_BALANCING_CALC REG_BIT(6) #define HW_FILTERING REG_BIT(5) diff --git a/drivers/gpu/drm/xe/xe_tuning.c b/drivers/gpu/drm/xe/xe_tuning.c index 0b78ec2bc6a4..fcb6698abc6e 100644 --- a/drivers/gpu/drm/xe/xe_tuning.c +++ b/drivers/gpu/drm/xe/xe_tuning.c @@ -129,7 +129,7 @@ static const struct xe_rtp_entry_sr engine_tunings[] = { static const struct xe_rtp_entry_sr lrc_tunings[] = { { XE_RTP_NAME("Tuning: Windower HW Filtering"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(3000, 3599), ENGINE_CLASS(RENDER)), - XE_RTP_ACTIONS(SET(COMMON_SLICE_CHICKEN4, HW_FILTERING)) + XE_RTP_ACTIONS(SET(XEHP_COMMON_SLICE_CHICKEN4, HW_FILTERING)) }, /* DG2 */ diff --git a/drivers/gpu/drm/xe/xe_wa.c b/drivers/gpu/drm/xe/xe_wa.c index 100569a62283..33df43d0bede 100644 --- a/drivers/gpu/drm/xe/xe_wa.c +++ b/drivers/gpu/drm/xe/xe_wa.c @@ -754,7 +754,7 @@ static const struct xe_rtp_entry_sr lrc_was[] = { }, { XE_RTP_NAME("22021007897"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2001, 2002), ENGINE_CLASS(RENDER)), - XE_RTP_ACTIONS(SET(COMMON_SLICE_CHICKEN4, SBE_PUSH_CONSTANT_BEHIND_FIX_ENABLE)) + XE_RTP_ACTIONS(SET(XEHP_COMMON_SLICE_CHICKEN4, SBE_PUSH_CONSTANT_BEHIND_FIX_ENABLE)) }, /* Xe3_LPG */ @@ -770,7 +770,7 @@ static const struct xe_rtp_entry_sr lrc_was[] = { }, { XE_RTP_NAME("22021007897"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(3000, 3005), ENGINE_CLASS(RENDER)), - XE_RTP_ACTIONS(SET(COMMON_SLICE_CHICKEN4, SBE_PUSH_CONSTANT_BEHIND_FIX_ENABLE)) + XE_RTP_ACTIONS(SET(XEHP_COMMON_SLICE_CHICKEN4, SBE_PUSH_CONSTANT_BEHIND_FIX_ENABLE)) }, { XE_RTP_NAME("14024681466"), XE_RTP_RULES(GRAPHICS_VERSION_RANGE(3000, 3005), ENGINE_CLASS(RENDER)), -- cgit v1.2.3 From 2a2451a34afdf563b3102d36a4b6cf335cf813e2 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Fri, 24 Apr 2026 10:50:35 +0100 Subject: wifi: ath11k: fix peer resolution on rx path when peer_id=0 It has been observed that on certain chipsets a peer can be assigned peer_id=0. For reception of non-aggregated MPDUs this is fine as ath11k_dp_rx_h_find_peer() has a fallback case where it locates the peer based upon the source MAC address. On an aggregated link, the mpdu_start header is only populated by hardware on the first sub-MSDU. This causes the peer resolution to be skipped for the subsequent MSDUs and the encryption type of these frames to be set to an incorrect value, resulting in these MSDUs being dropped by ieee80211. ath11k_pci 0000:03:00.0: data rx skb 000000002f4b704d len 1534 peer xx:xx:xx:xx:xx:xx 0 ucast sn 3063 he160 rate_idx 9 vht_nss 2 freq 5240 band 1 flag 0x40d1a fcs-err 0 mic-err 0 amsdu-more 0 peer_id 0 first_msdu 1 last_msdu 0 ath11k_pci 0000:03:00.0: data rx skb 0000000038acd580 len 1534 peer (null) 0 ucast sn 3063 he160 rate_idx 9 vht_nss 2 freq 5240 band 1 flag 0x40d00 fcs-err 0 mic-err 0 amsdu-more 0 peer_id 0 first_msdu 0 last_msdu 1 Remove the null peer_id checks in ath11k_dp_rx_h_find_peer() and ath11k_hal_rx_parse_mon_status_tlv(), allowing peers with an assigned ID of 0 to be resolved. Tested-on: QCA2066 hw2.1 PCI WLAN.HSP.1.1-03926.13-QCAHSPSWPL_V2_SILICONZ_CE-2.52297.9 Fixes: 2167fa606c0f ("ath11k: Add support for RX decapsulation offload") Reviewed-by: Baochen Qiang Signed-off-by: Matthew Leach Reviewed-by: P Praneesh Link: https://patch.msgid.link/20260424-ath11k-null-peerid-workaround-v4-1-252b224d3cf6@collabora.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 3 +-- drivers/net/wireless/ath/ath11k/hal_rx.c | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index fe79109adc70..72d5d933656d 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2214,8 +2214,7 @@ ath11k_dp_rx_h_find_peer(struct ath11k_base *ab, struct sk_buff *msdu) lockdep_assert_held(&ab->base_lock); - if (rxcb->peer_id) - peer = ath11k_peer_find_by_id(ab, rxcb->peer_id); + peer = ath11k_peer_find_by_id(ab, rxcb->peer_id); if (peer) return peer; diff --git a/drivers/net/wireless/ath/ath11k/hal_rx.c b/drivers/net/wireless/ath/ath11k/hal_rx.c index 753bd93f0212..51e0840bc0d1 100644 --- a/drivers/net/wireless/ath/ath11k/hal_rx.c +++ b/drivers/net/wireless/ath/ath11k/hal_rx.c @@ -1467,11 +1467,8 @@ ath11k_hal_rx_parse_mon_status_tlv(struct ath11k_base *ab, case HAL_RX_MPDU_START: { struct hal_rx_mpdu_info *mpdu_info = (struct hal_rx_mpdu_info *)tlv_data; - u16 peer_id; - peer_id = ath11k_hal_rx_mpduinfo_get_peerid(ab, mpdu_info); - if (peer_id) - ppdu_info->peer_id = peer_id; + ppdu_info->peer_id = ath11k_hal_rx_mpduinfo_get_peerid(ab, mpdu_info); break; } case HAL_RXPCU_PPDU_END_INFO: { -- cgit v1.2.3 From 72b8654e3b83548f64524add2e9145e9b6c8a852 Mon Sep 17 00:00:00 2001 From: Willmar Knikker Date: Tue, 5 May 2026 17:17:43 +0000 Subject: wifi: ath11k: fix use after free in ath11k_dp_rx_msdu_coalesce() In ath11k_dp_rx_msdu_coalesce() the loop uses ->is_continuation after the dev_kfree_skb_any(). This can cause a use after free kfence. Use flag for caching is_continuation for use after the dev_kfree_skb_any(). Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Willmar Knikker Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260505171709.547274-1-willmar@met-dubbel-l.nl Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 72d5d933656d..2a413e3a07a7 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -1761,6 +1761,7 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar, int buf_first_hdr_len, buf_first_len; struct hal_rx_desc *ldesc; int space_extra, rem_len, buf_len; + bool is_continuation; u32 hal_rx_desc_sz = ar->ab->hw_params.hal_desc_sz; /* As the msdu is spread across multiple rx buffers, @@ -1810,7 +1811,8 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar, rem_len = msdu_len - buf_first_len; while ((skb = __skb_dequeue(msdu_list)) != NULL && rem_len > 0) { rxcb = ATH11K_SKB_RXCB(skb); - if (rxcb->is_continuation) + is_continuation = rxcb->is_continuation; + if (is_continuation) buf_len = DP_RX_BUFFER_SIZE - hal_rx_desc_sz; else buf_len = rem_len; @@ -1828,7 +1830,7 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar, dev_kfree_skb_any(skb); rem_len -= buf_len; - if (!rxcb->is_continuation) + if (!is_continuation) break; } -- cgit v1.2.3 From f51e4b3b5574ad8cb5b16b11f8a1452147ece87a Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Wed, 13 May 2026 21:52:12 -0700 Subject: wifi: ath11k: clear shared SRNG pointer state on restart LMAC rings reuse the shared rdp/wrp pointer buffers without going through the normal SRNG hw-init path that zeros non-LMAC ring pointers. After restart, ath11k_hal_srng_clear() can therefore hand stale hp/tp state from the previous firmware instance back to the new one. Clear the shared pointer buffers while keeping the allocations in place so restart still avoids reallocating SRNG DMA memory, but starts with fresh ring-pointer state. Fixes: 32be3ca4cf78b ("wifi: ath11k: HAL SRNG: don't deinitialize and re-initialize again") Cc: stable@vger.kernel.org Closes: https://lore.kernel.org/all/CAOPSVF04q6uvVdq8GTRLHBrVMdpt9=o9wVcFMc6f-yhmSBcZqQ@mail.gmail.com/ Signed-off-by: Kyle Farnung Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260513-kfarnung-ath11k-srng-clear-pointer-state-v1-1-bc700dd8b333@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/hal.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c index e821e5a62c1c..98bd9e3f0aae 100644 --- a/drivers/net/wireless/ath/ath11k/hal.c +++ b/drivers/net/wireless/ath/ath11k/hal.c @@ -1387,14 +1387,22 @@ EXPORT_SYMBOL(ath11k_hal_srng_deinit); void ath11k_hal_srng_clear(struct ath11k_base *ab) { - /* No need to memset rdp and wrp memory since each individual - * segment would get cleared in ath11k_hal_srng_src_hw_init() - * and ath11k_hal_srng_dst_hw_init(). + /* + * Preserve the shared pointer buffers, but clear the previous + * firmware instance's hp/tp state before handing them back to FW. + * LMAC rings reuse this shared memory without going through the + * normal SRNG hw-init path that zeros non-LMAC ring pointers. */ memset(ab->hal.srng_list, 0, sizeof(ab->hal.srng_list)); memset(ab->hal.shadow_reg_addr, 0, sizeof(ab->hal.shadow_reg_addr)); + if (ab->hal.rdp.vaddr) + memset(ab->hal.rdp.vaddr, 0, + sizeof(*ab->hal.rdp.vaddr) * HAL_SRNG_RING_ID_MAX); + if (ab->hal.wrp.vaddr) + memset(ab->hal.wrp.vaddr, 0, + sizeof(*ab->hal.wrp.vaddr) * HAL_SRNG_NUM_LMAC_RINGS); ab->hal.avail_blk_resource = 0; ab->hal.current_blk_index = 0; ab->hal.num_shadow_reg_configured = 0; -- cgit v1.2.3 From 60fb2cf51e77bb1c0261160b4be44209d68956b1 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Thu, 14 May 2026 11:32:51 +0800 Subject: wifi: ath12k: fix EHT TX MCS limitation due to wrong 20 MHz-only parsing When connecting to an AP configured for EHT 20 MHz with a full EHT MCS/NSS map (supporting MCS 0-13) Supported EHT-MCS and NSS Set EHT-MCS Map (BW <= 80MHz): 0x444444 .... .... .... .... .... 0100 = Rx Max Nss That Supports EHT-MCS 0-9: 4 .... .... .... .... 0100 .... = Tx Max Nss That Supports EHT-MCS 0-9: 4 .... .... .... 0100 .... .... = Rx Max Nss That Supports EHT-MCS 10-11: 4 .... .... 0100 .... .... .... = Tx Max Nss That Supports EHT-MCS 10-11: 4 .... 0100 .... .... .... .... = Rx Max Nss That Supports EHT-MCS 12-13: 4 0100 .... .... .... .... .... = Tx Max Nss That Supports EHT-MCS 12-13: 4 TX throughput is observed to be significantly lower than expected. Investigation shows that TX rates are limited to EHT MCS 11, even though the AP advertises support for EHT MCS 12/13. The root cause is an incorrect parsing of the Supported EHT-MCS and NSS Set element in ath12k_peer_assoc_h_eht(). IEEE Std 802.11be-2024 Figure 9-1074as describes the format for 20 MHz-Only Non-AP STAs. IEEE Std 802.11be-2024 Figure 9-1074at describes the format for all other AP and non-AP STAs. Currently the first format is parsed when the peer advertises no wider HE channel width support, without considering whether it is an AP or a non-AP STA. This is incorrect: the peer AP's capabilities must be parsed using Figure 9-1074at even when it operates on 20 MHz only. Parsing it as Figure 9-1074as causes rx_tx_mcs13_max_nss to be interpreted as zero, which is then passed to firmware, leading firmware to assume the peer does not support MCS 13 and to limit TX rates at MCS 11. Fix this by parsing the Figure 9-1074as format only when the peer is a 20 MHz-Only non-AP STA, i.e. when the local interface operates as AP or mesh point. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: 6c95151e2e77 ("wifi: ath12k: Add EHT MCS/NSS rates to Peer Assoc") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260514-ath12k-fix-20mhz-only-mcs-map-v1-1-a38d4a9b21a2@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index df2334f3bad6..2cff9485c95a 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3446,7 +3446,9 @@ static void ath12k_peer_assoc_h_eht(struct ath12k *ar, arg->peer_eht_mcs_count++; fallthrough; default: - if (!(link_sta->he_cap.he_cap_elem.phy_cap_info[0] & + if ((vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_MESH_POINT) && + !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) { bw_20 = &eht_cap->eht_mcs_nss_supp.only_20mhz; @@ -3475,7 +3477,9 @@ static void ath12k_peer_assoc_h_eht(struct ath12k *ar, arg->punct_bitmap = ~arvif->punct_bitmap; arg->eht_disable_mcs15 = link_conf->eht_disable_mcs15; - if (!(link_sta->he_cap.he_cap_elem.phy_cap_info[0] & + if ((vif->type == NL80211_IFTYPE_AP || + vif->type == NL80211_IFTYPE_MESH_POINT) && + !(link_sta->he_cap.he_cap_elem.phy_cap_info[0] & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_MASK_ALL)) { if (bw_20->rx_tx_mcs13_max_nss) max_nss = max(max_nss, u8_get_bits(bw_20->rx_tx_mcs13_max_nss, -- cgit v1.2.3 From e9f5e8da29762df1111a58ae0b4a83091595d834 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 29 Apr 2026 11:58:59 +0200 Subject: drm/mediatek: mtk_hdmi_ddc_v2: Fix non-static global variable The struct 'mtk_hdmi_ddc_v2_driver' is not used outside of the mtk_hdmi_ddc_v2.c file, so make it static to silence sparse warning: ``` drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c:392:24: sparse: warning: symbol 'mtk_hdmi_ddc_v2_driver' was not declared. Should it be static? ``` Fixes: 8d0f79886273 ("drm/mediatek: Introduce HDMI/DDC v2 for MT8195/MT8188") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604132044.fcYjEcU8-lkp@intel.com/ Signed-off-by: Louis-Alexis Eyraud Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20260429-mediatek-drm-fix-sparse-warnings-v1-1-d95c4d118b83@collabora.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c index d937219fdb7e..31e81a6de6d8 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc_v2.c @@ -389,7 +389,7 @@ static const struct of_device_id mtk_hdmi_ddc_v2_match[] = { }; MODULE_DEVICE_TABLE(of, mtk_hdmi_ddc_v2_match); -struct platform_driver mtk_hdmi_ddc_v2_driver = { +static struct platform_driver mtk_hdmi_ddc_v2_driver = { .probe = mtk_hdmi_ddc_v2_probe, .driver = { .name = "mediatek-hdmi-ddc-v2", -- cgit v1.2.3 From dc245d9a7f1b06f86271d4e524d6e5634c5ce312 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 29 Apr 2026 11:59:00 +0200 Subject: drm/mediatek: mtk_hdmi_v2: Fix non-static global variable The struct 'mtk_hdmi_v2_clk_names' is not used outside of the mtk_hdmi_v2.c file, so make it static to silence sparse warning: ``` drivers/gpu/drm/mediatek/mtk_hdmi_v2.c:53:12: sparse: warning: symbol 'mtk_hdmi_v2_clk_names' was not declared. Should it be static? ``` Fixes: 8d0f79886273 ("drm/mediatek: Introduce HDMI/DDC v2 for MT8195/MT8188") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604132044.fcYjEcU8-lkp@intel.com/ Signed-off-by: Louis-Alexis Eyraud Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20260429-mediatek-drm-fix-sparse-warnings-v1-2-d95c4d118b83@collabora.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c index b5c738380dc2..a8eb6fd0908b 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_v2.c @@ -50,7 +50,7 @@ enum mtk_hdmi_v2_clk_id { MTK_HDMI_V2_CLK_COUNT, }; -const char *const mtk_hdmi_v2_clk_names[MTK_HDMI_V2_CLK_COUNT] = { +static const char *const mtk_hdmi_v2_clk_names[MTK_HDMI_V2_CLK_COUNT] = { [MTK_HDMI_V2_CLK_HDMI_APB_SEL] = "bus", [MTK_HDMI_V2_CLK_HDCP_SEL] = "hdcp", [MTK_HDMI_V2_CLK_HDCP_24M_SEL] = "hdcp24m", -- cgit v1.2.3 From 571f00a5fb725984049bd532ee8193cc34ff2994 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 29 Apr 2026 11:59:01 +0200 Subject: drm/mediatek: mtk_cec: Fix non-static global variable The struct 'mtk_cec_driver' is not used outside of the mtk_cec.c file, so make it static to silence sparse warning: ``` drivers/gpu/drm/mediatek/mtk_cec.c:243:24: sparse: warning: symbol 'mtk_cec_driver' was not declared. Should it be static? ``` Fixes: 1e914a89ab7e ("drm/mediatek: mtk_cec: Switch to register as module_platform_driver") Signed-off-by: Louis-Alexis Eyraud Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20260429-mediatek-drm-fix-sparse-warnings-v1-3-d95c4d118b83@collabora.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_cec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c index c7be530ca041..b8ccd6e55bed 100644 --- a/drivers/gpu/drm/mediatek/mtk_cec.c +++ b/drivers/gpu/drm/mediatek/mtk_cec.c @@ -240,7 +240,7 @@ static const struct of_device_id mtk_cec_of_ids[] = { }; MODULE_DEVICE_TABLE(of, mtk_cec_of_ids); -struct platform_driver mtk_cec_driver = { +static struct platform_driver mtk_cec_driver = { .probe = mtk_cec_probe, .remove = mtk_cec_remove, .driver = { -- cgit v1.2.3 From 87ed4e845d5a90bba1a56c0a5c580a13982e8648 Mon Sep 17 00:00:00 2001 From: Louis-Alexis Eyraud Date: Wed, 29 Apr 2026 11:59:02 +0200 Subject: drm/mediatek: mtk_hdmi_ddc: Fix non-static global variable The struct 'mtk_hdmi_ddc_driver' is not used outside of the mtk_hdmi_ddc.c file, so make it static to silence sparse warning: ``` drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c:331:24: sparse: warning: symbol 'mtk_hdmi_ddc_driver' was not declared. Should it be static? ``` Fixes: c241118b6216 ("drm/mediatek: mtk_hdmi_ddc: Switch to register as module_platform_driver") Signed-off-by: Louis-Alexis Eyraud Reviewed-by: CK Hu Link: https://patchwork.kernel.org/project/dri-devel/patch/20260429-mediatek-drm-fix-sparse-warnings-v1-4-d95c4d118b83@collabora.com/ Signed-off-by: Chun-Kuang Hu --- drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c index 6358e1af69b4..2acbdb025d89 100644 --- a/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c +++ b/drivers/gpu/drm/mediatek/mtk_hdmi_ddc.c @@ -328,7 +328,7 @@ static const struct of_device_id mtk_hdmi_ddc_match[] = { }; MODULE_DEVICE_TABLE(of, mtk_hdmi_ddc_match); -struct platform_driver mtk_hdmi_ddc_driver = { +static struct platform_driver mtk_hdmi_ddc_driver = { .probe = mtk_hdmi_ddc_probe, .remove = mtk_hdmi_ddc_remove, .driver = { -- cgit v1.2.3 From a7e8f3efd50a165ba0189f6dc57f7e51a7d149db Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 12 May 2026 09:43:34 +0200 Subject: spi: qup: fix error pointer deref after DMA setup failure The driver falls back to PIO mode if DMA setup fails during probe. Make sure to the clear the DMA channel pointers on setup failure to avoid dereferencing an error pointer (or attempting to release a channel a second time) on later probe errors or driver unbind. This issue was flagged by Sashiko when reviewing a devres allocation conversion patch. Fixes: 612762e82ae6 ("spi: qup: Add DMA capabilities") Link: https://sashiko.dev/#/patchset/20260505072909.618363-1-johan%40kernel.org?part=4 Cc: stable@vger.kernel.org # 4.1 Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260512074334.914735-1-johan@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-qup.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c index 45d9b4cb75e4..50bb7701b9d5 100644 --- a/drivers/spi/spi-qup.c +++ b/drivers/spi/spi-qup.c @@ -996,8 +996,11 @@ static int spi_qup_init_dma(struct spi_controller *host, resource_size_t base) err: dma_release_channel(host->dma_tx); + host->dma_tx = NULL; err_tx: dma_release_channel(host->dma_rx); + host->dma_rx = NULL; + return ret; } -- cgit v1.2.3 From 3d67fffb74267772d461c02c67f1eff893ad547d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 12 May 2026 09:47:33 +0200 Subject: spi: sprd: fix error pointer deref after DMA setup failure The driver falls back to PIO mode if DMA setup fails during probe. Make sure to check the dma.enabled flag before trying to release the DMA channels also on late probe errors to avoid dereferencing an error pointer (or attempting to release a channel a second time). This issue was flagged by Sashiko when reviewing a devres allocation conversion patch. Fixes: 386119bc7be9 ("spi: sprd: spi: sprd: Add DMA mode support") Link: https://sashiko.dev/#/patchset/20260505072909.618363-1-johan%40kernel.org?part=10 Cc: stable@vger.kernel.org # 5.1 Cc: Lanqing Liu Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260512074733.915029-1-johan@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-sprd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/spi/spi-sprd.c b/drivers/spi/spi-sprd.c index fd3fd0ce122c..acebf9c2e795 100644 --- a/drivers/spi/spi-sprd.c +++ b/drivers/spi/spi-sprd.c @@ -991,7 +991,8 @@ err_rpm_put: disable_clk: clk_disable_unprepare(ss->clk); release_dma: - sprd_spi_dma_release(ss); + if (ss->dma.enable) + sprd_spi_dma_release(ss); free_controller: spi_controller_put(sctlr); -- cgit v1.2.3 From ea6ec3343e05f7937a53eb6d7617b3abdb4abc19 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 12 May 2026 09:48:09 +0200 Subject: spi: ti-qspi: fix use-after-free after DMA setup failure The driver falls back to PIO mode if DMA setup fails during probe. Make sure to clear the DMA channel pointer also if buffer allocation fails to avoid passing a pointer to the released channel to the DMA engine (or trying to free the channel a second time on late probe errors or driver unbind). This issue was flagged by Sashiko when reviewing a devres allocation conversion patch. Fixes: c687c46e9e45 ("spi: spi-ti-qspi: Use bounce buffer if read buffer is not DMA'ble") Link: https://sashiko.dev/#/patchset/20260505072909.618363-1-johan%40kernel.org?part=17 Cc: stable@vger.kernel.org # 4.12 Cc: Vignesh R Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260512074809.915084-1-johan@kernel.org Signed-off-by: Mark Brown --- drivers/spi/spi-ti-qspi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c index 1fbd710d616f..e3b413b9828c 100644 --- a/drivers/spi/spi-ti-qspi.c +++ b/drivers/spi/spi-ti-qspi.c @@ -867,6 +867,7 @@ static int ti_qspi_probe(struct platform_device *pdev) dev_err(qspi->dev, "dma_alloc_coherent failed, using PIO mode\n"); dma_release_channel(qspi->rx_chan); + qspi->rx_chan = NULL; goto no_dma; } host->dma_rx = qspi->rx_chan; -- cgit v1.2.3 From 4d3a2a466b8d68d852a1f3bbf11204b718428dc4 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sun, 17 May 2026 13:51:01 +0900 Subject: HID: core: Fix size_t specifier in hid_report_raw_event() When building for 32-bit platforms, for which 'size_t' is 'unsigned int', there are warnings around using the incorrect format specifier to print bsize in hid_report_raw_event(): drivers/hid/hid-core.c:2054:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat] 2053 | hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n", | ~~~ | %zu 2054 | report->id, csize, bsize); | ^~~~~ drivers/hid/hid-core.c:2076:29: error: format specifies type 'long' but the argument has type 'size_t' (aka 'unsigned int') [-Werror,-Wformat] 2075 | hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n", | ~~~ | %zu 2076 | report->id, rsize, bsize); | ^~~~~ Use the proper 'size_t' format specifier, '%zu', to clear up the warnings. Cc: stable@vger.kernel.org Fixes: 2c85c61d1332 ("HID: pass the buffer size to hid_report_raw_event") Reported-by: Miguel Ojeda Closes: https://lore.kernel.org/20260516020430.110135-1-ojeda@kernel.org/ Signed-off-by: Nathan Chancellor Signed-off-by: Linus Torvalds --- drivers/hid/hid-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index b3596851c719..41a79e43c82b 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -2050,7 +2050,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * return 0; if (unlikely(bsize < csize)) { - hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n", + hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n", report->id, csize, bsize); return -EINVAL; } @@ -2072,7 +2072,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 * rsize = max_buffer_size; if (bsize < rsize) { - hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n", + hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n", report->id, rsize, bsize); return -EINVAL; } -- cgit v1.2.3 From b0fe80c0b9250b35e2211bf3117e7aca814a21b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Fri, 15 May 2026 12:07:14 -0300 Subject: drm/v3d: Fix use-after-free of CPU job query arrays on error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CPU job ioctl's fail label calls kvfree() on cpu_job's timestamp and performance query arrays after v3d_job_cleanup(), which drops the job's last reference and frees cpu_job. Reading cpu_job at that point is a use-after-free. Also, on the early v3d_job_init() failure path, it is a NULL dereference, since v3d_job_deallocate() zeroes the local pointer. In the success path, the arrays are released from the scheduler's .free_job callback, but on the error path, they are freed manually, as the job was never pushed to the scheduler. While the success path deals with this correctly, the fail path doesn't. On top of that, the manual kvfree() calls only free the array storage; they don't drm_syncobj_put() the per-query syncobjs that v3d_timestamp_query_info_free() and v3d_performance_query_info_free() release on the success path. So the same fail path that triggers the use-after-free also leaks one syncobj reference per query. Unify the CPU job teardown into the CPU job's kref destructor, mirroring v3d_render_job_free(). The scheduler's .free_job slot reverts to the generic v3d_sched_job_free() and the fail label drops the manual kvfree() calls, leaving a single teardown path that is reached from both the scheduler and the ioctl error path. That removes the use-after-free, the NULL dereference, and the syncobj leak by construction. Cc: stable@vger.kernel.org Fixes: 9ba0ff3e083f ("drm/v3d: Create a CPU job extension for the timestamp query job") Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260515-v3d-cpu-job-leaks-v1-1-7f147cbbf935@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_sched.c | 16 +--------------- drivers/gpu/drm/v3d/v3d_submit.c | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c index 1855ef5b3b5f..94bf628dc91c 100644 --- a/drivers/gpu/drm/v3d/v3d_sched.c +++ b/drivers/gpu/drm/v3d/v3d_sched.c @@ -125,20 +125,6 @@ v3d_performance_query_info_free(struct v3d_performance_query_info *query_info, } } -static void -v3d_cpu_job_free(struct drm_sched_job *sched_job) -{ - struct v3d_cpu_job *job = to_cpu_job(sched_job); - - v3d_timestamp_query_info_free(&job->timestamp_query, - job->timestamp_query.count); - - v3d_performance_query_info_free(&job->performance_query, - job->performance_query.count); - - v3d_job_cleanup(&job->base); -} - static void v3d_switch_perfmon(struct v3d_dev *v3d, struct v3d_job *job) { @@ -830,7 +816,7 @@ static const struct drm_sched_backend_ops v3d_cache_clean_sched_ops = { static const struct drm_sched_backend_ops v3d_cpu_sched_ops = { .run_job = v3d_cpu_job_run, - .free_job = v3d_cpu_job_free + .free_job = v3d_sched_job_free }; static int diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index ee4512db294b..e3a6e7cc7bd5 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -123,6 +123,21 @@ v3d_render_job_free(struct kref *ref) v3d_job_free(ref); } +static void +v3d_cpu_job_free(struct kref *ref) +{ + struct v3d_cpu_job *job = container_of(ref, struct v3d_cpu_job, + base.refcount); + + v3d_timestamp_query_info_free(&job->timestamp_query, + job->timestamp_query.count); + + v3d_performance_query_info_free(&job->performance_query, + job->performance_query.count); + + v3d_job_free(ref); +} + void v3d_job_cleanup(struct v3d_job *job) { if (!job) @@ -1302,7 +1317,7 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data, trace_v3d_submit_cpu_ioctl(&v3d->drm, cpu_job->job_type); ret = v3d_job_init(v3d, file_priv, &cpu_job->base, - v3d_job_free, 0, &se, V3D_CPU); + v3d_cpu_job_free, 0, &se, V3D_CPU); if (ret) { v3d_job_deallocate((void *)&cpu_job); goto fail; @@ -1385,8 +1400,6 @@ fail: v3d_job_cleanup((void *)csd_job); v3d_job_cleanup(clean_job); v3d_put_multisync_post_deps(&se); - kvfree(cpu_job->timestamp_query.queries); - kvfree(cpu_job->performance_query.queries); return ret; } -- cgit v1.2.3 From 6eb6e5acafa46854d4363e6c34981289995f3ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ADra=20Canal?= Date: Fri, 15 May 2026 12:07:15 -0300 Subject: drm/v3d: Release indirect CSD GEM reference on CPU job free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v3d_get_cpu_indirect_csd_params() takes a reference to the indirect BO via drm_gem_object_lookup() and stashes it in cpu_job->indirect_csd.indirect, but nothing on the CPU job teardown path ever drops that reference. Drop the extra reference in v3d_cpu_job_free(). The NULL check covers ioctl errors before the lookup ran and CPU job types other than V3D_CPU_JOB_TYPE_INDIRECT_CSD, which leave the field zero-initialised. Cc: stable@vger.kernel.org Fixes: 18b8413b25b7 ("drm/v3d: Create a CPU job extension for a indirect CSD job") Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Iago Toral Quiroga Link: https://patch.msgid.link/20260515-v3d-cpu-job-leaks-v1-2-7f147cbbf935@igalia.com Signed-off-by: Maíra Canal --- drivers/gpu/drm/v3d/v3d_submit.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c index e3a6e7cc7bd5..3ddd53b6f437 100644 --- a/drivers/gpu/drm/v3d/v3d_submit.c +++ b/drivers/gpu/drm/v3d/v3d_submit.c @@ -135,6 +135,9 @@ v3d_cpu_job_free(struct kref *ref) v3d_performance_query_info_free(&job->performance_query, job->performance_query.count); + if (job->indirect_csd.indirect) + drm_gem_object_put(job->indirect_csd.indirect); + v3d_job_free(ref); } -- cgit v1.2.3 From c326f9c68921e2f14dfcecb2f6b4216313d50248 Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Wed, 13 May 2026 09:46:13 +0300 Subject: net/mlx5e: xsk: Fix unlocked writing to ICOSQ During napi poll, when the affinity changes and there's still XSK work to be done, we trigger an ICOSQ interrupt on the new CPU. However, this triggering on the ICOSQ is done unprotected. There are 2 such races: A) mlx5e_trigger_irq() is called while mlx5e_xsk_alloc_rx_mpwqe() is running from a different CPU due to affinity change. This can happen because IRQ triggering is done after napi_complete_done(). At this point the NAPI can be scheduled on a different CPU. Like this: CPU A (old affinity, NAPI tail) CPU B (new affinity, fresh NAPI) ------------------------------- -------------------------------- napi_complete_done() clears SCHED mlx5e_cq_arm(...) napi_schedule_prep() sets SCHED mlx5e_napi_poll() mlx5e_xsk_alloc_rx_mpwqe() mlx5e_icosq_sync_lock() // noop memcpy 640 B UMR body advance sq->pc by 10 mlx5e_trigger_irq(&c->icosq) wqe_info[pi] = {NOP, 1} mlx5e_post_nop() advances sq->pc B) mlx5e_trigger_irq() is called on the ICOSQ when mlx5e_trigger_napi_icosq() is running. The obvious fix would be to lock the ICOSQ. But ICOSQ has an optimized locking scheme that doesn't work for this scenario. Kick the async ICOSQ instead which is always locked. This issue was noticed in the wild with the following splat: netdevice: ge-0-0-1: Bad OP in ICOSQ CQE: 0xd WARNING: drivers/net/ethernet/mellanox/mlx5/core/en_rx.c:826 [...] [...] Call Trace: mlx5e_napi_poll+0x11d/0x7f0 [mlx5_core] __napi_poll+0x30/0x200 ? skb_defer_free_flush+0x9c/0xc0 net_rx_action+0x2fe/0x3f0 handle_softirqs+0xd8/0x340 __irq_exit_rcu+0xbc/0xe0 common_interrupt+0x85/0xa0 asm_common_interrupt+0x26/0x40 [...] ---[ end trace 0000000000000000 ]--- mlx5_core 0000:08:00.0 ge-0-0-1: Error cqe on cqn 0x548, ci 0x2022, qn 0x8f4, opcode 0xd, syndrome 0x2, vendor syndrome 0x68 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000030: 00 00 00 00 01 00 68 02 01 00 08 f4 de 14 59 d2 WQE DUMP: WQ size 16384 WQ cur size 0, WQE index 0x1e14, len: 64 00000000: 00 00 00 01 d9 ed 80 02 00 00 00 01 d9 ed 90 02 00000010: 00 00 00 01 d9 ed a0 02 00 00 00 01 d9 ed b0 02 00000020: 00 00 00 01 d9 ed c0 02 00 00 00 01 d9 ed d0 02 00000030: 00 00 00 01 d9 ed e0 02 00 00 00 01 d9 ed f0 02 mlx5_core 0000:08:00.0 ge-0-0-1: Error cqe on cqn 0x548, ci 0x2023, qn 0x8f4, opcode 0xd, syndrome 0x5, vendor syndrome 0xf9 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000030: 00 00 00 00 01 00 f9 05 01 00 08 f4 de 15 cf d2 Fixes: db05815b36cb ("net/mlx5e: Add XSK zero-copy support") Reported-by: Paul Saab Signed-off-by: Dragos Tatulea Signed-off-by: Tariq Toukan Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260513064613.334602-1-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_txrx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_txrx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_txrx.c index b31f689fe271..e90c6c6df835 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_txrx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_txrx.c @@ -252,7 +252,7 @@ int mlx5e_napi_poll(struct napi_struct *napi, int budget) mlx5e_cq_arm(&c->xdpsq->cq); if (unlikely(aff_change && busy_xsk)) { - mlx5e_trigger_irq(&c->icosq); + mlx5e_trigger_napi_async_icosq(c); ch_stats->force_irq++; } -- cgit v1.2.3 From d00c953a8f69921f484b629801766da68f27f658 Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Thu, 14 May 2026 05:25:12 -0700 Subject: net: qualcomm: rmnet: fix endpoint use-after-free in rmnet_dellink() rmnet_dellink() removes the endpoint from the hash table with hlist_del_init_rcu() and then immediately frees it with kfree(). However, RCU readers on the receive path (rmnet_rx_handler -> __rmnet_map_ingress_handler) may still hold a reference to the endpoint and dereference ep->egress_dev after the memory has been freed. The endpoint is a kmalloc-32 object, and the stale read at offset 8 corresponds to the egress_dev pointer. BUG: unable to handle page fault for address: ffffffffde942eef Oops: 0002 [#1] SMP NOPTI CPU: 1 UID: 0 PID: 137 Comm: poc_write Not tainted 7.0.0+ #4 PREEMPTLAZY RIP: 0010:rmnet_vnd_rx_fixup (rmnet_vnd.c:27) Call Trace: __rmnet_map_ingress_handler (rmnet_handlers.c:48 rmnet_handlers.c:101) rmnet_rx_handler (rmnet_handlers.c:129 rmnet_handlers.c:235) __netif_receive_skb_core.constprop.0 (net/core/dev.c:6096) __netif_receive_skb_one_core (net/core/dev.c:6208) netif_receive_skb (net/core/dev.c:6467) tun_get_user (drivers/net/tun.c:1955) tun_chr_write_iter (drivers/net/tun.c:2003) vfs_write (fs/read_write.c:688) ksys_write (fs/read_write.c:740) Add an rcu_head field to struct rmnet_endpoint and replace kfree() with kfree_rcu() so the endpoint memory remains valid through the RCU grace period. Also remove the rmnet_vnd_dellink() call and inline only the nr_rmnet_devs decrement, since rmnet_vnd_dellink() would set ep->egress_dev to NULL during the grace period, creating a data race with lockless readers. Fixes: ceed73a2cf4a ("drivers: net: ethernet: qualcomm: rmnet: Initial implementation") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Link: https://patch.msgid.link/20260514122511.3083479-2-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c | 8 ++++---- drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c index 269c0449760c..78d4df55740a 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c @@ -213,8 +213,8 @@ static void rmnet_dellink(struct net_device *dev, struct list_head *head) ep = rmnet_get_endpoint(real_port, mux_id); if (ep) { hlist_del_init_rcu(&ep->hlnode); - rmnet_vnd_dellink(mux_id, real_port, ep); - kfree(ep); + real_port->nr_rmnet_devs--; + kfree_rcu(ep, rcu); } netdev_upper_dev_unlink(real_dev, dev); @@ -238,9 +238,9 @@ static void rmnet_force_unassociate_device(struct net_device *real_dev) hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) { unregister_netdevice_queue(ep->egress_dev, &list); netdev_upper_dev_unlink(real_dev, ep->egress_dev); - rmnet_vnd_dellink(ep->mux_id, port, ep); hlist_del_init_rcu(&ep->hlnode); - kfree(ep); + port->nr_rmnet_devs--; + kfree_rcu(ep, rcu); } rmnet_unregister_real_device(real_dev); unregister_netdevice_many(&list); diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h index ed112d51ac5a..f50fae1c6bdd 100644 --- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h +++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h @@ -18,6 +18,7 @@ struct rmnet_endpoint { u8 mux_id; struct net_device *egress_dev; struct hlist_node hlnode; + struct rcu_head rcu; }; struct rmnet_egress_agg_params { -- cgit v1.2.3 From 89bbff099bfc94888eb942d5b981592bbbe0c856 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Fri, 15 May 2026 11:24:08 -0700 Subject: ice: fix locking around wait_event_interruptible_locked_irq Commit 50327223a8bb ("ice: add lock to protect low latency interface") introduced a wait queue used to protect the low latency timer interface. The queue is used with the wait_event_interruptible_locked_irq macro, which unlocks the wait queue lock while sleeping. The irq variant uses spin_lock_irq and spin_unlock_irq to manage this. The wait queue lock was previously locked using spin_lock_irqsave. This difference in lock variants could lead to issues, since wait_event would unlock the wait queue and restore interrupts while sleeping. The ice_read_phy_tstamp_ll_e810() function is ultimately called through ice_read_phy_tstamp, which is called from ice_ptp_process_tx_tstamp or ice_ptp_clear_unexpected_tx_ready. The former is called through the miscellaneous IRQ thread function, while the latter is called from the service task work queue thread. Neither of these functions has interrupts disabled, so use spin_lock_irq instead of spin_lock_irqsave. Fixes: 50327223a8bb ("ice: add lock to protect low latency interface") Cc: stable@vger.kernel.org Reported-by: Jakub Kicinski Closes: https://lore.kernel.org/netdev/20250109181823.77f44c69@kernel.org/ Signed-off-by: Jacob Keller Signed-off-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-2-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 24fb7a3e14d6..672218e5d1f9 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -4503,18 +4503,17 @@ static int ice_read_phy_tstamp_ll_e810(struct ice_hw *hw, u8 idx, u8 *hi, u32 *lo) { struct ice_e810_params *params = &hw->ptp.phy.e810; - unsigned long flags; u32 val; int err; - spin_lock_irqsave(¶ms->atqbal_wq.lock, flags); + spin_lock_irq(¶ms->atqbal_wq.lock); /* Wait for any pending in-progress low latency interrupt */ err = wait_event_interruptible_locked_irq(params->atqbal_wq, !(params->atqbal_flags & ATQBAL_FLAGS_INTR_IN_PROGRESS)); if (err) { - spin_unlock_irqrestore(¶ms->atqbal_wq.lock, flags); + spin_unlock_irq(¶ms->atqbal_wq.lock); return err; } @@ -4529,7 +4528,7 @@ ice_read_phy_tstamp_ll_e810(struct ice_hw *hw, u8 idx, u8 *hi, u32 *lo) REG_LL_PROXY_H); if (err) { ice_debug(hw, ICE_DBG_PTP, "Failed to read PTP timestamp using low latency read\n"); - spin_unlock_irqrestore(¶ms->atqbal_wq.lock, flags); + spin_unlock_irq(¶ms->atqbal_wq.lock); return err; } @@ -4539,7 +4538,7 @@ ice_read_phy_tstamp_ll_e810(struct ice_hw *hw, u8 idx, u8 *hi, u32 *lo) /* Read the low 32 bit value and set the TS valid bit */ *lo = rd32(hw, REG_LL_PROXY_L) | TS_VALID; - spin_unlock_irqrestore(¶ms->atqbal_wq.lock, flags); + spin_unlock_irq(¶ms->atqbal_wq.lock); return 0; } -- cgit v1.2.3 From 3ba4dd024d26372733d1c02e13e076c6016e3320 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Fri, 15 May 2026 11:24:09 -0700 Subject: ice: fix VF queue configuration with low MTU values The ice driver's VF queue configuration validation rejects databuffer_size values below 1024 bytes, which prevents VFs from using MTU values below 871 bytes. The iavf driver calculates databuffer_size based on the MTU using: databuffer_size = ALIGN(MTU + LIBETH_RX_LL_LEN, 128) where LIBETH_RX_LL_LEN = 26 (ETH_HLEN + 2*VLAN_HLEN + ETH_FCS_LEN). For MTU values below 871: MTU 870: 870 + 26 = 896, aligned to 128 = 896 (< 1024, rejected) MTU 871: 871 + 26 = 897, aligned to 128 = 1024 (>= 1024, accepted) The 1024-byte minimum seems unnecessarily restrictive, because the hardware supports databuffer_size as low as 128 bytes (the alignment boundary), which should allow MTU values down to the standard minimum of 68 bytes. I haven't found the reason why the limit was configured in the commit 9c7dd7566d18 ("ice: add validation in OP_CONFIG_VSI_QUEUES VF message"), so with no more information and since it is working, change the minimum databuffer_size validation from 1024 to 128 bytes to allow standard low MTU values while still preventing invalid configurations. Fixes: 9c7dd7566d18 ("ice: add validation in OP_CONFIG_VSI_QUEUES VF message") cc: stable@vger.kernel.org Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Jacob Keller Reviewed-by: Michal Swiatkowski Reviewed-by: Paul Menzel Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-3-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/virt/queues.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/virt/queues.c b/drivers/net/ethernet/intel/ice/virt/queues.c index f73d5a3e83d4..31be2f76181c 100644 --- a/drivers/net/ethernet/intel/ice/virt/queues.c +++ b/drivers/net/ethernet/intel/ice/virt/queues.c @@ -840,7 +840,7 @@ int ice_vc_cfg_qs_msg(struct ice_vf *vf, u8 *msg) if (qpi->rxq.databuffer_size != 0 && (qpi->rxq.databuffer_size > ((16 * 1024) - 128) || - qpi->rxq.databuffer_size < 1024)) + qpi->rxq.databuffer_size < 128)) goto error_param; ring->rx_buf_len = qpi->rxq.databuffer_size; -- cgit v1.2.3 From ebc8de716c9ec2be384abdc2dd866da26c6580d1 Mon Sep 17 00:00:00 2001 From: Marcin Szycik Date: Fri, 15 May 2026 11:24:10 -0700 Subject: ice: fix setting promisc mode while adding VID filter There are at least two paths through which VSI promiscuous mode can be independently configured via ice_fltr_set_vsi_promisc(): - ice_vlan_rx_add_vid() (netdev op) - ice_service_task() -> ... -> ice_set_promisc() Both paths may try to program promiscuous mode concurrently. One such scenario is: 1. Add ice netdev to bond 2. Add the bond netdev to bridge 3. ice netdev enters allmulticast mode (IFF_ALLMULTI) 4. Service task programs promisc mode filter 5. Bridge -> bond calls ice_vlan_rx_add_vid() Crucially, ice_vlan_rx_add_vid() fails if ice_fltr_set_vsi_promisc() returns any error, including -EEXIST. This causes VLAN filtering setup to fail on the bond interface. ice_set_promisc() already handles -EEXIST correctly. Fix by adding the same -EEXIST check to ice_vlan_rx_add_vid(): if the promisc filter is already programmed, continue without returning error. Fixes: 1273f89578f2 ("ice: Fix broken IFF_ALLMULTI handling") Cc: stable@vger.kernel.org Signed-off-by: Marcin Szycik Signed-off-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-4-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index c52c465280f7..66642232b282 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -3682,7 +3682,7 @@ int ice_vlan_rx_add_vid(struct net_device *netdev, __be16 proto, u16 vid) ret = ice_fltr_set_vsi_promisc(&vsi->back->hw, vsi->idx, ICE_MCAST_VLAN_PROMISC_BITS, vid); - if (ret) + if (ret && ret != -EEXIST) goto finish; } -- cgit v1.2.3 From 781ff8f2d575a794a2a4f11605288ae06757f5eb Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Fri, 15 May 2026 11:24:11 -0700 Subject: ice: ptp: serialize E825 PHY timer start with PTP lock ice_start_phy_timer_eth56g() programs TIMETUS registers and issues INIT_INCVAL without holding the global PTP semaphore. This allows concurrent PTP command paths to interleave with PHY timer start, which can make the sequence fail and leave timer initialization inconsistent. Take the PTP lock around TIMETUS registers programming and INIT_INCVAL command execution, and make sure the lock is released on all error paths. Keep the subsequent sync step outside of this critical section, since ice_sync_phy_timer_eth56g() takes the same semaphore internally. Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products") Reviewed-by: Arkadiusz Kubalewski Signed-off-by: Grzegorz Nitka Reviewed-by: Aleksandr Loktionov Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-5-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 672218e5d1f9..8bb94e785f2a 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -2141,16 +2141,23 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port) } incval = (u64)hi << 32 | lo; + if (!ice_ptp_lock(hw)) { + dev_err(ice_hw_to_dev(hw), "Failed to acquire PTP semaphore\n"); + return -EBUSY; + } + err = ice_write_40b_ptp_reg_eth56g(hw, port, PHY_REG_TIMETUS_L, incval); if (err) - return err; + goto err_ptp_unlock; err = ice_ptp_one_port_cmd(hw, port, ICE_PTP_INIT_INCVAL); if (err) - return err; + goto err_ptp_unlock; ice_ptp_exec_tmr_cmd(hw); + ice_ptp_unlock(hw); + err = ice_sync_phy_timer_eth56g(hw, port); if (err) return err; @@ -2166,6 +2173,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port) ice_debug(hw, ICE_DBG_PTP, "Enabled clock on PHY port %u\n", port); return 0; + +err_ptp_unlock: + ice_ptp_unlock(hw); + return err; } /** -- cgit v1.2.3 From 7b28523546c7e4adbb8436f2986efcfc8382985e Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Fri, 15 May 2026 11:24:12 -0700 Subject: ice: ptp: use primary NAC semaphore on E825 For E825 2xNAC configurations, PTP semaphore operations must hit the primary NAC register block so both sides coordinate on the same lock. Commit e2193f9f9ec9 ("ice: enable timesync operation on 2xNAC E825 devices") updated other primary-only PTP register accesses to use the primary NAC on non-primary functions, but left ice_ptp_lock() and ice_ptp_unlock() operating on the local NAC. As a result, secondary NAC PTP paths can take a different semaphore than the primary side. Select the primary hardware in ice_ptp_lock() and ice_ptp_unlock() when the current function is not primary, keeping semaphore operations symmetric and consistent with the rest of the 2xNAC PTP register access path. Fixes: e2193f9f9ec9 ("ice: enable timesync operation on 2xNAC E825 devices") Reviewed-by: Arkadiusz Kubalewski Signed-off-by: Grzegorz Nitka Reviewed-by: Aleksandr Loktionov Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-6-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c index 8bb94e785f2a..2c18e16fe053 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c @@ -5264,9 +5264,13 @@ static void ice_ptp_init_phy_e830(struct ice_ptp_hw *ptp) */ bool ice_ptp_lock(struct ice_hw *hw) { + struct ice_pf *pf = container_of(hw, struct ice_pf, hw); u32 hw_lock; int i; + if (!ice_is_primary(hw)) + hw = ice_get_primary_hw(pf); + #define MAX_TRIES 15 for (i = 0; i < MAX_TRIES; i++) { @@ -5293,6 +5297,11 @@ bool ice_ptp_lock(struct ice_hw *hw) */ void ice_ptp_unlock(struct ice_hw *hw) { + struct ice_pf *pf = container_of(hw, struct ice_pf, hw); + + if (!ice_is_primary(hw)) + hw = ice_get_primary_hw(pf); + wr32(hw, PFTSYN_SEM + (PFTSYN_SEM_BYTES * hw->pf_id), 0); } -- cgit v1.2.3 From 975b564d195b13ca6ee1ef5e6a9561734898eb17 Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Fri, 15 May 2026 11:24:13 -0700 Subject: ice: restore PTP Rx timestamp config after ethtool set-channels When ethtool -L changes queue counts, ice_vsi_recfg_qs() closes and rebuilds the VSI, reallocating Rx rings. The newly allocated rings have ptp_rx cleared, so RX hardware timestamps are no longer attached to skb until hwtstamp configuration is applied again. Restore timestamp mode after ice_vsi_open() in the queue reconfiguration path, matching reset/rebuild behavior and ensuring newly rebuilt Rx rings have PTP RX timestamping re-enabled. Testing hints: - run ptp4l application in client synchronization mode: ptp4l -i ethX -m -s - run PTP traffic - change queue number on ethX netdev interface: ethtool -L ethX combined new_queue_size - observe ptp4l output - expected result: no "received DELAY_REQ without timestamp" messages Fixes: 77a781155a65 ("ice: enable receive hardware timestamping") Cc: stable@vger.kernel.org Reviewed-by: Aleksandr Loktionov Signed-off-by: Grzegorz Nitka Reviewed-by: Simon Horman Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-7-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ice/ice_main.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c index 66642232b282..e2fbe111f849 100644 --- a/drivers/net/ethernet/intel/ice/ice_main.c +++ b/drivers/net/ethernet/intel/ice/ice_main.c @@ -4104,6 +4104,12 @@ int ice_vsi_recfg_qs(struct ice_vsi *vsi, int new_rx, int new_tx, bool locked) } ice_pf_dcb_recfg(pf, locked); ice_vsi_open(vsi); + /* Rx rings are reallocated during VSI rebuild and lose their ptp_rx + * flag. Restore timestamp mode so newly allocated rings are set up + * for hardware Rx timestamping. + */ + if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags)) + ice_ptp_restore_timestamp_mode(pf); goto done; rebuild_err: -- cgit v1.2.3 From 5d49b568c188dc77199d8d2b959c91da8cc27cf1 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Fri, 15 May 2026 11:24:14 -0700 Subject: ixgbevf: fix use-after-free in VEPA multicast source pruning ixgbevf_clean_rx_irq() prunes frames whose source MAC matches the VF's own address (VEPA multicast workaround) by freeing the skb and continuing to the next descriptor: dev_kfree_skb_irq(skb); continue; The skb pointer is declared outside the while loop and persists across iterations. Because the continue skips the "skb = NULL" reset at the bottom of the loop, the next iteration enters the "else if (skb)" path and calls ixgbevf_add_rx_frag() on the freed skb, dereferencing skb_shinfo(skb)->nr_frags - a use-after-free in NAPI softirq context. The sibling driver iavf already handles this correctly by nulling the pointer before continuing. Apply the same pattern here. I do not have ixgbevf hardware; the bug was found by static analysis (scan_drop_continue_loops.py + semgrep drop_continue_in_loop, multi-tool corroboration with the highest score in the scan). The UAF was confirmed under KASAN by loading a test module that reproduces the exact code pattern (alloc skb, kfree_skb, then read skb_shinfo(skb)->nr_frags): BUG: KASAN: slab-use-after-free in ixgbevf_uaf_test_init+0x100/0x1000 Read of size 8 at addr 000000006163ae78 by task insmod/30 freed 208-byte region [000000006163adc0, 000000006163ae90) QEMU emulates igb (82576) but not ixgbe (82599), and the igbvf VF driver does not include the VEPA source pruning path, so a full end-to-end reproduction with emulated hardware was not possible. Fixes: bad17234ba70 ("ixgbevf: Change receive model to use double buffered page based receives") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Reviewed-by: Simon Horman Tested-by: Rafal Romanowski Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-8-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c b/drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c index 42f89a179a3f..4ba3be961ab6 100644 --- a/drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c +++ b/drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c @@ -1221,6 +1221,7 @@ static int ixgbevf_clean_rx_irq(struct ixgbevf_q_vector *q_vector, ether_addr_equal(rx_ring->netdev->dev_addr, eth_hdr(skb)->h_source)) { dev_kfree_skb_irq(skb); + skb = NULL; continue; } -- cgit v1.2.3 From 5acc641e590e008caaed480ed9ffae47cf7ecbdf Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Fri, 15 May 2026 11:24:15 -0700 Subject: igc: set tx buffer type for SMD frames Sashiko pointed out that igc_fpe_init_smd_frame() initializes igc_tx_buffer fields for an SMD skb, but does not set the buffer type: https://sashiko.dev/#/patchset/20260415025226.114115-1-kohei%40enjuk.jp Since igc_tx_buffer entries are reused, a stale XDP or XSK type can remain and make TX completion use the wrong cleanup path. Set the buffer type to IGC_TX_BUFFER_TYPE_SKB. Fixes: 5422570c0010 ("igc: add support for frame preemption verification") Signed-off-by: Kohei Enju Reviewed-by: Aleksandr Loktionov Reviewed-by: Simon Horman Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-9-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_tsn.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 8a110145bfee..725ba253165c 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -34,6 +34,7 @@ static int igc_fpe_init_smd_frame(struct igc_ring *ring, return -ENOMEM; } + buffer->type = IGC_TX_BUFFER_TYPE_SKB; buffer->skb = skb; buffer->protocol = 0; buffer->bytecount = skb->len; -- cgit v1.2.3 From e935c37b8a94bb256fada6395a5d05e1c0c6bdaf Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Fri, 15 May 2026 11:24:16 -0700 Subject: igc: fix potential skb leak in igc_fpe_xmit_smd_frame() When igc_fpe_init_tx_descriptor() fails, no one takes care of an allocated skb, leaking it. [1] Use dev_kfree_skb_any() on failure. Tested on an I226 adapter with the following command, while injecting faults in igc_fpe_init_tx_descriptor() to trigger the error path. # ethtool --set-mm $DEV verify-enabled on tx-enabled on pmac-enabled on [1] unreferenced object 0xffff888113c6cdc0 (size 224): ... backtrace (crc be3d3fda): kmem_cache_alloc_node_noprof+0x3b1/0x410 __alloc_skb+0xde/0x830 igc_fpe_xmit_smd_frame.isra.0+0xad/0x1b0 igc_fpe_send_mpacket+0x37/0x90 ethtool_mmsv_verify_timer+0x15e/0x300 Cc: stable@vger.kernel.org Fixes: 5422570c0010 ("igc: add support for frame preemption verification") Signed-off-by: Kohei Enju Reviewed-by: Simon Horman Reviewed-by: Faizal Rahim Tested-by: Avigail Dahan Signed-off-by: Tony Nguyen Link: https://patch.msgid.link/20260515182419.1597859-10-anthony.l.nguyen@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/igc/igc_tsn.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c index 725ba253165c..52de2bcbadbe 100644 --- a/drivers/net/ethernet/intel/igc/igc_tsn.c +++ b/drivers/net/ethernet/intel/igc/igc_tsn.c @@ -110,10 +110,16 @@ static int igc_fpe_xmit_smd_frame(struct igc_adapter *adapter, __netif_tx_lock(nq, cpu); err = igc_fpe_init_tx_descriptor(ring, skb, type); - igc_flush_tx_descriptors(ring); + if (err) + goto err_free_skb_any; + igc_flush_tx_descriptors(ring); __netif_tx_unlock(nq); + return 0; +err_free_skb_any: + __netif_tx_unlock(nq); + dev_kfree_skb_any(skb); return err; } -- cgit v1.2.3 From 86ed2d96db1965e9008e919b1936145ae66540e3 Mon Sep 17 00:00:00 2001 From: Chaitanya Kumar Borah Date: Mon, 11 May 2026 11:02:10 +0530 Subject: drm/i915/display: Copy color pipeline from plane in the primary joiner pipe When copying plane color state in a joiner configuration, use the plane in the primary joiner pipe since it carries the pipeline number selected by the user-space. This assumes that all pipes in the joiner are symmetric in their plane color capabilities. Cc: stable@vger.kernel.org # v6.19+ Fixes: a78f1b6baf4d ("drm/i915/color: Add framework to program CSC") Tested-by: Vidya Srinivas Signed-off-by: Chaitanya Kumar Borah Reviewed-by: Uma Shankar Signed-off-by: Ankit Nautiyal Link: https://patch.msgid.link/20260511053213.3122314-2-chaitanya.kumar.borah@intel.com (cherry picked from commit e8308fb5e05ca08ddfb8b46f6d947a6e3fd80cd7) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_plane.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_plane.c b/drivers/gpu/drm/i915/display/intel_plane.c index 5390ceb21ca4..82f445c83158 100644 --- a/drivers/gpu/drm/i915/display/intel_plane.c +++ b/drivers/gpu/drm/i915/display/intel_plane.c @@ -373,7 +373,7 @@ intel_plane_color_copy_uapi_to_hw_state(struct intel_plane_state *plane_state, bool changed = false; int i = 0; - iter_colorop = plane_state->uapi.color_pipeline; + iter_colorop = from_plane_state->uapi.color_pipeline; while (iter_colorop) { for_each_new_colorop_in_state(state, colorop, new_colorop_state, i) { -- cgit v1.2.3 From f87abd0c6604fb6cc31cc86fc7ccc6a576924352 Mon Sep 17 00:00:00 2001 From: Ankit Nautiyal Date: Mon, 11 May 2026 18:02:15 +0530 Subject: drm/i915/dp: Fix readback for target_rr in Adaptive Sync SDP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct the bit-shift logic to properly readback the 10 bit target_rr from DB3 and DB4. v2: Align the style with readback for vtotal. (Ville) Fixes: 12ea89291603 ("drm/i915/dp: Add Read/Write support for Adaptive Sync SDP") Cc: Mitul Golani Cc: Ankit Nautiyal Signed-off-by: Ankit Nautiyal Reviewed-by: Ville Syrjälä Link: https://patch.msgid.link/20260511123218.1589830-2-ankit.k.nautiyal@intel.com (cherry picked from commit f7abc4af2b19240a145a221461dfe756cc01d74a) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_dp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 50d34b4fdf82..6ef2a0043cda 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -5303,7 +5303,7 @@ int intel_dp_as_sdp_unpack(struct drm_dp_as_sdp *as_sdp, as_sdp->length = sdp->sdp_header.HB3 & DP_ADAPTIVE_SYNC_SDP_LENGTH; as_sdp->mode = sdp->db[0] & DP_ADAPTIVE_SYNC_SDP_OPERATION_MODE; as_sdp->vtotal = (sdp->db[2] << 8) | sdp->db[1]; - as_sdp->target_rr = (u64)sdp->db[3] | ((u64)sdp->db[4] & 0x3); + as_sdp->target_rr = ((sdp->db[4] & 0x3) << 8) | sdp->db[3]; as_sdp->target_rr_divider = sdp->db[4] & 0x20 ? true : false; return 0; -- cgit v1.2.3 From fbceb39b536e40c2f7cc47ab42037bb7c2b7ced9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20H=C3=B6gander?= Date: Fri, 15 May 2026 12:57:53 +0300 Subject: drm/i915/psr: Add defininitions for INTEL_WA_REGISTER_CAPS DPCD register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EDP specification says: "If either VSC SDP is unable to be transmitted 100 ns before the SU region, the Source device may optionally transmit the VSC SDP during the prior video scan line’s HBlank period There is a Intel specific drm dp register currently containing bits related how TCON can support PSR2 with SDP on prior line." Unfortunately many panels are having problems in implementing this. So there is a custom Intel specific DPCD register (INTEL_WA_REGISTER_CAPS) to figure out if this is properly implemented on a panel or if panel doesn't require that 100 ns delay before the SU region. Here are the definitions in this custom DPCD address: 0 = Panel doesn't support SDP on prior line 1 = Panel supports SDP on prior line 2 = Panel doesn't have 100ns requirement 3 = Reserved Add definitions for this new register and it's values into new header intel_dpcd.h. v2: add INTEL_DPCD_ prefix to definitions Bspec: 74741 Signed-off-by: Jouni Högander Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260515095756.2799483-2-jouni.hogander@intel.com (cherry picked from commit 1da1c9294825f08f622c473480d185680c2a3b75) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_dpcd.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.h (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_dpcd.h b/drivers/gpu/drm/i915/display/intel_dpcd.h new file mode 100644 index 000000000000..4aea5326f2ed --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpcd.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2026 Intel Corporation + */ + +#ifndef __INTEL_DPCD_H__ +#define __INTEL_DPCD_H__ + +#define INTEL_DPCD_INTEL_WA_REGISTER_CAPS 0x3f0 +# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK REG_GENMASK(1, 0) +# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1 0 +# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE 1 +# define INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE 2 + +#endif /* __INTEL_DPCD_H__ */ -- cgit v1.2.3 From f30bece421a4ae34359254e1dc2a187a42b6af9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20H=C3=B6gander?= Date: Fri, 15 May 2026 12:57:54 +0300 Subject: drm/i915/psr: Read Intel DPCD workaround register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read Intel DPCD workaround register and store it into intel_connector->dp.psr_caps. psr_caps was chosen as currently it contains only PSR workaround for PSR2 SDP on prior scanline implementation. Signed-off-by: Jouni Högander Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260515095756.2799483-3-jouni.hogander@intel.com (cherry picked from commit c48ff24d0f4ab7ad696b2d35ad64ce7e049c668c) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_display_types.h | 1 + drivers/gpu/drm/i915/display/intel_psr.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index f6cd0a062090..9c7c357afb09 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -584,6 +584,7 @@ struct intel_connector { struct { u8 dpcd[EDP_PSR_RECEIVER_CAP_SIZE]; + u8 intel_wa_dpcd; bool support; bool su_support; diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 53c10ae76ab5..82eac4048382 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -43,6 +43,7 @@ #include "intel_display_wa.h" #include "intel_dmc.h" #include "intel_dp.h" +#include "intel_dpcd.h" #include "intel_dp_aux.h" #include "intel_dsb.h" #include "intel_frontbuffer.h" @@ -716,8 +717,14 @@ static void _psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *co connector->dp.psr_caps.su_support ? "" : "not "); } - if (connector->dp.psr_caps.su_support) + if (connector->dp.psr_caps.su_support) { + ret = drm_dp_dpcd_read_byte(&intel_dp->aux, + INTEL_DPCD_INTEL_WA_REGISTER_CAPS, + &connector->dp.psr_caps.intel_wa_dpcd); + if (ret < 0) + return; _psr_compute_su_granularity(intel_dp, connector); + } } void intel_psr_init_dpcd(struct intel_dp *intel_dp, struct intel_connector *connector) -- cgit v1.2.3 From 4703049f768fc1c1caac754134118bee1a3af189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20H=C3=B6gander?= Date: Fri, 15 May 2026 12:57:55 +0300 Subject: drm/i915/psr: Apply Intel DPCD workaround when SDP on prior line used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is Intel specific workaround DPCD address containing workaround for case where SDP is on prior line. Apply this workaround according to values in the offset. Fixes: 61e887329e33 ("drm/i915/xelpd: Handle PSR2 SDP indication in the prior scanline") Cc: # v5.15+ Signed-off-by: Jouni Högander Reviewed-by: Suraj Kandpal Link: https://patch.msgid.link/20260515095756.2799483-4-jouni.hogander@intel.com (cherry picked from commit c3fe899fbeac86ea4a5ca9dd845b2cbc0da46249) Signed-off-by: Tvrtko Ursulin --- drivers/gpu/drm/i915/display/intel_psr.c | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 82eac4048382..29904a037575 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -1365,9 +1365,35 @@ static bool psr2_granularity_check(struct intel_crtc_state *crtc_state, return true; } -static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_dp, - struct intel_crtc_state *crtc_state) +static bool apply_scanline_indication_wa(struct intel_crtc_state *crtc_state, + struct intel_connector *connector) { + struct intel_dp *intel_dp = intel_attached_dp(connector); + u8 early_scanline_support = connector->dp.psr_caps.intel_wa_dpcd & + INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_EARLYSCANLINE_SDP_SUPPORT_MASK; + + if (intel_dp->edp_dpcd[0] >= DP_EDP_15) + return true; + + switch (early_scanline_support) { + case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_FALL_BACK_TO_PSR1: + crtc_state->req_psr2_sdp_prior_scanline = false; + return false; + case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITH_EARLY_SCANLINE: + return true; + case INTEL_DPCD_INTEL_WA_REGISTER_CAPS_PSR2_WITHOUT_EARLY_SCANLINE: + crtc_state->req_psr2_sdp_prior_scanline = false; + return true; + default: + MISSING_CASE(early_scanline_support); + return false; + } +} + +static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_crtc_state *crtc_state, + struct intel_connector *connector) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); struct intel_display *display = to_intel_display(intel_dp); const struct drm_display_mode *adjusted_mode = &crtc_state->uapi.adjusted_mode; u32 hblank_total, hblank_ns, req_ns; @@ -1386,7 +1412,8 @@ static bool _compute_psr2_sdp_prior_scanline_indication(struct intel_dp *intel_d return false; crtc_state->req_psr2_sdp_prior_scanline = true; - return true; + + return apply_scanline_indication_wa(crtc_state, connector); } static int intel_psr_entry_setup_frames(struct intel_dp *intel_dp, @@ -1667,7 +1694,7 @@ static bool intel_sel_update_config_valid(struct intel_crtc_state *crtc_state, conn_state)) goto unsupported; - if (!_compute_psr2_sdp_prior_scanline_indication(intel_dp, crtc_state)) { + if (!_compute_psr2_sdp_prior_scanline_indication(crtc_state, connector)) { drm_dbg_kms(display->drm, "Selective update not enabled, SDP indication do not fit in hblank\n"); goto unsupported; -- cgit v1.2.3 From 73d01051e8040c0b1de7fd26b3b8d0c2ffa6895c Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 30 Apr 2026 21:49:42 +0200 Subject: drm/bridge: chipone-icn6211: use devm_drm_bridge_add in i2c probe Use devm_drm_bridge_add() so the bridge is released if probe fails after registration, and drop drm_bridge_remove() in chipone_i2c_probe. Signed-off-by: Osama Abdelkader Fixes: 8dde6f7452a1 ("drm: bridge: icn6211: Add I2C configuration support") Cc: stable@vger.kernel.org Reviewed-by: Luca Ceresoli Link: https://patch.msgid.link/20260430194944.78119-1-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/chipone-icn6211.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index 814713c5bea9..553a1df4688d 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -758,7 +758,9 @@ static int chipone_i2c_probe(struct i2c_client *client) dev_set_drvdata(dev, icn); i2c_set_clientdata(client, icn); - drm_bridge_add(&icn->bridge); + ret = devm_drm_bridge_add(dev, &icn->bridge); + if (ret) + return ret; return chipone_dsi_host_attach(icn); } -- cgit v1.2.3 From d45d5c819f2cd0b6b5d76a194a537a5f4aeefecb Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Thu, 30 Apr 2026 21:56:59 +0200 Subject: drm/bridge: megachips: remove bridge when irq request fails If devm_request_threaded_irq() fails after drm_bridge_add(), remove the bridge before returning. Keep drm_bridge_add() rather than devm_drm_bridge_add(): registration is tied to the STDP4028 device while ge_b850v3_register() may complete from either I2C probe; devm would not unwind the bridge if the other client's probe fails. Signed-off-by: Osama Abdelkader Fixes: fcfa0ddc18ed ("drm/bridge: Drivers for megachips-stdpxxxx-ge-b850v3-fw (LVDS-DP++)") Cc: stable@vger.kernel.org Reviewed-by: Luca Ceresoli Tested-by: Ian Ray Link: https://patch.msgid.link/20260430195700.80317-1-osama.abdelkader@gmail.com Signed-off-by: Luca Ceresoli --- drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c index c9e6505cbd88..2d02cc69f237 100644 --- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c +++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c @@ -251,7 +251,6 @@ static void ge_b850v3_lvds_remove(void) goto out; drm_bridge_remove(&ge_b850v3_lvds_ptr->bridge); - ge_b850v3_lvds_ptr = NULL; out: mutex_unlock(&ge_b850v3_lvds_dev_mutex); @@ -261,6 +260,7 @@ static int ge_b850v3_register(void) { struct i2c_client *stdp4028_i2c = ge_b850v3_lvds_ptr->stdp4028_i2c; struct device *dev = &stdp4028_i2c->dev; + int ret; /* drm bridge initialization */ ge_b850v3_lvds_ptr->bridge.ops = DRM_BRIDGE_OP_DETECT | @@ -277,11 +277,15 @@ static int ge_b850v3_register(void) if (!stdp4028_i2c->irq) return 0; - return devm_request_threaded_irq(&stdp4028_i2c->dev, - stdp4028_i2c->irq, NULL, - ge_b850v3_lvds_irq_handler, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, - "ge-b850v3-lvds-dp", ge_b850v3_lvds_ptr); + ret = devm_request_threaded_irq(&stdp4028_i2c->dev, + stdp4028_i2c->irq, NULL, + ge_b850v3_lvds_irq_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ge-b850v3-lvds-dp", ge_b850v3_lvds_ptr); + if (ret) + drm_bridge_remove(&ge_b850v3_lvds_ptr->bridge); + + return ret; } static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c) -- cgit v1.2.3 From ea17fc4d7dc2ba6459b1a318962960520201baf1 Mon Sep 17 00:00:00 2001 From: Xiangxu Yin Date: Fri, 27 Feb 2026 20:15:01 +0800 Subject: phy: qcom: qmp-usbc: Fix out-of-bounds array access in dp swing config swing_tbl and pre_emphasis_tbl are 4x4 arrays (valid indices 0-3), but the boundary check uses "> 4" instead of ">= 4", allowing index 4 to cause an out-of-bounds access. Reported-by: Dan Carpenter Fixes: 81791c45c8e0 ("phy: qcom: qmp-usbc: Add QCS615 USB/DP PHY config and DP mode support") Signed-off-by: Xiangxu Yin Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260227-master-v1-1-8d91b9407fdb@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/phy/qualcomm/phy-qcom-qmp-usbc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c index c342479a3798..dff27d30fc99 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-usbc.c @@ -794,7 +794,7 @@ static int qmp_v2_configure_dp_swing(struct qmp_usbc *qmp) p_level = max(p_level, dp_opts->pre[i]); } - if (v_level > 4 || p_level > 4) { + if (v_level >= 4 || p_level >= 4) { dev_err(qmp->dev, "Invalid v(%d) | p(%d) level)\n", v_level, p_level); return -EINVAL; -- cgit v1.2.3 From e824e40d0e841fab66ab7897d6c7b14dc81c66a7 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 14 May 2026 15:04:21 +0100 Subject: net: dsa: mt7530: fix FDB entries not aging out with short timeout The DSA forwarding selftests bridge_vlan_aware.sh and bridge_vlan_unaware.sh configure the bridge with ageing_time set to LOW_AGEING_TIME (1000 centiseconds, i.e. 10 seconds) and then run learning_test() in lib.sh, which expects a learned FDB entry to be removed after ageing_time + 10 seconds. On MT7530/MT7531 the entry persisted past the deadline and the "Found FDB record when should not" assertion failed. With msecs=10000, the algorithm in mt7530_set_ageing_time() finds AGE_CNT=0 and AGE_UNIT=9 as the first exact match (starting the search from tmp_age_count=0). The per-entry aging counter is initialized to AGE_CNT when a MAC address is learned, so with AGE_CNT=0 new entries start with a counter value of 0, which the hardware treats as "already aged" and never removes, effectively disabling aging. Fix this by starting the search from tmp_age_count=1 to ensure entries always have a non-zero initial aging counter. For a 10-second ageing time this yields AGE_CNT=1 and AGE_UNIT=4 instead: the timer ticks every 5 seconds and entries are removed after 2 ticks. Starting the search at AGE_CNT=1 raises the minimum representable ageing time from 1 to 2 seconds. Without bounds, a stale ageing_time of 1 second would now make the loop fall through without setting age_count and age_unit, leaving them uninitialized when written to the MT7530_AAC hardware register. Set ds->ageing_time_min and ds->ageing_time_max so the DSA core validates the range before the callback is invoked, and drop the now-redundant range check from mt7530_set_ageing_time(). Fixes: ea6d5c924e39 ("net: dsa: mt7530: support setting ageing time") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/7788ded12dc07b1bce329ec35fa70f4b45f3f9b7.1778766629.git.daniel@makrotopia.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 44d670904ad8..cd311dfd3600 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -1023,12 +1023,16 @@ mt7530_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) unsigned int age_count; unsigned int age_unit; - /* Applied timer is (AGE_CNT + 1) * (AGE_UNIT + 1) seconds */ - if (secs < 1 || secs > (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1)) - return -ERANGE; - - /* iterate through all possible age_count to find the closest pair */ - for (tmp_age_count = 0; tmp_age_count <= AGE_CNT_MAX; ++tmp_age_count) { + /* Applied timer is (AGE_CNT + 1) * (AGE_UNIT + 1) seconds. + * The DSA core has already validated the range using + * ds->ageing_time_min and ds->ageing_time_max. + * + * Iterate through all possible age_count values to find the closest + * pair. Start from 1 because the per-entry aging counter is + * initialized to AGE_CNT and a value of 0 means the entry will + * never be aged out. + */ + for (tmp_age_count = 1; tmp_age_count <= AGE_CNT_MAX; ++tmp_age_count) { unsigned int tmp_age_unit = secs / (tmp_age_count + 1) - 1; if (tmp_age_unit <= AGE_UNIT_MAX) { @@ -2428,6 +2432,8 @@ mt7530_setup(struct dsa_switch *ds) ds->assisted_learning_on_cpu_port = true; ds->mtu_enforcement_ingress = true; + ds->ageing_time_min = 2 * 1000; + ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; if (priv->id == ID_MT7530) { regulator_set_voltage(priv->core_pwr, 1000000, 1000000); @@ -2617,6 +2623,8 @@ mt7531_setup_common(struct dsa_switch *ds) ds->assisted_learning_on_cpu_port = true; ds->mtu_enforcement_ingress = true; + ds->ageing_time_min = 2 * 1000; + ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; mt753x_trap_frames(priv); -- cgit v1.2.3 From 3ac85bcfd404b588298c95c6fba8aad4ad334f57 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 14 May 2026 15:04:35 +0100 Subject: net: dsa: mt7530: preserve VLAN tags on trapped link-local frames The BPC, RGAC1 and RGAC2 registers control the handling of link-local frames with reserved MAC DAs (01:80:C2:00:00:0x). These frames are correctly trapped to the CPU port, but the egress VLAN tag attribute was set to MT7530_VLAN_EG_UNTAGGED which causes the switch to strip any VLAN tags from trapped frames before they reach the CPU. This causes VLAN-tagged link-local frames (STP BPDUs, LLDP, PTP Peer Delay Requests) to arrive at the CPU without their VLAN tag, so they are delivered to the base network interface instead of the VLAN sub-interface. The DSA local_termination selftest confirms this: all link-local protocol tests on VLAN upper interfaces fail. Set the EG_TAG attribute to MT7530_VLAN_EG_DISABLED (system default) so that the switch does not modify VLAN tags in trapped frames. This way VLAN-tagged frames retain their original tag and are delivered to the correct VLAN sub-interface, matching the behavior of non-trapped frames which pass through without VLAN tag modification. Fixes: 69ddba9d170b ("net: dsa: mt7530: fix handling of all link-local frames") Signed-off-by: Daniel Golle Acked-by: Chester A. Unal Link: https://patch.msgid.link/891e0cd34db2a5fe20ceb73283a81fb5f71427ca.1778766629.git.daniel@makrotopia.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index cd311dfd3600..4f657ef6aa65 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -1300,37 +1300,40 @@ static void mt7530_setup_port5(struct dsa_switch *ds, phy_interface_t interface) static void mt753x_trap_frames(struct mt7530_priv *priv) { - /* Trap 802.1X PAE frames and BPDUs to the CPU port(s) and egress them - * VLAN-untagged. + /* Trap 802.1X PAE frames and BPDUs to the CPU port(s) and egress + * them with the EG_TAG attribute set to disabled (system default) + * so that any VLAN tags in the frame are not modified by the + * switch egress VLAN tag processing. This preserves VLAN tags + * for reception on VLAN sub-interfaces. */ mt7530_rmw(priv, MT753X_BPC, PAE_BPDU_FR | PAE_EG_TAG_MASK | PAE_PORT_FW_MASK | BPDU_EG_TAG_MASK | BPDU_PORT_FW_MASK, - PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_DISABLED) | PAE_PORT_FW(TO_CPU_FW_CPU_ONLY) | - BPDU_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + BPDU_EG_TAG(MT7530_VLAN_EG_DISABLED) | TO_CPU_FW_CPU_ONLY); - /* Trap frames with :01 and :02 MAC DAs to the CPU port(s) and egress - * them VLAN-untagged. + /* Trap frames with :01 and :02 MAC DAs to the CPU port(s) and + * egress them with EG_TAG disabled. */ mt7530_rmw(priv, MT753X_RGAC1, R02_BPDU_FR | R02_EG_TAG_MASK | R02_PORT_FW_MASK | R01_BPDU_FR | R01_EG_TAG_MASK | R01_PORT_FW_MASK, - R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + R02_BPDU_FR | R02_EG_TAG(MT7530_VLAN_EG_DISABLED) | R02_PORT_FW(TO_CPU_FW_CPU_ONLY) | R01_BPDU_FR | - R01_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + R01_EG_TAG(MT7530_VLAN_EG_DISABLED) | TO_CPU_FW_CPU_ONLY); - /* Trap frames with :03 and :0E MAC DAs to the CPU port(s) and egress - * them VLAN-untagged. + /* Trap frames with :03 and :0E MAC DAs to the CPU port(s) and + * egress them with EG_TAG disabled. */ mt7530_rmw(priv, MT753X_RGAC2, R0E_BPDU_FR | R0E_EG_TAG_MASK | R0E_PORT_FW_MASK | R03_BPDU_FR | R03_EG_TAG_MASK | R03_PORT_FW_MASK, - R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + R0E_BPDU_FR | R0E_EG_TAG(MT7530_VLAN_EG_DISABLED) | R0E_PORT_FW(TO_CPU_FW_CPU_ONLY) | R03_BPDU_FR | - R03_EG_TAG(MT7530_VLAN_EG_UNTAGGED) | + R03_EG_TAG(MT7530_VLAN_EG_DISABLED) | TO_CPU_FW_CPU_ONLY); } -- cgit v1.2.3 From 2c4c76cacc9d5553f4c3342eb332d7123a4c3f14 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Thu, 14 May 2026 15:04:50 +0100 Subject: net: dsa: mt7530: fix CPU port VLAN not being reset to unaware After a VLAN-aware bridge is destroyed, creating any VLAN-unaware bridge loses all connectivity. The VID 0 VLAN table entry used by VLAN-unaware ports in FALLBACK mode gets corrupted during VLAN-aware operation: mt7530_hw_vlan_add() overwrites its EG_CON flag with VTAG_EN and bridge teardown removes ports from its PORT_MEM. The cleanup code that should restore it never runs because the current port's dp->vlan_filtering flag is still true when checked (DSA updates it only after the driver callback returns). Even when restored, the deferred VLAN deletion events from the switchdev workqueue can corrupt VID 0 again after the restoration. Skip the current port in the all_user_ports_removed check, call mt7530_setup_vlan0() to restore the VID 0 entry, and protect VID 0 from being modified by bridge VLAN operations in port_vlan_add and port_vlan_del since it is managed exclusively by mt7530_setup_vlan0(). Remove the CPU port PCR and PVC register writes which were clobbering PORT_VLAN mode and VLAN_ATTR with wrong values. Fixes: 83163f7dca56 ("net: dsa: mediatek: add VLAN support for MT7530") Signed-off-by: Daniel Golle Link: https://patch.msgid.link/da8bdaf08b2427a9057e6cb33e26d41f8a8d5000.1778766629.git.daniel@makrotopia.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 111 ++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 49 deletions(-) (limited to 'drivers') diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 4f657ef6aa65..752ba92b0851 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -1623,6 +1623,49 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port, return 0; } +static int +mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) +{ + struct mt7530_dummy_poll p; + u32 val; + int ret; + + val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; + mt7530_write(priv, MT7530_VTCR, val); + + INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR); + ret = readx_poll_timeout(_mt7530_read, &p, val, + !(val & VTCR_BUSY), 20, 20000); + if (ret < 0) { + dev_err(priv->dev, "poll timeout\n"); + return ret; + } + + val = mt7530_read(priv, MT7530_VTCR); + if (val & VTCR_INVALID) { + dev_err(priv->dev, "read VTCR invalid\n"); + return -EINVAL; + } + + return 0; +} + +static int +mt7530_setup_vlan0(struct mt7530_priv *priv) +{ + u32 val; + + /* Validate the entry with independent learning, keep the original + * ingress tag attribute. + */ + val = IVL_MAC | EG_CON | PORT_MEM(MT7530_ALL_MEMBERS) | FID(FID_BRIDGED) | + VLAN_VALID; + mt7530_write(priv, MT7530_VAWD1, val); + mt7530_write(priv, MT7530_VAWD2, 0); + + return mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, 0); +} + static void mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) { @@ -1648,6 +1691,8 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) G0_PORT_VID_DEF); for (i = 0; i < priv->ds->num_ports; i++) { + if (i == port) + continue; if (dsa_is_user_port(ds, i) && dsa_port_is_vlan_filtering(dsa_to_port(ds, i))) { all_user_ports_removed = false; @@ -1659,13 +1704,9 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) * the CPU port get out of VLAN filtering mode. */ if (all_user_ports_removed) { - struct dsa_port *dp = dsa_to_port(ds, port); - struct dsa_port *cpu_dp = dp->cpu_dp; - - mt7530_write(priv, MT7530_PCR_P(cpu_dp->index), - PCR_MATRIX(dsa_user_ports(priv->ds))); - mt7530_write(priv, MT7530_PVC_P(cpu_dp->index), PORT_SPEC_TAG - | PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT)); + mutex_lock(&priv->reg_mutex); + mt7530_setup_vlan0(priv); + mutex_unlock(&priv->reg_mutex); } } @@ -1853,33 +1894,6 @@ mt7530_port_mdb_del(struct dsa_switch *ds, int port, return ret; } -static int -mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid) -{ - struct mt7530_dummy_poll p; - u32 val; - int ret; - - val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; - mt7530_write(priv, MT7530_VTCR, val); - - INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR); - ret = readx_poll_timeout(_mt7530_read, &p, val, - !(val & VTCR_BUSY), 20, 20000); - if (ret < 0) { - dev_err(priv->dev, "poll timeout\n"); - return ret; - } - - val = mt7530_read(priv, MT7530_VTCR); - if (val & VTCR_INVALID) { - dev_err(priv->dev, "read VTCR invalid\n"); - return -EINVAL; - } - - return 0; -} - static int mt7530_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct netlink_ext_ack *extack) @@ -1984,21 +1998,6 @@ mt7530_hw_vlan_update(struct mt7530_priv *priv, u16 vid, mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, vid); } -static int -mt7530_setup_vlan0(struct mt7530_priv *priv) -{ - u32 val; - - /* Validate the entry with independent learning, keep the original - * ingress tag attribute. - */ - val = IVL_MAC | EG_CON | PORT_MEM(MT7530_ALL_MEMBERS) | FID(FID_BRIDGED) | - VLAN_VALID; - mt7530_write(priv, MT7530_VAWD1, val); - - return mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, 0); -} - static int mt7530_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, @@ -2011,9 +2010,18 @@ mt7530_port_vlan_add(struct dsa_switch *ds, int port, mutex_lock(&priv->reg_mutex); + /* VID 0 is managed exclusively by mt7530_setup_vlan0() for + * VLAN-unaware bridge operation. Don't let the bridge overwrite + * its EG_CON flag with VTAG_EN and corrupt PORT_MEM. + */ + if (vlan->vid == 0) + goto skip_vlan_table; + mt7530_hw_vlan_entry_init(&new_entry, port, untagged); mt7530_hw_vlan_update(priv, vlan->vid, &new_entry, mt7530_hw_vlan_add); +skip_vlan_table: + if (pvid) { priv->ports[port].pvid = vlan->vid; @@ -2053,10 +2061,15 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port, mutex_lock(&priv->reg_mutex); + /* VID 0 is managed exclusively by mt7530_setup_vlan0(). */ + if (vlan->vid == 0) + goto skip_vlan_table; + mt7530_hw_vlan_entry_init(&target_entry, port, 0); mt7530_hw_vlan_update(priv, vlan->vid, &target_entry, mt7530_hw_vlan_del); +skip_vlan_table: /* PVID is being restored to the default whenever the PVID port * is being removed from the VLAN. */ -- cgit v1.2.3 From 4cb3cd670b2a29e52dd3cfd6463e44121674c9b8 Mon Sep 17 00:00:00 2001 From: Edward Parker Date: Thu, 14 May 2026 15:05:12 +0100 Subject: net: dsa: mt7530: untag VLAN-aware bridge PVID With bridge VLAN filtering enabled on a port configured as untagged member of the bridge PVID, ingress untagged frames do not reach the corresponding bridge VLAN upper interface (br-lan.). ARP and similar traffic is visible on the physical port but not delivered to the VLAN sub-interface. The MT7530/MT7531 forwards frames to the CPU port with the user port's PVID tag applied even when the frame ingressed untagged on the wire, because the CPU port is set to MT7530_VLAN_EG_CONSISTENT and is a tagged member of the VLAN entry created for the bridge VLAN. The DSA core then sees a hwaccel-tagged frame whose VID matches the port's PVID, which the bridge does not treat as the untagged-on-the-wire frame that the user expects. Set ds->untag_vlan_aware_bridge_pvid in the mt7530 and mt7531 setup paths so the DSA core strips that hwaccel tag in software when the parsed VID matches the bridge port's PVID, restoring the on-the-wire frame as the bridge expects to see it. Link: https://github.com/openwrt/openwrt/issues/18576 Fixes: 83163f7dca56 ("net: dsa: mediatek: add VLAN support for MT7530") Signed-off-by: Edward Parker [daniel@makrotopia.org: improve commit message] Signed-off-by: Daniel Golle Link: https://patch.msgid.link/85d25ea1b26d3c907f815649f2e0bde6560282a3.1778766629.git.daniel@makrotopia.org Signed-off-by: Paolo Abeni --- drivers/net/dsa/mt7530.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 752ba92b0851..3c2a3029b10c 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -2447,6 +2447,7 @@ mt7530_setup(struct dsa_switch *ds) } ds->assisted_learning_on_cpu_port = true; + ds->untag_vlan_aware_bridge_pvid = true; ds->mtu_enforcement_ingress = true; ds->ageing_time_min = 2 * 1000; ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; @@ -2638,6 +2639,7 @@ mt7531_setup_common(struct dsa_switch *ds) int ret, i; ds->assisted_learning_on_cpu_port = true; + ds->untag_vlan_aware_bridge_pvid = true; ds->mtu_enforcement_ingress = true; ds->ageing_time_min = 2 * 1000; ds->ageing_time_max = (AGE_CNT_MAX + 1) * (AGE_UNIT_MAX + 1) * 1000; -- cgit v1.2.3 From 023453cb7eb0f53c5dc36babed8e706c1b0b0187 Mon Sep 17 00:00:00 2001 From: Wenwen Wang Date: Sat, 5 May 2018 07:57:10 -0500 Subject: i2c: smbus: fix a potential uninitialization bug In i2c_smbus_xfer_emulated(), there are two buffers: msgbuf0 and msgbuf1, which are used to save a series of messages, as mentioned in the comment. According to the value of the variable 'size', msgbuf0 is initialized to various values. In contrast, msgbuf1 is left uninitialized until the function i2c_transfer() is invoked. However, msgbuf1 is not always initialized on all possible execution paths (implementation) of i2c_transfer(). Thus, it is possible that msgbuf1 may still be uninitialized even after the invocation of the function i2c_transfer(), especially when the return value of i2c_transfer() is not checked properly. In the following execution, the uninitialized msgbuf1 will be used, such as for security checks. Since uninitialized values can be random and arbitrary, this will cause undefined behaviors or even check bypass. For example, it is expected that if the value of 'size' is I2C_SMBUS_BLOCK_PROC_CALL, the value of data->block[0] should not be larger than I2C_SMBUS_BLOCK_MAX. This patch initializes the first byte of msgbuf1 with 0 to avoid such undefined behaviors or security issues. Signed-off-by: Wenwen Wang [wsa: reworded commit message a little] Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core-smbus.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c index ad6acb5ebadc..fa63bee0b345 100644 --- a/drivers/i2c/i2c-core-smbus.c +++ b/drivers/i2c/i2c-core-smbus.c @@ -353,6 +353,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, && size != I2C_SMBUS_I2C_BLOCK_DATA); msgbuf0[0] = command; + msgbuf1[0] = 0; switch (size) { case I2C_SMBUS_QUICK: msg[0].len = 0; -- cgit v1.2.3 From 35f0f0a2536a4d604b4dbad92c85c4a8fdebb870 Mon Sep 17 00:00:00 2001 From: Erni Sri Satya Vennela Date: Thu, 14 May 2026 12:41:51 -0700 Subject: net: mana: Fix TOCTOU double-fetch of hwc_msg_id from DMA buffer In mana_hwc_rx_event_handler(), resp->response.hwc_msg_id is read from DMA-coherent memory and bounds-checked, then mana_hwc_handle_resp() re-reads the same field from the same DMA buffer for test_bit() and pointer arithmetic. DMA-coherent memory is mapped uncacheable on x86 and is shared, unencrypted, in Confidential VMs (SEV-SNP/TDX), so each load goes directly to host-visible memory. A H/W can modify the value between the check and the use, bypassing the bounds validation. Fix this by reading hwc_msg_id exactly once using READ_ONCE() into a stack-local variable in mana_hwc_rx_event_handler(), and passing the validated value as a parameter to mana_hwc_handle_resp(). Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)") Signed-off-by: Erni Sri Satya Vennela Link: https://patch.msgid.link/20260514194156.466823-1-ernis@linux.microsoft.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/microsoft/mana/hw_channel.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c index dbbde0fa57e7..fd8b324d7fb6 100644 --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c @@ -77,21 +77,19 @@ static int mana_hwc_post_rx_wqe(const struct hwc_wq *hwc_rxq, } static void mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len, - struct hwc_work_request *rx_req) + struct hwc_work_request *rx_req, u16 msg_id) { const struct gdma_resp_hdr *resp_msg = rx_req->buf_va; struct hwc_caller_ctx *ctx; int err; - if (!test_bit(resp_msg->response.hwc_msg_id, - hwc->inflight_msg_res.map)) { - dev_err(hwc->dev, "hwc_rx: invalid msg_id = %u\n", - resp_msg->response.hwc_msg_id); + if (!test_bit(msg_id, hwc->inflight_msg_res.map)) { + dev_err(hwc->dev, "hwc_rx: invalid msg_id = %u\n", msg_id); mana_hwc_post_rx_wqe(hwc->rxq, rx_req); return; } - ctx = hwc->caller_ctx + resp_msg->response.hwc_msg_id; + ctx = hwc->caller_ctx + msg_id; err = mana_hwc_verify_resp_msg(ctx, resp_msg, resp_len); if (err) goto out; @@ -251,6 +249,7 @@ static void mana_hwc_rx_event_handler(void *ctx, u32 gdma_rxq_id, struct gdma_sge *sge; u64 rq_base_addr; u64 rx_req_idx; + u16 msg_id; u8 *wqe; if (WARN_ON_ONCE(hwc_rxq->gdma_wq->id != gdma_rxq_id)) @@ -269,13 +268,17 @@ static void mana_hwc_rx_event_handler(void *ctx, u32 gdma_rxq_id, rx_req = &hwc_rxq->msg_buf->reqs[rx_req_idx]; resp = (struct gdma_resp_hdr *)rx_req->buf_va; - if (resp->response.hwc_msg_id >= hwc->num_inflight_msg) { - dev_err(hwc->dev, "HWC RX: wrong msg_id=%u\n", - resp->response.hwc_msg_id); + /* Read msg_id once from DMA buffer to prevent TOCTOU: + * DMA memory is shared/unencrypted in CVMs - host can + * modify it between reads. + */ + msg_id = READ_ONCE(resp->response.hwc_msg_id); + if (msg_id >= hwc->num_inflight_msg) { + dev_err(hwc->dev, "HWC RX: wrong msg_id=%u\n", msg_id); return; } - mana_hwc_handle_resp(hwc, rx_oob->tx_oob_data_size, rx_req); + mana_hwc_handle_resp(hwc, rx_oob->tx_oob_data_size, rx_req, msg_id); /* Can no longer use 'resp', because the buffer is posted to the HW * in mana_hwc_handle_resp() above. -- cgit v1.2.3 From 0488073a6c84571dd3cffe581a4a73a5fceb099d Mon Sep 17 00:00:00 2001 From: Oliver White Date: Thu, 9 Apr 2026 15:43:47 +1200 Subject: platform/surface: aggregator_registry: omit battery & AC nodes on Surface Laptop 7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface Laptop 7 exposes battery and AC status via Qualcomm PMIC GLINK qcom_battmgr. Registering the standard SSAM battery and AC client devices on this platform causes duplicate power-supply devices to appear. Drop the SSAM battery and AC nodes from the Surface Laptop 7 registry group so that only the qcom_battmgr power supplies are instantiated. Fixes: b27622f13172 ("platform/surface: Add OF support") Signed-off-by: Oliver White Link: https://patch.msgid.link/20260409034347.17381-1-oliverjwhite07@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surface_aggregator_registry.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 0599d5adf02e..f0881edfb616 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -295,8 +295,6 @@ static const struct software_node *ssam_node_group_sl6[] = { /* Devices for Surface Laptop 7. */ static const struct software_node *ssam_node_group_sl7[] = { &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, &ssam_node_tmp_perf_profile_with_fan, &ssam_node_fan_speed, &ssam_node_hid_sam_keyboard, -- cgit v1.2.3 From e7537735028c3ad4b0bfc02ff8fa2a1a28aa04fe Mon Sep 17 00:00:00 2001 From: Heechan Kang Date: Sun, 17 May 2026 15:22:32 +0900 Subject: fwctl: pds: Validate RPC input size before parsing The fwctl core allocates the device-specific RPC input buffer with fwctl_rpc.in_len and passes that buffer to the driver callback. pdsfc_fw_rpc() casts the buffer to struct fwctl_rpc_pds and then calls pdsfc_validate_rpc(), which reads fields from that structure before checking that the input buffer is large enough to contain it. A short in_len can make pds_fwctl read beyond the allocation. Reject pds RPC buffers that are smaller than struct fwctl_rpc_pds before parsing any pds-specific fields. Fixes: 92c66ee829b9 ("pds_fwctl: add rpc and query support") Link: https://patch.msgid.link/r/20260517062232.1858747-1-gganji11@naver.com Cc: stable@vger.kernel.org # v6.15+ Signed-off-by: Heechan Kang Reviewed-by: Dave Jiang Signed-off-by: Jason Gunthorpe --- drivers/fwctl/pds/main.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/fwctl/pds/main.c b/drivers/fwctl/pds/main.c index 08872ee8422f..68fe254dd10a 100644 --- a/drivers/fwctl/pds/main.c +++ b/drivers/fwctl/pds/main.c @@ -362,6 +362,9 @@ static void *pdsfc_fw_rpc(struct fwctl_uctx *uctx, enum fwctl_rpc_scope scope, void *out = NULL; int err; + if (in_len < sizeof(*rpc)) + return ERR_PTR(-EINVAL); + err = pdsfc_validate_rpc(pdsfc, rpc, scope); if (err) return ERR_PTR(err); -- cgit v1.2.3 From 00c9753435e8a800761feeeea029a83c4c4847c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D0=B5=D0=BD=D0=BA=D0=BE=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B4=D1=80=D0=B5=D0=B9=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=87?= Date: Tue, 19 May 2026 16:37:42 +0300 Subject: =?UTF-8?q?hp-wmi:=20fix=20support=20for=20thermal=20profile=20Ome?= =?UTF-8?q?n=2016-=D1=810xxx=20laptops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HP Omen 16-c0xxx (board ID: 8902) has the same WMI interface as other Victus S boards, but requires additional quirks for correctly switching thermal profile. Add the DMI board name to victus_s_thermal_profile_boards[] table and map it to the omen_v1_legacy_thermal_params quirk. Testing on board 8902 confirmed that platform profile is registered successfully and fan RPMs are readable and controllable. Signed-off-by: Konenko Andrey Viktorovich Link: https://patch.msgid.link/T3DTKbKwQzOgk_0eUG-kMg@aquinas.su Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 6950bec2a9d8..f63bc00d9a9b 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -189,6 +189,10 @@ static const char * const victus_thermal_profile_boards[] = { /* DMI Board names of Victus 16-r and Victus 16-s laptops */ static const struct dmi_system_id victus_s_thermal_profile_boards[] __initconst = { + { + .matches = { DMI_MATCH(DMI_BOARD_NAME, "8902") }, + .driver_data = (void *)&omen_v1_legacy_thermal_params, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "8A44") }, .driver_data = (void *)&omen_v1_legacy_thermal_params, -- cgit v1.2.3 From e7a9a6ea40e352cd7977f6a8c80bdeadf65ad838 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 17:11:49 +0200 Subject: platform/x86: adv_swbutton: Check ACPI_HANDLE() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_HANDLE() check against NULL to the platform/x86 adv_swbutton driver. Fixes: 3d904005f686 ("platform/x86: add support for Advantech software defined button") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/5115425.31r3eYUQgx@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/adv_swbutton.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/adv_swbutton.c b/drivers/platform/x86/adv_swbutton.c index 6fa60f3fc53c..8f7a26e6de81 100644 --- a/drivers/platform/x86/adv_swbutton.c +++ b/drivers/platform/x86/adv_swbutton.c @@ -48,10 +48,14 @@ static int adv_swbutton_probe(struct platform_device *device) { struct adv_swbutton *button; struct input_dev *input; - acpi_handle handle = ACPI_HANDLE(&device->dev); + acpi_handle handle; acpi_status status; int error; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + button = devm_kzalloc(&device->dev, sizeof(*button), GFP_KERNEL); if (!button) return -ENOMEM; -- cgit v1.2.3 From abfbe5ee8ae89f1f5449790423d5dd3e423545bd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 17:12:40 +0200 Subject: platform/x86: hp_accel: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 hp_accel driver. Fixes: 8ebcb6c94c71 ("platform/x86: hp_accel: Convert to be a platform driver") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2425918.ElGaqSPkdT@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/hp/hp_accel.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/hp/hp_accel.c b/drivers/platform/x86/hp/hp_accel.c index 10d5af18d639..39b73dc473f1 100644 --- a/drivers/platform/x86/hp/hp_accel.c +++ b/drivers/platform/x86/hp/hp_accel.c @@ -300,6 +300,9 @@ static int lis3lv02d_probe(struct platform_device *device) int ret; lis3_dev.bus_priv = ACPI_COMPANION(&device->dev); + if (!lis3_dev.bus_priv) + return -ENODEV; + lis3_dev.init = lis3lv02d_acpi_init; lis3_dev.read = lis3lv02d_acpi_read; lis3_dev.write = lis3lv02d_acpi_write; -- cgit v1.2.3 From 5c69e090ae5dd93d910f70db0796357080707d26 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 17:13:28 +0200 Subject: platform/x86: intel-hid: Check ACPI_HANDLE() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_HANDLE() check against NULL to the platform/x86 intel-hid driver. Fixes: ecc83e52b28c ("intel-hid: new hid event driver for hotkeys") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/1971512.tdWV9SEqCh@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/hid.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 2ddd8af8c1ce..085093506dda 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -688,12 +688,16 @@ static bool button_array_present(struct platform_device *device) static int intel_hid_probe(struct platform_device *device) { - acpi_handle handle = ACPI_HANDLE(&device->dev); unsigned long long mode, dummy; struct intel_hid_priv *priv; + acpi_handle handle; acpi_status status; int err; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + intel_hid_init_dsm(handle); if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_HDMM_FN, &mode)) { -- cgit v1.2.3 From 2765f16c12af7c2533763e46b8113b727354012d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 17:15:32 +0200 Subject: platform/x86: intel_sar: Check ACPI_HANDLE() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_HANDLE() check against NULL to the platform/x86 intel_sar driver. Fixes: dcfbd31ef4bc ("platform/x86: BIOS SAR driver for Intel M.2 Modem") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/14023870.uLZWGnKmhe@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/int1092/intel_sar.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/int1092/intel_sar.c b/drivers/platform/x86/intel/int1092/intel_sar.c index 88822023a149..849f7b415c1e 100644 --- a/drivers/platform/x86/intel/int1092/intel_sar.c +++ b/drivers/platform/x86/intel/int1092/intel_sar.c @@ -245,15 +245,20 @@ static void sar_get_data(int reg, struct wwan_sar_context *context) static int sar_probe(struct platform_device *device) { struct wwan_sar_context *context; + acpi_handle handle; int reg; int result; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + context = kzalloc_obj(*context); if (!context) return -ENOMEM; context->sar_device = device; - context->handle = ACPI_HANDLE(&device->dev); + context->handle = handle; dev_set_drvdata(&device->dev, context); result = guid_parse(SAR_DSM_UUID, &context->guid); -- cgit v1.2.3 From a9f305c5a355efeb240d406d378491d9eec02d07 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 17:16:22 +0200 Subject: platform/x86: intel-vbtn: Check ACPI_HANDLE() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_HANDLE() check against NULL to the platform/x86 intel-vbtn driver. Fixes: 26173179fae1 ("platform/x86: intel-vbtn: Eval VBDL after registering our notifier") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3426431.aeNJFYEL58@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vbtn.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 9ca87e707582..874023c38fd1 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -275,12 +275,16 @@ static bool intel_vbtn_has_switches(acpi_handle handle, bool dual_accel) static int intel_vbtn_probe(struct platform_device *device) { - acpi_handle handle = ACPI_HANDLE(&device->dev); bool dual_accel, has_buttons, has_switches; struct intel_vbtn_priv *priv; + acpi_handle handle; acpi_status status; int err; + handle = ACPI_HANDLE(&device->dev); + if (!handle) + return -ENODEV; + dual_accel = dual_accel_detect(); has_buttons = acpi_has_method(handle, "VBDL"); has_switches = intel_vbtn_has_switches(handle, dual_accel); -- cgit v1.2.3 From 5798b46271e229dab05e47537ac30b76f81728da Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 18:30:26 +0200 Subject: platform/surface: surfacepro3_button: Check ACPI_COMPANION() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the surfacepro3_button driver. Fixes: d913a5a12b40 ("platform/surface: surfacepro3_button: Convert to a platform driver") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Chen Yu Link: https://patch.msgid.link/23119222.EfDdHjke4D@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/surface/surfacepro3_button.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c index 0293bc517b54..388a3e1a488c 100644 --- a/drivers/platform/surface/surfacepro3_button.c +++ b/drivers/platform/surface/surfacepro3_button.c @@ -185,12 +185,15 @@ static bool surface_button_check_MSHW0040(struct device *dev, acpi_handle handle static int surface_button_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct surface_button *button; + struct acpi_device *device; struct input_dev *input; - const char *hid = acpi_device_hid(device); int error; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, strlen(SURFACE_BUTTON_OBJ_NAME))) return -ENODEV; @@ -210,7 +213,8 @@ static int surface_button_probe(struct platform_device *pdev) } strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME); - snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); + snprintf(button->phys, sizeof(button->phys), "%s/buttons", + acpi_device_hid(device)); input->name = acpi_device_name(device); input->phys = button->phys; -- cgit v1.2.3 From c12cc42dadd85dea210d5699d4f21def827382eb Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 13 May 2026 01:21:38 +0200 Subject: platform/x86: uniwill-laptop: Properly initialize charging threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EC might initialize the charge threshold with 0 to signal that said threshold is uninitialized. Detect this and replace said value with 100 to signal the EC that we want to take control of battery charging. Also set the threshold to 100 if the EC-provided value is invalid. Fixes: d050479693bb ("platform/x86: Add Uniwill laptop driver") Reviewed-by: Werner Sembach Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260512232145.329260-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 945df5092637..d9c202fc8c71 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1359,6 +1359,16 @@ static int uniwill_led_init(struct uniwill_data *data) &init_data); } +static unsigned int uniwill_sanitize_battery_threshold(unsigned int value) +{ + /* 0 means "charging threshold not active" */ + if (!value) + return 100; + + /* Guard against invalid values */ + return min(value, 100); +} + static int uniwill_get_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, union power_supply_propval *val) @@ -1405,7 +1415,8 @@ static int uniwill_get_property(struct power_supply *psy, const struct power_sup if (ret < 0) return ret; - val->intval = clamp_val(FIELD_GET(CHARGE_CTRL_MASK, regval), 0, 100); + regval = FIELD_GET(CHARGE_CTRL_MASK, regval); + val->intval = uniwill_sanitize_battery_threshold(regval); return 0; default: return -EINVAL; @@ -1500,11 +1511,33 @@ static int uniwill_remove_battery(struct power_supply *battery, struct acpi_batt static int uniwill_battery_init(struct uniwill_data *data) { + unsigned int value, threshold, sanitized; int ret; if (!uniwill_device_supports(data, UNIWILL_FEATURE_BATTERY)) return 0; + ret = regmap_read(data->regmap, EC_ADDR_CHARGE_CTRL, &value); + if (ret < 0) + return ret; + + /* + * The charge control threshold might be initialized with 0 by + * the EC to signal that said threshold is uninitialized. We thus + * need to replace this placeholder value with a valid one (100) + * to signal that we want to take control of battery charging. + * For the sake of completeness we also apply this to other + * invalid threshold values. + */ + threshold = FIELD_GET(CHARGE_CTRL_MASK, value); + sanitized = uniwill_sanitize_battery_threshold(threshold); + if (threshold != sanitized) { + FIELD_MODIFY(CHARGE_CTRL_MASK, &value, sanitized); + ret = regmap_write(data->regmap, EC_ADDR_CHARGE_CTRL, value); + if (ret < 0) + return ret; + } + ret = devm_mutex_init(data->dev, &data->battery_lock); if (ret < 0) return ret; -- cgit v1.2.3 From c16a4823cc60a32b891f7a148bb30c0f51d12cf4 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 13 May 2026 01:21:39 +0200 Subject: platform/x86: uniwill-laptop: Accept charging threshold of 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The power supply sysfs ABI states that: Not all hardware is capable of setting this to an arbitrary percentage. Drivers will round written values to the nearest supported value. Reading back the value will show the actual threshold set by the driver. The driver currently violates this ABI by rejecting a charging threshold of 0. Fix this by clamping this value to 1. Fixes: d050479693bb ("platform/x86: Add Uniwill laptop driver") Reviewed-by: Werner Sembach Reviewed-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260512232145.329260-3-W_Armin@gmx.de Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index d9c202fc8c71..1f9e9f61d387 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -1431,11 +1431,11 @@ static int uniwill_set_property(struct power_supply *psy, const struct power_sup switch (psp) { case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: - if (val->intval < 1 || val->intval > 100) + if (val->intval < 0 || val->intval > 100) return -EINVAL; return regmap_update_bits(data->regmap, EC_ADDR_CHARGE_CTRL, CHARGE_CTRL_MASK, - val->intval); + max(val->intval, 1)); default: return -EINVAL; } -- cgit v1.2.3 From fb4b67c44557cb4cbb15900083d4e1af22320339 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 13 May 2026 01:21:40 +0200 Subject: platform/x86: uniwill-laptop: Fix behavior of "force" module param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users might want to force-enable all possible features even on machines with a valid device descriptor. Until now the "force" module param was ignored on such machines. Fix this to make it easier to test for support of new features. Fixes: d050479693bb ("platform/x86: Add Uniwill laptop driver") Reviewed-by: Werner Sembach Reviewed-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260512232145.329260-4-W_Armin@gmx.de Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 1f9e9f61d387..481c4cf46e63 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2489,8 +2489,6 @@ static int __init uniwill_init(void) if (!force) return -ENODEV; - /* Assume that the device supports all features */ - device_descriptor.features = UINT_MAX; pr_warn("Loading on a potentially unsupported device\n"); } else { /* @@ -2508,6 +2506,12 @@ static int __init uniwill_init(void) device_descriptor = *descriptor; } + if (force) { + /* Assume that the device supports all features */ + device_descriptor.features = UINT_MAX; + pr_warn("Enabling potentially unsupported features\n"); + } + ret = platform_driver_register(&uniwill_driver); if (ret < 0) return ret; -- cgit v1.2.3 From 26cbe119f99c86dcb4a0136d2bc73c0c716d80e4 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 13 May 2026 01:21:41 +0200 Subject: platform/x86: uniwill-laptop: Do not enable the charging limit even when forced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It seems that on some older models (~2020) the battery charging limit can permanently damage the battery. Prevent users from enabling this feature thru the "force" module parameter to avoid causing permanent hardware damage on such devices. Fixes: d050479693bb ("platform/x86: Add Uniwill laptop driver") Link: https://www.reddit.com/r/XMG_gg/comments/ld9yyf/battery_limit_hidden_function_discovered_on/ Reviewed-by: Werner Sembach Reviewed-by: Ilpo Järvinen Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260512232145.329260-5-W_Armin@gmx.de Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/uniwill/uniwill-acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c index 481c4cf46e63..8cc01bec77b9 100644 --- a/drivers/platform/x86/uniwill/uniwill-acpi.c +++ b/drivers/platform/x86/uniwill/uniwill-acpi.c @@ -2507,8 +2507,8 @@ static int __init uniwill_init(void) } if (force) { - /* Assume that the device supports all features */ - device_descriptor.features = UINT_MAX; + /* Assume that the device supports all features except the charge limit */ + device_descriptor.features = UINT_MAX & ~UNIWILL_FEATURE_BATTERY; pr_warn("Enabling potentially unsupported features\n"); } -- cgit v1.2.3 From 348ccc754d8939e21ca5956ff45720b81d6e407f Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 14 May 2026 07:40:42 +0200 Subject: platform/x86/intel/vsec: Fix enable_cnt imbalance on PCIe error recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a PCIe Uncorrectable Error has been reported by a device with Intel Vendor Specific Extended Capabilities and has been recovered through a Secondary Bus Reset, its driver calls intel_vsec_pci_probe() to rescan and reinitialize VSECs. intel_vsec_pci_probe() invokes pcim_enable_device() and thereby adds another devm action which calls pcim_disable_device() on driver unbind. So once the driver unbinds, pcim_disable_device() will be called as many times as an Uncorrectable Error occurred, plus one. This will lead to an enable_cnt imbalance on driver unbind. Additionally, since commit dc957ab6aa05 ("platform/x86/intel/vsec: Add private data for per-device data"), a devm_kzalloc() allocation is leaked on every Uncorrectable Error. Avoid by splitting the VSEC rescan out of intel_vsec_pci_probe() into a separate helper and calling that on PCIe error recovery. Fixes: 936874b77dd0 ("platform/x86/intel/vsec: Add PCI error recovery support to Intel PMT") Signed-off-by: Lukas Wunner Cc: stable@vger.kernel.org # v6.0+ Link: https://patch.msgid.link/bd594d09fa866dc51dddc9a447c3b23f9b1402cc.1778736835.git.lukas@wunner.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/vsec.c | 54 ++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index 7d5dbc1c1d05..18e4a892bf0f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -649,29 +649,13 @@ static void intel_vsec_skip_missing_dependencies(struct pci_dev *pdev) } } -static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +static int intel_vsec_pci_init(struct pci_dev *pdev) { - const struct intel_vsec_platform_info *info; - struct vsec_priv *priv; - int num_caps, ret; + struct vsec_priv *priv = pci_get_drvdata(pdev); + const struct intel_vsec_platform_info *info = priv->info; int run_once = 0; bool found_any = false; - - ret = pcim_enable_device(pdev); - if (ret) - return ret; - - pci_save_state(pdev); - info = (const struct intel_vsec_platform_info *)id->driver_data; - if (!info) - return -EINVAL; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->info = info; - pci_set_drvdata(pdev, priv); + int num_caps; num_caps = hweight_long(info->caps); while (num_caps--) { @@ -692,6 +676,31 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id return 0; } +static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + const struct intel_vsec_platform_info *info; + struct vsec_priv *priv; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + pci_save_state(pdev); + info = (const struct intel_vsec_platform_info *)id->driver_data; + if (!info) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + pci_set_drvdata(pdev, priv); + + return intel_vsec_pci_init(pdev); +} + int intel_vsec_set_mapping(struct oobmsm_plat_info *plat_info, struct intel_vsec_device *vsec_dev) { @@ -832,7 +841,6 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) { struct intel_vsec_device *intel_vsec_dev; pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT; - const struct pci_device_id *pci_dev_id; unsigned long index; dev_info(&pdev->dev, "Resetting PCI slot\n"); @@ -853,10 +861,8 @@ static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev) devm_release_action(&pdev->dev, intel_vsec_remove_aux, &intel_vsec_dev->auxdev); } - pci_disable_device(pdev); pci_restore_state(pdev); - pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev); - intel_vsec_pci_probe(pdev, pci_dev_id); + intel_vsec_pci_init(pdev); out: return status; -- cgit v1.2.3 From d2d2e7c8fb37b27301ee5c8343b2f7037efc6ea6 Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Sun, 17 May 2026 18:30:11 +0000 Subject: platform/x86: asus-armoury: fix mini-LED mode get/set on MODE2 devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mini-LED current_value attribute does not work on devices that use ASUS_WMI_DEVID_MINI_LED_MODE2 (2024 and newer models). Reading is broken: mini_led_mode_current_value_show() fetches the mode from the device but then decodes a literal 0 instead of the value it just read: mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, 0); So mode is always 0, and the attribute always reports the same thing regardless of the real hardware state. Writing is broken too. The number a user writes is an index; the value the firmware actually wants is looked up from that index in mini_led_mode_map[]. mini_led_mode_current_value_store() skips that lookup and passes the raw index straight to armoury_attr_uint_store(). On 2024 devices the firmware numbers its modes differently from the index, so some writes are rejected with -EINVAL and the rest send the wrong mode to the hardware. Fix both paths: decode the value actually read from the device when reading, and look up the firmware value before sending it when writing. Older (MODE1) devices were unaffected because there the index and the firmware value are the same. Fixes: f99eb098090e ("platform/x86: asus-armoury: move existing tunings to asus-armoury module") Signed-off-by: Ahmed Yaseen Reviewed-by: Denis Benato Link: https://patch.msgid.link/20260517182957.11069-1-yaseen@ghoul.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 5b0987ccc270..495dc1e31d40 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -370,7 +370,7 @@ static ssize_t mini_led_mode_current_value_show(struct kobject *kobj, if (err) return err; - mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, 0); + mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, mode); for (i = 0; i < mini_led_mode_map_size; i++) if (mode == mini_led_mode_map[i]) @@ -386,6 +386,7 @@ static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, { u32 *mini_led_mode_map; size_t mini_led_mode_map_size; + char mapped_value[12]; u32 mode; int err; @@ -414,9 +415,16 @@ static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, return -ENODEV; } - return armoury_attr_uint_store(kobj, attr, buf, count, - 0, mini_led_mode_map[mode], - NULL, asus_armoury.mini_led_dev_id); + /* + * armoury_attr_uint_store() parses and sends the value from the + * passed buffer; hand it the mapped firmware value so the device + * receives the translated mode instead of the raw index. + */ + snprintf(mapped_value, sizeof(mapped_value), "%u", mini_led_mode_map[mode]); + + return armoury_attr_uint_store(kobj, attr, mapped_value, count, 0, + mini_led_mode_map[mode], NULL, + asus_armoury.mini_led_dev_id); } static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, -- cgit v1.2.3 From 9ac7c8dc07af88f5169f217fec431c992b4eb550 Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Sun, 17 May 2026 22:00:02 +0000 Subject: platform/x86: asus-armoury: add support for FX607VU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TDP data for laptop model FX607VU. Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260517220005.4594-2-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index c30d2b451e01..bd394ef94264 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -886,6 +886,33 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FX607VU"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 28, + .ppt_pl1_spl_def = 115, + .ppt_pl1_spl_max = 135, + .ppt_pl2_sppt_min = 28, + .ppt_pl2_sppt_max = 135, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 45, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 60, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA401Q"), -- cgit v1.2.3 From 239c7acb9e3f73ada3ccce06302f6f3d2d89762d Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Sun, 17 May 2026 22:00:03 +0000 Subject: platform/x86: asus-armoury: add support for G614FR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TDP data for laptop model G614FR. Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260517220005.4594-3-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index bd394ef94264..6c8f46d289d8 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1786,6 +1786,40 @@ static const struct dmi_system_id power_limits[] = { .requires_fan_curve = true, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "G614FR"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 30, + .ppt_pl1_spl_max = 120, + .ppt_pl2_sppt_min = 65, + .ppt_pl2_sppt_def = 140, + .ppt_pl2_sppt_max = 162, + .ppt_pl3_fppt_min = 65, + .ppt_pl3_fppt_def = 140, + .ppt_pl3_fppt_max = 162, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 25, + .nv_tgp_min = 65, + .nv_tgp_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 65, + .ppt_pl2_sppt_min = 25, + .ppt_pl2_sppt_max = 65, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 75, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "G614J"), -- cgit v1.2.3 From df1e0b209fa09676585402d5437ee190a424302b Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Sun, 17 May 2026 22:00:04 +0000 Subject: platform/x86: asus-armoury: add support for FA401EA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TDP data for laptop model FA401EA. Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260517220005.4594-4-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 6c8f46d289d8..71df2ca061a7 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -346,6 +346,29 @@ struct power_data { * _def is not required and will be assumed to be default == max if missing. */ static const struct dmi_system_id power_limits[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FA401EA"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 95, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 100, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 115, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 15, + .ppt_pl1_spl_max = 71, + .ppt_pl2_sppt_min = 35, + .ppt_pl2_sppt_max = 71, + .ppt_pl3_fppt_min = 35, + .ppt_pl3_fppt_max = 71, + }, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "FA401UM"), -- cgit v1.2.3 From 841ff62aa5f85b13771b33e7b9c102cbaf87432f Mon Sep 17 00:00:00 2001 From: Denis Benato Date: Sun, 17 May 2026 22:00:05 +0000 Subject: platform/x86: asus-armoury: add support for GU605CP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TDP data for laptop model GU605CP. Signed-off-by: Denis Benato Link: https://patch.msgid.link/20260517220005.4594-5-denis.benato@linux.dev Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-armoury.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h index 71df2ca061a7..692978b61959 100644 --- a/drivers/platform/x86/asus-armoury.h +++ b/drivers/platform/x86/asus-armoury.h @@ -1303,6 +1303,35 @@ static const struct dmi_system_id power_limits[] = { }, }, }, + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "GU605CP"), + }, + .driver_data = &(struct power_data) { + .ac_data = &(struct power_limits) { + .ppt_pl1_spl_min = 45, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 56, + .ppt_pl2_sppt_max = 95, + .nv_dynamic_boost_min = 5, + .nv_dynamic_boost_max = 15, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + .nv_tgp_min = 55, + .nv_tgp_def = 75, + .nv_tgp_max = 95, + }, + .dc_data = &(struct power_limits) { + .ppt_pl1_spl_min = 25, + .ppt_pl1_spl_max = 75, + .ppt_pl2_sppt_min = 32, + .ppt_pl2_sppt_max = 95, + .nv_temp_target_min = 75, + .nv_temp_target_max = 87, + }, + .requires_fan_curve = true, + }, + }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605CR"), -- cgit v1.2.3 From 13c6da02e767152c9ac4330962247a5e47011035 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Tue, 19 May 2026 10:03:00 +0200 Subject: efi: Allocate runtime workqueue before ACPI init Since commit 5894cf571e14 ("acpi/prmt: Use EFI runtime sandbox to invoke PRM handlers") ACPI PRM calls are delegated to a workqueue which runs in a kernel thread, making it easier to detect and mitigate faulting memory accesses performed by the firmware. Rafael reports that such PRM accesses may occur before efisubsys_init() executes, which is where the workqueue is allocated, leading to NULL pointer dereferences. Since acpi_init() [which triggers the early PRM accesses] executes as a subsys_initcall() as well, and has its own dependencies that may be sensitive to initcall ordering, deferring acpi_init() is not an option. So instead, split off the workqueue allocation into its own postcore initcall, as this is the only missing piece to allow EFI runtime calls to be made. This ensures that EFI runtime call (including PRM calls) are accessible to all code running at subsys_initcall() level. Cc: Fixes: 5894cf571e14 ("acpi/prmt: Use EFI runtime sandbox to invoke PRM handlers") Reviewed-by: Rafael J. Wysocki (Intel) Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/efi.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index d04be38f1750..318d1cc9a066 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -402,21 +402,11 @@ static void __init efi_debugfs_init(void) static inline void efi_debugfs_init(void) {} #endif -/* - * We register the efi subsystem with the firmware subsystem and the - * efivars subsystem with the efi subsystem, if the system was booted with - * EFI. - */ -static int __init efisubsys_init(void) +static int __init efipostcore_init(void) { - int error; - if (!efi_enabled(EFI_RUNTIME_SERVICES)) efi.runtime_supported_mask = 0; - if (!efi_enabled(EFI_BOOT)) - return 0; - if (efi.runtime_supported_mask) { /* * Since we process only one efi_runtime_service() at a time, an @@ -428,9 +418,23 @@ static int __init efisubsys_init(void) pr_err("Creating efi_rts_wq failed, EFI runtime services disabled.\n"); clear_bit(EFI_RUNTIME_SERVICES, &efi.flags); efi.runtime_supported_mask = 0; - return 0; } } + return 0; +} +postcore_initcall(efipostcore_init); + +/* + * We register the efi subsystem with the firmware subsystem and the + * efivars subsystem with the efi subsystem, if the system was booted with + * EFI. + */ +static int __init efisubsys_init(void) +{ + int error; + + if (!efi_enabled(EFI_BOOT)) + return 0; if (efi_rt_services_supported(EFI_RT_SUPPORTED_TIME_SERVICES)) platform_device_register_simple("rtc-efi", 0, NULL, 0); -- cgit v1.2.3 From d8809f6931065cbbf3554647a50a65a471ab5983 Mon Sep 17 00:00:00 2001 From: Marius Hoch Date: Sun, 17 May 2026 21:23:40 +0200 Subject: efi: sysfb_efi: Extend quirk to cover IdeaPad Duet 3 10IGL5-LTE The LTE enabled version of the IdeaPad Duet 3 10IGL5 needs the same quirk as the non-LTE version. As these are the only two IdeaPad Duet 3 10IGL5 versions, we can safely use non exact matching. Tested on a IdeaPad Duet 3 10IGL5-LTE. Signed-off-by: Marius Hoch Signed-off-by: Ard Biesheuvel --- drivers/firmware/efi/sysfb_efi.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/efi/sysfb_efi.c b/drivers/firmware/efi/sysfb_efi.c index 4c3986ddcd54..685283bb7327 100644 --- a/drivers/firmware/efi/sysfb_efi.c +++ b/drivers/firmware/efi/sysfb_efi.c @@ -311,11 +311,14 @@ static const struct dmi_system_id efifb_dmi_swap_width_height[] __initconst = { .callback = efifb_swap_width_height, }, { - /* Lenovo IdeaPad Duet 3 10IGL5 with 1200x1920 portrait screen */ + /* + * Lenovo IdeaPad Duet 3 10IGL5 and 10IGL5-LTE with + * 1200x1920 portrait screen + */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, - "IdeaPad Duet 3 10IGL5"), + /* Non exact match to also match the LTE version */ + DMI_MATCH(DMI_PRODUCT_VERSION, "IdeaPad Duet 3 10IGL5"), }, .callback = efifb_swap_width_height, }, -- cgit v1.2.3 From 00907da2126ed785451b2a2f0fef282246dad104 Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 18 May 2026 12:16:40 -0700 Subject: drm/xe/multi_queue: Fix secondary queue error case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If xe_lrc_create() fails, the secondary queue added to the multi-queue group list is not removed before freeing the queue. Fix error path handling for secondary queues by removing it from the multi-queue group list at the right place. Reported-by: Sebastian Österlund Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/7979 Fixes: d716a5088c88 ("drm/xe/multi_queue: Handle tearing down of a multi queue") Cc: stable@vger.kernel.org # v7.0+ Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260518191639.320890-2-niranjana.vishwanathapura@intel.com (cherry picked from commit d2d23c12789cf69eddc35b8d38cd8eaabd0168f1) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_guc_submit.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 10556156eaad..912182dc7704 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -1673,6 +1673,14 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q) struct xe_guc_exec_queue *ge = q->guc; struct xe_guc *guc = exec_queue_to_guc(q); + if (xe_exec_queue_is_multi_queue_secondary(q)) { + struct xe_exec_queue_group *group = q->multi_queue.group; + + mutex_lock(&group->list_lock); + list_del(&q->multi_queue.link); + mutex_unlock(&group->list_lock); + } + release_guc_id(guc, q); xe_sched_entity_fini(&ge->entity); xe_sched_fini(&ge->sched); @@ -1694,14 +1702,6 @@ static void __guc_exec_queue_destroy_async(struct work_struct *w) guard(xe_pm_runtime)(guc_to_xe(guc)); trace_xe_exec_queue_destroy(q); - if (xe_exec_queue_is_multi_queue_secondary(q)) { - struct xe_exec_queue_group *group = q->multi_queue.group; - - mutex_lock(&group->list_lock); - list_del(&q->multi_queue.link); - mutex_unlock(&group->list_lock); - } - /* Confirm no work left behind accessing device structures */ cancel_delayed_work_sync(&ge->sched.base.work_tdr); -- cgit v1.2.3 From 457b046b7dfc0fd5b3437766a4ec43d16d02482c Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Mon, 18 May 2026 08:57:05 -0700 Subject: accel/amdxdna: Remove mmap and export support for ubuf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ubuf pages should not be mmaped or exported. Remove the ubuf mmap callback and return -EOPNOTSUPP when exporting ubuf objects. ubuf vmap is also removed for there is not a real use case yet. Fixes: bd72d4acda10 ("accel/amdxdna: Support user space allocated buffer") Reviewed-by: Christian König Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20260518155706.937461-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_gem.c | 9 ++++++- drivers/accel/amdxdna/amdxdna_gem.h | 2 ++ drivers/accel/amdxdna/amdxdna_ubuf.c | 50 ------------------------------------ 3 files changed, 10 insertions(+), 51 deletions(-) (limited to 'drivers') diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 238ee244d4a6..6e367ddb9e1b 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -490,6 +490,9 @@ static struct dma_buf *amdxdna_gem_prime_export(struct drm_gem_object *gobj, int struct amdxdna_gem_obj *abo = to_xdna_obj(gobj); DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + if (abo->private_buffer) + return ERR_PTR(-EOPNOTSUPP); + if (abo->dma_buf) { get_dma_buf(abo->dma_buf); return abo->dma_buf; @@ -685,6 +688,7 @@ amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create { struct amdxdna_dev *xdna = to_xdna_dev(dev); struct amdxdna_drm_va_tbl va_tbl; + struct amdxdna_gem_obj *abo; struct drm_gem_object *gobj; struct dma_buf *dma_buf; @@ -711,7 +715,10 @@ amdxdna_gem_create_ubuf_object(struct drm_device *dev, struct amdxdna_drm_create dma_buf_put(dma_buf); - return to_xdna_obj(gobj); + abo = to_xdna_obj(gobj); + abo->private_buffer = true; + + return abo; } struct drm_gem_object * diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h index 4fc48a1189d2..957305ccb485 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.h +++ b/drivers/accel/amdxdna/amdxdna_gem.h @@ -54,6 +54,8 @@ struct amdxdna_gem_obj { /* True, if BO is managed by XRT, not application */ bool internal; + /* True, if BO is not exportable */ + bool private_buffer; }; #define to_gobj(obj) (&(obj)->base.base) diff --git a/drivers/accel/amdxdna/amdxdna_ubuf.c b/drivers/accel/amdxdna/amdxdna_ubuf.c index fb999aa25318..85390e3cc9f9 100644 --- a/drivers/accel/amdxdna/amdxdna_ubuf.c +++ b/drivers/accel/amdxdna/amdxdna_ubuf.c @@ -69,60 +69,10 @@ static void amdxdna_ubuf_release(struct dma_buf *dbuf) kfree(ubuf); } -static vm_fault_t amdxdna_ubuf_vm_fault(struct vm_fault *vmf) -{ - struct vm_area_struct *vma = vmf->vma; - struct amdxdna_ubuf_priv *ubuf; - unsigned long pfn; - pgoff_t pgoff; - - ubuf = vma->vm_private_data; - pgoff = (vmf->address - vma->vm_start) >> PAGE_SHIFT; - - pfn = page_to_pfn(ubuf->pages[pgoff]); - return vmf_insert_pfn(vma, vmf->address, pfn); -} - -static const struct vm_operations_struct amdxdna_ubuf_vm_ops = { - .fault = amdxdna_ubuf_vm_fault, -}; - -static int amdxdna_ubuf_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma) -{ - struct amdxdna_ubuf_priv *ubuf = dbuf->priv; - - vma->vm_ops = &amdxdna_ubuf_vm_ops; - vma->vm_private_data = ubuf; - vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP); - - return 0; -} - -static int amdxdna_ubuf_vmap(struct dma_buf *dbuf, struct iosys_map *map) -{ - struct amdxdna_ubuf_priv *ubuf = dbuf->priv; - void *kva; - - kva = vmap(ubuf->pages, ubuf->nr_pages, VM_MAP, PAGE_KERNEL); - if (!kva) - return -EINVAL; - - iosys_map_set_vaddr(map, kva); - return 0; -} - -static void amdxdna_ubuf_vunmap(struct dma_buf *dbuf, struct iosys_map *map) -{ - vunmap(map->vaddr); -} - static const struct dma_buf_ops amdxdna_ubuf_dmabuf_ops = { .map_dma_buf = amdxdna_ubuf_map, .unmap_dma_buf = amdxdna_ubuf_unmap, .release = amdxdna_ubuf_release, - .mmap = amdxdna_ubuf_mmap, - .vmap = amdxdna_ubuf_vmap, - .vunmap = amdxdna_ubuf_vunmap, }; struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, -- cgit v1.2.3 From eb359cc314365f50272883805adfe96b1e4ecefc Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Fri, 8 May 2026 12:21:20 +0530 Subject: drm/amdgpu/userq: use drm_exec in amdgpu_userq_fence_read_wptr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To access the bo from vm mapping first lock the root bo and then the object bo of the mapping to make sure both locks are taken safely. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 3aab50410653fe7eb35eb6f9c2b27e3549ab09e6) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c | 55 ++++++++++++------------- 1 file changed, 26 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c index 53a8944bab05..a41fb72dba94 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq_fence.c @@ -370,51 +370,48 @@ static int amdgpu_userq_fence_read_wptr(struct amdgpu_device *adev, { struct amdgpu_bo_va_mapping *mapping; struct amdgpu_bo *bo; + struct drm_exec exec; u64 addr, *ptr; - int r; - - r = amdgpu_bo_reserve(queue->vm->root.bo, false); - if (r) - return r; + int ret; addr = queue->userq_prop->wptr_gpu_addr; addr &= AMDGPU_GMC_HOLE_MASK; - mapping = amdgpu_vm_bo_lookup_mapping(queue->vm, addr >> PAGE_SHIFT); - if (!mapping) { - amdgpu_bo_unreserve(queue->vm->root.bo); - DRM_ERROR("Failed to lookup amdgpu_bo_va_mapping\n"); - return -EINVAL; - } + drm_exec_init(&exec, DRM_EXEC_IGNORE_DUPLICATES, 2); + drm_exec_until_all_locked(&exec) { + ret = amdgpu_vm_lock_pd(queue->vm, &exec, 1); + drm_exec_retry_on_contention(&exec); + if (unlikely(ret)) + goto lock_error; - bo = amdgpu_bo_ref(mapping->bo_va->base.bo); - amdgpu_bo_unreserve(queue->vm->root.bo); - r = amdgpu_bo_reserve(bo, true); - if (r) { - amdgpu_bo_unref(&bo); - DRM_ERROR("Failed to reserve userqueue wptr bo"); - return r; + mapping = amdgpu_vm_bo_lookup_mapping(queue->vm, addr >> PAGE_SHIFT); + if (!mapping) { + ret = -EINVAL; + goto lock_error; + } + + ret = drm_exec_lock_obj(&exec, &mapping->bo_va->base.bo->tbo.base); + drm_exec_retry_on_contention(&exec); + if (unlikely(ret)) + goto lock_error; } - r = amdgpu_bo_kmap(bo, (void **)&ptr); - if (r) { + bo = mapping->bo_va->base.bo; + ret = amdgpu_bo_kmap(bo, (void **)&ptr); + if (ret) { DRM_ERROR("Failed mapping the userqueue wptr bo"); - goto map_error; + goto lock_error; } *wptr = le64_to_cpu(*ptr); amdgpu_bo_kunmap(bo); - amdgpu_bo_unreserve(bo); - amdgpu_bo_unref(&bo); - + drm_exec_fini(&exec); return 0; -map_error: - amdgpu_bo_unreserve(bo); - amdgpu_bo_unref(&bo); - - return r; +lock_error: + drm_exec_fini(&exec); + return ret; } static void -- cgit v1.2.3 From be045c5c8305fca1214ebdd4b8433ac80635339b Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Fri, 8 May 2026 15:58:09 +0530 Subject: drm/amdgpu/userq: pin mqd and fw object bo to avoid eviction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mqd and fw objects are queue core objects which should remain valid and never be unmapped and evicted for user queues to work properly. During eviction if these buffers are evicted the hw continue to use the invalid addresses and caused page faults and system hung. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit a3bbf32a336939a1d21b9561f8e53333b684b7ef) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 70d74f04d2dd..8841955927bb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -504,16 +504,20 @@ int amdgpu_userq_create_object(struct amdgpu_userq_mgr *uq_mgr, goto free_obj; } + r = amdgpu_bo_pin(userq_obj->obj, AMDGPU_GEM_DOMAIN_GTT); + if (r) + goto unresv; + r = amdgpu_ttm_alloc_gart(&(userq_obj->obj)->tbo); if (r) { drm_file_err(uq_mgr->file, "Failed to alloc GART for userqueue object (%d)", r); - goto unresv; + goto unpin_bo; } r = amdgpu_bo_kmap(userq_obj->obj, &userq_obj->cpu_ptr); if (r) { drm_file_err(uq_mgr->file, "Failed to map BO for userqueue (%d)", r); - goto unresv; + goto unpin_bo; } userq_obj->gpu_addr = amdgpu_bo_gpu_offset(userq_obj->obj); @@ -521,11 +525,13 @@ int amdgpu_userq_create_object(struct amdgpu_userq_mgr *uq_mgr, memset(userq_obj->cpu_ptr, 0, size); return 0; +unpin_bo: + amdgpu_bo_unpin(userq_obj->obj); unresv: amdgpu_bo_unreserve(userq_obj->obj); - free_obj: amdgpu_bo_unref(&userq_obj->obj); + return r; } @@ -533,6 +539,7 @@ void amdgpu_userq_destroy_object(struct amdgpu_userq_mgr *uq_mgr, struct amdgpu_userq_obj *userq_obj) { amdgpu_bo_kunmap(userq_obj->obj); + amdgpu_bo_unpin(userq_obj->obj); amdgpu_bo_unref(&userq_obj->obj); } -- cgit v1.2.3 From c8ed2de0f2ee842d108ef96c125e931ea82b453c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 21 Apr 2026 12:39:54 +0200 Subject: drm/amdgpu: rework userq reset work handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is illegal to schedule reset work from another reset work! Fix this by scheduling the userq reset work directly on the work queue of the reset domain. Not fully tested, I leave that to the IGT test cases. Signed-off-by: Christian König Reviewed-by: Prike Liang Reviewed-by: Sunil Khatri Signed-off-by: Alex Deucher (cherry picked from commit fd9200ccefab94f27877d1943761d6b0ccbd89c8) --- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 1 - drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 3 +- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 84 ++++++++++++++++-------------- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h | 16 +++++- 4 files changed, 60 insertions(+), 44 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 8bc591deb546..fd50da4c7b18 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -1190,7 +1190,6 @@ struct amdgpu_device { bool apu_prefer_gtt; bool userq_halt_for_enforce_isolation; - struct work_struct userq_reset_work; struct amdgpu_uid *uid_info; struct amdgpu_uma_carveout_info uma_info; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 66ca043658ff..1424c98d2006 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -3787,7 +3787,6 @@ int amdgpu_device_init(struct amdgpu_device *adev, } INIT_WORK(&adev->xgmi_reset_work, amdgpu_device_xgmi_reset_func); - INIT_WORK(&adev->userq_reset_work, amdgpu_userq_reset_work); amdgpu_coredump_init(adev); @@ -5478,7 +5477,7 @@ static inline void amdgpu_device_stop_pending_resets(struct amdgpu_device *adev) if (!amdgpu_sriov_vf(adev)) cancel_work(&adev->reset_work); #endif - cancel_work(&adev->userq_reset_work); + amdgpu_userq_mgr_cancel_reset_work(adev); if (adev->kfd.dev) cancel_work(&adev->kfd.reset_work); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 8841955927bb..aa6d4c71fba6 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -82,19 +82,11 @@ static bool amdgpu_userq_is_reset_type_supported(struct amdgpu_device *adev, return false; } -static void amdgpu_userq_gpu_reset(struct amdgpu_device *adev) -{ - if (amdgpu_device_should_recover_gpu(adev)) { - amdgpu_reset_domain_schedule(adev->reset_domain, - &adev->userq_reset_work); - /* Wait for the reset job to complete */ - flush_work(&adev->userq_reset_work); - } -} - -static int -amdgpu_userq_detect_and_reset_queues(struct amdgpu_userq_mgr *uq_mgr) +static void amdgpu_userq_mgr_reset_work(struct work_struct *work) { + struct amdgpu_userq_mgr *uq_mgr = + container_of(work, struct amdgpu_userq_mgr, + reset_work); struct amdgpu_device *adev = uq_mgr->adev; const int queue_types[] = { AMDGPU_RING_TYPE_COMPUTE, @@ -103,12 +95,11 @@ amdgpu_userq_detect_and_reset_queues(struct amdgpu_userq_mgr *uq_mgr) }; const int num_queue_types = ARRAY_SIZE(queue_types); bool gpu_reset = false; - int r = 0; - int i; + int i, r; if (unlikely(adev->debug_disable_gpu_ring_reset)) { dev_err(adev->dev, "userq reset disabled by debug mask\n"); - return 0; + return; } /* @@ -116,7 +107,7 @@ amdgpu_userq_detect_and_reset_queues(struct amdgpu_userq_mgr *uq_mgr) * skip all reset detection logic */ if (!amdgpu_gpu_recovery) - return 0; + return; /* * Iterate through all queue types to detect and reset problematic queues @@ -141,10 +132,19 @@ amdgpu_userq_detect_and_reset_queues(struct amdgpu_userq_mgr *uq_mgr) } } - if (gpu_reset) - amdgpu_userq_gpu_reset(adev); + if (gpu_reset) { + struct amdgpu_reset_context reset_context; - return r; + memset(&reset_context, 0, sizeof(reset_context)); + + reset_context.method = AMD_RESET_METHOD_NONE; + reset_context.reset_req_dev = adev; + reset_context.src = AMDGPU_RESET_SRC_USERQ; + set_bit(AMDGPU_NEED_FULL_RESET, &reset_context.flags); + /*set_bit(AMDGPU_SKIP_COREDUMP, &reset_context.flags);*/ + + amdgpu_device_gpu_recover(adev, NULL, &reset_context); + } } static void amdgpu_userq_hang_detect_work(struct work_struct *work) @@ -153,7 +153,11 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) container_of(work, struct amdgpu_usermode_queue, hang_detect_work.work); - amdgpu_userq_detect_and_reset_queues(queue->userq_mgr); + /* + * Don't schedule the work here! Scheduling or queue work from one reset + * handler to another is illegal if you don't take extra precautions! + */ + amdgpu_userq_mgr_reset_work(&queue->userq_mgr->reset_work); } /* @@ -182,8 +186,8 @@ void amdgpu_userq_start_hang_detect_work(struct amdgpu_usermode_queue *queue) break; } - schedule_delayed_work(&queue->hang_detect_work, - msecs_to_jiffies(timeout_ms)); + queue_delayed_work(adev->reset_domain->wq, &queue->hang_detect_work, + msecs_to_jiffies(timeout_ms)); } void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell) @@ -1259,28 +1263,13 @@ amdgpu_userq_evict_all(struct amdgpu_userq_mgr *uq_mgr) if (ret) { drm_file_err(uq_mgr->file, "Couldn't unmap all the queues, eviction failed ret=%d\n", ret); - amdgpu_userq_detect_and_reset_queues(uq_mgr); + amdgpu_reset_domain_schedule(uq_mgr->adev->reset_domain, + &uq_mgr->reset_work); + flush_work(&uq_mgr->reset_work); } return ret; } -void amdgpu_userq_reset_work(struct work_struct *work) -{ - struct amdgpu_device *adev = container_of(work, struct amdgpu_device, - userq_reset_work); - struct amdgpu_reset_context reset_context; - - memset(&reset_context, 0, sizeof(reset_context)); - - reset_context.method = AMD_RESET_METHOD_NONE; - reset_context.reset_req_dev = adev; - reset_context.src = AMDGPU_RESET_SRC_USERQ; - set_bit(AMDGPU_NEED_FULL_RESET, &reset_context.flags); - /*set_bit(AMDGPU_SKIP_COREDUMP, &reset_context.flags);*/ - - amdgpu_device_gpu_recover(adev, NULL, &reset_context); -} - static void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) { @@ -1314,9 +1303,24 @@ int amdgpu_userq_mgr_init(struct amdgpu_userq_mgr *userq_mgr, struct drm_file *f userq_mgr->file = file_priv; INIT_DELAYED_WORK(&userq_mgr->resume_work, amdgpu_userq_restore_worker); + INIT_WORK(&userq_mgr->reset_work, amdgpu_userq_mgr_reset_work); return 0; } +void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev) +{ + struct xarray *xa = &adev->userq_doorbell_xa; + struct amdgpu_usermode_queue *queue; + unsigned long flags, queue_id; + + xa_lock_irqsave(xa, flags); + xa_for_each(xa, queue_id, queue) { + cancel_delayed_work(&queue->hang_detect_work); + cancel_work(&queue->userq_mgr->reset_work); + } + xa_unlock_irqrestore(xa, flags); +} + void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr) { cancel_delayed_work_sync(&userq_mgr->resume_work); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 85f460e7c31b..49b33e2d6932 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -84,7 +84,13 @@ struct amdgpu_usermode_queue { u32 xcp_id; int priority; struct dentry *debugfs_queue; - struct delayed_work hang_detect_work; + + /** + * @hang_detect_work: + * + * Delayed work which runs when userq_fences time out. + */ + struct delayed_work hang_detect_work; struct kref refcount; struct list_head userq_va_list; @@ -116,6 +122,13 @@ struct amdgpu_userq_mgr { struct amdgpu_device *adev; struct delayed_work resume_work; struct drm_file *file; + + /** + * @reset_work: + * + * Reset work which is used when eviction fails. + */ + struct work_struct reset_work; atomic_t userq_count[AMDGPU_RING_TYPE_MAX]; }; @@ -134,6 +147,7 @@ int amdgpu_userq_ioctl(struct drm_device *dev, void *data, struct drm_file *filp int amdgpu_userq_mgr_init(struct amdgpu_userq_mgr *userq_mgr, struct drm_file *file_priv, struct amdgpu_device *adev); +void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev); void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr); void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr); -- cgit v1.2.3 From 291df3dc7d10855c26841b8f42843bc996d097cf Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 12 May 2026 14:52:40 +0530 Subject: drm/amdgpu/userq: cancel reset work while tear down in progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While tear down of a userq_mgr is happening when all the queues are free we should cancel any reset work if pending before exiting. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 160164609f71f774c4f661227a9b7a370a86b112) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index aa6d4c71fba6..49e9c75d3151 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -1346,6 +1346,14 @@ void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr) } xa_destroy(&userq_mgr->userq_xa); + + /* + * Drain any in-flight reset_work. By this point all queues are freed + * and userq_count is 0, so if reset_work starts now it exits early. + * We still need to wait in case it was already executing gpu_recover. + */ + cancel_work_sync(&userq_mgr->reset_work); + mutex_destroy(&userq_mgr->userq_mutex); } -- cgit v1.2.3 From 0be97436bf249503b2c5898e7459744962b70f15 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 12 May 2026 16:00:18 +0530 Subject: drm/amdgpu/userq: update the vm task info during signal ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pagefaults does not have process information correctly populated as vm->task is not set during vm_init but should be updated while real submission. So setting that up during signal_ioctl to get the correct submission process details. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit a9b14d88b4d83e21ab965f23d1fb7b07b87e0517) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 49e9c75d3151..f79e54e0a04a 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -802,6 +802,9 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) goto clean_fence_driver; } + /* Update VM owner at userq submit-time for page-fault attribution. */ + amdgpu_vm_set_task_info(&fpriv->vm); + amdgpu_userq_ensure_ev_fence(&fpriv->userq_mgr, &fpriv->evf_mgr); /* don't map the queue if scheduling is halted */ -- cgit v1.2.3 From b6074630a461b1322a814988779005cbc43612ea Mon Sep 17 00:00:00 2001 From: Alan Liu Date: Fri, 1 May 2026 12:35:48 +0800 Subject: drm/amdgpu/vpe: Force collaborate sync after TRAP VPE1 could possibly hang and fail to power off at the end of commands in collaboration mode. This workaround adds a COLLAB_SYNC after TRAP to force instances synchronized to avoid VPE1 fail to power off. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Alan liu Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/5171 Signed-off-by: Alex Deucher (cherry picked from commit a8b749c5c5afb7e5daa2bfb95d958fb3c6b8f055) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_vpe.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vpe.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vpe.c index fd881388d612..f27f917e3cdb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vpe.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vpe.c @@ -562,6 +562,11 @@ static void vpe_ring_emit_fence(struct amdgpu_ring *ring, uint64_t addr, amdgpu_ring_write(ring, 0); } + /* WA: Force sync after TRAP to avoid VPE1 fail to power off */ + if (ring->adev->vpe.collaborate_mode) { + amdgpu_ring_write(ring, VPE_CMD_HEADER(VPE_CMD_OPCODE_COLLAB_SYNC, 0)); + amdgpu_ring_write(ring, 0xabcd); + } } static void vpe_ring_emit_pipeline_sync(struct amdgpu_ring *ring) @@ -968,7 +973,7 @@ static const struct amdgpu_ring_funcs vpe_ring_funcs = { .emit_frame_size = 5 + /* vpe_ring_init_cond_exec */ 6 + /* vpe_ring_emit_pipeline_sync */ - 10 + 10 + 10 + /* vpe_ring_emit_fence */ + 12 + 12 + 12 + /* vpe_ring_emit_fence */ /* vpe_ring_emit_vm_flush */ SOC15_FLUSH_GPU_TLB_NUM_WREG * 3 + SOC15_FLUSH_GPU_TLB_NUM_REG_WAIT * 6, -- cgit v1.2.3 From 2c34c7b88ba4f04b3a862fac879bfd8567f06541 Mon Sep 17 00:00:00 2001 From: Amir Shetaia Date: Thu, 7 May 2026 13:24:55 -0400 Subject: drm/amdgpu: reject non-user addresses early in GEM_USERPTR ioctl amdgpu_gem_userptr_ioctl() currently accepts any value of args->addr and only discovers an out-of-range pointer much later, inside amdgpu_gem_object_create() and the HMM mirror registration path. Userspace can drive that path with kernel-side virtual addresses; the get_user_pages() layer rejects them, but only after the driver has already allocated a GEM object and started wiring up notifier state that then has to be torn down on failure. Add an access_ok() guard at the top of the ioctl, right after the existing page-alignment check and before flag validation, so any address that does not lie within the calling task's user address range is rejected with -EFAULT before any allocation occurs. No legitimate ROCm/HSA userspace passes kernel-mode pointers through this interface, so this is defense-in-depth rather than a behaviour change for valid callers; -EFAULT matches the convention already used by other uaccess-style rejections in the kernel. Also add an explicit #include ; access_ok() is otherwise only available transitively through other headers in this translation unit. Signed-off-by: Amir Shetaia Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 7a076df36397d780d7e4fb595287b4980451a7f5) --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 5376035d32fe..23f2304ee7e0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -508,6 +509,9 @@ int amdgpu_gem_userptr_ioctl(struct drm_device *dev, void *data, if (offset_in_page(args->addr | args->size)) return -EINVAL; + if (!access_ok((void __user *)(uintptr_t)args->addr, args->size)) + return -EFAULT; + /* reject unknown flag values */ if (args->flags & ~(AMDGPU_GEM_USERPTR_READONLY | AMDGPU_GEM_USERPTR_ANONONLY | AMDGPU_GEM_USERPTR_VALIDATE | -- cgit v1.2.3 From d892a6eca7a847dd57ede6e55d7494a01b43fa5f Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Tue, 12 May 2026 22:29:48 +0530 Subject: drm/amdgpu: remove va cursors for all mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit va_cursor struct needs to be cleaned even if the mapping has been removed already. Also simplify it by make it a void function as return value check isn't needed as its called during tear down. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 4d35a45c9b4c1ac5b6e3219f83c3db706b675fa2) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index f79e54e0a04a..6111f6858d2d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -303,13 +303,14 @@ static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) static void amdgpu_userq_buffer_va_list_del(struct amdgpu_bo_va_mapping *mapping, struct amdgpu_userq_va_cursor *va_cursor) { - atomic_set(&mapping->bo_va->userq_va_mapped, 0); + if (mapping) + atomic_set(&mapping->bo_va->userq_va_mapped, 0); list_del(&va_cursor->list); kfree(va_cursor); } -static int amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, - struct amdgpu_usermode_queue *queue) +static void amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, + struct amdgpu_usermode_queue *queue) { struct amdgpu_userq_va_cursor *va_cursor, *tmp; struct amdgpu_bo_va_mapping *mapping; @@ -319,15 +320,11 @@ static int amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, list_for_each_entry_safe(va_cursor, tmp, &queue->userq_va_list, list) { mapping = amdgpu_vm_bo_lookup_mapping(queue->vm, va_cursor->gpu_addr); - if (!mapping) { - return -EINVAL; - } - dev_dbg(adev->dev, "delete the userq:%p va:%llx\n", - queue, va_cursor->gpu_addr); + if (mapping) + dev_dbg(adev->dev, "delete the userq:%p va:%llx\n", + queue, va_cursor->gpu_addr); amdgpu_userq_buffer_va_list_del(mapping, va_cursor); } - - return 0; } static int amdgpu_userq_preempt_helper(struct amdgpu_usermode_queue *queue) -- cgit v1.2.3 From 9baf02bf88a8228248c546a3be7bbe3bfe74b7d5 Mon Sep 17 00:00:00 2001 From: Lijo Lazar Date: Tue, 12 May 2026 20:29:52 +0530 Subject: drm/amdgpu: Fix discovery offset check under VF Discovery table may be kept at offset 0 by host driver. Remove the validation check. Fixes: 01bdc7e219c4 ("drm/amdgpu: New interface to get IP discovery binary v3") Signed-off-by: Lijo Lazar Reviewed-by: Ellen Pan Signed-off-by: Alex Deucher (cherry picked from commit d3f5bbd007133c64a20e81ef290a93e46c75df40) --- drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c index 8d99bfaa498f..80efeca0ab73 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_discovery.c @@ -304,7 +304,7 @@ static int amdgpu_discovery_get_tmr_info(struct amdgpu_device *adev, adev->virt.crit_regn_tbl[AMD_SRIOV_MSG_IPD_TABLE_ID].offset; adev->discovery.size = adev->virt.crit_regn_tbl[AMD_SRIOV_MSG_IPD_TABLE_ID].size_kb << 10; - if (!adev->discovery.offset || !adev->discovery.size) + if (!adev->discovery.size) return -EINVAL; } else { goto out; -- cgit v1.2.3 From d796558def777f9a9cc274861e06b8b61851b409 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Sat, 9 May 2026 15:20:39 +0800 Subject: drm/amd/pm: fix memleak of dpm_policies on smu v15 In smu_v15_0_fini_smc_tables, dpm_policies was not freed or NULLed, causing a memory leak. Add kfree() and NULL assignment to properly release memory and avoid dangling pointers. Fixes: 2beedc3a92b7 ("drm/amd/pm: Add initial support for smu v15_0_8"); Signed-off-by: Yang Wang Reviewed-by: Asad Kamal Signed-off-by: Alex Deucher (cherry picked from commit 014f329074f688b9b49383e8b70e79e9ef99359e) --- drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c index c3cb36813806..940b43105817 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu15/smu_v15_0.c @@ -435,10 +435,12 @@ int smu_v15_0_fini_smc_tables(struct smu_context *smu) smu_table->watermarks_table = NULL; smu_table->metrics_time = 0; + kfree(smu_dpm->dpm_policies); kfree(smu_dpm->dpm_context); kfree(smu_dpm->golden_dpm_context); kfree(smu_dpm->dpm_current_power_state); kfree(smu_dpm->dpm_request_power_state); + smu_dpm->dpm_policies = NULL; smu_dpm->dpm_context = NULL; smu_dpm->golden_dpm_context = NULL; smu_dpm->dpm_context_size = 0; -- cgit v1.2.3 From 48b13bfbdf94e683cc5b8c5cb35b5af4221e657f Mon Sep 17 00:00:00 2001 From: Sunday Clement Date: Wed, 13 May 2026 11:22:19 -0400 Subject: drm/amdkfd: Fix OOB memory exposure in get_wave_state() The get_wave_state() function for v9 trusts cp_hqd_cntl_stack_size and cp_hqd_cntl_stack_offset values read directly from the MQD, which are written by GPU microcode and fully attacker-controlled on the CRIU-restore path (via AMDKFD_IOC_RESTORE_PROCESS with H3). this leads to an unbounded copy_to_user() that can leak adjacent GTT/kernel memory. If offset > size, integer underflow produces a ~4 GiB read length, if size is set to 1 MiB against a 4 KiB allocation, we leak 1 MiB of adjacent kernel memory (other queues' MQDs, ring buffers, KASLR pointers). Fix by clamping both cp_hqd_cntl_stack_size to the actual allocated buffer size (q->ctl_stack_size) and cp_hqd_cntl_stack_offset to the clamped size before performing arithmetic and copy_to_user(). This ensures we never read beyond the allocated kernel BO regardless of attacker-supplied MQD field values. Signed-off-by: Sunday Clement Acked-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 7ef144458f48d5589e36f1b3d83e83db2e5c5ba5) --- drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c index e8f97de9d6e4..f6d9d81003dc 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v9.c @@ -364,11 +364,15 @@ static int get_wave_state(struct mqd_manager *mm, void *mqd, { struct v9_mqd *m; struct kfd_context_save_area_header header; + u32 cntl_stack_size; + u32 cntl_stack_offset; /* Control stack is located one page after MQD. */ void *mqd_ctl_stack = (void *)((uintptr_t)mqd + AMDGPU_GPU_PAGE_SIZE); m = get_mqd(mqd); + cntl_stack_size = min_t(u32, m->cp_hqd_cntl_stack_size, q->ctl_stack_size); + cntl_stack_offset = min_t(u32, m->cp_hqd_cntl_stack_offset, cntl_stack_size); *ctl_stack_used_size = m->cp_hqd_cntl_stack_size - m->cp_hqd_cntl_stack_offset; @@ -384,9 +388,10 @@ static int get_wave_state(struct mqd_manager *mm, void *mqd, if (copy_to_user(ctl_stack, &header, sizeof(header.wave_state))) return -EFAULT; - if (copy_to_user(ctl_stack + m->cp_hqd_cntl_stack_offset, - mqd_ctl_stack + m->cp_hqd_cntl_stack_offset, - *ctl_stack_used_size)) + *ctl_stack_used_size = cntl_stack_size - cntl_stack_offset; + + if (copy_to_user(ctl_stack + cntl_stack_offset, mqd_ctl_stack + cntl_stack_offset, + *ctl_stack_used_size)) return -EFAULT; return 0; -- cgit v1.2.3 From 4d798ea0712fddbd35b439cef32b8ac735eb76f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:08 +0200 Subject: drm/amdgpu: Align amdgpu_gtt_mgr entries to TLB size on Tahiti (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TLB is organized in groups of 8 entries, each one is 4K. On Tahiti, the HW requires these GART entries to be 32K-aligned. This fixes a VCE 1 firmware validation failure that can happen after suspend/resume since we use amdgpu_gtt_mgr for VCE 1. v2: - Change variable declaration order - Add comment about "V bit HW bug" Fixes: 698fa62f56aa ("drm/amdgpu: Add helper to alloc GART entries") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 530411b465ef0b2c0cc18c2e3d7e38422b1117d1) --- drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c index 620fddde4c4d..a5d26b943f6d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gtt_mgr.c @@ -199,11 +199,18 @@ int amdgpu_gtt_mgr_alloc_entries(struct amdgpu_gtt_mgr *mgr, enum drm_mm_insert_mode mode) { struct amdgpu_device *adev = container_of(mgr, typeof(*adev), mman.gtt_mgr); + u32 alignment = 0; int r; + /* Align to TLB L2 cache entry size to work around "V bit HW bug" */ + if (adev->asic_type == CHIP_TAHITI) { + alignment = 32 * 1024 / AMDGPU_GPU_PAGE_SIZE; + num_pages = ALIGN(num_pages, alignment); + } + spin_lock(&mgr->lock); r = drm_mm_insert_node_in_range(&mgr->mm, mm_node, num_pages, - 0, GART_ENTRY_WITHOUT_BO_COLOR, 0, + alignment, GART_ENTRY_WITHOUT_BO_COLOR, 0, adev->gmc.gart_size >> PAGE_SHIFT, mode); spin_unlock(&mgr->lock); -- cgit v1.2.3 From 9f907adb66d8369dd45412794a04845011503fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:09 +0200 Subject: drm/amdgpu/vce1: Check that the GPU address is < 128 MiB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ensuring the low 32-bit address, make sure it is less than 128 MiB, otherwise the VCE seems to fail to initialize. This seems to be an undocumented limitation of the firmware validation mechanism. Note that in case of VCE1 the BAR address is zero and we can't change it also due to the firmware validator. When programming the mmVCE_VCPU_CACHE_OFFSETn registers, don't AND them with a mask. This is incorrect because the register mask is actually 0x0fffffff and useless because we already ensure the addresses are below the limit. Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit e729ae5f3ac73c861c062080ac8c3d666c972404) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index 5b7b46d242c6..edabec442cb6 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -313,17 +313,17 @@ static int vce_v1_0_mc_resume(struct amdgpu_device *adev) offset = adev->vce.gpu_addr + AMDGPU_VCE_FIRMWARE_OFFSET; size = VCE_V1_0_FW_SIZE; - WREG32(mmVCE_VCPU_CACHE_OFFSET0, offset & 0x7fffffff); + WREG32(mmVCE_VCPU_CACHE_OFFSET0, offset); WREG32(mmVCE_VCPU_CACHE_SIZE0, size); offset += size; size = VCE_V1_0_STACK_SIZE; - WREG32(mmVCE_VCPU_CACHE_OFFSET1, offset & 0x7fffffff); + WREG32(mmVCE_VCPU_CACHE_OFFSET1, offset); WREG32(mmVCE_VCPU_CACHE_SIZE1, size); offset += size; size = VCE_V1_0_DATA_SIZE; - WREG32(mmVCE_VCPU_CACHE_OFFSET2, offset & 0x7fffffff); + WREG32(mmVCE_VCPU_CACHE_OFFSET2, offset); WREG32(mmVCE_VCPU_CACHE_SIZE2, size); WREG32_P(mmVCE_LMI_CTRL2, 0x0, ~0x100); @@ -527,11 +527,15 @@ static int vce_v1_0_early_init(struct amdgpu_ip_block *ip_block) * To accomodate that, we put GART to the LOW address range * and reserve some GART pages where we map the VCPU BO, * so that it gets a 32-bit address. + * + * The BAR address is zero and we can't change it + * due to the firmware validation mechanism. + * It seems that it fails to initialize if the address is >= 128 MiB. */ static int vce_v1_0_ensure_vcpu_bo_32bit_addr(struct amdgpu_device *adev) { u64 bo_size = amdgpu_bo_size(adev->vce.vcpu_bo); - u64 max_vcpu_bo_addr = 0xffffffff - bo_size; + u64 max_vcpu_bo_addr = 0x07ffffff - bo_size; u64 num_pages = ALIGN(bo_size, AMDGPU_GPU_PAGE_SIZE) / AMDGPU_GPU_PAGE_SIZE; u64 pa = amdgpu_gmc_vram_pa(adev, adev->vce.vcpu_bo); u64 flags = AMDGPU_PTE_READABLE | AMDGPU_PTE_WRITEABLE | AMDGPU_PTE_VALID; -- cgit v1.2.3 From d993851b6db9abf0840e8b100e33df232bcc879e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:10 +0200 Subject: drm/amdgpu/vce1: Remove superfluous address check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same thing is already checked a few lines above. Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit c1dc555e760dbfc4a4710f7270f525a03d433af8) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index edabec442cb6..884f24be3685 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -557,8 +557,6 @@ static int vce_v1_0_ensure_vcpu_bo_32bit_addr(struct amdgpu_device *adev) amdgpu_gart_map_vram_range(adev, pa, adev->vce.gart_node.start, num_pages, flags, adev->gart.ptr); adev->vce.gpu_addr = adev->gmc.gart_start + vce_gart_start_offs; - if (adev->vce.gpu_addr > max_vcpu_bo_addr) - return -EINVAL; return 0; } -- cgit v1.2.3 From 12b60cf345e84aa7546dd225b3ce3380c9ab97f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:11 +0200 Subject: drm/amdgpu/vce1: Check if VRAM address is lower than GART. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, I had assumed this was not possible so it was OK to not handle it, but now we got a report from a user who has a board that is configured this way. When the VCPU BO is already located in a low 32-bit address in VRAM (eg. when VRAM is mapped to the low address space), don't do the workaround. Fixes: 71aec08f80e7 ("amdgpu/vce: use amdgpu_gtt_mgr_alloc_entries") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit f370ec9b164698a9ca1a7b59bfbea07f70df769d) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index 884f24be3685..a49f11be74b2 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -542,6 +542,9 @@ static int vce_v1_0_ensure_vcpu_bo_32bit_addr(struct amdgpu_device *adev) u64 vce_gart_start_offs; int r; + if (adev->gmc.vram_start < adev->gmc.gart_start) + return amdgpu_bo_gpu_offset(adev->vce.vcpu_bo) <= max_vcpu_bo_addr ? 0 : -EINVAL; + r = amdgpu_gtt_mgr_alloc_entries(&adev->mman.gtt_mgr, &adev->vce.gart_node, num_pages, DRM_MM_INSERT_LOW); -- cgit v1.2.3 From 3ebcab11320588fd9bb17a26027dddce4419ae43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:12 +0200 Subject: drm/amdgpu/vce1: Don't repeat GTT MGR node allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only allocate entries from the GTT manager when the VCE GTT node is not allocated yet. This prevents the possibility of allocating them multiple times, which causes issues during GPU reset and suspend/resume. Fixes: 71aec08f80e7 ("amdgpu/vce: use amdgpu_gtt_mgr_alloc_entries") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 8d2a20c1721cb17e22821e1b4ecbb02d475d91c5) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index a49f11be74b2..92c3cf3fce4f 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -545,11 +545,13 @@ static int vce_v1_0_ensure_vcpu_bo_32bit_addr(struct amdgpu_device *adev) if (adev->gmc.vram_start < adev->gmc.gart_start) return amdgpu_bo_gpu_offset(adev->vce.vcpu_bo) <= max_vcpu_bo_addr ? 0 : -EINVAL; - r = amdgpu_gtt_mgr_alloc_entries(&adev->mman.gtt_mgr, - &adev->vce.gart_node, num_pages, - DRM_MM_INSERT_LOW); - if (r) - return r; + if (!drm_mm_node_allocated(&adev->vce.gart_node)) { + r = amdgpu_gtt_mgr_alloc_entries(&adev->mman.gtt_mgr, + &adev->vce.gart_node, num_pages, + DRM_MM_INSERT_LOW); + if (r) + return r; + } vce_gart_start_offs = amdgpu_gtt_node_to_byte_offset(&adev->vce.gart_node); -- cgit v1.2.3 From 3e5a1d5bb2ff061e64c7992f8e5404dfd4c2d0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:13 +0200 Subject: drm/amdgpu/vce1: Fix VCE 1 firmware size and offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VCPU BO contains the actual FW at an offset, but it was not calculated into the VCPU BO size. Subtract this from the FW size to make sure there is no out of bounds access. Make sure the stack and data offsets are aligned to the 32K TLB size. Check that the FW microcode actually fits in the space that is reserved for it. Fixes: d4a640d4b9f3 ("drm/amdgpu/vce1: Implement VCE1 IP block (v2)") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit c16fe59f622a080fc457a57b3e8f14c780699449) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index 92c3cf3fce4f..32ee6452f95d 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -42,9 +42,10 @@ #include "oss/oss_1_0_d.h" #include "oss/oss_1_0_sh_mask.h" +#define VCE_V1_0_ALIGNMENT (32 * 1024) #define VCE_V1_0_FW_SIZE (256 * 1024) #define VCE_V1_0_STACK_SIZE (64 * 1024) -#define VCE_V1_0_DATA_SIZE (7808 * (AMDGPU_MAX_VCE_HANDLES + 1)) +#define VCE_V1_0_DATA_SIZE (ALIGN(7808 * (AMDGPU_MAX_VCE_HANDLES + 1), VCE_V1_0_ALIGNMENT)) #define VCE_STATUS_VCPU_REPORT_FW_LOADED_MASK 0x02 static void vce_v1_0_set_ring_funcs(struct amdgpu_device *adev); @@ -189,17 +190,22 @@ static int vce_v1_0_load_fw_signature(struct amdgpu_device *adev) { const struct common_firmware_header *hdr; struct vce_v1_0_fw_signature *sign; - unsigned int ucode_offset; + u32 ucode_offset; + u32 ucode_size; uint32_t chip_id; u32 *cpu_addr; int i; hdr = (const struct common_firmware_header *)adev->vce.fw->data; ucode_offset = le32_to_cpu(hdr->ucode_array_offset_bytes); + ucode_size = hdr->ucode_size_bytes - sizeof(struct vce_v1_0_fw_signature *); cpu_addr = adev->vce.cpu_addr; sign = (void *)adev->vce.fw->data + ucode_offset; + if (ucode_size > VCE_V1_0_FW_SIZE - AMDGPU_VCE_FIRMWARE_OFFSET) + return -EINVAL; + switch (adev->asic_type) { case CHIP_TAHITI: chip_id = 0x01000014; @@ -231,7 +237,7 @@ static int vce_v1_0_load_fw_signature(struct amdgpu_device *adev) cpu_addr[4] = cpu_to_le32(le32_to_cpu(sign->length) + 64); memset_io(&cpu_addr[5], 0, 44); - memcpy_toio(&cpu_addr[16], &sign[1], hdr->ucode_size_bytes - sizeof(*sign)); + memcpy_toio(&cpu_addr[16], &sign[1], ucode_size); cpu_addr += (le32_to_cpu(sign->length) + 64) / 4; memcpy_toio(&cpu_addr[0], &sign->val[i].sigval[0], 16); @@ -312,17 +318,22 @@ static int vce_v1_0_mc_resume(struct amdgpu_device *adev) WREG32(mmVCE_VCPU_SCRATCH7, AMDGPU_MAX_VCE_HANDLES); offset = adev->vce.gpu_addr + AMDGPU_VCE_FIRMWARE_OFFSET; - size = VCE_V1_0_FW_SIZE; + size = VCE_V1_0_FW_SIZE - AMDGPU_VCE_FIRMWARE_OFFSET; WREG32(mmVCE_VCPU_CACHE_OFFSET0, offset); WREG32(mmVCE_VCPU_CACHE_SIZE0, size); offset += size; size = VCE_V1_0_STACK_SIZE; + WARN_ON(!IS_ALIGNED(offset, VCE_V1_0_ALIGNMENT)); + WARN_ON(!IS_ALIGNED(size, VCE_V1_0_ALIGNMENT)); WREG32(mmVCE_VCPU_CACHE_OFFSET1, offset); WREG32(mmVCE_VCPU_CACHE_SIZE1, size); offset += size; size = VCE_V1_0_DATA_SIZE; + WARN_ON(!IS_ALIGNED(offset, VCE_V1_0_ALIGNMENT)); + WARN_ON(!IS_ALIGNED(size, VCE_V1_0_ALIGNMENT)); + WARN_ON((offset + size - adev->vce.gpu_addr) > amdgpu_bo_size(adev->vce.vcpu_bo)); WREG32(mmVCE_VCPU_CACHE_OFFSET2, offset); WREG32(mmVCE_VCPU_CACHE_SIZE2, size); -- cgit v1.2.3 From f5a247e0377cd30d658d52ae8a70c82978cc37df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:14 +0200 Subject: drm/amdgpu/vce1: Stop using amdgpu_vce_resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VCE1 firmware works slightly differently and is already loaded by vce_v1_0_load_fw(). It doesn't actually need to call amdgpu_vce_resume(). Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 33d8951405e2dd81ac61edebc680e2dfb6b4fc9f) --- drivers/gpu/drm/amd/amdgpu/vce_v1_0.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c index 32ee6452f95d..93253db5e2de 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v1_0.c @@ -178,7 +178,7 @@ static void vce_v1_0_init_cg(struct amdgpu_device *adev) } /** - * vce_v1_0_load_fw_signature - load firmware signature into VCPU BO + * vce_v1_0_load_fw() - load firmware signature into VCPU BO * * @adev: amdgpu_device pointer * @@ -186,7 +186,7 @@ static void vce_v1_0_init_cg(struct amdgpu_device *adev) * This function finds the signature appropriate for the current * ASIC and writes that into the VCPU BO. */ -static int vce_v1_0_load_fw_signature(struct amdgpu_device *adev) +static int vce_v1_0_load_fw(struct amdgpu_device *adev) { const struct common_firmware_header *hdr; struct vce_v1_0_fw_signature *sign; @@ -232,6 +232,8 @@ static int vce_v1_0_load_fw_signature(struct amdgpu_device *adev) return -EINVAL; } + memset_io(&cpu_addr[0], 0, amdgpu_bo_size(adev->vce.vcpu_bo)); + cpu_addr += (256 - 64) / 4; memcpy_toio(&cpu_addr[0], &sign->val[i].nonce[0], 16); cpu_addr[4] = cpu_to_le32(le32_to_cpu(sign->length) + 64); @@ -592,10 +594,7 @@ static int vce_v1_0_sw_init(struct amdgpu_ip_block *ip_block) if (r) return r; - r = amdgpu_vce_resume(adev); - if (r) - return r; - r = vce_v1_0_load_fw_signature(adev); + r = vce_v1_0_load_fw(adev); if (r) return r; r = vce_v1_0_ensure_vcpu_bo_32bit_addr(adev); @@ -714,10 +713,7 @@ static int vce_v1_0_resume(struct amdgpu_ip_block *ip_block) struct amdgpu_device *adev = ip_block->adev; int r; - r = amdgpu_vce_resume(adev); - if (r) - return r; - r = vce_v1_0_load_fw_signature(adev); + r = vce_v1_0_load_fw(adev); if (r) return r; r = vce_v1_0_ensure_vcpu_bo_32bit_addr(adev); -- cgit v1.2.3 From 5dc3d16cd072a3b8595f430b6683d688d1d62f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:15 +0200 Subject: drm/amdgpu/vce2: Fix VCE 2 firmware size and offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VCPU BO contains the actual FW at an offset, but it was not calculated into the VCPU BO size. Subtract this from the FW size to make sure there is no out of bounds access. Additionally, increase the VCE_V2_0_DATA_SIZE to have extra space after the VCE handles. Also increase the data size used for each VCE handle. The FW needs 23744 bytes, use 24K to be safe. This fixes VM faults when using VCE 2. Cc: John Olender Closes: https://gitlab.freedesktop.org/drm/amd/-/work_items/4802 Fixes: e98226221467 ("drm/amdgpu: recalculate VCE firmware BO size") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit a20d21df625548c1738c0745f753c5d6eb823bc3) --- drivers/gpu/drm/amd/amdgpu/vce_v2_0.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v2_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v2_0.c index db149eda6204..3a6fc8604108 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v2_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v2_0.c @@ -37,9 +37,14 @@ #include "oss/oss_2_0_d.h" #include "oss/oss_2_0_sh_mask.h" + +/* Use 24K to be safe. The FW supposedly only requires 23744 bytes. */ +#define VCE_V2_0_DATA_ENTRY_SIZE (24 * 1024) + #define VCE_V2_0_FW_SIZE (256 * 1024) #define VCE_V2_0_STACK_SIZE (64 * 1024) -#define VCE_V2_0_DATA_SIZE (23552 * AMDGPU_MAX_VCE_HANDLES) +#define VCE_V2_0_DATA_SIZE (VCE_V2_0_DATA_ENTRY_SIZE * (AMDGPU_MAX_VCE_HANDLES + 1)) + #define VCE_STATUS_VCPU_REPORT_FW_LOADED_MASK 0x02 static void vce_v2_0_set_ring_funcs(struct amdgpu_device *adev); @@ -183,7 +188,7 @@ static void vce_v2_0_mc_resume(struct amdgpu_device *adev) WREG32(mmVCE_LMI_VCPU_CACHE_40BIT_BAR, (adev->vce.gpu_addr >> 8)); offset = AMDGPU_VCE_FIRMWARE_OFFSET; - size = VCE_V2_0_FW_SIZE; + size = VCE_V2_0_FW_SIZE - AMDGPU_VCE_FIRMWARE_OFFSET; WREG32(mmVCE_VCPU_CACHE_OFFSET0, offset & 0x7fffffff); WREG32(mmVCE_VCPU_CACHE_SIZE0, size); -- cgit v1.2.3 From 0c61a9732a35b0a96213119c8212349da9cda117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timur=20Krist=C3=B3f?= Date: Wed, 13 May 2026 22:04:16 +0200 Subject: drm/amdgpu/vce3: Fix VCE 3 firmware size and offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VCPU BO contains the actual FW at an offset, but it was not calculated into the VCPU BO size. Subtract this from the FW size to make sure there is no out of bounds access. This may fix VM faults when using VCE 3. Cc: John Olender Fixes: e98226221467 ("drm/amdgpu: recalculate VCE firmware BO size") Signed-off-by: Timur Kristóf Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 15c369257bd85f47a514744f960c5a51c867716f) --- drivers/gpu/drm/amd/amdgpu/vce_v3_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c index 03d79e464f04..c69f7d82060f 100644 --- a/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c +++ b/drivers/gpu/drm/amd/amdgpu/vce_v3_0.c @@ -574,7 +574,7 @@ static void vce_v3_0_mc_resume(struct amdgpu_device *adev, int idx) } else WREG32(mmVCE_LMI_VCPU_CACHE_40BIT_BAR, (adev->vce.gpu_addr >> 8)); offset = AMDGPU_VCE_FIRMWARE_OFFSET; - size = VCE_V3_0_FW_SIZE; + size = VCE_V3_0_FW_SIZE - AMDGPU_VCE_FIRMWARE_OFFSET; WREG32(mmVCE_VCPU_CACHE_OFFSET0, offset & 0x7fffffff); WREG32(mmVCE_VCPU_CACHE_SIZE0, size); -- cgit v1.2.3 From a1d4b228e3dc5134c4bd06e55e81dbb604c8cadb Mon Sep 17 00:00:00 2001 From: David Francis Date: Tue, 12 May 2026 15:15:33 -0400 Subject: drm/amdkfd: Check bounds on allocate_doorbell allocated_doorbell has an option to set the doorbell id to a specific value (used by CRIU). This value was not bounds checked. Check to confirm it's less than KFD_MAX_NUM_OF_QUEUES_PER_PROCESS. Signed-off-by: David Francis Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher (cherry picked from commit 1f087bb8cf9e8797633da35c85435e557ef74d06) --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index 9185ebe4c079..c9239728afd6 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -475,6 +475,9 @@ static int allocate_doorbell(struct qcm_process_device *qpd, } else { /* For CP queues on SOC15 */ if (restore_id) { + if (*restore_id >= KFD_MAX_NUM_OF_QUEUES_PER_PROCESS) + return -EINVAL; + /* make sure that ID is free */ if (__test_and_set_bit(*restore_id, qpd->doorbell_bitmap)) return -EINVAL; -- cgit v1.2.3 From 0978406224d21bd35f9230a25534849ec06bf74c Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Thu, 14 May 2026 12:31:00 +0530 Subject: drm/amdgpu: use atomic operation to achieve lockless serialization In amdgpu_seq64_alloc there is a possibility that two difference cores from two separate NODES can try to and could get the same free slot. So this fixes that race here using atomic test_and_set clear operations. Signed-off-by: Sunil Khatri Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher (cherry picked from commit 4d50a14d346141e03a7c3905e496d91e048bc30c) --- drivers/gpu/drm/amd/amdgpu/amdgpu_seq64.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_seq64.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_seq64.c index a0b479d5fff1..f4be19223588 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_seq64.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_seq64.c @@ -175,11 +175,14 @@ int amdgpu_seq64_alloc(struct amdgpu_device *adev, u64 *va, { unsigned long bit_pos; - bit_pos = find_first_zero_bit(adev->seq64.used, adev->seq64.num_sem); - if (bit_pos >= adev->seq64.num_sem) - return -ENOSPC; + for (;;) { + bit_pos = find_first_zero_bit(adev->seq64.used, adev->seq64.num_sem); + if (bit_pos >= adev->seq64.num_sem) + return -ENOSPC; - __set_bit(bit_pos, adev->seq64.used); + if (!test_and_set_bit(bit_pos, adev->seq64.used)) + break; + } *va = bit_pos * sizeof(u64) + amdgpu_seq64_get_va_base(adev); @@ -205,7 +208,7 @@ void amdgpu_seq64_free(struct amdgpu_device *adev, u64 va) bit_pos = (va - amdgpu_seq64_get_va_base(adev)) / sizeof(u64); if (bit_pos < adev->seq64.num_sem) - __clear_bit(bit_pos, adev->seq64.used); + clear_bit(bit_pos, adev->seq64.used); } /** -- cgit v1.2.3 From 6dc2c49a705195c89b09b134d0bc4dc5e42d1fea Mon Sep 17 00:00:00 2001 From: David Francis Date: Tue, 12 May 2026 15:18:18 -0400 Subject: drm/amdkfd: Check bounds for allocate_sdma_queue restore_sdma_id allocate_sdma_queue has an option where the sdma queue id can be specified (used by CRIU). We weren't bounds-checking that value. Confirm it's less than the maximum number of queues. Signed-off-by: David Francis Reviewed-by: Harish Kasiviswanathan Signed-off-by: Alex Deucher (cherry picked from commit bfe9a7545b2a7be1c543f1741e16f2d5ec4116ae) --- drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c index c9239728afd6..e0a31e11f0ff 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_device_queue_manager.c @@ -1590,6 +1590,9 @@ static int allocate_sdma_queue(struct device_queue_manager *dqm, } if (restore_sdma_id) { + if (*restore_sdma_id >= get_num_sdma_queues(dqm)) + return -EINVAL; + /* Re-use existing sdma_id */ if (!test_bit(*restore_sdma_id, dqm->sdma_bitmap)) { dev_err(dev, "SDMA queue already in use\n"); @@ -1616,6 +1619,9 @@ static int allocate_sdma_queue(struct device_queue_manager *dqm, return -ENOMEM; } if (restore_sdma_id) { + if (*restore_sdma_id >= get_num_xgmi_sdma_queues(dqm)) + return -EINVAL; + /* Re-use existing sdma_id */ if (!test_bit(*restore_sdma_id, dqm->xgmi_sdma_bitmap)) { dev_err(dev, "SDMA queue already in use\n"); -- cgit v1.2.3 From cd86529ec61474a38c3837fb7823790a7c3f8cce Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Mon, 4 May 2026 11:14:45 -0400 Subject: drm/amd/display: Fix integer overflow in bios_get_image() [Why&How] The bounds check in bios_get_image() computes 'offset + size' using unsigned 32-bit arithmetic before comparing against bios_size. If a VBIOS image contains a near-UINT32_MAX offset the addition wraps to a small value, the comparison passes, and the function returns a wild pointer past the VBIOS mapping. Additionally, the comparison uses '<' (strict), which incorrectly rejects the valid exact-fit case where offset + size == bios_size. Fix both issues by restructuring the check to avoid the addition entirely: first reject if offset alone exceeds bios_size, then check size against the remaining space (bios_size - offset). This eliminates the overflow and correctly permits exact-fit accesses. Assisted-by: GitHub Copilot:claude-opus-4.6 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit d40fb392af659c4a02b560319f226842f6ec1a95) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/bios/bios_parser_helper.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser_helper.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser_helper.c index 8d2cf95ae739..e00dc05c2d9d 100644 --- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser_helper.c +++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser_helper.c @@ -37,10 +37,13 @@ uint8_t *bios_get_image(struct dc_bios *bp, uint32_t offset, uint32_t size) { - if (bp->bios && offset + size < bp->bios_size) - return bp->bios + offset; - else + if (!bp->bios) return NULL; + + if (offset > bp->bios_size || size > bp->bios_size - offset) + return NULL; + + return bp->bios + offset; } #include "reg_helper.h" -- cgit v1.2.3 From 86d2b20644b11d21fe52c596e6e922b4590a3e3f Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Mon, 4 May 2026 16:14:11 -0400 Subject: drm/amd/display: Validate GPIO pin LUT table size before iterating [Why&How] The GPIO pin table parsers in get_gpio_i2c_info() and bios_parser_get_gpio_pin_info() derive an element count from the VBIOS table_header.structuresize field, then iterate over gpio_pin[] entries. However, GET_IMAGE() only validates that the table header itself fits within the BIOS image. If the VBIOS reports a structuresize larger than the actual mapped data, the loop reads past the end of the BIOS image, causing an out-of-bounds read. Fix this by calling bios_get_image() to validate that the full claimed structuresize is accessible within the BIOS image before entering the loop in both functions. Assisted-by: GitHub Copilot:claude-opus-4-6 Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit ba5e95b43b773ae1bf1f66ee6b31eb774e65afe3) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c index a1c08e1cc411..c51c4b2c6fae 100644 --- a/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c +++ b/drivers/gpu/drm/amd/display/dc/bios/bios_parser2.c @@ -493,6 +493,10 @@ static enum bp_result get_gpio_i2c_info( - sizeof(struct atom_common_table_header)) / sizeof(struct atom_gpio_pin_assignment); + if (!bios_get_image(&bp->base, DATA_TABLES(gpio_pin_lut), + le16_to_cpu(header->table_header.structuresize))) + return BP_RESULT_BADBIOSTABLE; + pin = (struct atom_gpio_pin_assignment *) header->gpio_pin; for (table_index = 0; table_index < count; table_index++) { @@ -681,6 +685,11 @@ static enum bp_result bios_parser_get_gpio_pin_info( count = (le16_to_cpu(header->table_header.structuresize) - sizeof(struct atom_common_table_header)) / sizeof(struct atom_gpio_pin_assignment); + + if (!bios_get_image(&bp->base, DATA_TABLES(gpio_pin_lut), + le16_to_cpu(header->table_header.structuresize))) + return BP_RESULT_BADBIOSTABLE; + for (i = 0; i < count; ++i) { if (header->gpio_pin[i].gpio_id != gpio_id) continue; -- cgit v1.2.3 From 6c92f6d9600efa3ef0d9e560a2b52776d9803c29 Mon Sep 17 00:00:00 2001 From: Harry Wentland Date: Thu, 7 May 2026 16:26:31 -0400 Subject: drm/amd/display: Validate payload length and link_index in dc_process_dmub_aux_transfer_async [Why&How] dc_process_dmub_aux_transfer_async() copies payload->length bytes into a 16-byte stack buffer (dpaux.data[16]) guarded only by an ASSERT(), which is a no-op in release builds. If a caller ever passes length > 16 this results in a stack buffer overflow via memcpy. Additionally, link_index is used to dereference dc->links[] without bounds checking against dc->link_count, risking an out-of-bounds access. Replace the ASSERT with a hard runtime check that returns false when payload->length exceeds the destination buffer size, and add a bounds check for link_index before it is used. Assisted-by: GitHub Copilot:Claude claude-4-opus Reviewed-by: Alex Hung Signed-off-by: Harry Wentland Signed-off-by: Ivan Lipski Tested-by: Dan Wheeler Signed-off-by: Alex Deucher (cherry picked from commit ba4caa9fecdf7a38f98c878ad05a8a64148b6881) Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/display/dc/core/dc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/display/dc/core/dc.c b/drivers/gpu/drm/amd/display/dc/core/dc.c index 419f894c87b0..b3530fbf32f7 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc.c @@ -6071,7 +6071,11 @@ bool dc_process_dmub_aux_transfer_async(struct dc *dc, uint8_t action; union dmub_rb_cmd cmd = {0}; - ASSERT(payload->length <= 16); + if (link_index >= dc->link_count || !dc->links[link_index]) + return false; + + if (payload->length > sizeof(cmd.dp_aux_access.aux_control.dpaux.data)) + return false; cmd.dp_aux_access.header.type = DMUB_CMD__DP_AUX_ACCESS; cmd.dp_aux_access.header.payload_bytes = 0; -- cgit v1.2.3 From 353f7430d1eccd481cc089decd1fc377d4312f4a Mon Sep 17 00:00:00 2001 From: Yifan Zhang Date: Mon, 11 May 2026 22:14:23 +0800 Subject: drm/amdgpu: unmap all user mappings of framebuffer and doorbell before mode1 reset During Mode 1 reset, the ASIC undergoes a reset cycle and becomes temporarily inaccessible via PCIe. Any attempt to access framebuffer or MMIO registers during this window can result in uncompleted PCIe transactions, leading to NMI panics or system hangs. To prevent this, Unmap all of the applications mappings of the framebuffer and doorbell BARs before mode1 reset. Also prevent new mappings from coming in during the reset process. v2: remove inode in kfd_dev (Christian) v3: correct unmap offset (Felix), remove prevent new mappings part to avoid deadlock (Christian) Reviewed-by: Felix Kuehling Signed-off-by: Yifan Zhang Signed-off-by: Alex Deucher (cherry picked from commit 70cadefcc6160c575b04f763ada34c20e868d577) --- drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c | 25 +++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 6 ++++++ drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 22 ++++++++++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 + 5 files changed, 55 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c index d9e283f3b57d..9783a3cefb04 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.c @@ -36,6 +36,9 @@ #include "amdgpu_ras.h" #include "amdgpu_umc.h" #include "amdgpu_reset.h" +#if IS_ENABLED(CONFIG_HSA_AMD) +#include "kfd_priv.h" +#endif /* Total memory size in system memory and all GPU VRAM. Used to * estimate worst case amount of memory to reserve for page tables @@ -320,6 +323,28 @@ void amdgpu_amdkfd_gpu_reset(struct amdgpu_device *adev) (void)amdgpu_reset_domain_schedule(adev->reset_domain, &adev->kfd.reset_work); } +void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev) +{ +#if IS_ENABLED(CONFIG_HSA_AMD) + struct kfd_dev *kfd = adev->kfd.dev; + unsigned int i; + + if (!kfd) + return; + + for (i = 0; i < kfd->num_nodes; i++) { + struct kfd_node *node = kfd->nodes[i]; + + kfd_dev_unmap_mapping_range(KFD_MMAP_TYPE_DOORBELL | + KFD_MMAP_GPU_ID(node->id), + kfd_doorbell_process_slice(kfd)); + kfd_dev_unmap_mapping_range(KFD_MMAP_TYPE_MMIO | + KFD_MMAP_GPU_ID(node->id), + PAGE_SIZE); + } +#endif +} + int amdgpu_amdkfd_alloc_kernel_mem(struct amdgpu_device *adev, size_t size, u32 domain, void **mem_obj, uint64_t *gpu_addr, void **cpu_ptr, bool cp_mqd_gfx9) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h index cdbab7f8cee8..2b4108f83f48 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_amdkfd.h @@ -358,6 +358,7 @@ int amdgpu_amdkfd_reserve_mem_limit(struct amdgpu_device *adev, uint64_t size, u32 alloc_flag, int8_t xcp_id); void amdgpu_amdkfd_unreserve_mem_limit(struct amdgpu_device *adev, uint64_t size, u32 alloc_flag, int8_t xcp_id); +void amdgpu_amdkfd_clear_kfd_mapping(struct amdgpu_device *adev); u64 amdgpu_amdkfd_xcp_memory_size(struct amdgpu_device *adev, int xcp_id); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index 1424c98d2006..feab90e3efd1 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -5835,6 +5835,12 @@ int amdgpu_device_gpu_recover(struct amdgpu_device *adev, /* We need to lock reset domain only once both for XGMI and single device */ amdgpu_device_recovery_get_reset_lock(adev, &device_list); + /* unmap all the mappings of doorbell and framebuffer to prevent user space from + * accessing them + */ + unmap_mapping_range(adev->ddev.anon_inode->i_mapping, 0, 0, 1); + amdgpu_amdkfd_clear_kfd_mapping(adev); + amdgpu_device_halt_activities(adev, job, reset_context, &device_list, hive, need_emergency_restart); if (need_emergency_restart) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index f95bf6d95534..03b266b26738 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -67,6 +67,21 @@ static const struct class kfd_class = { .name = kfd_dev_name, }; +/* + * Cache the address space of the chardev on first open so that the reset + * path can drop all userspace mappings of doorbell and MMIO ranges via + * unmap_mapping_range(). + */ +static struct address_space *kfd_dev_mapping; + +void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) +{ + struct address_space *mapping = READ_ONCE(kfd_dev_mapping); + + if (mapping) + unmap_mapping_range(mapping, holebegin, holelen, 1); +} + static inline struct kfd_process_device *kfd_lock_pdd_by_id(struct kfd_process *p, __u32 gpu_id) { struct kfd_process_device *pdd; @@ -133,6 +148,13 @@ static int kfd_open(struct inode *inode, struct file *filep) if (iminor(inode) != 0) return -ENODEV; + /* + * /dev/kfd is a single chardev so all opens share one inode. Cache + * its address_space on the first open for use by the reset path. + */ + if (!READ_ONCE(kfd_dev_mapping)) + cmpxchg(&kfd_dev_mapping, NULL, inode->i_mapping); + is_32bit_user_mode = in_compat_syscall(); if (is_32bit_user_mode) { diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 7b5b12206919..d5b07789eda4 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -395,6 +395,7 @@ enum kfd_mempool { /* Character device interface */ int kfd_chardev_init(void); void kfd_chardev_exit(void); +void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen); /** * enum kfd_unmap_queues_filter - Enum for queue filters. -- cgit v1.2.3 From 893fea60f8393fc99fa522f1718690421a5f9951 Mon Sep 17 00:00:00 2001 From: Xiang Liu Date: Mon, 11 May 2026 21:28:59 +0800 Subject: drm/amd/ras: Fix UMC error address allocation leak amdgpu_umc_handle_bad_pages() allocates err_data->err_addr before querying UMC error information. In the direct and firmware query paths, the pointer is reassigned to a fresh allocation before the original buffer is released, so the initial allocation is leaked on each handled event. Free the existing buffer before replacing it in those query paths so the function exit cleanup only owns the active allocation. Signed-off-by: Xiang Liu Reviewed-by: Stanley.Yang Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher (cherry picked from commit 911b1bdd22c3712a22b60fcc58f7b9f2d07b0803) --- drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c index 0238c2798de4..b8ed931f8a40 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_umc.c @@ -130,6 +130,7 @@ void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, if (adev->umc.ras && adev->umc.ras->ras_block.hw_ops && adev->umc.ras->ras_block.hw_ops->query_ras_error_address && adev->umc.max_ras_err_cnt_per_query) { + kfree(err_data->err_addr); err_data->err_addr = kzalloc_objs(struct eeprom_table_record, adev->umc.max_ras_err_cnt_per_query); @@ -160,6 +161,7 @@ void amdgpu_umc_handle_bad_pages(struct amdgpu_device *adev, if (adev->umc.ras && adev->umc.ras->ecc_info_query_ras_error_address && adev->umc.max_ras_err_cnt_per_query) { + kfree(err_data->err_addr); err_data->err_addr = kzalloc_objs(struct eeprom_table_record, adev->umc.max_ras_err_cnt_per_query); -- cgit v1.2.3 From cd7cfcdb4dd45c79ef6825a3dc918782f604f5ed Mon Sep 17 00:00:00 2001 From: Ce Sun Date: Mon, 11 May 2026 18:04:57 +0800 Subject: drm/amdgpu: avoid integer overflow in VA range check The original addition operation in 64-bit unsigned type may encounter overflow situations. To prevent such issues and safely reject invalid inputs, the check_add_overflow() function is used. Signed-off-by: Ce Sun Reviewed-by: Tao Zhou Signed-off-by: Alex Deucher (cherry picked from commit cc768f4dd0bb9083c813683eeec44fc23921f771) --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 23f2304ee7e0..123d4a09114d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -825,7 +825,7 @@ int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data, struct drm_syncobj *timeline_syncobj = NULL; struct dma_fence_chain *timeline_chain = NULL; struct drm_exec exec; - uint64_t vm_size; + uint64_t vm_size, tmp; int r = 0; /* Validate virtual address range against reserved regions. */ @@ -849,7 +849,7 @@ int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data, vm_size = adev->vm_manager.max_pfn * AMDGPU_GPU_PAGE_SIZE; vm_size -= AMDGPU_VA_RESERVED_TOP; - if (args->va_address + args->map_size > vm_size) { + if (check_add_overflow(args->va_address, args->map_size, &tmp) || tmp > vm_size) { dev_dbg(dev->dev, "va_address 0x%llx is in top reserved area 0x%llx\n", args->va_address + args->map_size, vm_size); -- cgit v1.2.3 From b6a28b77b88e776a8cc7739005718e03c4c9be57 Mon Sep 17 00:00:00 2001 From: Sunil Khatri Date: Wed, 13 May 2026 13:29:35 +0530 Subject: drm/amdgpu: userq_va_mapped should remain true once done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple queues needs these bo_va objects belonging to the same uq_mgr. So once they are mapped lets not unmap them as at any point of time any of the queues might be using it. Also userq_va_mapped should be a boolean than atomic. Signed-off-by: Sunil Khatri Reviewed-by: Christian König Signed-off-by: Alex Deucher (cherry picked from commit 5c02889ea22575c3bcfdf212e65fac316cbc6c6a) --- drivers/gpu/drm/amd/amdgpu/amdgpu_object.h | 3 ++- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 16 ++++------------ drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h index 912c9afaf9e1..4d68732d6223 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_object.h @@ -96,7 +96,8 @@ struct amdgpu_bo_va { * if non-zero, cannot unmap from GPU because user queues may still access it */ unsigned int queue_refcount; - atomic_t userq_va_mapped; + /* Indicates if this buffer is mapped for any user queue. Once set, never reset. */ + bool userq_va_mapped; }; struct amdgpu_bo { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index 6111f6858d2d..f677a9f2d4ad 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -227,7 +227,7 @@ static int amdgpu_userq_buffer_va_list_add(struct amdgpu_usermode_queue *queue, INIT_LIST_HEAD(&va_cursor->list); va_cursor->gpu_addr = addr; - atomic_set(&va_map->bo_va->userq_va_mapped, 1); + va_map->bo_va->userq_va_mapped = true; list_add(&va_cursor->list, &queue->userq_va_list); return 0; @@ -274,7 +274,7 @@ static bool amdgpu_userq_buffer_va_mapped(struct amdgpu_vm *vm, u64 addr) dma_resv_assert_held(vm->root.bo->tbo.base.resv); mapping = amdgpu_vm_bo_lookup_mapping(vm, addr); - if (!IS_ERR_OR_NULL(mapping) && atomic_read(&mapping->bo_va->userq_va_mapped)) + if (!IS_ERR_OR_NULL(mapping) && mapping->bo_va->userq_va_mapped) r = true; else r = false; @@ -300,15 +300,6 @@ static bool amdgpu_userq_buffer_vas_mapped(struct amdgpu_usermode_queue *queue) return false; } -static void amdgpu_userq_buffer_va_list_del(struct amdgpu_bo_va_mapping *mapping, - struct amdgpu_userq_va_cursor *va_cursor) -{ - if (mapping) - atomic_set(&mapping->bo_va->userq_va_mapped, 0); - list_del(&va_cursor->list); - kfree(va_cursor); -} - static void amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, struct amdgpu_usermode_queue *queue) { @@ -323,7 +314,8 @@ static void amdgpu_userq_buffer_vas_list_cleanup(struct amdgpu_device *adev, if (mapping) dev_dbg(adev->dev, "delete the userq:%p va:%llx\n", queue, va_cursor->gpu_addr); - amdgpu_userq_buffer_va_list_del(mapping, va_cursor); + list_del(&va_cursor->list); + kfree(va_cursor); } } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index 9ba9de16a27a..fccd758b6699 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2002,7 +2002,7 @@ int amdgpu_vm_bo_unmap(struct amdgpu_device *adev, * during user requests GEM unmap IOCTL except for forcing the unmap * from user space. */ - if (unlikely(atomic_read(&bo_va->userq_va_mapped) > 0)) + if (unlikely(bo_va->userq_va_mapped)) amdgpu_userq_gem_va_unmap_validate(adev, mapping, saddr); list_del(&mapping->list); -- cgit v1.2.3 From d093c01d30fc32cb82458731c33fa2a787ff1e7b Mon Sep 17 00:00:00 2001 From: Vitaliy Triang3l Kuzmin Date: Sat, 16 May 2026 00:48:32 +0300 Subject: drm/radeon/evergreen_cs: Add missing NULL prefix check in surface check 'evergreen_surface_check' is called with a NULL warning prefix when handling potentially recoverable issues or just to compute the alignment requirements, and 'evergreen_surface_check' is called again in case of failure (with the correct prefix, as opposed to NULL), therefore, the initial check must not print a warning, because the surface may be accepted successfully after having been corrected, however if it isn't, the final check will print the warning anyway. The surface check functions specific to array modes already implement this behavior, but the 'evergreen_surface_check' function itself doesn't. This is also supposed to fix the "'%s' directive argument is null [-Werror=format-overflow=]" compiler warning. Fixes: 285484e2d55e ("drm/radeon: add support for evergreen/ni tiling informations v11") Reported-by: Arnd Bergmann Signed-off-by: Vitaliy Triang3l Kuzmin Signed-off-by: Alex Deucher (cherry picked from commit e20ea411c99f6968af35fd03e9ee21f70d799144) --- drivers/gpu/drm/radeon/evergreen_cs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/radeon/evergreen_cs.c b/drivers/gpu/drm/radeon/evergreen_cs.c index 3142ef4da7f4..9196f85db9ce 100644 --- a/drivers/gpu/drm/radeon/evergreen_cs.c +++ b/drivers/gpu/drm/radeon/evergreen_cs.c @@ -312,8 +312,10 @@ static int evergreen_surface_check(struct radeon_cs_parser *p, case ARRAY_2D_TILED_THIN1: return evergreen_surface_check_2d(p, surf, prefix); default: - dev_warn(p->dev, "%s:%d %s invalid array mode %d\n", - __func__, __LINE__, prefix, surf->mode); + if (prefix) { + dev_warn(p->dev, "%s:%d %s invalid array mode %d\n", + __func__, __LINE__, prefix, surf->mode); + } return -EINVAL; } return -EINVAL; -- cgit v1.2.3 From b6fe4ff340560ecf39e10733366f85832550699a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Mon, 27 Apr 2026 16:31:31 +0200 Subject: drm/amdgpu: fix handling in amdgpu_userq_create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Well mostly the same issues the other code had as well: 1. Memory allocation while holding the userq_mutex lock is forbidden! 2. Things were created/started/published in the wrong order. 3. The reset lock was taken in the wrong order and seems to be unecessary in the first place. 4. Error messages on invalid input parameters can spam the logs. 5. Error messages on memory allocation failures are usually superflous as well. Signed-off-by: Christian König Reviewed-by: Sunil Khatri Reviewed-by: Prike Liang Signed-off-by: Alex Deucher (cherry picked from commit 89e50de5654dbe7a137e03d78629542e17ba7202) --- drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c | 118 +++++++++++++----------------- 1 file changed, 52 insertions(+), 66 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index f677a9f2d4ad..f070ea37d918 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -708,14 +708,14 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) const struct amdgpu_userq_funcs *uq_funcs; struct amdgpu_usermode_queue *queue; struct amdgpu_db_info db_info; - bool skip_map_queue; - u32 qid; uint64_t index; - int r = 0; - int priority = - (args->in.flags & AMDGPU_USERQ_CREATE_FLAGS_QUEUE_PRIORITY_MASK) >> - AMDGPU_USERQ_CREATE_FLAGS_QUEUE_PRIORITY_SHIFT; + int priority; + u32 qid; + int r; + priority = + (args->in.flags & AMDGPU_USERQ_CREATE_FLAGS_QUEUE_PRIORITY_MASK) + >> AMDGPU_USERQ_CREATE_FLAGS_QUEUE_PRIORITY_SHIFT; r = amdgpu_userq_priority_permit(filp, priority); if (r) return r; @@ -728,40 +728,43 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) uq_funcs = adev->userq_funcs[args->in.ip_type]; if (!uq_funcs) { - drm_file_err(uq_mgr->file, "Usermode queue is not supported for this IP (%u)\n", - args->in.ip_type); r = -EINVAL; goto err_pm_runtime; } queue = kzalloc_obj(struct amdgpu_usermode_queue); if (!queue) { - drm_file_err(uq_mgr->file, "Failed to allocate memory for queue\n"); r = -ENOMEM; goto err_pm_runtime; } + kref_init(&queue->refcount); INIT_LIST_HEAD(&queue->userq_va_list); queue->doorbell_handle = args->in.doorbell_handle; queue->queue_type = args->in.ip_type; queue->vm = &fpriv->vm; queue->priority = priority; - - db_info.queue_type = queue->queue_type; - db_info.doorbell_handle = queue->doorbell_handle; - db_info.db_obj = &queue->db_obj; - db_info.doorbell_offset = args->in.doorbell_offset; - queue->userq_mgr = uq_mgr; + INIT_DELAYED_WORK(&queue->hang_detect_work, + amdgpu_userq_hang_detect_work); - /* Validate the userq virtual address.*/ - r = amdgpu_bo_reserve(fpriv->vm.root.bo, false); + mutex_init(&queue->fence_drv_lock); + xa_init_flags(&queue->fence_drv_xa, XA_FLAGS_ALLOC); + r = amdgpu_userq_fence_driver_alloc(adev, &queue->fence_drv); if (r) goto free_queue; - if (amdgpu_userq_input_va_validate(adev, queue, args->in.queue_va, args->in.queue_size) || - amdgpu_userq_input_va_validate(adev, queue, args->in.rptr_va, AMDGPU_GPU_PAGE_SIZE) || - amdgpu_userq_input_va_validate(adev, queue, args->in.wptr_va, AMDGPU_GPU_PAGE_SIZE)) { + /* Make sure the queue can actually run with those virtual addresses. */ + r = amdgpu_bo_reserve(fpriv->vm.root.bo, false); + if (r) + goto free_fence_drv; + + if (amdgpu_userq_input_va_validate(adev, queue, args->in.queue_va, + args->in.queue_size) || + amdgpu_userq_input_va_validate(adev, queue, args->in.rptr_va, + AMDGPU_GPU_PAGE_SIZE) || + amdgpu_userq_input_va_validate(adev, queue, args->in.wptr_va, + AMDGPU_GPU_PAGE_SIZE)) { r = -EINVAL; amdgpu_bo_unreserve(fpriv->vm.root.bo); goto clean_mapping; @@ -769,6 +772,10 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) amdgpu_bo_unreserve(fpriv->vm.root.bo); /* Convert relative doorbell offset into absolute doorbell index */ + db_info.queue_type = queue->queue_type; + db_info.doorbell_handle = queue->doorbell_handle; + db_info.db_obj = &queue->db_obj; + db_info.doorbell_offset = args->in.doorbell_offset; index = amdgpu_userq_get_doorbell_index(uq_mgr, &db_info, filp); if (index == (uint64_t)-EINVAL) { drm_file_err(uq_mgr->file, "Failed to get doorbell for queue\n"); @@ -777,85 +784,64 @@ amdgpu_userq_create(struct drm_file *filp, union drm_amdgpu_userq *args) } queue->doorbell_index = index; - mutex_init(&queue->fence_drv_lock); - xa_init_flags(&queue->fence_drv_xa, XA_FLAGS_ALLOC); - r = amdgpu_userq_fence_driver_alloc(adev, &queue->fence_drv); - if (r) { - drm_file_err(uq_mgr->file, "Failed to alloc fence driver\n"); - goto clean_mapping; - } - r = uq_funcs->mqd_create(queue, &args->in); if (r) { drm_file_err(uq_mgr->file, "Failed to create Queue\n"); - goto clean_fence_driver; + goto clean_mapping; } /* Update VM owner at userq submit-time for page-fault attribution. */ amdgpu_vm_set_task_info(&fpriv->vm); + r = xa_err(xa_store_irq(&adev->userq_doorbell_xa, index, queue, + GFP_KERNEL)); + if (r) + goto clean_mqd; + amdgpu_userq_ensure_ev_fence(&fpriv->userq_mgr, &fpriv->evf_mgr); /* don't map the queue if scheduling is halted */ - if (adev->userq_halt_for_enforce_isolation && - ((queue->queue_type == AMDGPU_HW_IP_GFX) || - (queue->queue_type == AMDGPU_HW_IP_COMPUTE))) - skip_map_queue = true; - else - skip_map_queue = false; - if (!skip_map_queue) { + if (!adev->userq_halt_for_enforce_isolation || + ((queue->queue_type != AMDGPU_HW_IP_GFX) && + (queue->queue_type != AMDGPU_HW_IP_COMPUTE))) { r = amdgpu_userq_map_helper(queue); if (r) { drm_file_err(uq_mgr->file, "Failed to map Queue\n"); - goto clean_mqd; + mutex_unlock(&uq_mgr->userq_mutex); + goto clean_doorbell; } } - /* drop this refcount during queue destroy */ - kref_init(&queue->refcount); - - /* Wait for mode-1 reset to complete */ - down_read(&adev->reset_domain->sem); + atomic_inc(&uq_mgr->userq_count[queue->queue_type]); + mutex_unlock(&uq_mgr->userq_mutex); r = xa_alloc(&uq_mgr->userq_xa, &qid, queue, - XA_LIMIT(1, AMDGPU_MAX_USERQ_COUNT), GFP_KERNEL); - if (r) { - if (!skip_map_queue) - amdgpu_userq_unmap_helper(queue); - r = -ENOMEM; - goto clean_reset_domain; - } - - r = xa_err(xa_store_irq(&adev->userq_doorbell_xa, index, queue, GFP_KERNEL)); + XA_LIMIT(1, AMDGPU_MAX_USERQ_COUNT), + GFP_KERNEL); if (r) { - xa_erase(&uq_mgr->userq_xa, qid); - if (!skip_map_queue) - amdgpu_userq_unmap_helper(queue); - goto clean_reset_domain; + /* + * This drops the last reference which should take care of + * all cleanup. + */ + amdgpu_userq_put(queue); + return r; } - up_read(&adev->reset_domain->sem); amdgpu_debugfs_userq_init(filp, queue, qid); - INIT_DELAYED_WORK(&queue->hang_detect_work, - amdgpu_userq_hang_detect_work); - args->out.queue_id = qid; - atomic_inc(&uq_mgr->userq_count[queue->queue_type]); - mutex_unlock(&uq_mgr->userq_mutex); return 0; -clean_reset_domain: - up_read(&adev->reset_domain->sem); +clean_doorbell: + xa_erase_irq(&adev->userq_doorbell_xa, index); clean_mqd: - mutex_unlock(&uq_mgr->userq_mutex); uq_funcs->mqd_destroy(queue); -clean_fence_driver: - amdgpu_userq_fence_driver_free(queue); clean_mapping: amdgpu_bo_reserve(fpriv->vm.root.bo, true); amdgpu_userq_buffer_vas_list_cleanup(adev, queue); amdgpu_bo_unreserve(fpriv->vm.root.bo); mutex_destroy(&queue->fence_drv_lock); +free_fence_drv: + amdgpu_userq_fence_driver_free(queue); free_queue: kfree(queue); err_pm_runtime: -- cgit v1.2.3 From c35cb4fc7231702d1e9952aec1a442f3e27df6f5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 15 May 2026 19:03:59 +0200 Subject: ACPI: battery: Fix system wakeup on critical battery status Commit 0a869409a981 ("ACPI: battery: Convert the driver to a platform one") changed the parent of the battery wakeup source to the platform device used for driver binding, but it forgot to update the acpi_pm_wakeup_event() call in acpi_battery_update() accordingly. Do it now to unbreak waking up the system on critical battery status during suspend-to-idle and during transitions to ACPI S3/S4. Fixes: 0a869409a981 ("ACPI: battery: Convert the driver to a platform one") Signed-off-by: Rafael J. Wysocki Cc: 7.0+ # 7.0+ Link: https://patch.msgid.link/12898712.O9o76ZdvQC@rafael.j.wysocki --- drivers/acpi/battery.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index d4ae21a71007..b82dd67d98c9 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -94,6 +94,7 @@ struct acpi_battery { struct power_supply *bat; struct power_supply_desc bat_desc; struct acpi_device *device; + struct device *phys_dev; struct notifier_block pm_nb; struct list_head list; unsigned long update_time; @@ -1033,7 +1034,7 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume) if ((battery->state & ACPI_BATTERY_STATE_CRITICAL) || (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) && (battery->capacity_now <= battery->alarm))) - acpi_pm_wakeup_event(&battery->device->dev); + acpi_pm_wakeup_event(battery->phys_dev); return result; } @@ -1231,6 +1232,7 @@ static int acpi_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, battery); + battery->phys_dev = &pdev->dev; battery->device = device; result = devm_mutex_init(&pdev->dev, &battery->update_lock); -- cgit v1.2.3 From 01f99f8c4a0adec6875f192702a57c5e88978af5 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Wed, 13 May 2026 14:33:23 -0300 Subject: RDMA/core: Move the _ib_copy_validate_udata* functions to ib_core_uverbs It was incorrect to place them in uverbs_ioctl because that makes every driver depends on ib_uverbs.ko, which is undesired. ib_core_uverbs.c is for functions used by alot of drivers that are linked into ib_core instead. Fixes: 1de9287ece44 ("RDMA: Add ib_copy_validate_udata_in()") Link: https://patch.msgid.link/r/1-v1-045258567bd6+9fe-ib_uverbs_support_ko_jgg@nvidia.com Signed-off-by: Jason Gunthorpe --- drivers/infiniband/core/ib_core_uverbs.c | 89 ++++++++++++++++++++++ drivers/infiniband/core/uverbs.h | 35 +++++++++ drivers/infiniband/core/uverbs_ioctl.c | 122 ------------------------------- 3 files changed, 124 insertions(+), 122 deletions(-) (limited to 'drivers') diff --git a/drivers/infiniband/core/ib_core_uverbs.c b/drivers/infiniband/core/ib_core_uverbs.c index 1f7a5c119cc9..685030e0c60f 100644 --- a/drivers/infiniband/core/ib_core_uverbs.c +++ b/drivers/infiniband/core/ib_core_uverbs.c @@ -9,6 +9,7 @@ #include #include "uverbs.h" #include "core_priv.h" +#include "rdma_core.h" MODULE_IMPORT_NS("DMA_BUF"); @@ -416,3 +417,91 @@ struct ib_device *rdma_udata_to_dev(struct ib_udata *udata) } EXPORT_SYMBOL(rdma_udata_to_dev); +#if IS_ENABLED(CONFIG_INFINIBAND_USER_ACCESS) +uverbs_api_ioctl_handler_fn uverbs_get_handler_fn(struct ib_udata *udata) +{ + struct uverbs_attr_bundle *bundle = + rdma_udata_to_uverbs_attr_bundle(udata); + struct bundle_priv *pbundle = + container_of(&bundle->hdr, struct bundle_priv, bundle); + + lockdep_assert_held(&bundle->ufile->device->disassociate_srcu); + + return srcu_dereference(pbundle->method_elm->handler, + &bundle->ufile->device->disassociate_srcu); +} + +int _ib_copy_validate_udata_in(struct ib_udata *udata, void *req, + size_t kernel_size, size_t minimum_size) +{ + int err; + + if (udata->inlen < minimum_size) { + ibdev_dbg( + rdma_udata_to_dev(udata), + "System call driver input udata too small (%zu < %zu) for ioctl %ps called by %pSR\n", + udata->inlen, minimum_size, + uverbs_get_handler_fn(udata), + __builtin_return_address(0)); + return -EINVAL; + } + + err = copy_struct_from_user(req, kernel_size, udata->inbuf, + udata->inlen); + if (err) { + if (err == -E2BIG) { + ibdev_dbg( + rdma_udata_to_dev(udata), + "System call driver input udata not zero from %zu -> %zu for ioctl %ps called by %pSR\n", + minimum_size, udata->inlen, + uverbs_get_handler_fn(udata), + __builtin_return_address(0)); + return -EOPNOTSUPP; + } + ibdev_dbg( + rdma_udata_to_dev(udata), + "System call driver input udata EFAULT for ioctl %ps called by %pSR\n", + uverbs_get_handler_fn(udata), + __builtin_return_address(0)); + return err; + } + return 0; +} +EXPORT_SYMBOL(_ib_copy_validate_udata_in); + +int _ib_copy_validate_udata_cm_fail(struct ib_udata *udata, u64 req_cm, + u64 valid_cm) +{ + ibdev_dbg( + rdma_udata_to_dev(udata), + "System call driver input udata has unsupported comp_mask %llx & ~%llx = %llx for ioctl %ps called by %pSR\n", + req_cm, valid_cm, req_cm & ~valid_cm, + uverbs_get_handler_fn(udata), __builtin_return_address(0)); + return -EOPNOTSUPP; +} +EXPORT_SYMBOL(_ib_copy_validate_udata_cm_fail); + +int _ib_respond_udata(struct ib_udata *udata, const void *src, size_t len) +{ + size_t copy_len; + + /* 0 length copy_len is a NOP for copy_to_user() and doesn't fail. */ + copy_len = min(len, udata->outlen); + if (copy_to_user(udata->outbuf, src, copy_len)) + goto err_fault; + if (copy_len < udata->outlen) { + if (clear_user(udata->outbuf + copy_len, + udata->outlen - copy_len)) + goto err_fault; + } + return 0; +err_fault: + ibdev_dbg( + rdma_udata_to_dev(udata), + "System call driver out udata has EFAULT (%zu into %zu) for ioctl %ps called by %pSR\n", + len, udata->outlen, uverbs_get_handler_fn(udata), + __builtin_return_address(0)); + return -EFAULT; +} +EXPORT_SYMBOL(_ib_respond_udata); +#endif diff --git a/drivers/infiniband/core/uverbs.h b/drivers/infiniband/core/uverbs.h index 6d4295277e0e..a74a2dff1301 100644 --- a/drivers/infiniband/core/uverbs.h +++ b/drivers/infiniband/core/uverbs.h @@ -229,6 +229,41 @@ int uverbs_dealloc_mw(struct ib_mw *mw); void ib_uverbs_detach_umcast(struct ib_qp *qp, struct ib_uqp_object *uobj); +struct bundle_alloc_head { + struct_group_tagged(bundle_alloc_head_hdr, hdr, + struct bundle_alloc_head *next; + ); + u8 data[]; +}; + +struct bundle_priv { + /* Must be first */ + struct bundle_alloc_head_hdr alloc_head; + struct bundle_alloc_head *allocated_mem; + size_t internal_avail; + size_t internal_used; + + struct radix_tree_root *radix; + const struct uverbs_api_ioctl_method *method_elm; + void __rcu **radix_slots; + unsigned long radix_slots_len; + u32 method_key; + + struct ib_uverbs_attr __user *user_attrs; + struct ib_uverbs_attr *uattrs; + + DECLARE_BITMAP(uobj_finalize, UVERBS_API_ATTR_BKEY_LEN); + DECLARE_BITMAP(spec_finalize, UVERBS_API_ATTR_BKEY_LEN); + DECLARE_BITMAP(uobj_hw_obj_valid, UVERBS_API_ATTR_BKEY_LEN); + + /* + * Must be last. bundle ends in a flex array which overlaps + * internal_buffer. + */ + struct uverbs_attr_bundle_hdr bundle; + u64 internal_buffer[32]; +}; + long ib_uverbs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); struct ib_uverbs_flow_spec { diff --git a/drivers/infiniband/core/uverbs_ioctl.c b/drivers/infiniband/core/uverbs_ioctl.c index b61af625e679..33feb88d652b 100644 --- a/drivers/infiniband/core/uverbs_ioctl.c +++ b/drivers/infiniband/core/uverbs_ioctl.c @@ -35,54 +35,6 @@ #include "rdma_core.h" #include "uverbs.h" -struct bundle_alloc_head { - struct_group_tagged(bundle_alloc_head_hdr, hdr, - struct bundle_alloc_head *next; - ); - u8 data[]; -}; - -struct bundle_priv { - /* Must be first */ - struct bundle_alloc_head_hdr alloc_head; - struct bundle_alloc_head *allocated_mem; - size_t internal_avail; - size_t internal_used; - - struct radix_tree_root *radix; - const struct uverbs_api_ioctl_method *method_elm; - void __rcu **radix_slots; - unsigned long radix_slots_len; - u32 method_key; - - struct ib_uverbs_attr __user *user_attrs; - struct ib_uverbs_attr *uattrs; - - DECLARE_BITMAP(uobj_finalize, UVERBS_API_ATTR_BKEY_LEN); - DECLARE_BITMAP(spec_finalize, UVERBS_API_ATTR_BKEY_LEN); - DECLARE_BITMAP(uobj_hw_obj_valid, UVERBS_API_ATTR_BKEY_LEN); - - /* - * Must be last. bundle ends in a flex array which overlaps - * internal_buffer. - */ - struct uverbs_attr_bundle_hdr bundle; - u64 internal_buffer[32]; -}; - -uverbs_api_ioctl_handler_fn uverbs_get_handler_fn(struct ib_udata *udata) -{ - struct uverbs_attr_bundle *bundle = - rdma_udata_to_uverbs_attr_bundle(udata); - struct bundle_priv *pbundle = - container_of(&bundle->hdr, struct bundle_priv, bundle); - - lockdep_assert_held(&bundle->ufile->device->disassociate_srcu); - - return srcu_dereference(pbundle->method_elm->handler, - &bundle->ufile->device->disassociate_srcu); -} - /* * Each method has an absolute minimum amount of memory it needs to allocate, * precompute that amount and determine if the onstack memory can be used or @@ -860,77 +812,3 @@ void uverbs_finalize_uobj_create(const struct uverbs_attr_bundle *bundle, pbundle->uobj_hw_obj_valid); } EXPORT_SYMBOL(uverbs_finalize_uobj_create); - -int _ib_copy_validate_udata_in(struct ib_udata *udata, void *req, - size_t kernel_size, size_t minimum_size) -{ - int err; - - if (udata->inlen < minimum_size) { - ibdev_dbg( - rdma_udata_to_dev(udata), - "System call driver input udata too small (%zu < %zu) for ioctl %ps called by %pSR\n", - udata->inlen, minimum_size, - uverbs_get_handler_fn(udata), - __builtin_return_address(0)); - return -EINVAL; - } - - err = copy_struct_from_user(req, kernel_size, udata->inbuf, - udata->inlen); - if (err) { - if (err == -E2BIG) { - ibdev_dbg( - rdma_udata_to_dev(udata), - "System call driver input udata not zero from %zu -> %zu for ioctl %ps called by %pSR\n", - minimum_size, udata->inlen, - uverbs_get_handler_fn(udata), - __builtin_return_address(0)); - return -EOPNOTSUPP; - } - ibdev_dbg( - rdma_udata_to_dev(udata), - "System call driver input udata EFAULT for ioctl %ps called by %pSR\n", - uverbs_get_handler_fn(udata), - __builtin_return_address(0)); - return err; - } - return 0; -} -EXPORT_SYMBOL(_ib_copy_validate_udata_in); - -int _ib_copy_validate_udata_cm_fail(struct ib_udata *udata, u64 req_cm, - u64 valid_cm) -{ - ibdev_dbg( - rdma_udata_to_dev(udata), - "System call driver input udata has unsupported comp_mask %llx & ~%llx = %llx for ioctl %ps called by %pSR\n", - req_cm, valid_cm, req_cm & ~valid_cm, - uverbs_get_handler_fn(udata), __builtin_return_address(0)); - return -EOPNOTSUPP; -} -EXPORT_SYMBOL(_ib_copy_validate_udata_cm_fail); - -int _ib_respond_udata(struct ib_udata *udata, const void *src, size_t len) -{ - size_t copy_len; - - /* 0 length copy_len is a NOP for copy_to_user() and doesn't fail. */ - copy_len = min(len, udata->outlen); - if (copy_to_user(udata->outbuf, src, copy_len)) - goto err_fault; - if (copy_len < udata->outlen) { - if (clear_user(udata->outbuf + copy_len, - udata->outlen - copy_len)) - goto err_fault; - } - return 0; -err_fault: - ibdev_dbg( - rdma_udata_to_dev(udata), - "System call driver out udata has EFAULT (%zu into %zu) for ioctl %ps called by %pSR\n", - len, udata->outlen, uverbs_get_handler_fn(udata), - __builtin_return_address(0)); - return -EFAULT; -} -EXPORT_SYMBOL(_ib_respond_udata); -- cgit v1.2.3 From 7122ff96068a03595bde2fbafaca82ca2ed8084e Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Wed, 13 May 2026 12:00:16 -0300 Subject: RDMA/core: Do not read wild stack memory in uverbs_get_handler_fn() Sashiko points out the legacy write path in ib_uverbs_write() does allocate a struct uverbs_attr_bundle, but it doesn't wrap it in a bundle_priv so downcasting here isn't safe. Instead lift the method_elm out of the bundle_priv and use it for the debug function. The legacy write path will leave it set as NULL since the write method_elm uses a different type. Cc: stable@vger.kernel.org Fixes: 1de9287ece44 ("RDMA: Add ib_copy_validate_udata_in()") Signed-off-by: Jason Gunthorpe Signed-off-by: Leon Romanovsky --- drivers/infiniband/core/ib_core_uverbs.c | 4 +--- drivers/infiniband/core/uverbs.h | 1 - drivers/infiniband/core/uverbs_ioctl.c | 26 ++++++++++++++------------ 3 files changed, 15 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/infiniband/core/ib_core_uverbs.c b/drivers/infiniband/core/ib_core_uverbs.c index 685030e0c60f..8a0e6fa2a528 100644 --- a/drivers/infiniband/core/ib_core_uverbs.c +++ b/drivers/infiniband/core/ib_core_uverbs.c @@ -422,12 +422,10 @@ uverbs_api_ioctl_handler_fn uverbs_get_handler_fn(struct ib_udata *udata) { struct uverbs_attr_bundle *bundle = rdma_udata_to_uverbs_attr_bundle(udata); - struct bundle_priv *pbundle = - container_of(&bundle->hdr, struct bundle_priv, bundle); lockdep_assert_held(&bundle->ufile->device->disassociate_srcu); - return srcu_dereference(pbundle->method_elm->handler, + return srcu_dereference(bundle->method_elm->handler, &bundle->ufile->device->disassociate_srcu); } diff --git a/drivers/infiniband/core/uverbs.h b/drivers/infiniband/core/uverbs.h index a74a2dff1301..f2e192b51e60 100644 --- a/drivers/infiniband/core/uverbs.h +++ b/drivers/infiniband/core/uverbs.h @@ -244,7 +244,6 @@ struct bundle_priv { size_t internal_used; struct radix_tree_root *radix; - const struct uverbs_api_ioctl_method *method_elm; void __rcu **radix_slots; unsigned long radix_slots_len; u32 method_key; diff --git a/drivers/infiniband/core/uverbs_ioctl.c b/drivers/infiniband/core/uverbs_ioctl.c index 33feb88d652b..2552a7efe2fb 100644 --- a/drivers/infiniband/core/uverbs_ioctl.c +++ b/drivers/infiniband/core/uverbs_ioctl.c @@ -397,13 +397,13 @@ static int ib_uverbs_run_method(struct bundle_priv *pbundle, struct uverbs_attr_bundle *bundle = container_of(&pbundle->bundle, struct uverbs_attr_bundle, hdr); size_t uattrs_size = array_size(sizeof(*pbundle->uattrs), num_attrs); - unsigned int destroy_bkey = pbundle->method_elm->destroy_bkey; + unsigned int destroy_bkey = bundle->method_elm->destroy_bkey; unsigned int i; int ret; /* See uverbs_disassociate_api() */ handler = srcu_dereference( - pbundle->method_elm->handler, + bundle->method_elm->handler, &pbundle->bundle.ufile->device->disassociate_srcu); if (!handler) return -EIO; @@ -421,12 +421,12 @@ static int ib_uverbs_run_method(struct bundle_priv *pbundle, } /* User space did not provide all the mandatory attributes */ - if (unlikely(!bitmap_subset(pbundle->method_elm->attr_mandatory, + if (unlikely(!bitmap_subset(bundle->method_elm->attr_mandatory, pbundle->bundle.attr_present, - pbundle->method_elm->key_bitmap_len))) + bundle->method_elm->key_bitmap_len))) return -EINVAL; - if (pbundle->method_elm->has_udata) + if (bundle->method_elm->has_udata) uverbs_fill_udata(bundle, &pbundle->bundle.driver_udata, UVERBS_ATTR_UHW_IN, UVERBS_ATTR_UHW_OUT); else @@ -451,7 +451,7 @@ static int ib_uverbs_run_method(struct bundle_priv *pbundle, * assume that the driver wrote to its UHW_OUT and flag userspace * appropriately. */ - if (!ret && pbundle->method_elm->has_udata) { + if (!ret && bundle->method_elm->has_udata) { const struct uverbs_attr *attr = uverbs_attr_get(bundle, UVERBS_ATTR_UHW_OUT); @@ -472,7 +472,7 @@ static int ib_uverbs_run_method(struct bundle_priv *pbundle, static void bundle_destroy(struct bundle_priv *pbundle, bool commit) { - unsigned int key_bitmap_len = pbundle->method_elm->key_bitmap_len; + unsigned int key_bitmap_len = pbundle->bundle.method_elm->key_bitmap_len; struct uverbs_attr_bundle *bundle = container_of(&pbundle->bundle, struct uverbs_attr_bundle, hdr); struct bundle_alloc_head *memblock; @@ -560,7 +560,7 @@ static int ib_uverbs_cmd_verbs(struct ib_uverbs_file *ufile, } /* Space for the pbundle->bundle.attrs flex array */ - pbundle->method_elm = method_elm; + pbundle->bundle.method_elm = method_elm; pbundle->method_key = attrs_iter.index; pbundle->bundle.ufile = ufile; pbundle->bundle.context = NULL; /* only valid if bundle has uobject */ @@ -569,10 +569,12 @@ static int ib_uverbs_cmd_verbs(struct ib_uverbs_file *ufile, pbundle->radix_slots_len = radix_tree_chunk_size(&attrs_iter); pbundle->user_attrs = user_attrs; - pbundle->internal_used = ALIGN(pbundle->method_elm->key_bitmap_len * - sizeof(*container_of(&pbundle->bundle, - struct uverbs_attr_bundle, hdr)->attrs), - sizeof(*pbundle->internal_buffer)); + pbundle->internal_used = ALIGN( + pbundle->bundle.method_elm->key_bitmap_len * + sizeof(*container_of(&pbundle->bundle, + struct uverbs_attr_bundle, hdr) + ->attrs), + sizeof(*pbundle->internal_buffer)); memset(pbundle->bundle.attr_present, 0, sizeof(pbundle->bundle.attr_present)); memset(pbundle->uobj_finalize, 0, sizeof(pbundle->uobj_finalize)); -- cgit v1.2.3 From c9a40f6531b81baa9619bcc2697ff86896afcce7 Mon Sep 17 00:00:00 2001 From: Shiraz Saleem Date: Tue, 12 May 2026 02:42:09 -0700 Subject: RDMA/mana_ib: Report max_msg_sz in mana_ib_query_port Report max_msg_sz for mana_ib, which is 16MB. Fixes: 4bda1d5332ec ("RDMA/mana_ib: Implement port parameters") Signed-off-by: Shiraz Saleem Signed-off-by: Konstantin Taranov Link: https://patch.msgid.link/20260512094209.264955-1-kotaranov@linux.microsoft.com Reviewed-by: Long Li Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mana/main.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/infiniband/hw/mana/main.c b/drivers/infiniband/hw/mana/main.c index ac5e75dd3494..afc2fc124fee 100644 --- a/drivers/infiniband/hw/mana/main.c +++ b/drivers/infiniband/hw/mana/main.c @@ -606,6 +606,7 @@ int mana_ib_query_port(struct ib_device *ibdev, u32 port, if (mana_ib_is_rnic(dev)) { props->gid_tbl_len = 16; props->ip_gids = true; + props->max_msg_sz = SZ_16M; if (port == 1) props->port_cap_flags = IB_PORT_CM_SUP; } -- cgit v1.2.3 From 5b74373390113fba798a76b483837029ab010fef Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Thu, 14 May 2026 19:38:34 +0800 Subject: RDMA/rtrs: Fix use-after-free in path file creation cleanup In the error path of rtrs_srv_create_path_files(), the sysfs root folders may already have been created and srv_path->kobj may already have been initialized. If a later step fails, the cleanup currently calls kobject_put(&srv_path->kobj) before rtrs_srv_destroy_once_sysfs_root_folders(srv_path). kobject_put() may drop the last reference to srv_path->kobj and invoke the release callback, rtrs_srv_release(), which frees srv_path. The following call to rtrs_srv_destroy_once_sysfs_root_folders(srv_path) then dereferences srv_path internally to access srv_path->srv, resulting in a use-after-free. This failure path is reached before rtrs_srv_create_path_files() returns success, so the successful-path lifetime handling is not involved. Fix this by destroying the sysfs root folders before calling kobject_put(&srv_path->kobj), so srv_path is still valid while the helper accesses it. This issue was found by a static analysis tool I am developing. Fixes: ae4c81644e91 ("RDMA/rtrs-srv: Rename rtrs_srv_sess to rtrs_srv_path") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260514113834.865530-1-lgs201920130244@gmail.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/ulp/rtrs/rtrs-srv-sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/infiniband/ulp/rtrs/rtrs-srv-sysfs.c b/drivers/infiniband/ulp/rtrs/rtrs-srv-sysfs.c index 51727c7d710c..9dd9141c86a5 100644 --- a/drivers/infiniband/ulp/rtrs/rtrs-srv-sysfs.c +++ b/drivers/infiniband/ulp/rtrs/rtrs-srv-sysfs.c @@ -295,8 +295,8 @@ remove_group: put_kobj: kobject_del(&srv_path->kobj); destroy_root: - kobject_put(&srv_path->kobj); rtrs_srv_destroy_once_sysfs_root_folders(srv_path); + kobject_put(&srv_path->kobj); return err; } -- cgit v1.2.3 From 33d35975cbead3fa6b738ee57e5e45e14fbe0886 Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Fri, 15 May 2026 14:31:03 +0000 Subject: net: pse-pd: fix sign on -ENOENT check in of_load_pse_pis() of_count_phandle_with_args() returns the count on success and a negative errno on failure, including -ENOENT when the "pairsets" property is absent. The existing comparison in of_load_pse_pis() checks against ENOENT (positive 2) instead of -ENOENT, so the branch is taken for any error return: legitimate DTs that omit "pairsets" trigger a spurious "wrong number of pairsets" error and probe fails with -EINVAL. Compare against -ENOENT so a missing "pairsets" property is correctly treated as "this PI has no pairsets, continue". Fixes: 9be9567a7c59 ("net: pse-pd: Add support for PSE PIs") Cc: stable@vger.kernel.org Signed-off-by: Jonas Jelonek Acked-by: Oleksij Rempel Link: https://patch.msgid.link/20260515143103.1721888-1-jelonek.jonas@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/pse-pd/pse_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 87aa4f4e9724..69dbdbde9d71 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -210,7 +210,7 @@ static int of_load_pse_pis(struct pse_controller_dev *pcdev) ret = of_load_pse_pi_pairsets(node, &pi, ret); if (ret) goto out; - } else if (ret != ENOENT) { + } else if (ret != -ENOENT) { dev_err(pcdev->dev, "error: wrong number of pairsets. Should be 1 or 2, got %d (%pOF)\n", ret, node); -- cgit v1.2.3 From 9b244c242bec48b37e82b89787afd6a4c43457e1 Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Fri, 15 May 2026 23:18:26 +0800 Subject: octeontx2-pf: avoid double free of pool->stack on AQ init failure otx2_pool_aq_init() frees pool->stack when mailbox sync or retry allocation fails, but leaves the pointer unchanged. Later, otx2_sq_aura_pool_init() unwinds the partial setup through otx2_aura_pool_free(), which frees pool->stack again. The CN20K-specific cn20k_pool_aq_init() implementation has the same bug in its corresponding error path. Set pool->stack to NULL immediately after the local free so the shared cleanup path does not free the same stack again while cleaning up partially initialized pool state. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc3. Runtime validation was not performed because reproducing this path requires OcteonTX2/CN20K hardware. Fixes: caa2da34fd25 ("octeontx2-pf: Initialize and config queues") Fixes: d322fbd17203 ("octeontx2-pf: Initialize cn20k specific aura and pool contexts") Cc: stable@vger.kernel.org Signed-off-by: Zilin Guan Signed-off-by: Dawei Feng Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260515151826.1005397-1-dawei.feng@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c | 2 ++ drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c | 2 ++ 2 files changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c index a5a8f4558717..dbf173196608 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/cn20k.c @@ -619,11 +619,13 @@ static int cn20k_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, err = otx2_sync_mbox_msg(&pfvf->mbox); if (err) { qmem_free(pfvf->dev, pool->stack); + pool->stack = NULL; return err; } aq = otx2_mbox_alloc_msg_npa_cn20k_aq_enq(&pfvf->mbox); if (!aq) { qmem_free(pfvf->dev, pool->stack); + pool->stack = NULL; return -ENOMEM; } } diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c index 971fcab1c248..3d253132a17f 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_common.c @@ -1482,11 +1482,13 @@ int otx2_pool_aq_init(struct otx2_nic *pfvf, u16 pool_id, err = otx2_sync_mbox_msg(&pfvf->mbox); if (err) { qmem_free(pfvf->dev, pool->stack); + pool->stack = NULL; return err; } aq = otx2_mbox_alloc_msg_npa_aq_enq(&pfvf->mbox); if (!aq) { qmem_free(pfvf->dev, pool->stack); + pool->stack = NULL; return -ENOMEM; } } -- cgit v1.2.3 From 960e77ce14a83ef7f226e8e4b4d75765633ba48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nerijus=20Bend=C5=BEi=C5=ABnas?= Date: Sat, 16 May 2026 18:02:51 +0300 Subject: net: phy: skip EEE advertisement write when autoneg is disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit genphy_c45_an_config_eee_aneg() writes the EEE advertisement to the auto-negotiation device's MMD register space (MDIO_MMD_AN, register MDIO_AN_EEE_ADV). These registers are read by the link partner only during auto-negotiation, so writing them while autoneg is disabled cannot influence the link. On some PHYs (e.g. Broadcom BCM54213PE) the write nevertheless reaches the chip and disturbs the receive datapath. Concretely, running ethtool -s eth0 speed 100 duplex full autoneg off ethtool --set-eee eth0 eee off leaves eth0 with TX working and RX completely silent on a Raspberry Pi 4 / CM4 board (bcmgenet + BCM54213PE in rgmii-rxid). Switching back to autoneg recovers the link. Prior to commit f26a29a038ee ("net: phy: ensure that genphy_c45_an_config_eee_aneg() sees new value of phydev->eee_cfg.eee_enabled"), the disable path was effectively a no-op because the helper read the stale eee_cfg.eee_enabled, so the underlying PHY behavior never surfaced. Bisected on rpi-6.12.y between commits 83943264 (good) and effcbc88 (bad) to f26a29a038ee. Fixes: f26a29a038ee ("net: phy: ensure that genphy_c45_an_config_eee_aneg() sees new value of phydev->eee_cfg.eee_enabled") Cc: stable@vger.kernel.org Signed-off-by: Nerijus Bendžiūnas Reviewed-by: Nicolai Buchwitz Tested-by: Nicolai Buchwitz Link: https://patch.msgid.link/20260516150251.879680-1-nerijus.bendziunas@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-c45.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index d48aa7231b37..126951741428 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -940,6 +940,14 @@ EXPORT_SYMBOL_GPL(genphy_c45_read_eee_abilities); */ int genphy_c45_an_config_eee_aneg(struct phy_device *phydev) { + /* Writing MMD AN advertisements while autoneg is disabled has no + * effect on link-partner negotiation, but on some PHYs (e.g. the + * Broadcom BCM54213PE) the write itself disturbs the receive + * datapath. Skip it. + */ + if (phydev->autoneg == AUTONEG_DISABLE) + return 0; + if (!phydev->eee_cfg.eee_enabled) { __ETHTOOL_DECLARE_LINK_MODE_MASK(adv) = {}; -- cgit v1.2.3 From 3655063e083889ed4b79b7dda9cec65478dce09a Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 18 May 2026 10:23:09 +0200 Subject: net: phy: honor eee_disabled_modes in phy_support_eee() phy_support_eee() copies supported_eee into advertising_eee unconditionally, overwriting any filtering applied during phy_probe() based on DT eee-broken-* properties or driver-populated eee_disabled_modes. MAC drivers that call phy_support_eee() after probe (e.g. bcmgenet, fec, lan743x, lan78xx, r8169) then cause the PHY to advertise EEE for modes the user marked as broken. The symptom is that ethtool --show-eee on the local interface reports "not supported" (supported & ~eee_disabled_modes is empty) while the link partner sees EEE negotiated and active. phy_probe() already filters advertising_eee via eee_disabled_modes after calling of_set_phy_eee_broken(). Apply the same mask in phy_support_eee() so the filtering survives the copy. Fixes: 49168d1980e2 ("net: phy: Add phy_support_eee() indicating MAC support EEE") Signed-off-by: Nicolai Buchwitz Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260518-devel-phy-support-eee-fix-v2-1-05b52626fa68@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy_device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index c2cdf1ae3542..83b074bc4a8f 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -2903,7 +2903,8 @@ EXPORT_SYMBOL_GPL(phy_advertise_eee_all); */ void phy_support_eee(struct phy_device *phydev) { - linkmode_copy(phydev->advertising_eee, phydev->supported_eee); + linkmode_andnot(phydev->advertising_eee, phydev->supported_eee, + phydev->eee_disabled_modes); phydev->eee_cfg.tx_lpi_enabled = true; phydev->eee_cfg.eee_enabled = true; -- cgit v1.2.3 From 8baa7506d793f0636e3f6f01b01ef7be19674d06 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 18 May 2026 10:23:10 +0200 Subject: net: phy: honor eee_disabled_modes in phy_advertise_eee_all() phy_advertise_eee_all() copies supported_eee into advertising_eee unconditionally, overwriting any filtering applied during phy_probe() based on DT eee-broken-* properties or driver-populated eee_disabled_modes. genphy_c45_ethtool_set_eee() calls this helper when user space passes an empty advertisement, undoing the filtering. Apply the same eee_disabled_modes mask in phy_advertise_eee_all() so the filtering survives the copy, matching the pattern in phy_probe() and phy_support_eee(). Fixes: b64691274f5d ("net: phy: add helper phy_advertise_eee_all") Signed-off-by: Nicolai Buchwitz Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260518-devel-phy-support-eee-fix-v2-2-05b52626fa68@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy_device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 83b074bc4a8f..3370eb822017 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -2877,7 +2877,8 @@ EXPORT_SYMBOL(phy_advertise_supported); */ void phy_advertise_eee_all(struct phy_device *phydev) { - linkmode_copy(phydev->advertising_eee, phydev->supported_eee); + linkmode_andnot(phydev->advertising_eee, phydev->supported_eee, + phydev->eee_disabled_modes); } EXPORT_SYMBOL_GPL(phy_advertise_eee_all); -- cgit v1.2.3 From 0e46b6635b03d29807f810c3b415c4755a3f958d Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Fri, 15 May 2026 21:29:05 +0000 Subject: pds_core: fix error handling in pdsc_devcmd_wait Fix two cases where pdsc_devcmd_wait() returns stale success from the completion register instead of an error: 1. FW crash: If firmware stops running, the wait loop breaks early with running=false. The condition "if ((!done || timeout) && running)" is false, so error handling is bypassed and stale status is returned. Check !running first and return -ENXIO. 2. Timeout: If a command times out, err is set to -ETIMEDOUT but then overwritten by pdsc_err_to_errno(status) which reads stale status. Return -ETIMEDOUT immediately after cleaning up. Both errors now propagate to pdsc_devcmd_locked() which queues health_work for recovery. Fixes: 45d76f492938 ("pds_core: set up device and adminq") Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260515212907.998028-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/dev.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c index 2e1d0d01d03a..bded6b33289c 100644 --- a/drivers/net/ethernet/amd/pds_core/dev.c +++ b/drivers/net/ethernet/amd/pds_core/dev.c @@ -162,12 +162,19 @@ static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) dev_dbg(dev, "DEVCMD %d %s after %ld secs\n", opcode, pdsc_devcmd_str(opcode), duration / HZ); - if ((!done || timeout) && running) { + if (!running) { + dev_err(dev, "DEVCMD %d %s fw not running\n", + opcode, pdsc_devcmd_str(opcode)); + pdsc_devcmd_clean(pdsc); + return -ENXIO; + } + + if (!done || timeout) { dev_err(dev, "DEVCMD %d %s timeout, done %d timeout %d max_seconds=%d\n", opcode, pdsc_devcmd_str(opcode), done, timeout, max_seconds); - err = -ETIMEDOUT; pdsc_devcmd_clean(pdsc); + return -ETIMEDOUT; } status = pdsc_devcmd_status(pdsc); -- cgit v1.2.3 From dc416e32baaeb620b9809e9e25fc7b30889686e9 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Fri, 15 May 2026 21:29:07 +0000 Subject: pds_core: fix debugfs_lookup dentry leak and error handling debugfs_lookup() returns a dentry with an elevated reference count that must be released with dput(). The current code discards the returned dentry without calling dput(), causing a reference leak on every firmware reset recovery. Additionally, when CONFIG_DEBUG_FS is disabled, debugfs_lookup() returns ERR_PTR(-ENODEV), not NULL. The current check passes for error pointers and would call dput() on an invalid pointer, causing a crash. Fixes: bc90fbe0c318 ("pds_core: Rework teardown/setup flow to be more common") Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260515212907.998028-3-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/debugfs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/amd/pds_core/debugfs.c b/drivers/net/ethernet/amd/pds_core/debugfs.c index 04c5e3abd8d7..810a0cd9bcac 100644 --- a/drivers/net/ethernet/amd/pds_core/debugfs.c +++ b/drivers/net/ethernet/amd/pds_core/debugfs.c @@ -64,9 +64,14 @@ DEFINE_SHOW_ATTRIBUTE(identity); void pdsc_debugfs_add_ident(struct pdsc *pdsc) { + struct dentry *dentry; + /* This file will already exist in the reset flow */ - if (debugfs_lookup("identity", pdsc->dentry)) + dentry = debugfs_lookup("identity", pdsc->dentry); + if (!IS_ERR_OR_NULL(dentry)) { + dput(dentry); return; + } debugfs_create_file("identity", 0400, pdsc->dentry, pdsc, &identity_fops); -- cgit v1.2.3 From dd7b6a8671939708cc4b7a46786d8c11297e8f69 Mon Sep 17 00:00:00 2001 From: Shitalkumar Gandhi Date: Mon, 11 May 2026 09:57:32 +0530 Subject: wifi: wilc1000: fix dma_buffer leak on bus acquire failure wilc_wlan_firmware_download() allocates dma_buffer with kmalloc() at the top of the function and uses a 'fail:' label to free it via kfree(dma_buffer) on error. All later error paths correctly use 'goto fail' to route through this cleanup. However, the early failure path after the first acquire_bus() call uses a bare 'return ret;', which leaks dma_buffer whenever the bus acquire fails. Replace the early return with goto fail so the existing cleanup path runs. Found via a custom Coccinelle semantic patch hunting for kmalloc'd locals leaked on early-return error paths in driver firmware-download code. Fixes: 1241c5650ff7 ("wifi: wilc1000: Fill in missing error handling") Signed-off-by: Shitalkumar Gandhi Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260511042732.998311-1-shitalkumar.gandhi@cambiumnetworks.com Signed-off-by: Johannes Berg --- drivers/net/wireless/microchip/wilc1000/wlan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/wireless/microchip/wilc1000/wlan.c b/drivers/net/wireless/microchip/wilc1000/wlan.c index 3fa8592eb250..4b116fe6f9ea 100644 --- a/drivers/net/wireless/microchip/wilc1000/wlan.c +++ b/drivers/net/wireless/microchip/wilc1000/wlan.c @@ -1265,7 +1265,7 @@ int wilc_wlan_firmware_download(struct wilc *wilc, const u8 *buffer, ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP); if (ret) - return ret; + goto fail; wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, ®); reg &= ~BIT(10); -- cgit v1.2.3 From dc334b9bf9d28070f7a3e413fead759abdce19ba Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:03:21 +0200 Subject: platform/x86: acer-wireless: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 acer-wireless driver. Fixes: f7e648027d7e ("platform/x86: acer-wireless: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/4746824.LvFx2qVVIh@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/acer-wireless.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c index f464b13a58af..fae8e5ad0f97 100644 --- a/drivers/platform/x86/acer-wireless.c +++ b/drivers/platform/x86/acer-wireless.c @@ -37,9 +37,14 @@ static void acer_wireless_notify(acpi_handle handle, u32 event, void *data) static int acer_wireless_probe(struct platform_device *pdev) { + struct acpi_device *adev; struct input_dev *idev; int ret; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + idev = devm_input_allocate_device(&pdev->dev); if (!idev) return -ENOMEM; @@ -57,8 +62,7 @@ static int acer_wireless_probe(struct platform_device *pdev) if (ret) return ret; - return acpi_dev_install_notify_handler(ACPI_COMPANION(&pdev->dev), - ACPI_DEVICE_NOTIFY, + return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, acer_wireless_notify, &pdev->dev); } -- cgit v1.2.3 From 08f29dc34d1cf39696d04bc1ff31538dc2d74c72 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:04:23 +0200 Subject: platform/x86: asus-laptop: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 asus-laptop driver. Fixes: ba19eb10170b ("platform/x86: asus-laptop: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/5083741.GXAFRqVoOG@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/asus-laptop.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index dbbb6292cd11..140ac8a10537 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1826,10 +1826,14 @@ static bool asus_device_present; static int asus_acpi_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; struct asus_laptop *asus; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + pr_notice("Asus Laptop Support version %s\n", ASUS_LAPTOP_VERSION); asus = kzalloc_obj(struct asus_laptop); -- cgit v1.2.3 From 8a17c62b02fc4baa02300b57a2ad882c7a06a984 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:05:07 +0200 Subject: platform/x86: dell/dell-rbtn: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 dell-rbtn driver. Fixes: 19ebacfb442b ("platform/x86: dell/dell-rbtn: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2276487.irdbgypaU6@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/dell/dell-rbtn.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c index 34af9f4ff741..180b8c6720e6 100644 --- a/drivers/platform/x86/dell/dell-rbtn.c +++ b/drivers/platform/x86/dell/dell-rbtn.c @@ -396,11 +396,15 @@ static void rbtn_cleanup(struct device *dev) static int rbtn_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct rbtn_data *rbtn_data; + struct acpi_device *device; enum rbtn_type type; int ret = 0; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + type = rbtn_check(device); if (type == RBTN_UNKNOWN) { dev_info(&pdev->dev, "Unknown device type\n"); -- cgit v1.2.3 From 814830394764cef79c506629edd55f072e0207d1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:05:47 +0200 Subject: platform/x86: eeepc-laptop: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 eeepc-laptop driver. Fixes: 079b59fd2d79 ("platform/x86: eeepc-laptop: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3056852.e9J7NaK4W3@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/eeepc-laptop.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 02a71095920e..d18a80907611 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -1363,10 +1363,14 @@ static bool eeepc_device_present; static int eeepc_acpi_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; struct eeepc_laptop *eeepc; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + pr_notice(EEEPC_LAPTOP_NAME "\n"); eeepc = kzalloc_obj(struct eeepc_laptop); if (!eeepc) -- cgit v1.2.3 From e99829a15989d58ffe1ee3e283e0a5eb8e41ee8b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:08:58 +0200 Subject: platform/x86: fujitsu: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add requisite ACPI_COMPANION() checks against NULL to the platform/x86 fujitsu-laptop driver. Fixes: 6da22b031a3c ("platform/x86: fujitsu: Convert laptop driver to a platform one") Fixes: d5c9212ccfaa ("platform/x86: fujitsu: Convert backlight driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Acked-by: Jonathan Woithe Link: https://patch.msgid.link/3430329.44csPzL39Z@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-laptop.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 2e265be2267e..54d0b9cec4d3 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -530,10 +530,14 @@ static void acpi_fujitsu_bl_notify(acpi_handle handle, u32 event, void *data) static int acpi_fujitsu_bl_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; struct fujitsu_bl *priv; int ret; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) return -ENODEV; @@ -993,10 +997,14 @@ static void acpi_fujitsu_laptop_notify(acpi_handle handle, u32 event, void *data static int acpi_fujitsu_laptop_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct fujitsu_laptop *priv; + struct acpi_device *device; int ret, i = 0; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; -- cgit v1.2.3 From ab743c6d7b118235f6d48a513a6560afbc5e615a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:09:58 +0200 Subject: platform/x86: fujitsu-tablet: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add requisite ACPI_COMPANION() checks against NULL to the platform/x86 fujitsu-tablet driver. Fixes: bd13b265d386 ("platform/x86: fujitsu-tablet: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/10861611.nUPlyArG6x@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/fujitsu-tablet.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index 8319df28e9b8..2f8c1b89cbca 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -445,10 +445,14 @@ static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data) static int acpi_fujitsu_probe(struct platform_device *pdev) { - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct acpi_device *adev; acpi_status status; int error; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, fujitsu_walk_resources, NULL); if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) -- cgit v1.2.3 From 51e91ad0a0ccb0906306c653f9e8b97a180d8608 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:11:00 +0200 Subject: platform/x86: intel/rst: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 intel/rst driver. Fixes: 163a68a31f74 ("platform/x86: intel/rst: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2051525.PYKUYFuaPT@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/rst.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/rst.c b/drivers/platform/x86/intel/rst.c index 4bd10927aad9..bb19f0d89305 100644 --- a/drivers/platform/x86/intel/rst.c +++ b/drivers/platform/x86/intel/rst.c @@ -102,9 +102,13 @@ static struct device_attribute irst_timeout_attr = { static int irst_probe(struct platform_device *pdev) { - struct acpi_device *acpi = ACPI_COMPANION(&pdev->dev); + struct acpi_device *acpi; int error; + acpi = ACPI_COMPANION(&pdev->dev); + if (!acpi) + return -ENODEV; + error = device_create_file(&acpi->dev, &irst_timeout_attr); if (unlikely(error)) return error; -- cgit v1.2.3 From 922952f2bbcfe21375973d0ea0e662a1a4837b10 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:11:43 +0200 Subject: platform/x86: intel/smartconnect: Check ACPI_HANDLE() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_HANDLE() check against NULL to the platform/x86 intel/smartconnect driver. Fixes: 8a44bd3ffdb2 ("platform/x86: intel/smartconnect: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/7956676.EvYhyI6sBW@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/smartconnect.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel/smartconnect.c b/drivers/platform/x86/intel/smartconnect.c index 4d866b6366d6..71e91ac60e5d 100644 --- a/drivers/platform/x86/intel/smartconnect.c +++ b/drivers/platform/x86/intel/smartconnect.c @@ -12,10 +12,14 @@ MODULE_LICENSE("GPL"); static int smartconnect_acpi_probe(struct platform_device *pdev) { - acpi_handle handle = ACPI_HANDLE(&pdev->dev); unsigned long long value; + acpi_handle handle; acpi_status status; + handle = ACPI_HANDLE(&pdev->dev); + if (!handle) + return -ENODEV; + status = acpi_evaluate_integer(handle, "GAOS", NULL, &value); if (ACPI_FAILURE(status)) return -EINVAL; -- cgit v1.2.3 From 7e169326c2263ebc4878baae536c956fa3118eff Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:12:27 +0200 Subject: platform/x86: lg-laptop: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 lg-laptop driver. Fixes: 2d9cb20610f7 ("platform/x86: lg-laptop: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3706551.iIbC2pHGDl@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/lg-laptop.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index 9681412d694b..a8f2f465ef3f 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -761,12 +761,11 @@ static void lg_laptop_remove_address_space_handler(void *data) static int acpi_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct platform_device_info pdev_info = { - .fwnode = acpi_fwnode_handle(device), .name = PLATFORM_NAME, .id = PLATFORM_DEVID_NONE, }; + struct acpi_device *device; acpi_status status; int ret; const char *product; @@ -775,6 +774,12 @@ static int acpi_probe(struct platform_device *pdev) if (pf_device) return 0; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + + pdev_info.fwnode = acpi_fwnode_handle(device), + status = acpi_install_address_space_handler(device->handle, LG_ADDRESS_SPACE_ID, &lg_laptop_address_space_handler, NULL, &pdev->dev); -- cgit v1.2.3 From f15b0a3043f193677903be52d7dbef3d5c8df282 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:13:13 +0200 Subject: platform/x86: panasonic-laptop: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 panasonic-laptop driver. Fixes: de6837243af0 ("platform/x86: panasonic-laptop: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3353471.5fSG56mABF@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/panasonic-laptop.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 1337f7c49805..b83113c26f88 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -981,11 +981,15 @@ static int acpi_pcc_hotkey_resume(struct device *dev) static int acpi_pcc_hotkey_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct backlight_properties props; + struct acpi_device *device; struct pcc_acpi *pcc; int num_sifr, result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + num_sifr = acpi_pcc_get_sqty(device); /* -- cgit v1.2.3 From f2ec69363fb52fbb2010e5edbec2b8ca0951aadf Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:13:59 +0200 Subject: platform/x86: sony-laptop: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add requisite ACPI_COMPANION() checks against NULL to the platform/x86 sony-laptop driver. Fixes: 138db7ee58c0 ("platform/x86: sony-laptop: Convert PIC driver to a platform one") Fixes: 14004dd31caa ("platform/x86: sony-laptop: Convert NC driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/1871155.VLH7GnMWUR@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/sony-laptop.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index b18f00e9082f..67370967df6f 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -3147,11 +3147,15 @@ static void sony_nc_backlight_cleanup(void) static int sony_nc_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + struct acpi_device *device; acpi_status status; int result = 0; struct sony_nc_value *item; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + sony_nc_acpi_device = device; strscpy(acpi_device_class(device), "sony/hotkey"); @@ -4509,11 +4513,15 @@ static void sony_pic_remove(struct platform_device *pdev) static int sony_pic_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct sony_pic_ioport *io, *tmp_io; struct sony_pic_irq *irq, *tmp_irq; + struct acpi_device *device; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + spic_dev.acpi_dev = device; strscpy(acpi_device_class(device), "sony/hotkey"); sony_pic_detect_device_type(&spic_dev); -- cgit v1.2.3 From 840bcd6bd5bcbc807b679e367ae7a148a1f07370 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:14:43 +0200 Subject: platform/x86: system76: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 system76 driver. Fixes: 80b8f68b94ab ("platform/x86: system76: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2072699.usQuhbGJ8B@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/system76_acpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c index 693cbb461382..dd7b1b07c316 100644 --- a/drivers/platform/x86/system76_acpi.c +++ b/drivers/platform/x86/system76_acpi.c @@ -674,10 +674,14 @@ static void system76_notify(acpi_handle handle, u32 event, void *context) // Probe a System76 platform device static int system76_probe(struct platform_device *pdev) { - struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); + struct acpi_device *acpi_dev; struct system76_data *data; int err; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; -- cgit v1.2.3 From 0978824a64f160f1d2e8dc821a522ad91e7b4215 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:15:31 +0200 Subject: platform/x86: toshiba_acpi: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 toshiba_acpi driver. Fixes: 246d6cefe525 ("platform/x86: toshiba_acpi: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/1973170.CQOukoFCf9@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_acpi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 35d899c01740..7cecb3a70b9c 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -3374,7 +3374,7 @@ static const struct dmi_system_id toshiba_dmi_quirks[] __initconst = { static int toshiba_acpi_probe(struct platform_device *pdev) { - struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); + struct acpi_device *acpi_dev; struct toshiba_acpi_dev *dev; const char *hci_method; u32 dummy; @@ -3383,6 +3383,10 @@ static int toshiba_acpi_probe(struct platform_device *pdev) if (toshiba_acpi) return -EBUSY; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + pr_info("Toshiba Laptop ACPI Extras version %s\n", TOSHIBA_ACPI_VERSION); -- cgit v1.2.3 From 48b06fffb16901237dc0acbb0474a54b3cc5730f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:16:18 +0200 Subject: platform/x86: toshiba_bluetooth: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 toshiba_bluetooth driver. Fixes: 553b2ac59fbb ("platform/x86: toshiba_bluetooth: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2715450.Lt9SDvczpP@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_bluetooth.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index e50d4fc1e603..e00abba58c7c 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -230,10 +230,14 @@ static int toshiba_bt_resume(struct device *dev) static int toshiba_bt_rfkill_probe(struct platform_device *pdev) { - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); struct toshiba_bluetooth_dev *bt_dev; + struct acpi_device *device; int result; + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; + result = toshiba_bluetooth_present(device->handle); if (result) return result; -- cgit v1.2.3 From 4840f8bb3e9aad183e707950f24f829fd220eb07 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:17:00 +0200 Subject: platform/x86: toshiba_haps: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 toshiba_haps driver. Fixes: 3a96c7915d93 ("platform/x86: toshiba_haps: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2285136.Mh6RI2rZIc@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/toshiba_haps.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index 1486252b5983..8d12241924df 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -182,13 +182,17 @@ static int toshiba_haps_available(acpi_handle handle) static int toshiba_haps_probe(struct platform_device *pdev) { - struct acpi_device *acpi_dev = ACPI_COMPANION(&pdev->dev); struct toshiba_haps_dev *haps; + struct acpi_device *acpi_dev; int ret; if (toshiba_haps) return -EBUSY; + acpi_dev = ACPI_COMPANION(&pdev->dev); + if (!acpi_dev) + return -ENODEV; + if (!toshiba_haps_available(acpi_dev->handle)) return -ENODEV; -- cgit v1.2.3 From 53a8f95cbb407608ef77a864ad4a59f25ddd906c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 May 2026 16:17:44 +0200 Subject: platform/x86: wireless-hotkey: Check ACPI_COMPANION() against NULL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), so platform drivers that rely on the existence of a device's ACPI companion object need to verify its presence. Accordingly, add a requisite ACPI_COMPANION() check against NULL to the platform/x86 wireless-hotkey driver. Fixes: 8507277ef132 ("platform/x86: wireless-hotkey: Convert ACPI driver to a platform one") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3899916.MHq7AAxBmi@rafael.j.wysocki Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/wireless-hotkey.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/wireless-hotkey.c b/drivers/platform/x86/wireless-hotkey.c index f680d8ff8e87..3151844d1699 100644 --- a/drivers/platform/x86/wireless-hotkey.c +++ b/drivers/platform/x86/wireless-hotkey.c @@ -89,9 +89,14 @@ static void wl_notify(acpi_handle handle, u32 event, void *data) static int wl_probe(struct platform_device *pdev) { + struct acpi_device *adev; struct wl_button *button; int err; + adev = ACPI_COMPANION(&pdev->dev); + if (!adev) + return -ENODEV; + button = kzalloc_obj(struct wl_button); if (!button) return -ENOMEM; @@ -104,8 +109,8 @@ static int wl_probe(struct platform_device *pdev) kfree(button); return err; } - err = acpi_dev_install_notify_handler(ACPI_COMPANION(&pdev->dev), - ACPI_DEVICE_NOTIFY, wl_notify, button); + err = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + wl_notify, button); if (err) { pr_err("Failed to install ACPI notify handler\n"); wireless_input_destroy(&pdev->dev); -- cgit v1.2.3 From 9af1b6e175c82daf4b423da339a722d8e67a735a Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Tue, 19 May 2026 13:52:47 +0530 Subject: drm/virtio: use uninterruptible resv lock for plane updates virtio_gpu_cursor_plane_update() and virtio_gpu_resource_flush() lock the framebuffer BO's dma_resv via virtio_gpu_array_lock_resv() and ignore its return value. The function can fail with -EINTR from dma_resv_lock_interruptible() (signal during lock wait) or with -ENOMEM from dma_resv_reserve_fences() (fence slot allocation), leaving the resv lock not held. The queue path then walks the object array and calls dma_resv_add_fence(), which requires the lock held; with lockdep enabled this trips dma_resv_assert_held(): WARNING: drivers/dma-buf/dma-resv.c:296 at dma_resv_add_fence+0x71e/0x840 Call Trace: virtio_gpu_array_add_fence virtio_gpu_queue_ctrl_sgs virtio_gpu_queue_fenced_ctrl_buffer virtio_gpu_cursor_plane_update drm_atomic_helper_commit_planes drm_atomic_helper_commit_tail commit_tail drm_atomic_helper_commit drm_atomic_commit drm_atomic_helper_update_plane __setplane_atomic drm_mode_cursor_universal drm_mode_cursor_common drm_mode_cursor_ioctl drm_ioctl __x64_sys_ioctl Beyond the WARN, mutating the dma_resv fence list without the lock races with concurrent readers/writers and can corrupt the list. Both call sites run inside the .atomic_update plane callback, which DRM atomic helpers do not allow to fail (by the time it runs, the commit has been signed off to userspace and there is no clean rollback path). Moving the lock acquisition to .prepare_fb was rejected because the broader lock scope deadlocks against other BO locking paths in the same atomic commit. Introduce virtio_gpu_lock_one_resv_uninterruptible() that uses dma_resv_lock() instead of dma_resv_lock_interruptible(). This eliminates the -EINTR failure mode -- the realistic syzbot trigger -- without extending the lock hold across the commit. The helper locks a single BO and rejects nents > 1 with -EINVAL; both fix sites lock exactly one BO. Use it from virtio_gpu_cursor_plane_update() and virtio_gpu_resource_flush(); check the return value to handle the remaining -ENOMEM case from dma_resv_reserve_fences() by freeing the objs and skipping the plane update for that frame. The framebuffer BOs touched here are not shared with other contexts and lock contention is expected to be brief, so the loss of signal-interruptibility is acceptable. Other callers of virtio_gpu_array_lock_resv() (the ioctl paths) continue to use the interruptible variant. The bug was reported by syzbot, triggered via fault injection (fail_nth) on the DRM_IOCTL_MODE_CURSOR path, which forces the -ENOMEM branch in dma_resv_reserve_fences(). Reported-by: syzbot+72bd3dd3a5d5f39a0271@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=72bd3dd3a5d5f39a0271 Fixes: 5cfd31c5b3a3 ("drm/virtio: fix virtio_gpu_cursor_plane_update().") Cc: stable@vger.kernel.org Signed-off-by: Deepanshu Kartikey Signed-off-by: Dmitry Osipenko Link: https://patch.msgid.link/20260519082247.34470-1-kartikey406@gmail.com --- drivers/gpu/drm/virtio/virtgpu_drv.h | 1 + drivers/gpu/drm/virtio/virtgpu_gem.c | 17 +++++++++++++++++ drivers/gpu/drm/virtio/virtgpu_plane.c | 10 ++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index f17660a71a3e..2f3531950aa4 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -317,6 +317,7 @@ virtio_gpu_array_from_handles(struct drm_file *drm_file, u32 *handles, u32 nents void virtio_gpu_array_add_obj(struct virtio_gpu_object_array *objs, struct drm_gem_object *obj); int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs); +int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs); void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs); void virtio_gpu_array_add_fence(struct virtio_gpu_object_array *objs, struct dma_fence *fence); diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index f22dc5c21cd4..435d37d36034 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -238,6 +238,23 @@ int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs) return ret; } +int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs) +{ + int ret; + + if (objs->nents != 1) + return -EINVAL; + + dma_resv_lock(objs->objs[0]->resv, NULL); + + ret = dma_resv_reserve_fences(objs->objs[0]->resv, 1); + if (ret) { + virtio_gpu_array_unlock_resv(objs); + return ret; + } + return 0; +} + void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs) { if (objs->nents == 1) { diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index a126d1b25f46..652352424744 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -215,7 +215,10 @@ static void virtio_gpu_resource_flush(struct drm_plane *plane, if (!objs) return; virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]); - virtio_gpu_array_lock_resv(objs); + if (virtio_gpu_lock_one_resv_uninterruptible(objs)) { + virtio_gpu_array_put_free(objs); + return; + } virtio_gpu_cmd_resource_flush(vgdev, bo->hw_res_handle, x, y, width, height, objs, vgplane_st->fence); @@ -459,7 +462,10 @@ static void virtio_gpu_cursor_plane_update(struct drm_plane *plane, if (!objs) return; virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]); - virtio_gpu_array_lock_resv(objs); + if (virtio_gpu_lock_one_resv_uninterruptible(objs)) { + virtio_gpu_array_put_free(objs); + return; + } virtio_gpu_cmd_transfer_to_host_2d (vgdev, 0, plane->state->crtc_w, -- cgit v1.2.3 From 9fc75b71fdd38465c76c6f6a884cdd4ae3c72d90 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Tue, 19 May 2026 23:07:26 +0200 Subject: rbd: eliminate a race in lock_dwork draining on unmap Given how rbd_lock_add_request() and rbd_img_exclusive_lock() are written, lock_dwork may be (re)queued more than it's actually needed: for example in case a new I/O request comes in while we are in the middle of rbd_acquire_lock() on behalf of another I/O request. This is expected and with rbd_release_lock() preemptively canceling lock_dwork is benign under normal operation. A more problematic example is maybe_kick_acquire(): if (have_requests || delayed_work_pending(&rbd_dev->lock_dwork)) { dout("%s rbd_dev %p kicking lock_dwork\n", __func__, rbd_dev); mod_delayed_work(rbd_dev->task_wq, &rbd_dev->lock_dwork, 0); } It's not unrealistic for lock_dwork to get canceled right after delayed_work_pending() returns true and for mod_delayed_work() to requeue it right there anyway. This is a classic TOCTOU race. When it comes to unmapping the image, there is an implicit assumption of no self-initiated exclusive lock activity past the point of return from rbd_dev_image_unlock() which unlocks the lock if it happens to be held. This unlock is assumed to be final and lock_dwork (as well as all other exclusive lock tasks, really) isn't expected to get queued again. However, lock_dwork is canceled only in cancel_tasks_sync() (i.e. later in the unmap sequence) and on top of that the cancellation can get in effect nullified by maybe_kick_acquire(). This may result in rbd_acquire_lock() executing after rbd_dev_device_release() and rbd_dev_image_release() run and free and/or reset a bunch of things. One of the possible failure modes then is a violated rbd_assert(rbd_image_format_valid(rbd_dev->image_format)); in rbd_dev_header_info() which is called via rbd_dev_refresh() from rbd_post_acquire_action(). Redo exclusive lock task draining to provide saner semantics and try to meet the assumptions around rbd_dev_image_unlock(). Cc: stable@vger.kernel.org Signed-off-by: Ilya Dryomov Reviewed-by: Viacheslav Dubeyko --- drivers/block/rbd.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 4065336ebd1f..6c1e7347e6a7 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -4565,24 +4565,12 @@ out: return ret; } -static void cancel_tasks_sync(struct rbd_device *rbd_dev) -{ - dout("%s rbd_dev %p\n", __func__, rbd_dev); - - cancel_work_sync(&rbd_dev->acquired_lock_work); - cancel_work_sync(&rbd_dev->released_lock_work); - cancel_delayed_work_sync(&rbd_dev->lock_dwork); - cancel_work_sync(&rbd_dev->unlock_work); -} - /* * header_rwsem must not be held to avoid a deadlock with * rbd_dev_refresh() when flushing notifies. */ static void rbd_unregister_watch(struct rbd_device *rbd_dev) { - cancel_tasks_sync(rbd_dev); - mutex_lock(&rbd_dev->watch_mutex); if (rbd_dev->watch_state == RBD_WATCH_STATE_REGISTERED) __rbd_unregister_watch(rbd_dev); @@ -6548,10 +6536,18 @@ out_err: static void rbd_dev_image_unlock(struct rbd_device *rbd_dev) { + dout("%s rbd_dev %p\n", __func__, rbd_dev); + + disable_delayed_work_sync(&rbd_dev->lock_dwork); + disable_work_sync(&rbd_dev->unlock_work); + down_write(&rbd_dev->lock_rwsem); if (__rbd_is_lock_owner(rbd_dev)) __rbd_release_lock(rbd_dev); up_write(&rbd_dev->lock_rwsem); + + flush_work(&rbd_dev->acquired_lock_work); + flush_work(&rbd_dev->released_lock_work); } /* -- cgit v1.2.3 From 88365d04fdc821dc4e9eb0cc00fdf6905430d172 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 15 May 2026 00:32:48 +0530 Subject: Bluetooth: btintel_pcie: Fix incorrect MAC access programming btintel_pcie_get_mac_access() and btintel_pcie_release_mac_access() were programming STOP_MAC_ACCESS_DIS and XTAL_CLK_REQ in addition to the MAC_ACCESS_REQ handshake. These bits are not part of the host MAC-access handshake on the supported parts; the driver was programming them incorrectly. Drop the writes so the register update contains only the bits the controller actually consumes. Fixes: b9465e6670a2 ("Bluetooth: btintel_pcie: Read hardware exception data") Signed-off-by: Kiran K Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btintel_pcie.c | 20 ++++++-------------- drivers/bluetooth/btintel_pcie.h | 3 --- 2 files changed, 6 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index a3643e67b33f..37e050763633 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -582,12 +582,10 @@ static int btintel_pcie_get_mac_access(struct btintel_pcie_data *data) reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG); - reg |= BTINTEL_PCIE_CSR_FUNC_CTRL_STOP_MAC_ACCESS_DIS; - reg |= BTINTEL_PCIE_CSR_FUNC_CTRL_XTAL_CLK_REQ; - if ((reg & BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_STS) == 0) + if (!(reg & BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ)) { reg |= BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ; - - btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG, reg); + btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG, reg); + } do { reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG); @@ -607,16 +605,10 @@ static void btintel_pcie_release_mac_access(struct btintel_pcie_data *data) reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG); - if (reg & BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ) + if (reg & BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ) { reg &= ~BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ; - - if (reg & BTINTEL_PCIE_CSR_FUNC_CTRL_STOP_MAC_ACCESS_DIS) - reg &= ~BTINTEL_PCIE_CSR_FUNC_CTRL_STOP_MAC_ACCESS_DIS; - - if (reg & BTINTEL_PCIE_CSR_FUNC_CTRL_XTAL_CLK_REQ) - reg &= ~BTINTEL_PCIE_CSR_FUNC_CTRL_XTAL_CLK_REQ; - - btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG, reg); + btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_FUNC_CTRL_REG, reg); + } } static void *btintel_pcie_copy_tlv(void *dest, enum btintel_pcie_tlv_type type, diff --git a/drivers/bluetooth/btintel_pcie.h b/drivers/bluetooth/btintel_pcie.h index f922abd1e7d8..13efef499e4e 100644 --- a/drivers/bluetooth/btintel_pcie.h +++ b/drivers/bluetooth/btintel_pcie.h @@ -34,9 +34,6 @@ #define BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_STS (BIT(20)) #define BTINTEL_PCIE_CSR_FUNC_CTRL_MAC_ACCESS_REQ (BIT(21)) -/* Stop MAC Access disconnection request */ -#define BTINTEL_PCIE_CSR_FUNC_CTRL_STOP_MAC_ACCESS_DIS (BIT(22)) -#define BTINTEL_PCIE_CSR_FUNC_CTRL_XTAL_CLK_REQ (BIT(23)) #define BTINTEL_PCIE_CSR_FUNC_CTRL_BUS_MASTER_STS (BIT(28)) #define BTINTEL_PCIE_CSR_FUNC_CTRL_BUS_MASTER_DISCON (BIT(29)) -- cgit v1.2.3 From dd1dda6b8d6e1f4376a5b3055a04f0ecbdb4d6bd Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Mon, 18 May 2026 10:24:02 +0800 Subject: Bluetooth: btmtk: fix urb->setup_packet leak in error paths The setup_packet of control urb is not freed if usb_submit_urb fails or the submitted urb is killed. Add free in these two paths. Fixes: a1c49c434e150 ("Bluetooth: btusb: Add protocol support for MediaTek MT7668U USB devices") Signed-off-by: Jiajia Liu Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btmtk.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c index a29f72216c34..8ff66b276af0 100644 --- a/drivers/bluetooth/btmtk.c +++ b/drivers/bluetooth/btmtk.c @@ -537,6 +537,7 @@ static void btmtk_usb_wmt_recv(struct urb *urb) return; } else if (urb->status == -ENOENT) { /* Avoid suspend failed when usb_kill_urb */ + kfree(urb->setup_packet); return; } @@ -610,6 +611,7 @@ static int btmtk_usb_submit_wmt_recv_urb(struct hci_dev *hdev) if (err != -EPERM && err != -ENODEV) bt_dev_err(hdev, "urb %p submission failed (%d)", urb, -err); + kfree(dr); usb_unanchor_urb(urb); } -- cgit v1.2.3 From c1bb9336ae6b54a5f6a353c4bd4ed9a4307e429b Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Mon, 18 May 2026 10:49:49 +0800 Subject: Bluetooth: hci_uart: fix UAFs and race conditions in close and init paths Vulnerabilities leading to Use-After-Free (UAF) and Null Pointer Dereference (NPD) conditions were observed in the lifecycle management of hci_uart. The primary issue arises because the workqueues (init_ready and write_work) are only flushed/cancelled if the HCI_UART_PROTO_READY flag is set during TTY close. If a hangup occurs before setup completes, hci_uart_tty_close() skips the teardown of these workqueues and proceeds to free the `hu` struct. When the scheduled work executes later, it blindly dereferences the freed `hu` struct. Furthermore, several data races and UAFs were identified in the teardown sequence: 1. Calling hci_uart_flush() from hci_uart_close() without effectively disabling write_work causes a race condition where both can concurrently double-free hu->tx_skb. This happens because protocol timers can concurrently invoke hci_uart_tx_wakeup() and requeue write_work. 2. Calling hci_free_dev(hdev) before hu->proto->close(hu) causes a UAF when vendor specific protocol close callbacks dereference hu->hdev. 3. In the initialization error paths, failing to take the proto_lock write lock before clearing PROTO_READY leads to races with active readers. Additionally, hci_uart_tty_receive() accesses hu->hdev outside the read lock, leading to UAFs if the initialization error path frees hdev concurrently. Fix these synchronization and lifecycle issues by: 1. Re-ordering hci_uart_tty_close() to clear HCI_UART_PROTO_READY first, followed immediately by a cancel_work_sync(&hu->write_work). Clearing the flag locks out concurrent protocol timers from successfully invoking hci_uart_tx_wakeup(), effectively rendering the cancellation permanent and preventing the tx_skb double-free. 2. Note: Clearing PROTO_READY early causes hci_uart_close() to skip hu->proto->flush(). This is perfectly safe in the tty_close path because hu->proto->close() executes shortly after, which intrinsically purges all protocol SKB queues and tears down the state. 3. Relocating hu->proto->close(hu) strictly prior to hci_free_dev(hdev) across all close and error paths to prevent vendor-level UAFs. 4. Moving the hdev->stat.byte_rx increment in hci_uart_tty_receive() inside the proto_lock read-side critical section to safely synchronize with device unregistration. 5. Adding cancel_work_sync(&hu->write_work) to hci_uart_close() to safely flush the workqueue before hci_uart_flush() is invoked via the HCI core. 6. Utilizing cancel_work_sync() instead of disable_work_sync() across all paths to prevent permanently breaking user-space retry capabilities. Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering") Cc: stable@vger.kernel.org Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/hci_ldisc.c | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 275ea865bc29..47f4902b40b4 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -194,7 +194,15 @@ void hci_uart_init_work(struct work_struct *work) err = hci_register_dev(hu->hdev); if (err < 0) { BT_ERR("Can't register HCI device"); + + percpu_down_write(&hu->proto_lock); clear_bit(HCI_UART_PROTO_READY, &hu->flags); + percpu_up_write(&hu->proto_lock); + + /* Safely cancel work after clearing flags */ + cancel_work_sync(&hu->write_work); + + /* Close protocol before freeing hdev */ hu->proto->close(hu); hdev = hu->hdev; hu->hdev = NULL; @@ -263,8 +271,12 @@ static int hci_uart_open(struct hci_dev *hdev) /* Close device */ static int hci_uart_close(struct hci_dev *hdev) { + struct hci_uart *hu = hci_get_drvdata(hdev); + BT_DBG("hdev %p", hdev); + cancel_work_sync(&hu->write_work); + hci_uart_flush(hdev); hdev->flush = NULL; return 0; @@ -531,6 +543,7 @@ static void hci_uart_tty_close(struct tty_struct *tty) { struct hci_uart *hu = tty->disc_data; struct hci_dev *hdev; + bool proto_ready; BT_DBG("tty %p", tty); @@ -540,24 +553,38 @@ static void hci_uart_tty_close(struct tty_struct *tty) if (!hu) return; - hdev = hu->hdev; - if (hdev) - hci_uart_close(hdev); + /* Wait for init_ready to finish to prevent registration races */ + cancel_work_sync(&hu->init_ready); - if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) { + proto_ready = test_bit(HCI_UART_PROTO_READY, &hu->flags); + if (proto_ready) { percpu_down_write(&hu->proto_lock); clear_bit(HCI_UART_PROTO_READY, &hu->flags); percpu_up_write(&hu->proto_lock); + } - cancel_work_sync(&hu->init_ready); - cancel_work_sync(&hu->write_work); + /* + * Unconditionally cancel write_work AFTER clearing PROTO_READY. + * This ensures that concurrent protocol timers cannot requeue + * write_work via hci_uart_tx_wakeup(), permanently preventing + * double-free races and UAFs. + */ + cancel_work_sync(&hu->write_work); + + hdev = hu->hdev; + if (hdev) + hci_uart_close(hdev); /* proto->flush is safely skipped */ + if (proto_ready) { if (hdev) { if (test_bit(HCI_UART_REGISTERED, &hu->flags)) hci_unregister_dev(hdev); - hci_free_dev(hdev); } + /* Close protocol before freeing hdev (intrinsically purges queues) */ hu->proto->close(hu); + + if (hdev) + hci_free_dev(hdev); } clear_bit(HCI_UART_PROTO_SET, &hu->flags); @@ -625,11 +652,12 @@ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, * tty caller */ hu->proto->recv(hu, data, count); - percpu_up_read(&hu->proto_lock); if (hu->hdev) hu->hdev->stat.byte_rx += count; + percpu_up_read(&hu->proto_lock); + tty_unthrottle(tty); } @@ -695,6 +723,10 @@ static int hci_uart_register_dev(struct hci_uart *hu) percpu_down_write(&hu->proto_lock); clear_bit(HCI_UART_PROTO_INIT, &hu->flags); percpu_up_write(&hu->proto_lock); + /* Cancel work after clearing flags */ + cancel_work_sync(&hu->write_work); + + /* Close protocol before freeing hdev */ hu->proto->close(hu); hu->hdev = NULL; hci_free_dev(hdev); -- cgit v1.2.3 From abe003b33223ff33552f291644bf35d9c2f992fb Mon Sep 17 00:00:00 2001 From: Prathamesh Deshpande Date: Sun, 10 May 2026 23:59:00 +0100 Subject: net/mlx5e: Fix eswitch mode block underflow on IPsec acquire SA mlx5e_xfrm_add_state() handles acquire-flow temporary SAs by allocating software state and skipping hardware offload setup. That path jumps to the common success label before taking the eswitch mode block. After tunnel-mode validation was moved earlier, the common success label unconditionally calls mlx5_eswitch_unblock_mode(). For acquire SAs, this decrements esw->offloads.num_block_mode without a matching increment. Return directly after installing the acquire SA offload handle, so only the paths that successfully called mlx5_eswitch_block_mode() call the matching unblock. Fixes: 22239eb258bc ("net/mlx5e: Prevent tunnel reformat when tunnel mode not allowed") Signed-off-by: Prathamesh Deshpande Reviewed-by: Simon Horman Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260510225903.13184-1-prathameshdeshpande7@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c index a52e12c3c95a..db260e3d1412 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c @@ -792,8 +792,10 @@ static int mlx5e_xfrm_add_state(struct net_device *dev, sa_entry->dev = dev; sa_entry->ipsec = ipsec; /* Check if this SA is originated from acquire flow temporary SA */ - if (x->xso.flags & XFRM_DEV_OFFLOAD_FLAG_ACQ) - goto out; + if (x->xso.flags & XFRM_DEV_OFFLOAD_FLAG_ACQ) { + x->xso.offload_handle = (unsigned long)sa_entry; + return 0; + } err = mlx5e_xfrm_validate_state(priv->mdev, x, extack); if (err) @@ -870,7 +872,6 @@ static int mlx5e_xfrm_add_state(struct net_device *dev, xa_unlock_bh(&ipsec->sadb); } -out: x->xso.offload_handle = (unsigned long)sa_entry; if (allow_tunnel_mode) mlx5_eswitch_unblock_encap(priv->mdev); -- cgit v1.2.3 From e7c70bf97e90d974cd575e4c90f8f9b07d056da3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 16 May 2026 14:26:16 -0700 Subject: net: ag71xx: check error for platform_get_irq Complete error handling for a failed platform_get_irq() call Fixes: d51b6ce441d3 ("net: ethernet: add ag71xx driver") Signed-off-by: Rosen Penev Reviewed-by: Oleksij Rempel Link: https://patch.msgid.link/20260516212616.11758-1-rosenp@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/atheros/ag71xx.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/atheros/ag71xx.c b/drivers/net/ethernet/atheros/ag71xx.c index a5ab99474179..4e4794c4dfdc 100644 --- a/drivers/net/ethernet/atheros/ag71xx.c +++ b/drivers/net/ethernet/atheros/ag71xx.c @@ -1856,6 +1856,9 @@ static int ag71xx_probe(struct platform_device *pdev) ag71xx_int_disable(ag, AG71XX_INT_POLL); ndev->irq = platform_get_irq(pdev, 0); + if (ndev->irq < 0) + return ndev->irq; + err = devm_request_irq(&pdev->dev, ndev->irq, ag71xx_interrupt, 0x0, dev_name(&pdev->dev), ndev); if (err) { -- cgit v1.2.3 From fd948c3f96b18ff9ba7d3e8eae13d196593e1aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20L=C3=B3pez?= Date: Tue, 12 May 2026 12:00:42 +0200 Subject: virt: sev-guest: Explicitly leak pages in unknown state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When set_memory_{encrypted,decrypted}() fail, the user cannot know at which point the function failed, meaning that the pages are left in an unknown state from the point of view of the caller. Since the pages may be left in an unencrypted state, they are not suitable for general use, and cannot be returned safely to the buddy allocator. Avoid the issue by never freeing the pages, and then do the proper accounting by calling snp_leak_pages(). Fixes: 3e385c0d6ce8 ("virt: sev-guest: Move SNP Guest Request data pages handling under snp_cmd_mutex") Signed-off-by: Carlos López Signed-off-by: Borislav Petkov (AMD) Cc: stable@kernel.org --- drivers/virt/coco/sev-guest/sev-guest.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/virt/coco/sev-guest/sev-guest.c b/drivers/virt/coco/sev-guest/sev-guest.c index 910a1de0d5a7..d186ae55cf63 100644 --- a/drivers/virt/coco/sev-guest/sev-guest.c +++ b/drivers/virt/coco/sev-guest/sev-guest.c @@ -176,6 +176,7 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques struct snp_guest_req req = {}; int ret, npages = 0, resp_len; sockptr_t certs_address; + u64 pfn; if (sockptr_is_null(io->req_data) || sockptr_is_null(io->resp_data)) return -EINVAL; @@ -215,10 +216,11 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques if (!req.certs_data) return -ENOMEM; + pfn = PHYS_PFN(virt_to_phys(req.certs_data)); ret = set_memory_decrypted((unsigned long)req.certs_data, npages); if (ret) { pr_err("failed to mark page shared, ret=%d\n", ret); - free_pages_exact(req.certs_data, npages << PAGE_SHIFT); + snp_leak_pages(pfn, npages); return -EFAULT; } @@ -272,10 +274,12 @@ e_free: kfree(report_resp); e_free_data: if (npages) { - if (set_memory_encrypted((unsigned long)req.certs_data, npages)) + if (set_memory_encrypted((unsigned long)req.certs_data, npages)) { WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n"); - else + snp_leak_pages(pfn, npages); + } else { free_pages_exact(req.certs_data, npages << PAGE_SHIFT); + } } return ret; } -- cgit v1.2.3 From fa997ddef508b1b37b2fe4d2dad7c4b70958335e Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 19 May 2026 15:22:05 +0200 Subject: dpll: zl3073x: fix memory leak on pin registration failure If zl3073x_dpll_pin_register() fails, the allocated pin is not yet added to zldpll->pins list. The error path calls zl3073x_dpll_pins_unregister() which only iterates pins on the list, so the current pin is leaked. Free the pin before jumping to the error label. Additionally move the pin->dpll_pin = NULL assignment in zl3073x_dpll_pin_register() from err_register to the common err_pin_get path. When dpll_pin_get() fails, pin->dpll_pin holds an ERR_PTR value. Without this fix the subsequent zl3073x_dpll_pin_free() would trigger a spurious WARN because it checks pin->dpll_pin for non-NULL. Fixes: 75a71ecc2412 ("dpll: zl3073x: Register DPLL devices and pins") Reviewed-by: Petr Oros Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260519132205.161847-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/dpll.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index c95e93ef3ab0..64b4e9e3e8fe 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1394,8 +1394,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index) err_register: dpll_pin_put(pin->dpll_pin, &pin->tracker); - pin->dpll_pin = NULL; err_pin_get: + pin->dpll_pin = NULL; fwnode_handle_put(pin->fwnode); pin->fwnode = NULL; zl3073x_pin_props_put(props); @@ -1563,8 +1563,10 @@ zl3073x_dpll_pins_register(struct zl3073x_dpll *zldpll) } rc = zl3073x_dpll_pin_register(pin, index); - if (rc) + if (rc) { + zl3073x_dpll_pin_free(pin); goto error; + } list_add(&pin->list, &zldpll->pins); } -- cgit v1.2.3 From be460cedb67ab803c1bebceac19b1d44acb85d30 Mon Sep 17 00:00:00 2001 From: Stepan Ionichev Date: Wed, 20 May 2026 16:05:04 +0500 Subject: gpio: pca953x: propagate regulator_enable() error from resume pca953x_resume() returns 0 when regulator_enable() fails, dropping the real error code and masking the failure as a successful resume. The caller then proceeds as if the chip is powered, while the regulator is in fact disabled. Return ret so PM core sees the actual failure. Signed-off-by: Stepan Ionichev Link: https://patch.msgid.link/20260520110504.13969-1-sozdayvek@gmail.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-pca953x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c index 52e96cc5f67b..b9c905a0ffa9 100644 --- a/drivers/gpio/gpio-pca953x.c +++ b/drivers/gpio/gpio-pca953x.c @@ -1411,7 +1411,7 @@ static int pca953x_resume(struct device *dev) ret = regulator_enable(chip->regulator); if (ret) { dev_err(dev, "Failed to enable regulator: %d\n", ret); - return 0; + return ret; } } -- cgit v1.2.3 From 96031b31a4b3b6ec836b9fe7be8f6e6ebcfe8d67 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 21 May 2026 00:04:22 +0200 Subject: irqchip/exynos-combiner: Switch to raw_spinlock The exynos-combiner driver uses a regular spinlock to protect access to the combiner interrupt status register in combiner_handle_cascade_irq(), which is invoked in hard interrupt context as a chained interrupt handler. When PREEMPT_RT is enabled on ARM, regular spinlock is converted to a sleeping lock (mutex-based), which must not be used in atomic context such as hard interrupt handlers. Switch the irq_controller_lock to raw_spinlock, which remains a true non-sleeping spinlock even under PREEMPT_RT. Fixes: a900e5d99718 ("ARM: exynos: move exynos4210-combiner to drivers/irqchip") Signed-off-by: Marek Szyprowski Signed-off-by: Thomas Gleixner --- drivers/irqchip/exynos-combiner.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index 11d105457798..03cafcc5c835 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -24,7 +24,7 @@ #define IRQ_IN_COMBINER 8 -static DEFINE_SPINLOCK(irq_controller_lock); +static DEFINE_RAW_SPINLOCK(irq_controller_lock); struct combiner_chip_data { unsigned int hwirq_offset; @@ -72,9 +72,9 @@ static void combiner_handle_cascade_irq(struct irq_desc *desc) chained_irq_enter(chip, desc); - spin_lock(&irq_controller_lock); + raw_spin_lock(&irq_controller_lock); status = readl_relaxed(chip_data->base + COMBINER_INT_STATUS); - spin_unlock(&irq_controller_lock); + raw_spin_unlock(&irq_controller_lock); status &= chip_data->irq_mask; if (status == 0) -- cgit v1.2.3 From 23386defe949c0db4f746bed7098fc5e06746083 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Mon, 18 May 2026 10:20:55 +0800 Subject: net: stmmac: eswin: fix HSP CSR init ordering after clock enable Fix the initialization ordering of the HSP CSR configuration in the EIC7700 DWMAC glue driver. The HSP CSR registers control MAC-side RGMII delay behavior and must only be accessed after the corresponding clocks are enabled. The previous implementation could trigger register access before clock enablement, leading to undefined behavior depending on boot state. Move the HSP CSR configuration into the post-clock-enable initialization path to ensure all register accesses occur under valid clock domains. This change ensures deterministic initialization and prevents clock-dependent register access failures during probe or resume. Fixes: ea77dbbdbc4e ("net: stmmac: add Eswin EIC7700 glue driver") Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260518022055.444-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 73 ++++++++++++---------- 1 file changed, 41 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index bcb8e000e720..63001c4acdb7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -42,6 +42,11 @@ static const char * const eic7700_clk_names[] = { struct eic7700_qos_priv { struct plat_stmmacenet_data *plat_dat; + struct regmap *eic7700_hsp_regmap; + u32 eth_axi_lp_ctrl_offset; + u32 eth_phy_ctrl_offset; + u32 eth_clk_offset; + u32 eth_clk_dly_param; }; static int eic7700_clks_config(void *priv, bool enabled) @@ -61,8 +66,28 @@ static int eic7700_clks_config(void *priv, bool enabled) static int eic7700_dwmac_init(struct device *dev, void *priv) { struct eic7700_qos_priv *dwc = priv; + int ret; + + ret = eic7700_clks_config(dwc, true); + if (ret) + return ret; + + ret = regmap_set_bits(dwc->eic7700_hsp_regmap, + dwc->eth_phy_ctrl_offset, + EIC7700_ETH_TX_CLK_SEL | + EIC7700_ETH_PHY_INTF_SELI); + if (ret) { + eic7700_clks_config(dwc, false); + return ret; + } + + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_axi_lp_ctrl_offset, + EIC7700_ETH_CSYSREQ_VAL); - return eic7700_clks_config(dwc, true); + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, + dwc->eth_clk_dly_param); + + return 0; } static void eic7700_dwmac_exit(struct device *dev, void *priv) @@ -93,12 +118,6 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; struct eic7700_qos_priv *dwc_priv; - struct regmap *eic7700_hsp_regmap; - u32 eth_axi_lp_ctrl_offset; - u32 eth_phy_ctrl_offset; - u32 eth_phy_ctrl_regset; - u32 eth_rxd_dly_offset; - u32 eth_dly_param = 0; u32 delay_ps; int i, ret; @@ -121,8 +140,9 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) "rx-internal-delay-ps", &delay_ps)) { u32 val = min(delay_ps / 100, EIC7700_MAX_DELAY_UNIT); - eth_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; - eth_dly_param |= FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val); + dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; + dwc_priv->eth_clk_dly_param |= + FIELD_PREP(EIC7700_ETH_RX_ADJ_DELAY, val); } else { return dev_err_probe(&pdev->dev, -EINVAL, "missing required property rx-internal-delay-ps\n"); @@ -133,53 +153,42 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) "tx-internal-delay-ps", &delay_ps)) { u32 val = min(delay_ps / 100, EIC7700_MAX_DELAY_UNIT); - eth_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; - eth_dly_param |= FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val); + dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; + dwc_priv->eth_clk_dly_param |= + FIELD_PREP(EIC7700_ETH_TX_ADJ_DELAY, val); } else { return dev_err_probe(&pdev->dev, -EINVAL, "missing required property tx-internal-delay-ps\n"); } - eic7700_hsp_regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "eswin,hsp-sp-csr"); - if (IS_ERR(eic7700_hsp_regmap)) + dwc_priv->eic7700_hsp_regmap = + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "eswin,hsp-sp-csr"); + if (IS_ERR(dwc_priv->eic7700_hsp_regmap)) return dev_err_probe(&pdev->dev, - PTR_ERR(eic7700_hsp_regmap), + PTR_ERR(dwc_priv->eic7700_hsp_regmap), "Failed to get hsp-sp-csr regmap\n"); ret = of_property_read_u32_index(pdev->dev.of_node, "eswin,hsp-sp-csr", - 1, ð_phy_ctrl_offset); + 1, &dwc_priv->eth_phy_ctrl_offset); if (ret) return dev_err_probe(&pdev->dev, ret, "can't get eth_phy_ctrl_offset\n"); - regmap_read(eic7700_hsp_regmap, eth_phy_ctrl_offset, - ð_phy_ctrl_regset); - eth_phy_ctrl_regset |= - (EIC7700_ETH_TX_CLK_SEL | EIC7700_ETH_PHY_INTF_SELI); - regmap_write(eic7700_hsp_regmap, eth_phy_ctrl_offset, - eth_phy_ctrl_regset); - ret = of_property_read_u32_index(pdev->dev.of_node, "eswin,hsp-sp-csr", - 2, ð_axi_lp_ctrl_offset); + 2, &dwc_priv->eth_axi_lp_ctrl_offset); if (ret) return dev_err_probe(&pdev->dev, ret, "can't get eth_axi_lp_ctrl_offset\n"); - regmap_write(eic7700_hsp_regmap, eth_axi_lp_ctrl_offset, - EIC7700_ETH_CSYSREQ_VAL); - ret = of_property_read_u32_index(pdev->dev.of_node, "eswin,hsp-sp-csr", - 3, ð_rxd_dly_offset); + 3, &dwc_priv->eth_clk_offset); if (ret) return dev_err_probe(&pdev->dev, ret, - "can't get eth_rxd_dly_offset\n"); - - regmap_write(eic7700_hsp_regmap, eth_rxd_dly_offset, - eth_dly_param); + "can't get eth_clk_offset\n"); plat_dat->num_clks = ARRAY_SIZE(eic7700_clk_names); plat_dat->clks = devm_kcalloc(&pdev->dev, -- cgit v1.2.3 From 6872fb088edc1a3c36792b301f8e4a1c35dd7c35 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Mon, 18 May 2026 10:21:37 +0800 Subject: net: stmmac: eswin: clear TXD and RXD delay registers during initialization Clear the TXD and RXD delay control registers during EIC7700 DWMAC initialization. These registers may retain values programmed by the bootloader. If left unchanged, residual delays can alter the effective RGMII timing seen by the MAC and override the configuration described by the device tree. This may violate the expected RGMII timing model and can cause link instability or prevent the Ethernet controller from operating correctly. Explicitly clearing these registers ensures that the MAC delay settings are determined solely by the kernel configuration. The corresponding register offsets are optional, and the registers are only cleared when the offsets are provided in the device tree. Fixes: ea77dbbdbc4e ("net: stmmac: add Eswin EIC7700 glue driver") Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260518022137.464-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index 63001c4acdb7..541b279f08a1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -46,7 +46,11 @@ struct eic7700_qos_priv { u32 eth_axi_lp_ctrl_offset; u32 eth_phy_ctrl_offset; u32 eth_clk_offset; + u32 eth_txd_offset; + u32 eth_rxd_offset; u32 eth_clk_dly_param; + bool has_txd_offset; + bool has_rxd_offset; }; static int eic7700_clks_config(void *priv, bool enabled) @@ -84,6 +88,12 @@ static int eic7700_dwmac_init(struct device *dev, void *priv) regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_axi_lp_ctrl_offset, EIC7700_ETH_CSYSREQ_VAL); + if (dwc->has_txd_offset) + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_txd_offset, 0); + + if (dwc->has_rxd_offset) + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_rxd_offset, 0); + regmap_write(dwc->eic7700_hsp_regmap, dwc->eth_clk_offset, dwc->eth_clk_dly_param); @@ -190,6 +200,18 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "can't get eth_clk_offset\n"); + ret = of_property_read_u32_index(pdev->dev.of_node, + "eswin,hsp-sp-csr", + 4, &dwc_priv->eth_txd_offset); + if (!ret) + dwc_priv->has_txd_offset = true; + + ret = of_property_read_u32_index(pdev->dev.of_node, + "eswin,hsp-sp-csr", + 5, &dwc_priv->eth_rxd_offset); + if (!ret) + dwc_priv->has_rxd_offset = true; + plat_dat->num_clks = ARRAY_SIZE(eic7700_clk_names); plat_dat->clks = devm_kcalloc(&pdev->dev, plat_dat->num_clks, -- cgit v1.2.3 From 6ffcef9bc1fc2ad8110777decd6d026e3cb468ce Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Mon, 18 May 2026 10:21:52 +0800 Subject: net: stmmac: eswin: correct RGMII delay granularity to 20 ps The EIC7700 MAC implements programmable RGMII delay adjustment with a granularity of 20 ps per hardware step. The driver previously converted rx-internal-delay-ps and tx-internal-delay-ps values using a 100 ps step size, resulting in incorrect delay programming. Update the conversion to use the correct 20 ps granularity so the programmed delay matches the values described in the device tree. Fixes: ea77dbbdbc4e ("net: stmmac: add Eswin EIC7700 glue driver") Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260518022156.484-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index 541b279f08a1..ef60cab24533 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -28,8 +28,8 @@ /* * TX/RX Clock Delay Bit Masks: - * - TX Delay: bits [14:8] — TX_CLK delay (unit: 0.1ns per bit) - * - RX Delay: bits [30:24] — RX_CLK delay (unit: 0.1ns per bit) + * - TX Delay: bits [14:8] — TX_CLK delay (unit: 0.02ns per bit) + * - RX Delay: bits [30:24] — RX_CLK delay (unit: 0.02ns per bit) */ #define EIC7700_ETH_TX_ADJ_DELAY GENMASK(14, 8) #define EIC7700_ETH_RX_ADJ_DELAY GENMASK(30, 24) @@ -148,7 +148,7 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) /* Read rx-internal-delay-ps and update rx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps", &delay_ps)) { - u32 val = min(delay_ps / 100, EIC7700_MAX_DELAY_UNIT); + u32 val = min(delay_ps / 20, EIC7700_MAX_DELAY_UNIT); dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= @@ -161,7 +161,7 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) /* Read tx-internal-delay-ps and update tx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "tx-internal-delay-ps", &delay_ps)) { - u32 val = min(delay_ps / 100, EIC7700_MAX_DELAY_UNIT); + u32 val = min(delay_ps / 20, EIC7700_MAX_DELAY_UNIT); dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= -- cgit v1.2.3 From c2e152f7ce3208b9333d212d41a87637ec1dd170 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Mon, 18 May 2026 10:22:13 +0800 Subject: net: stmmac: eswin: validate RGMII delay values Validate rx-internal-delay-ps and tx-internal-delay-ps against the hardware capabilities of the EIC7700 MAC. The programmable RGMII delay supports 20 ps steps and a maximum value of 2540 ps. The driver previously accepted arbitrary values and silently truncated unsupported settings when converting them to hardware units. As a result, invalid device tree values could lead to unexpected delay programming and incorrect RGMII timing. Reject delay values that are not multiples of 20 ps or exceed the supported hardware range. Fixes: ea77dbbdbc4e ("net: stmmac: add Eswin EIC7700 glue driver") Signed-off-by: Zhi Li Link: https://patch.msgid.link/20260518022214.507-1-lizhi2@eswincomputing.com Signed-off-by: Paolo Abeni --- .../net/ethernet/stmicro/stmmac/dwmac-eic7700.c | 29 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c index ef60cab24533..4ac979d874d6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-eic7700.c @@ -34,7 +34,10 @@ #define EIC7700_ETH_TX_ADJ_DELAY GENMASK(14, 8) #define EIC7700_ETH_RX_ADJ_DELAY GENMASK(30, 24) -#define EIC7700_MAX_DELAY_UNIT 0x7F +#define EIC7700_MAX_DELAY_STEPS 0x7F +#define EIC7700_DELAY_STEP_PS 20 +#define EIC7700_MAX_DELAY_PS \ + (EIC7700_MAX_DELAY_STEPS * EIC7700_DELAY_STEP_PS) static const char * const eic7700_clk_names[] = { "tx", "axi", "cfg", @@ -128,7 +131,7 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) struct plat_stmmacenet_data *plat_dat; struct stmmac_resources stmmac_res; struct eic7700_qos_priv *dwc_priv; - u32 delay_ps; + u32 delay_ps, val; int i, ret; ret = stmmac_get_platform_resources(pdev, &stmmac_res); @@ -148,7 +151,16 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) /* Read rx-internal-delay-ps and update rx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "rx-internal-delay-ps", &delay_ps)) { - u32 val = min(delay_ps / 20, EIC7700_MAX_DELAY_UNIT); + if (delay_ps % EIC7700_DELAY_STEP_PS) + return dev_err_probe(&pdev->dev, -EINVAL, + "rx delay must be multiple of %dps\n", + EIC7700_DELAY_STEP_PS); + + if (delay_ps > EIC7700_MAX_DELAY_PS) + return dev_err_probe(&pdev->dev, -EINVAL, + "rx delay out of range\n"); + + val = delay_ps / EIC7700_DELAY_STEP_PS; dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_RX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= @@ -161,7 +173,16 @@ static int eic7700_dwmac_probe(struct platform_device *pdev) /* Read tx-internal-delay-ps and update tx_clk delay */ if (!of_property_read_u32(pdev->dev.of_node, "tx-internal-delay-ps", &delay_ps)) { - u32 val = min(delay_ps / 20, EIC7700_MAX_DELAY_UNIT); + if (delay_ps % EIC7700_DELAY_STEP_PS) + return dev_err_probe(&pdev->dev, -EINVAL, + "tx delay must be multiple of %dps\n", + EIC7700_DELAY_STEP_PS); + + if (delay_ps > EIC7700_MAX_DELAY_PS) + return dev_err_probe(&pdev->dev, -EINVAL, + "tx delay out of range\n"); + + val = delay_ps / EIC7700_DELAY_STEP_PS; dwc_priv->eth_clk_dly_param &= ~EIC7700_ETH_TX_ADJ_DELAY; dwc_priv->eth_clk_dly_param |= -- cgit v1.2.3 From 3e6ccd790ed69bedd3d9626d01dd35cf9821c121 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 21 May 2026 10:42:16 +0200 Subject: gpio: cdev: check if uAPI v2 config attributes are correctly zeroed We check the padding of other uAPI v2 structures but not that of line config attributes. For used attributes: check if their padding is zeroed, for unused: check if the entire structure is zeroed. Fixes: 3c0d9c635ae2 ("gpiolib: cdev: support GPIO_V2_GET_LINE_IOCTL and GPIO_V2_LINE_GET_VALUES_IOCTL") Reviewed-by: Kent Gibson Link: https://patch.msgid.link/20260521-gpio-cdev-attr-padding-check-v3-1-ec3bcbe2e358@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-cdev.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index f36b7c06996d..82f27db0b230 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1184,6 +1184,7 @@ static int gpio_v2_line_flags_validate(u64 flags) static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc, unsigned int num_lines) { + size_t unused_attrs; unsigned int i; u64 flags; int ret; @@ -1191,9 +1192,21 @@ static int gpio_v2_line_config_validate(struct gpio_v2_line_config *lc, if (lc->num_attrs > GPIO_V2_LINE_NUM_ATTRS_MAX) return -EINVAL; + unused_attrs = GPIO_V2_LINE_NUM_ATTRS_MAX - lc->num_attrs; + if (!mem_is_zero(lc->padding, sizeof(lc->padding))) return -EINVAL; + for (i = 0; i < lc->num_attrs; i++) { + if (lc->attrs[i].attr.padding != 0) + return -EINVAL; + } + + if (unused_attrs) { + if (!mem_is_zero(&lc->attrs[lc->num_attrs], unused_attrs * sizeof(*lc->attrs))) + return -EINVAL; + } + for (i = 0; i < num_lines; i++) { flags = gpio_v2_line_config_flags(lc, i); ret = gpio_v2_line_flags_validate(flags); -- cgit v1.2.3 From 30c073cab97afb31901f94de9605177b6b84367e Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 20 May 2026 10:49:11 +0200 Subject: gpio: aggregator: fix a potential use-after-free On error we free aggr->lookups->dev_id before removing the entry from the lookup table. If a concurrent thread calls gpiod_find() before we remove the entry, it could iterate over the list and call gpiod_match_lookup_table() which unconditionally dereferences dev_id when calling strcmp(). Reverse the order of cleanup. Fixes: 86f162e73d2d ("gpio: aggregator: introduce basic configfs interface") Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260520084911.27938-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 5915209e1e21..b53230065f50 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -979,8 +979,8 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr) err_unregister_pdev: platform_device_unregister(pdev); err_remove_lookup_table: - kfree(aggr->lookups->dev_id); gpiod_remove_lookup_table(aggr->lookups); + kfree(aggr->lookups->dev_id); err_remove_swnode: fwnode_remove_software_node(swnode); err_remove_lookups: -- cgit v1.2.3 From 61fef83f239ecace1cce716135762a2d9b7b1fc6 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 20 May 2026 14:16:31 +0200 Subject: gpio: aggregator: remove the software node when deactivating the aggregator The dynamic software node we create for the aggregator platform device when using configfs is leaked when the device is deactivated. Destroy it as the last step in the tear-down path. Fixes: 86f162e73d2d ("gpio: aggregator: introduce basic configfs interface") Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/all/CAMuHMdVZ=XUvJTGdDAjnkxgtw7Uvnn61iOy3XN_5XNZM2anctw@mail.gmail.com/ Link: https://patch.msgid.link/20260520121631.33976-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index b53230065f50..a9ad809708fb 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -991,11 +991,15 @@ err_remove_lookups: static void gpio_aggregator_deactivate(struct gpio_aggregator *aggr) { + struct fwnode_handle *swnode; + + swnode = dev_fwnode(&aggr->pdev->dev); platform_device_unregister(aggr->pdev); aggr->pdev = NULL; gpiod_remove_lookup_table(aggr->lookups); kfree(aggr->lookups->dev_id); kfree(aggr->lookups); + fwnode_remove_software_node(swnode); } static void gpio_aggregator_lockup_configfs(struct gpio_aggregator *aggr, -- cgit v1.2.3 From e70ae40d6660c5428c790c329318c570b4d038ab Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 18 May 2026 11:53:17 +0200 Subject: gpio: sim: lock device when calling device_is_bound() The kerneldoc for device_is_bound() says it must be called with the device lock taken. Add missing synchronization to this driver. Fixes: 7fb3287946f9 ("gpio: sim: stop using dev-sync-probe") Link: https://patch.msgid.link/20260518-gpio-dev-lock-v1-1-cc4736f3ff0b@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-sim.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c index e19701c2ed67..0da2c5a45843 100644 --- a/drivers/gpio/gpio-sim.c +++ b/drivers/gpio/gpio-sim.c @@ -901,7 +901,7 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) struct platform_device *pdev; struct fwnode_handle *swnode; struct gpio_sim_bank *bank; - int ret; + int ret = 0; lockdep_assert_held(&dev->lock); @@ -945,9 +945,12 @@ static int gpio_sim_device_activate(struct gpio_sim_device *dev) } wait_for_device_probe(); - if (!device_is_bound(&pdev->dev)) { - ret = -ENXIO; - goto err_unregister_pdev; + + scoped_guard(device, &pdev->dev) { + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } } dev->pdev = pdev; -- cgit v1.2.3 From 598a2b3e2e0e6aa2e9f7843c96c45b5ea11e0411 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 18 May 2026 11:53:18 +0200 Subject: gpio: aggregator: lock device when calling device_is_bound() The kerneldoc for device_is_bound() says it must be called with the device lock taken. Add missing synchronization to this driver. Fixes: 3a27f40b4570 ("gpio: aggregator: stop using dev-sync-probe") Link: https://patch.msgid.link/20260518-gpio-dev-lock-v1-2-cc4736f3ff0b@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-aggregator.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index a9ad809708fb..bc6699a821ee 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -968,9 +968,12 @@ static int gpio_aggregator_activate(struct gpio_aggregator *aggr) } wait_for_device_probe(); - if (!device_is_bound(&pdev->dev)) { - ret = -ENXIO; - goto err_unregister_pdev; + + scoped_guard(device, &pdev->dev) { + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } } aggr->pdev = pdev; -- cgit v1.2.3 From a4fa45c1d980bc2b9837f469119af24a9304a1fc Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 18 May 2026 11:53:19 +0200 Subject: gpio: virtuser: lock device when calling device_is_bound() The kerneldoc for device_is_bound() says it must be called with the device lock taken. Add missing synchronization to this driver. Fixes: c3e2a8aef28c ("gpio: virtuser: stop using dev-sync-probe") Link: https://patch.msgid.link/20260518-gpio-dev-lock-v1-3-cc4736f3ff0b@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-virtuser.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpio/gpio-virtuser.c b/drivers/gpio/gpio-virtuser.c index fe0eac920ced..128520d340d4 100644 --- a/drivers/gpio/gpio-virtuser.c +++ b/drivers/gpio/gpio-virtuser.c @@ -1477,9 +1477,12 @@ gpio_virtuser_device_activate(struct gpio_virtuser_device *dev) } wait_for_device_probe(); - if (!device_is_bound(&pdev->dev)) { - ret = -ENXIO; - goto err_unregister_pdev; + + scoped_guard(device, &pdev->dev) { + if (!device_is_bound(&pdev->dev)) { + ret = -ENXIO; + goto err_unregister_pdev; + } } dev->pdev = pdev; -- cgit v1.2.3 From 03d8273542146f228c0019f08b57545fdee79704 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Thu, 21 May 2026 20:58:36 +0800 Subject: efi/loongarch: Randomize kernel preferred address for KASLR Introduce efi_get_kimg_kaslr_address() helper to compute the preferred kernel image load address dynamically when CONFIG_RANDOMIZE_BASE is enabled. The function derives a random offset by using the EFI-provided randomness combined with the timer tick value, and constrains it within CONFIG_RANDOMIZE_BASE_MAX_OFFSET. Update EFI_KIMG_PREFERRED_ADDRESS to call this helper so that the EFI stub can select a randomized load address when KASLR is active, while preserving the original base address behavior when KASLR is disabled or "nokaslr" is specified. Note: LoongArch can't KASLR for hibernation, so set efi_nokaslr to true if "resume=" is explicitly specified in cmdline. Acked-by: Ard Biesheuvel Signed-off-by: WANG Rui Signed-off-by: Huacai Chen --- drivers/firmware/efi/libstub/efi-stub-helper.c | 4 ++++ drivers/firmware/efi/libstub/loongarch.c | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) (limited to 'drivers') diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c index 7aa2f9ad2935..f27f2e1f0019 100644 --- a/drivers/firmware/efi/libstub/efi-stub-helper.c +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c @@ -79,6 +79,10 @@ efi_status_t efi_parse_options(char const *cmdline) efi_noinitrd = true; } else if (IS_ENABLED(CONFIG_X86_64) && !strcmp(param, "no5lvl")) { efi_no5lvl = true; + } else if (IS_ENABLED(CONFIG_LOONGARCH) && + IS_ENABLED(CONFIG_HIBERNATION) && + !strcmp(param, "resume") && val) { + efi_nokaslr = true; /* LoongArch can't KASLR for hibernation */ } else if (IS_ENABLED(CONFIG_ARCH_HAS_MEM_ENCRYPT) && !strcmp(param, "mem_encrypt") && val) { if (parse_option_str(val, "on")) diff --git a/drivers/firmware/efi/libstub/loongarch.c b/drivers/firmware/efi/libstub/loongarch.c index f7938d5c196a..2b0c87dc9908 100644 --- a/drivers/firmware/efi/libstub/loongarch.c +++ b/drivers/firmware/efi/libstub/loongarch.c @@ -23,6 +23,22 @@ void efi_cache_sync_image(unsigned long image_base, unsigned long alloc_size) asm volatile ("ibar 0" ::: "memory"); } +unsigned long efi_get_kimg_kaslr_address(void) +{ + unsigned int random_offset = 0; + +#ifdef CONFIG_RANDOMIZE_BASE + if (!efi_nokaslr) { + efi_get_random_bytes(sizeof(random_offset), (u8 *)&random_offset); + random_offset ^= (random_get_entropy() << 16); + random_offset &= (CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1); + random_offset = ALIGN(random_offset + SZ_64K, SZ_64K); + } +#endif + + return PHYSADDR(VMLINUX_LOAD_ADDRESS) + random_offset; +} + struct exit_boot_struct { efi_memory_desc_t *runtime_map; int runtime_entry_count; -- cgit v1.2.3 From 6c4e001cb0567b81340270d8fc3d53e39435ae70 Mon Sep 17 00:00:00 2001 From: Kean Ren Date: Thu, 21 May 2026 11:52:27 +0800 Subject: hwmon: (lenovo-ec-sensors): Convert to devm_request_region() Replace manual request_region()/release_region() with devm_request_region(). This lets the device-managed framework handle I/O region lifetime automatically and fixes: - A double release_region() when probe fails after acquiring the I/O region: the probe error path releases it, and then lenovo_ec_init() releases it again on the same error path. - A release-after-use window in lenovo_ec_exit() where release_region() was called before platform_device_unregister(), leaving the hwmon device active with a released I/O region. - Missing release_region() in lenovo_ec_probe() if devm_hwmon_device_register_with_info() fails. Remove all four manual release_region() calls that are now handled automatically and replace request_region with devm_request_region, use dev_err replace pr_err. Also remove the now-unnecessary braces around the single-statement if body. Fixes: 70118f85e6538 ("hwmon: Add EC Chip driver for Lenovo ThinkStation motherboards") Reviewed-by: Mark Pearson Signed-off-by: Kean Ren Link: https://lore.kernel.org/r/20260521035228.533317-2-rh_king@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lenovo-ec-sensors.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/lenovo-ec-sensors.c b/drivers/hwmon/lenovo-ec-sensors.c index 8681bbf6665b..a16cc5e4053a 100644 --- a/drivers/hwmon/lenovo-ec-sensors.c +++ b/drivers/hwmon/lenovo-ec-sensors.c @@ -519,8 +519,8 @@ static int lenovo_ec_probe(struct platform_device *pdev) if (!ec_data) return -ENOMEM; - if (!request_region(IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) { - pr_err(":request fail\n"); + if (!devm_request_region(dev, IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) { + dev_err(dev, "Failed to request I/O region\n"); return -EIO; } @@ -540,10 +540,8 @@ static int lenovo_ec_probe(struct platform_device *pdev) if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') && (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') && (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') && - (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) { - release_region(IO_REGION_START, IO_REGION_LENGTH); + (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) return -ENODEV; - } dmi_id = dmi_first_match(thinkstation_dmi_table); @@ -577,7 +575,6 @@ static int lenovo_ec_probe(struct platform_device *pdev) lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8; break; default: - release_region(IO_REGION_START, IO_REGION_LENGTH); return -ENODEV; } @@ -606,10 +603,8 @@ static int __init lenovo_ec_init(void) platform_create_bundle(&lenovo_ec_sensors_platform_driver, lenovo_ec_probe, NULL, 0, NULL, 0); - if (IS_ERR(lenovo_ec_sensors_platform_device)) { - release_region(IO_REGION_START, IO_REGION_LENGTH); + if (IS_ERR(lenovo_ec_sensors_platform_device)) return PTR_ERR(lenovo_ec_sensors_platform_device); - } return 0; } @@ -617,7 +612,6 @@ module_init(lenovo_ec_init); static void __exit lenovo_ec_exit(void) { - release_region(IO_REGION_START, IO_REGION_LENGTH); platform_device_unregister(lenovo_ec_sensors_platform_device); platform_driver_unregister(&lenovo_ec_sensors_platform_driver); } -- cgit v1.2.3 From e6056b1f5a38bbe57ccbbba2609efecbd87ae08c Mon Sep 17 00:00:00 2001 From: Kean Ren Date: Thu, 21 May 2026 11:52:28 +0800 Subject: hwmon: (lenovo-ec-sensors): Fix EC "MCHP" signature validation logic The EC signature check uses && instead of || between the four byte comparisons. With &&, the condition is true only when ALL four bytes fail to match simultaneously, meaning the driver accepts a device as a valid Microchip EC if ANY single byte of the 4-byte "MCHP" signature happens to match. Due to short-circuit evaluation, if the first byte reads back as 'M' (0x4D, a very common register value), the remaining three comparisons are skipped entirely and the device is accepted. Change && to || so the check rejects devices that do not fully match the expected EC signature, as originally intended. Fixes: 70118f85e6538 ("hwmon: Add EC Chip driver for Lenovo ThinkStation motherboards") Reviewed-by: Mark Pearson Signed-off-by: Kean Ren Link: https://lore.kernel.org/r/20260521035228.533317-3-rh_king@163.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lenovo-ec-sensors.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/lenovo-ec-sensors.c b/drivers/hwmon/lenovo-ec-sensors.c index a16cc5e4053a..24a182abf9a3 100644 --- a/drivers/hwmon/lenovo-ec-sensors.c +++ b/drivers/hwmon/lenovo-ec-sensors.c @@ -537,9 +537,9 @@ static int lenovo_ec_probe(struct platform_device *pdev) outw_p(MCHP_SING_IDX, MCHP_EMI0_EC_ADDRESS); mutex_unlock(&ec_data->mec_mutex); - if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') && - (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') && - (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') && + if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') || + (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') || + (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') || (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) return -ENODEV; -- cgit v1.2.3 From b86095e3d7dcf2bf80c747349a35912a87a85098 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 15 May 2026 15:11:47 -0700 Subject: hwmon: (pmbus/adm1266) seed timestamp from the real-time clock adm1266_set_rtc() seeds the chip's SET_RTC register from ktime_get_seconds(), which returns CLOCK_MONOTONIC -- i.e. seconds since the host last booted, not seconds since the Unix epoch. The chip stamps that value into every blackbox record it captures. Userspace reading those timestamps back expects wall-clock seconds: that's what the SET_RTC frame layout documents (datasheet Rev. D, Table 84) and what every other consumer of "seconds since epoch" assumes. Seeding from CLOCK_MONOTONIC gives blackbox records a timestamp that is only meaningful within a single boot of the host and silently resets to small values on every reboot. Switch to ktime_get_real_seconds() so the seed matches what the register is documented to hold. Fixes: 15609d189302 ("hwmon: (pmbus/adm1266) read blackbox") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260515-adm1266-fixes-v1-1-1c1ea1349cfe@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index d90f8f80be8e..a86666c73a5e 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -432,7 +432,7 @@ static int adm1266_set_rtc(struct adm1266_data *data) char write_buf[6]; int i; - kt = ktime_get_seconds(); + kt = ktime_get_real_seconds(); memset(write_buf, 0, sizeof(write_buf)); -- cgit v1.2.3 From eee213daa1e1b402eb631bcd1b8c5aa340a6b081 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 15 May 2026 15:11:48 -0700 Subject: hwmon: (pmbus/adm1266) widen blackbox-info buffer to I2C_SMBUS_BLOCK_MAX adm1266_nvmem_read_blackbox() declares a 5-byte stack buffer and passes it to i2c_smbus_read_block_data() to retrieve the 4-byte BLACKBOX_INFO response. i2c_smbus_read_block_data() does not honour caller buffer sizes -- it memcpy()s data.block[0] bytes from the SMBus transaction (where data.block[0] is the length byte returned by the slave device, up to I2C_SMBUS_BLOCK_MAX = 32): memcpy(values, &data.block[1], data.block[0]); If the device returns any block length above 5, the call overflows the caller's 5-byte stack buffer before the post-call if (ret != 4) return -EIO; check has a chance to reject the response. Widen the local buffer to I2C_SMBUS_BLOCK_MAX so the helper has room for any well-formed SMBus block response, matching the convention used by the other i2c_smbus_read_block_data() callers in this driver. Fixes: 15609d189302 ("hwmon: (pmbus/adm1266) read blackbox") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260515-adm1266-fixes-v1-2-1c1ea1349cfe@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index a86666c73a5e..94691dec1359 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -349,7 +349,7 @@ static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) { int record_count; char index; - u8 buf[5]; + u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO, buf); -- cgit v1.2.3 From 4afca954622d672ea65ed961bed01cf91caa034e Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 15 May 2026 15:11:49 -0700 Subject: hwmon: (pmbus/adm1266) reject implausible blackbox record_count adm1266_nvmem_read_blackbox() loops over a record_count that comes straight from byte 3 of the BLACKBOX_INFO response. The destination buffer is data->dev_mem, sized for the nvmem cell's declared 2048 bytes (ADM1266_BLACKBOX_MAX_RECORDS * ADM1266_BLACKBOX_SIZE = 32 * 64). A device that reports a record_count greater than 32 -- whether due to firmware bugs, bus corruption, or a non-responsive slave returning 0xff -- would walk read_buff past the end of the dev_mem allocation on the trailing iterations. Cap record_count at ADM1266_BLACKBOX_MAX_RECORDS (introduced here) before entering the loop and return -EIO on any larger value, so a malformed BLACKBOX_INFO response cannot drive the loop out of bounds. Fixes: 15609d189302 ("hwmon: (pmbus/adm1266) read blackbox") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260515-adm1266-fixes-v1-3-1c1ea1349cfe@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 94691dec1359..43d9e7407795 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -46,6 +46,7 @@ #define ADM1266_BLACKBOX_OFFSET 0 #define ADM1266_BLACKBOX_SIZE 64 +#define ADM1266_BLACKBOX_MAX_RECORDS 32 #define ADM1266_PMBUS_BLOCK_MAX 255 @@ -360,6 +361,8 @@ static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) return -EIO; record_count = buf[3]; + if (record_count > ADM1266_BLACKBOX_MAX_RECORDS) + return -EIO; for (index = 0; index < record_count; index++) { ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, read_buff); -- cgit v1.2.3 From 487566cb1ccdf3756fdd7bf8d875e612ff3169bb Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 15 May 2026 15:11:50 -0700 Subject: hwmon: (pmbus/adm1266) include PEC byte in pmbus_block_xfer read buffer adm1266_pmbus_block_xfer() sets up the read transaction with .buf = data->read_buf, .len = ADM1266_PMBUS_BLOCK_MAX + 2, but read_buf in struct adm1266_data is declared as u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1]; For a max-length block response (length byte = 255 + up to 1 PEC byte), the i2c controller is told to write 257 bytes into a 256-byte buffer, putting one byte past the end of read_buf. The same response also makes the subsequent PEC compare if (crc != msgs[1].buf[msgs[1].buf[0] + 1]) read a byte beyond the array. Bump the read_buf declaration to ADM1266_PMBUS_BLOCK_MAX + 2 so the buffer can hold the length byte, up to 255 payload bytes, and the PEC byte the i2c_msg length already accounts for. Fixes: 407dc802a9c0 ("hwmon: (pmbus/adm1266) Add Block process call") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260515-adm1266-fixes-v1-4-1c1ea1349cfe@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 43d9e7407795..5c68e3177f64 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -61,7 +61,7 @@ struct adm1266_data { u8 *dev_mem; struct mutex buf_mutex; u8 write_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; - u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 1] ____cacheline_aligned; + u8 read_buf[ADM1266_PMBUS_BLOCK_MAX + 2] ____cacheline_aligned; }; static const struct nvmem_cell_info adm1266_nvmem_cells[] = { -- cgit v1.2.3 From 4d25342543c01310fc4e0cba7cb17c775e2421e2 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Thu, 14 May 2026 20:32:10 +0000 Subject: drm/xe/oa: Fix exec_queue leak on width check in stream open In xe_oa_stream_open_ioctl(), when param.exec_q->width > 1 the function returns -EOPNOTSUPP directly, skipping the existing err_exec_q cleanup path. The exec_queue reference obtained by xe_exec_queue_lookup() is leaked. The exec queue holds a reference on the xe_file, which is only dropped during queue teardown. The leaked lookup ref is not on the file's exec_queue xarray, so file close cannot release it. This keeps both the exec queue and the file private state pinned indefinitely. Jump to err_exec_q instead of returning directly so the reference is released. Fixes: f0ed39830e60 ("xe/oa: Fix query mode of operation for OAR/OAC") Assisted-by: Claude:claude-opus-4.6 Reviewed-by: Ashutosh Dixit Link: https://patch.msgid.link/20260514203210.593488-1-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin (cherry picked from commit 339fa0be9e4a5d69fa47e91f4a36574224fb478f) Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_oa.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c index 6337e671c97a..d908f4e03906 100644 --- a/drivers/gpu/drm/xe/xe_oa.c +++ b/drivers/gpu/drm/xe/xe_oa.c @@ -2032,8 +2032,10 @@ int xe_oa_stream_open_ioctl(struct drm_device *dev, u64 data, struct drm_file *f if (XE_IOCTL_DBG(oa->xe, !param.exec_q)) return -ENOENT; - if (XE_IOCTL_DBG(oa->xe, param.exec_q->width > 1)) - return -EOPNOTSUPP; + if (XE_IOCTL_DBG(oa->xe, param.exec_q->width > 1)) { + ret = -EOPNOTSUPP; + goto err_exec_q; + } } /* -- cgit v1.2.3 From b0ddda571d15528e6caee7090beff4a66dfdb1a2 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Tue, 12 May 2026 11:56:28 -0700 Subject: hwmon: (pmbus/adm1266) include adapter number in GPIO line label Platforms that fit more than one ADM1266 on different I2C buses at the same 7-bit slave address (a common shelf-management pattern, e.g. one device per power domain) end up with duplicate GPIO line labels because the existing format only includes the slave address. Including the adapter number disambiguates them. The adapter number is formatted as decimal to match the i2c-N convention used elsewhere in Linux (sysfs paths, dev nodes); the slave address keeps its conventional hexadecimal form. The label is purely informational (visible via gpioinfo and the gpiochip /sys/class/gpio name); no DT or ABI consumer parses it. Fixes: d98dfad35c38c ("hwmon: (pmbus/adm1266) Add support for GPIOs") Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260512-adm1266-v3-5-a81a479b0bb0@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 5c68e3177f64..9820dc9e1f2e 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -291,8 +291,9 @@ static int adm1266_config_gpio(struct adm1266_data *data) int i; for (i = 0; i < ARRAY_SIZE(data->gpio_names); i++) { - gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "adm1266-%x-%s", - data->client->addr, adm1266_names[i]); + gpio_name = devm_kasprintf(&data->client->dev, GFP_KERNEL, "adm1266-%d-%x-%s", + data->client->adapter->nr, data->client->addr, + adm1266_names[i]); if (!gpio_name) return -ENOMEM; -- cgit v1.2.3 From 43cae21424ff8e33894a0f86c6b80b840c049fd7 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Fri, 15 May 2026 15:11:51 -0700 Subject: hwmon: (pmbus/adm1266) bounce blackbox records through a protocol-sized buffer adm1266_pmbus_block_xfer() copies the device-supplied block payload into the caller-provided buffer using the device-supplied length: memcpy(data_r, &msgs[1].buf[1], msgs[1].buf[0]); The helper does not know how large data_r is and trusts the device to return at most one record's worth of bytes. adm1266_nvmem_read_blackbox() violates that contract: it advances read_buff inside data->dev_mem in ADM1266_BLACKBOX_SIZE (64-byte) strides while the helper is willing to write up to ADM1266_PMBUS_BLOCK_MAX (255) bytes. A device that returns more than 64 bytes on the trailing record (read_buff offset 1984 in the 2048-byte dev_mem allocation) overflows dev_mem by up to 191 bytes before the post-call if (ret != ADM1266_BLACKBOX_SIZE) return -EIO; can reject the response. Contain the fix in the caller without changing the helper signature: read each record into a 255-byte local bounce buffer that matches the helper's maximum output, validate the returned length, and only then copy exactly ADM1266_BLACKBOX_SIZE bytes into the dev_mem slot. Fixes: 407dc802a9c0 ("hwmon: (pmbus/adm1266) Add Block process call") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260515-adm1266-fixes-v1-5-1c1ea1349cfe@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 9820dc9e1f2e..1fcd86ee96fe 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -349,6 +349,7 @@ static void adm1266_init_debugfs(struct adm1266_data *data) static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) { + u8 record[ADM1266_PMBUS_BLOCK_MAX]; int record_count; char index; u8 buf[I2C_SMBUS_BLOCK_MAX]; @@ -366,13 +367,14 @@ static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff) return -EIO; for (index = 0; index < record_count; index++) { - ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, read_buff); + ret = adm1266_pmbus_block_xfer(data, ADM1266_READ_BLACKBOX, 1, &index, record); if (ret < 0) return ret; if (ret != ADM1266_BLACKBOX_SIZE) return -EIO; + memcpy(read_buff, record, ADM1266_BLACKBOX_SIZE); read_buff += ADM1266_BLACKBOX_SIZE; } -- cgit v1.2.3 From d7834d92251baade796812876e95555e2066fa9f Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:25 -0700 Subject: hwmon: (pmbus/adm1266) cap PDIO scan in get_multiple at ADM1266_PDIO_NR adm1266_gpio_get_multiple() iterates the PDIO portion of the caller-supplied mask using for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) { ... } where ADM1266_PDIO_STATUS is the PMBus command code (0xE9, i.e. 233), not the number of PDIO pins. The intended upper bound is ADM1266_GPIO_NR + ADM1266_PDIO_NR = 25. gpiolib hands in a mask sized for gc.ngpio (= 25 bits on this chip), so the iteration walks find_next_bit() up to 242, reading up to 217 extra bits (a handful of unsigned-long words: four on 64-bit, seven on 32-bit) of whatever lives past the end of the mask in the caller's stack. Any incidental set bit in that range then drives a set_bit(gpio_nr, bits) call that writes past the end of the caller-supplied bits array too -- both out-of-bounds. Substitute ADM1266_PDIO_NR for the constant so the scan stops at the last real PDIO bit. Fixes: d98dfad35c38 ("hwmon: (pmbus/adm1266) Add support for GPIOs") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Reviewed-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-1-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 1fcd86ee96fe..4b2a8a765a4e 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -212,7 +212,7 @@ static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask status = read_buf[0] + (read_buf[1] << 8); *bits = 0; - for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_STATUS) { + for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_NR) { if (test_bit(gpio_nr - ADM1266_GPIO_NR, &status)) set_bit(gpio_nr, bits); } -- cgit v1.2.3 From 3327a12aee9e10ffa903e28b8445dfd1af5307c0 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:26 -0700 Subject: hwmon: (pmbus/adm1266) don't clobber GPIO bits before PDIO read in get_multiple adm1266_gpio_get_multiple() zeroes *bits before the GPIO_STATUS loop and then a second time before the PDIO_STATUS loop: *bits = 0; for_each_set_bit(gpio_nr, mask, ADM1266_GPIO_NR) { ... set_bit(gpio_nr, bits); } ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, ...); ... *bits = 0; for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_NR) { ... set_bit(gpio_nr, bits); } The second *bits = 0 throws away every GPIO bit the first loop just populated, so callers asking for any combination of GPIO and PDIO pins always see the GPIO portion of the returned bits as zero. Drop the redundant second assignment so both halves of the result survive. Fixes: d98dfad35c38 ("hwmon: (pmbus/adm1266) Add support for GPIOs") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Reviewed-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-2-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 4b2a8a765a4e..3bf512df30cd 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -211,7 +211,6 @@ static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask status = read_buf[0] + (read_buf[1] << 8); - *bits = 0; for_each_set_bit_from(gpio_nr, mask, ADM1266_GPIO_NR + ADM1266_PDIO_NR) { if (test_bit(gpio_nr - ADM1266_GPIO_NR, &status)) set_bit(gpio_nr, bits); -- cgit v1.2.3 From a7232f68c43ca62f545049b7f5fbfc75137b843b Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:27 -0700 Subject: hwmon: (pmbus/adm1266) reject short block-read responses in the GPIO accessors adm1266_gpio_get() and adm1266_gpio_get_multiple() both compose the pin-status word as pins_status = read_buf[0] + (read_buf[1] << 8); right after i2c_smbus_read_block_data(), guarding only against an error return. A well-behaved device returns 2 bytes for GPIO_STATUS/PDIO_STATUS, but the helper happily reports a 0- or 1-byte response too. If the device returns 0 bytes, both read_buf slots are uninitialized stack memory; if it returns 1 byte, read_buf[1] is. The composed value then flows through set_bit() into the caller's *bits in adm1266_gpio_get_multiple(), or into the return value of adm1266_gpio_get(), and ends up in userspace via gpiolib (sysfs and the char-dev ioctls). That leaks a few bits of kernel stack per request on any device whose firmware glitch, bus error, or hostile slave produces a short block-read response. Add the missing length check to both call sites and surface a short response as -EIO. Fixes: d98dfad35c38 ("hwmon: (pmbus/adm1266) Add support for GPIOs") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-3-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 3bf512df30cd..6f904a5a8ea6 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -176,6 +176,8 @@ static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset) ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf); if (ret < 0) return ret; + if (ret < 2) + return -EIO; pins_status = read_buf[0] + (read_buf[1] << 8); if (offset < ADM1266_GPIO_NR) @@ -196,6 +198,8 @@ static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf); if (ret < 0) return ret; + if (ret < 2) + return -EIO; status = read_buf[0] + (read_buf[1] << 8); @@ -208,6 +212,8 @@ static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, read_buf); if (ret < 0) return ret; + if (ret < 2) + return -EIO; status = read_buf[0] + (read_buf[1] << 8); -- cgit v1.2.3 From 491403b9b76cf66abd81301c5901aa4a4549f1e8 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:28 -0700 Subject: hwmon: (pmbus/adm1266) register the gpio_chip after pmbus_do_probe() adm1266_probe() calls adm1266_config_gpio() -- which goes on to devm_gpiochip_add_data() and exposes the gpio_chip callbacks to gpiolib -- before pmbus_do_probe() has initialised the per-client PMBus state (notably the pmbus_lock mutex the core hands out via pmbus_get_data()). That ordering is already a latent hazard: any GPIO access that lands between adm1266_config_gpio() and the end of pmbus_do_probe() (for example a sysfs read from a user space agent that opens the gpiochip the instant gpiolib advertises it) races pmbus_do_probe()'s own device accesses with no serialisation. Move adm1266_config_gpio() down past pmbus_do_probe() so the chip isn't reachable from userspace until the PMBus state it depends on is fully initialised. Fixes: d98dfad35c38 ("hwmon: (pmbus/adm1266) Add support for GPIOs") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-4-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 6f904a5a8ea6..c2ac5d228b7d 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -473,10 +473,6 @@ static int adm1266_probe(struct i2c_client *client) crc8_populate_msb(pmbus_crc_table, 0x7); mutex_init(&data->buf_mutex); - ret = adm1266_config_gpio(data); - if (ret < 0) - return ret; - ret = adm1266_set_rtc(data); if (ret < 0) return ret; @@ -489,6 +485,10 @@ static int adm1266_probe(struct i2c_client *client) if (ret) return ret; + ret = adm1266_config_gpio(data); + if (ret < 0) + return ret; + adm1266_init_debugfs(data); return 0; -- cgit v1.2.3 From 6af713af91d5c34ec049eb3cc2c5b3f5eba953b8 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:29 -0700 Subject: hwmon: (pmbus/adm1266) register the nvmem device after pmbus_do_probe() adm1266_probe() calls adm1266_config_nvmem() -- which goes on to devm_nvmem_register() and exposes adm1266_nvmem_read() to userspace -- before pmbus_do_probe() has initialised the per-client PMBus state. Same latent hazard as the gpio_chip one fixed in the previous patch: once the nvmem device is registered, gpiolib's nvmem char-dev / sysfs interface is reachable, and any concurrent read triggers adm1266_nvmem_read() -> adm1266_nvmem_read_blackbox(), which issues PMBus traffic that races pmbus_do_probe()'s own device accesses with no serialisation. Move adm1266_config_nvmem() down past pmbus_do_probe() so the nvmem device isn't reachable from userspace until the PMBus state the nvmem accessors depend on is fully initialised. Fixes: 15609d189302 ("hwmon: (pmbus/adm1266) read blackbox") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-5-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index c2ac5d228b7d..3e8f2619cb9b 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -477,14 +477,14 @@ static int adm1266_probe(struct i2c_client *client) if (ret < 0) return ret; - ret = adm1266_config_nvmem(data); - if (ret < 0) - return ret; - ret = pmbus_do_probe(client, &data->info); if (ret) return ret; + ret = adm1266_config_nvmem(data); + if (ret < 0) + return ret; + ret = adm1266_config_gpio(data); if (ret < 0) return ret; -- cgit v1.2.3 From bab8c6fb5af8df7e753d196c1262cb78e92ca872 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:30 -0700 Subject: hwmon: (pmbus/adm1266) serialize GPIO PMBus accesses with pmbus_lock adm1266_gpio_get(), adm1266_gpio_get_multiple(), and adm1266_gpio_dbg_show() all issue PMBus reads against the device but none of them take pmbus_lock. The pmbus_core framework holds pmbus_lock around its own multi-transaction sequences (notably the "set PAGE, then read paged register" pattern used by hwmon attributes), so an unlocked GPIO accessor can land between a PAGE write and the subsequent paged read in another thread and corrupt either side's view of the device state machine. Take pmbus_lock at the top of each of the three accessors via the scope-based guard(). The lock is uncontended in the common case and adds only a single mutex round-trip per call. Fixes: d98dfad35c38 ("hwmon: (pmbus/adm1266) Add support for GPIOs") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Reviewed-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-6-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 3e8f2619cb9b..0eef58dd69a6 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -173,6 +173,8 @@ static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset) else pmbus_cmd = ADM1266_PDIO_STATUS; + guard(pmbus_lock)(data->client); + ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf); if (ret < 0) return ret; @@ -195,6 +197,8 @@ static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask unsigned int gpio_nr; int ret; + guard(pmbus_lock)(data->client); + ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf); if (ret < 0) return ret; @@ -236,6 +240,8 @@ static void adm1266_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) int ret; int i; + guard(pmbus_lock)(data->client); + for (i = 0; i < ADM1266_GPIO_NR; i++) { write_cmd = adm1266_gpio_mapping[i][1]; ret = adm1266_pmbus_block_xfer(data, ADM1266_GPIO_CONFIG, 1, &write_cmd, read_buf); -- cgit v1.2.3 From 9f1dd8f9491eb840cbea7ffdf4cad031e25f8ae0 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:31 -0700 Subject: hwmon: (pmbus/adm1266) serialize NVMEM blackbox read with pmbus_lock adm1266_nvmem_read() is the reg_read callback the NVMEM core invokes when userspace reads /sys/bus/nvmem/devices/.../nvmem on this chip. On the first byte of every read it does a memset of data->dev_mem, walks the device blackbox through adm1266_nvmem_read_blackbox() (which issues a chain of PMBus block transactions), and then memcpys the refreshed buffer out to userspace. None of that runs under pmbus_lock today. Two consequences: - The PMBus traffic the refresh issues is not serialised against pmbus_core's own multi-step PAGE+register sequences. A paged hwmon attribute read from another thread can land between a PAGE write and the paged read in either direction and corrupt one side's view of the device state machine. - The NVMEM core does not serialise concurrent reg_read calls, so two userspace readers racing at offset 0 can interleave the memset of data->dev_mem with another reader's adm1266_nvmem_read_blackbox() refill or memcpy out, returning torn data to userspace. Take pmbus_lock at the top of adm1266_nvmem_read() via the scope-based guard(). Patch 5 of this series moves adm1266_config_nvmem() past pmbus_do_probe() so the lock is guaranteed to be live before the callback is reachable from userspace. Fixes: 15609d189302 ("hwmon: (pmbus/adm1266) read blackbox") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-7-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 0eef58dd69a6..5ddca5701032 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -400,6 +400,8 @@ static int adm1266_nvmem_read(void *priv, unsigned int offset, void *val, size_t if (offset + bytes > data->nvmem_config.size) return -EINVAL; + guard(pmbus_lock)(data->client); + if (offset == 0) { memset(data->dev_mem, 0, data->nvmem_config.size); -- cgit v1.2.3 From 4e4af55aaca7f6d7673d5f9889ad0529db86a048 Mon Sep 17 00:00:00 2001 From: Abdurrahman Hussain Date: Mon, 18 May 2026 17:52:32 -0700 Subject: hwmon: (pmbus/adm1266) serialize sequencer_state debugfs read with pmbus_lock adm1266_state_read() backs the sequencer_state debugfs entry and issues an i2c_smbus_read_word_data(client, ADM1266_READ_STATE) against the device without taking pmbus_lock. pmbus_core holds pmbus_lock around its own multi-transaction sequences (notably the "set PAGE, then read paged register" pattern used by hwmon attributes), so an unlocked debugfs reader can land between a PAGE write and the subsequent paged read in another thread. READ_STATE itself is not paged, so it cannot corrupt PAGE in flight, but the same defensive serialisation that applies to the GPIO accessors applies here: any direct device access from outside pmbus_core should be ordered with respect to pmbus_core's own. Take pmbus_lock at the top of adm1266_state_read() via the scope-based guard(). Fixes: ed1ff457e187 ("hwmon: (pmbus/adm1266) add debugfs for states") Cc: stable@vger.kernel.org Signed-off-by: Abdurrahman Hussain Link: https://lore.kernel.org/r/20260518-adm1266-gpio-fixes-v3-8-e425e4f88139@nexthop.ai Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1266.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c index 5ddca5701032..6f6ad7b20e9a 100644 --- a/drivers/hwmon/pmbus/adm1266.c +++ b/drivers/hwmon/pmbus/adm1266.c @@ -335,6 +335,7 @@ static int adm1266_state_read(struct seq_file *s, void *pdata) struct i2c_client *client = to_i2c_client(dev); int ret; + guard(pmbus_lock)(client); ret = i2c_smbus_read_word_data(client, ADM1266_READ_STATE); if (ret < 0) return ret; -- cgit v1.2.3 From 85686c72966c5ee637893f124ddb31a1cace7bee Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 19 May 2026 18:03:44 -0700 Subject: nvme-pci: fix dma_vecs leak on p2p memory We don't unmap P2P memory, so we don't need to track it. The dma_vec allocation was getting leaked on the completion. Fixes: b8b7570a7ec87 ("nvme-pci: fix dma unmapping when using PRPs and not using the IOVA mapping") Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 139a10cd687f..f423f1718439 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -966,7 +966,8 @@ static bool nvme_pci_prp_save_mapping(struct request *req, { struct nvme_iod *iod = blk_mq_rq_to_pdu(req); - if (dma_use_iova(&iod->dma_state) || !dma_need_unmap(dma_dev)) + if (dma_use_iova(&iod->dma_state) || !dma_need_unmap(dma_dev) || + (iod->flags & IOD_DATA_P2P)) return true; if (!iod->nr_dma_vecs) { -- cgit v1.2.3 From 1bf86336e4b6cf40873fda47a7fe191446864937 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 19 May 2026 13:01:57 -0700 Subject: nvme-pci: fix dma mapping leak on data setup error We're leaking the initial DMA mapping during iteration if we fail to allocate the tracking descriptor for both PRP and SGL. Unmap the iterator directly; we can't use the existing unmap helper because it depends on the tracking descriptor being successfully allocated, so a new one for an in-use iterator is provided. The mappings were also leaking when the driver detects an invalid bio_vec when mapping PRPs, so fix that too. Fixes: b8b7570a7ec87 ("nvme-pci: fix dma unmapping when using PRPs and not using the IOVA mapping") Fixes: 7ce3c1dd78fca ("nvme-pci: convert the data mapping to blk_rq_dma_map") Reviewed-by: Christoph Hellwig Signed-off-by: Keith Busch --- drivers/nvme/host/pci.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index f423f1718439..b5f846200678 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -997,6 +997,23 @@ static bool nvme_pci_prp_iter_next(struct request *req, struct device *dma_dev, return nvme_pci_prp_save_mapping(req, dma_dev, iter); } +static void nvme_unmap_iter(struct request *req, struct blk_dma_iter *iter, + struct dma_iova_state *state) +{ + struct nvme_queue *nvmeq = req->mq_hctx->driver_data; + struct device *dev = nvmeq->dev->dev; + + if (!blk_rq_dma_unmap(req, dev, state, iter->len, iter->p2pdma.map)) { + unsigned int attrs = 0; + + if (iter->p2pdma.map == PCI_P2PDMA_MAP_THRU_HOST_BRIDGE) + attrs |= DMA_ATTR_MMIO; + + dma_unmap_phys(dev, iter->addr, iter->len, rq_dma_dir(req), + attrs); + } +} + static blk_status_t nvme_pci_setup_data_prp(struct request *req, struct blk_dma_iter *iter) { @@ -1007,8 +1024,10 @@ static blk_status_t nvme_pci_setup_data_prp(struct request *req, unsigned int prp_len, i; __le64 *prp_list; - if (!nvme_pci_prp_save_mapping(req, nvmeq->dev->dev, iter)) + if (!nvme_pci_prp_save_mapping(req, nvmeq->dev->dev, iter)) { + nvme_unmap_iter(req, iter, &iod->dma_state); return iter->status; + } /* * PRP1 always points to the start of the DMA transfers. @@ -1113,6 +1132,7 @@ bad_sgl: dev_err_once(nvmeq->dev->dev, "Incorrectly formed request for payload:%d nents:%d\n", blk_rq_payload_bytes(req), blk_rq_nr_phys_segments(req)); + nvme_unmap_data(req); return BLK_STS_IOERR; } @@ -1156,8 +1176,11 @@ static blk_status_t nvme_pci_setup_data_sgl(struct request *req, sg_list = dma_pool_alloc(nvme_dma_pool(nvmeq, iod), GFP_ATOMIC, &sgl_dma); - if (!sg_list) + if (!sg_list) { + nvme_unmap_iter(req, iter, &iod->dma_state); return BLK_STS_RESOURCE; + } + iod->descriptors[iod->nr_descriptors++] = sg_list; do { @@ -1314,8 +1337,10 @@ static blk_status_t nvme_pci_setup_meta_iter(struct request *req) sg_list = dma_pool_alloc(nvmeq->descriptor_pools.small, GFP_ATOMIC, &sgl_dma); - if (!sg_list) + if (!sg_list) { + nvme_unmap_iter(req, &iter, &iod->meta_dma_state); return BLK_STS_RESOURCE; + } iod->meta_descriptor = sg_list; iod->meta_dma = sgl_dma; -- cgit v1.2.3 From c5d93b2c40355e999715262a824965aac025a427 Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Tue, 19 May 2026 11:57:39 +0530 Subject: net: wwan: iosm: fix potential memory leaks in ipc_imem_init() The memory allocated in ipc_protocol_init() is not freed on the error paths that follow in ipc_imem_init(). Fix that by calling the corresponding release function ipc_protocol_deinit() in the error path. Fixes: 3670970dd8c6 ("net: iosm: shared memory IPC interface") Cc: stable@vger.kernel.org Signed-off-by: Abdun Nihaal Link: https://patch.msgid.link/20260519062815.55545-1-nihaal@cse.iitm.ac.in Signed-off-by: Jakub Kicinski --- drivers/net/wwan/iosm/iosm_ipc_imem.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem.c b/drivers/net/wwan/iosm/iosm_ipc_imem.c index 1b7bc7d63a2e..4405c8531888 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem.c +++ b/drivers/net/wwan/iosm/iosm_ipc_imem.c @@ -1425,6 +1425,8 @@ imem_config_fail: protocol_init_fail: cancel_work_sync(&ipc_imem->run_state_worker); ipc_task_deinit(ipc_imem->ipc_task); + if (ipc_imem->ipc_protocol) + ipc_protocol_deinit(ipc_imem->ipc_protocol); ipc_task_init_fail: kfree(ipc_imem->ipc_task); ipc_task_fail: -- cgit v1.2.3 From 9eddc819f00b5b74bb4ac91396f80bd35f5f3561 Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Wed, 20 May 2026 10:00:36 +0530 Subject: octeontx2-af: npc: Fix allmulticast skip logic for LBK and SDP VFs When installing the allmulticast NPC rule, rvu_npc_install_allmulti_entry() should skip LBK and SDP VFs (only CGX PF/VF may add the entry). The code combined is_lbk_vf() and is_sdp_vf() with logical AND, which is never true for a single pcifunc, so the intended early return never ran. Use logical OR instead. Cc: Geetha sowjanya Fixes: ae703539f49d2 ("octeontx2-af: Cleanup loopback device checks") Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260520043036.1523798-1-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c index 3c814d157ab9..607d0cf1a778 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_npc.c @@ -990,7 +990,7 @@ void rvu_npc_install_allmulti_entry(struct rvu *rvu, u16 pcifunc, int nixlf, u16 vf_func; /* Only CGX PF/VF can add allmulticast entry */ - if (is_lbk_vf(rvu, pcifunc) && is_sdp_vf(rvu, pcifunc)) + if (is_lbk_vf(rvu, pcifunc) || is_sdp_vf(rvu, pcifunc)) return; blkaddr = rvu_get_blkaddr(rvu, BLKTYPE_NPC, 0); -- cgit v1.2.3 From b809d0409991b75a6cff846a5ac27c3062953f84 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Tue, 19 May 2026 22:15:53 -0700 Subject: net: mana: validate rx_req_idx to prevent out-of-bounds array access In mana_hwc_rx_event_handler(), rx_req_idx is derived from sge->address in DMA-coherent memory. In Confidential VMs (SEV-SNP/TDX), this memory is shared unencrypted and HW can modify WQE contents at any time. No bounds check exists on rx_req_idx, which can lead to an out-of-bounds access into reqs[]. Add bounds check on rx_req_idx in mana_hwc_rx_event_handler() before using it to index the reqs[] array. Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)") Signed-off-by: Aditya Garg Reviewed-by: Haiyang Zhang Link: https://patch.msgid.link/20260520051553.857120-1-gargaditya@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/hw_channel.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c index fd8b324d7fb6..e3c24d50dad0 100644 --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c @@ -265,6 +265,12 @@ static void mana_hwc_rx_event_handler(void *ctx, u32 gdma_rxq_id, rq_base_addr = hwc_rxq->msg_buf->mem_info.dma_handle; rx_req_idx = (sge->address - rq_base_addr) / hwc->max_req_msg_size; + if (rx_req_idx >= hwc_rxq->msg_buf->num_reqs) { + dev_err(hwc->dev, "HWC RX: wrong rx_req_idx=%llu, num_reqs=%u\n", + rx_req_idx, hwc_rxq->msg_buf->num_reqs); + return; + } + rx_req = &hwc_rxq->msg_buf->reqs[rx_req_idx]; resp = (struct gdma_resp_hdr *)rx_req->buf_va; -- cgit v1.2.3 From 2bccfb8476ca5f3548afbd623dc7a6980d4e77de Mon Sep 17 00:00:00 2001 From: Dawei Feng Date: Wed, 20 May 2026 15:03:23 +0800 Subject: qed: fix double free in qed_cxt_tables_alloc() If one of the later PF or VF CID bitmap allocations fails, qed_cid_map_alloc() jumps to cid_map_fail and frees the previously allocated CID bitmaps before returning an error. qed_cxt_tables_alloc() then calls qed_cxt_mngr_free(), which invokes qed_cid_map_free() again. Fix this by setting each CID bitmap pointer to NULL after bitmap_free() to avoid double free. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. Manual inspection confirms that the bug is still present in v7.1-rc3. Runtime reproduction was not attempted because exercising the failing allocation path requires device-specific setup. Fixes: fe56b9e6a8d9 ("qed: Add module with basic common support") Cc: stable@vger.kernel.org Signed-off-by: Zilin Guan Signed-off-by: Dawei Feng Link: https://patch.msgid.link/20260520070323.2762379-1-dawei.feng@seu.edu.cn Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/qlogic/qed/qed_cxt.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/qlogic/qed/qed_cxt.c b/drivers/net/ethernet/qlogic/qed/qed_cxt.c index 9861daa82d9e..b70262e70baf 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_cxt.c +++ b/drivers/net/ethernet/qlogic/qed/qed_cxt.c @@ -1036,11 +1036,13 @@ static void qed_cid_map_free(struct qed_hwfn *p_hwfn) for (type = 0; type < MAX_CONN_TYPES; type++) { bitmap_free(p_mngr->acquired[type].cid_map); + p_mngr->acquired[type].cid_map = NULL; p_mngr->acquired[type].max_count = 0; p_mngr->acquired[type].start_cid = 0; for (vf = 0; vf < MAX_NUM_VFS; vf++) { bitmap_free(p_mngr->acquired_vf[type][vf].cid_map); + p_mngr->acquired_vf[type][vf].cid_map = NULL; p_mngr->acquired_vf[type][vf].max_count = 0; p_mngr->acquired_vf[type][vf].start_cid = 0; } -- cgit v1.2.3 From bddc09212c24934643bd44fc794748d2bbb3b6cd Mon Sep 17 00:00:00 2001 From: Weiming Shi Date: Wed, 20 May 2026 00:57:38 -0700 Subject: tap: fix stack info leak in tap_ioctl() SIOCGIFHWADDR In the SIOCGIFHWADDR path, tap_ioctl() copies 16 bytes of an uninitialised on-stack struct sockaddr_storage to userspace via ifr_hwaddr, but netif_get_mac_address() only writes sa_family and dev->addr_len (6 for Ethernet) bytes, leaving sa_data[6..13] uninitialised. Those 8 trailing bytes leak kernel stack contents; SIOCGIFHWADDR on a macvtap chardev returns kernel .text and direct-map pointers, defeating KASLR. Initialise ss at declaration. Fixes: 3b23a32a6321 ("net: fix dev_ifsioc_locked() race condition") Reported-by: Xiang Mei Signed-off-by: Weiming Shi Reviewed-by: Willem de Bruijn Link: https://patch.msgid.link/20260520075736.3415676-3-bestswngs@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/tap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/tap.c b/drivers/net/tap.c index b8240737dc51..a590e07ce0a9 100644 --- a/drivers/net/tap.c +++ b/drivers/net/tap.c @@ -919,11 +919,11 @@ static long tap_ioctl(struct file *file, unsigned int cmd, struct tap_queue *q = file->private_data; struct tap_dev *tap; void __user *argp = (void __user *)arg; + struct sockaddr_storage ss = {}; struct ifreq __user *ifr = argp; unsigned int __user *up = argp; unsigned short u; int __user *sp = argp; - struct sockaddr_storage ss; int s; int ret; -- cgit v1.2.3 From 985d4a55e64e43bd86eeb896b81ceba453301989 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Wed, 20 May 2026 15:12:02 +0200 Subject: net: airoha: Disable GDM2 forwarding before configuring GDM2 loopback Hw design requires to disable GDM2 forwarding before configuring GDM2 loopback in airoha_set_gdm2_loopback routine. Fixes: 9cd451d414f6e ("net: airoha: Add loopback support for GDM2") Tested-by: Madhur Agrawal Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260520-airoha-disable-gdm2-fwd-v1-1-1eeea5dffc2f@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/airoha/airoha_eth.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c index d0c0c0ec8a80..cecd66251dba 100644 --- a/drivers/net/ethernet/airoha/airoha_eth.c +++ b/drivers/net/ethernet/airoha/airoha_eth.c @@ -1793,11 +1793,8 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port) u32 val, pse_port, chan; int i, src_port; - /* Forward the traffic to the proper GDM port */ - pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3 - : FE_PSE_PORT_GDM4; airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), - pse_port); + FE_PSE_PORT_DROP); airoha_fe_clear(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), GDM_STRIP_CRC_MASK); @@ -1815,6 +1812,11 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port) GDM_SHORT_LEN_MASK | GDM_LONG_LEN_MASK, FIELD_PREP(GDM_SHORT_LEN_MASK, 60) | FIELD_PREP(GDM_LONG_LEN_MASK, AIROHA_MAX_MTU)); + /* Forward the traffic to the proper GDM port */ + pse_port = port->id == AIROHA_GDM3_IDX ? FE_PSE_PORT_GDM3 + : FE_PSE_PORT_GDM4; + airoha_set_gdm_port_fwd_cfg(eth, REG_GDM_FWD_CFG(AIROHA_GDM2_IDX), + pse_port); /* Disable VIP and IFC for GDM2 */ airoha_fe_clear(eth, REG_FE_VIP_PORT_EN, BIT(AIROHA_GDM2_IDX)); -- cgit v1.2.3 From 3d4432d34c1992701289cbe12df9fd024f315998 Mon Sep 17 00:00:00 2001 From: "Nikhil P. Rao" Date: Wed, 20 May 2026 20:58:42 +0000 Subject: pds_core: ensure null-termination for firmware version strings The driver passes fw_version directly to devlink_info_version_stored_put() without ensuring null-termination. While current firmware null-terminates these strings, the driver should not rely on this behavior. Add explicit null-termination to prevent potential issues if firmware behavior changes. Fixes: 45d76f492938 ("pds_core: set up device and adminq") Signed-off-by: Nikhil P. Rao Link: https://patch.msgid.link/20260520205842.1486718-1-nikhil.rao@amd.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/amd/pds_core/devlink.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index b576be626a29..3f0e56b951bf 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -122,12 +122,14 @@ int pdsc_dl_info_get(struct devlink *dl, struct devlink_info_req *req, listlen = min(fw_list.num_fw_slots, ARRAY_SIZE(fw_list.fw_names)); for (i = 0; i < listlen; i++) { + char *fw_ver = fw_list.fw_names[i].fw_version; + if (i < ARRAY_SIZE(fw_slotnames)) strscpy(buf, fw_slotnames[i], sizeof(buf)); else snprintf(buf, sizeof(buf), "fw.slot_%d", i); - err = devlink_info_version_stored_put(req, buf, - fw_list.fw_names[i].fw_version); + fw_ver[sizeof(fw_list.fw_names[i].fw_version) - 1] = '\0'; + err = devlink_info_version_stored_put(req, buf, fw_ver); if (err) return err; } -- cgit v1.2.3 From 28db0338db61ec75d146192463907351907a0dbc Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Wed, 20 May 2026 12:18:49 +0100 Subject: Revert "drivers: net: 3com: 3c509: Remove this driver" This reverts commit 91f3a27ae9f66d81a5906461762c37c8a2bcab06. Contrary to the assumption stated with the original commit description this driver is in use and I'm going to maintain it for the foreseeable future. Signed-off-by: Maciej W. Rozycki Link: https://patch.msgid.link/alpine.DEB.2.21.2605201204260.1450@angie.orcam.me.uk Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/3com/3c509.c | 1448 ++++++++++++++++++++++++++++++++++++ drivers/net/ethernet/3com/Kconfig | 14 + drivers/net/ethernet/3com/Makefile | 1 + 3 files changed, 1463 insertions(+) create mode 100644 drivers/net/ethernet/3com/3c509.c (limited to 'drivers') diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c new file mode 100644 index 000000000000..fb68339e1511 --- /dev/null +++ b/drivers/net/ethernet/3com/3c509.c @@ -0,0 +1,1448 @@ +/* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */ +/* + Written 1993-2000 by Donald Becker. + + Copyright 1994-2000 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + + This driver is for the 3Com EtherLinkIII series. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + Known limitations: + Because of the way 3c509 ISA detection works it's difficult to predict + a priori which of several ISA-mode cards will be detected first. + + This driver does not use predictive interrupt mode, resulting in higher + packet latency but lower overhead. If interrupts are disabled for an + unusually long time it could also result in missed packets, but in + practice this rarely happens. + + + FIXES: + Alan Cox: Removed the 'Unexpected interrupt' bug. + Michael Meskes: Upgraded to Donald Becker's version 1.07. + Alan Cox: Increased the eeprom delay. Regardless of + what the docs say some people definitely + get problems with lower (but in card spec) + delays + v1.10 4/21/97 Fixed module code so that multiple cards may be detected, + other cleanups. -djb + Andrea Arcangeli: Upgraded to Donald Becker's version 1.12. + Rick Payne: Fixed SMP race condition + v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable variable -djb + v1.14 10/15/97 Avoided waiting..discard message for fast machines -djb + v1.15 1/31/98 Faster recovery for Tx errors. -djb + v1.16 2/3/98 Different ID port handling to avoid sound cards. -djb + v1.18 12Mar2001 Andrew Morton + - Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz) + - Reviewed against 1.18 from scyld.com + v1.18a 17Nov2001 Jeff Garzik + - ethtool support + v1.18b 1Mar2002 Zwane Mwaikambo + - Power Management support + v1.18c 1Mar2002 David Ruggiero + - Full duplex support + v1.19 16Oct2002 Zwane Mwaikambo + - Additional ethtool features + v1.19a 28Oct2002 Davud Ruggiero + - Increase *read_eeprom udelay to workaround oops with 2 cards. + v1.19b 08Nov2002 Marc Zyngier + - Introduce driver model for EISA cards. + v1.20 04Feb2008 Ondrej Zary + - convert to isa_driver and pnp_driver and some cleanups +*/ + +#define DRV_NAME "3c509" + +/* A few values that may be tweaked. */ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (400*HZ/1000) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for udelay() */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef EL3_DEBUG +static int el3_debug = EL3_DEBUG; +#else +static int el3_debug = 2; +#endif + +/* Used to do a global count of all the cards in the system. Must be + * a global variable so that the eisa probe routines can increment + * it */ +static int el3_cards = 0; +#define EL3_MAX_CARDS 8 + +/* To minimize the size of the driver source I only define operating + constants if they are used several times. You'll need the manual + anyway if you want to understand driver details. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e +#define EEPROM_READ 0x80 + +#define EL3_IO_EXTENT 16 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum c509cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11, PowerUp = 27<<11, + PowerDown = 28<<11, PowerAuto = 29<<11}; + +enum c509status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO 0x00 +#define RX_FIFO 0x00 +#define RX_STATUS 0x08 +#define TX_STATUS 0x0B +#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ + +#define WN0_CONF_CTRL 0x04 /* Window 0: Configuration control register */ +#define WN0_ADDR_CONF 0x06 /* Window 0: Address configuration register */ +#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ +#define WN4_NETDIAG 0x06 /* Window 4: Net diagnostic */ +#define FD_ENABLE 0x8000 /* Enable full-duplex ("external loopback") */ + +/* + * Must be a power of two (we use a binary and in the + * circular queue) + */ +#define SKB_QUEUE_SIZE 64 + +enum el3_cardtype { EL3_ISA, EL3_PNP, EL3_EISA }; + +struct el3_private { + spinlock_t lock; + /* skb send-queue */ + int head, size; + struct sk_buff *queue[SKB_QUEUE_SIZE]; + enum el3_cardtype type; +}; +static int id_port; +static int current_tag; +static struct net_device *el3_devs[EL3_MAX_CARDS]; + +/* Parameters that may be passed into the module. */ +static int debug = -1; +static int irq[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 10; +#ifdef CONFIG_PNP +static int nopnp; +#endif + +static int el3_common_init(struct net_device *dev); +static void el3_common_remove(struct net_device *dev); +static ushort id_read_eeprom(int index); +static ushort read_eeprom(int ioaddr, int index); +static int el3_open(struct net_device *dev); +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev); +static int el3_close(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static void el3_tx_timeout (struct net_device *dev, unsigned int txqueue); +static void el3_down(struct net_device *dev); +static void el3_up(struct net_device *dev); +static const struct ethtool_ops ethtool_ops; +#ifdef CONFIG_PM +static int el3_suspend(struct device *, pm_message_t); +static int el3_resume(struct device *); +#else +#define el3_suspend NULL +#define el3_resume NULL +#endif + + +/* generic device remove for all device types */ +static int el3_device_remove (struct device *device); +#ifdef CONFIG_NET_POLL_CONTROLLER +static void el3_poll_controller(struct net_device *dev); +#endif + +/* Return 0 on success, 1 on error, 2 when found already detected PnP card */ +static int el3_isa_id_sequence(__be16 *phys_addr) +{ + short lrs_state = 0xff; + int i; + + /* ISA boards are detected by sending the ID sequence to the + ID_PORT. We find cards past the first by setting the 'current_tag' + on cards as they are found. Cards with their tag set will not + respond to subsequent ID sequences. */ + + outb(0x00, id_port); + outb(0x00, id_port); + for (i = 0; i < 255; i++) { + outb(lrs_state, id_port); + lrs_state <<= 1; + lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state; + } + /* For the first probe, clear all board's tag registers. */ + if (current_tag == 0) + outb(0xd0, id_port); + else /* Otherwise kill off already-found boards. */ + outb(0xd8, id_port); + if (id_read_eeprom(7) != 0x6d50) + return 1; + /* Read in EEPROM data, which does contention-select. + Only the lowest address board will stay "on-line". + 3Com got the byte order backwards. */ + for (i = 0; i < 3; i++) + phys_addr[i] = htons(id_read_eeprom(i)); +#ifdef CONFIG_PNP + if (!nopnp) { + /* The ISA PnP 3c509 cards respond to the ID sequence too. + This check is needed in order not to register them twice. */ + for (i = 0; i < el3_cards; i++) { + struct el3_private *lp = netdev_priv(el3_devs[i]); + if (lp->type == EL3_PNP && + ether_addr_equal((u8 *)phys_addr, el3_devs[i]->dev_addr)) { + if (el3_debug > 3) + pr_debug("3c509 with address %02x %02x %02x %02x %02x %02x was found by ISAPnP\n", + phys_addr[0] & 0xff, phys_addr[0] >> 8, + phys_addr[1] & 0xff, phys_addr[1] >> 8, + phys_addr[2] & 0xff, phys_addr[2] >> 8); + /* Set the adaptor tag so that the next card can be found. */ + outb(0xd0 + ++current_tag, id_port); + return 2; + } + } + } +#endif /* CONFIG_PNP */ + return 0; + +} + +static void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr, + int irq, int if_port, enum el3_cardtype type) +{ + struct el3_private *lp = netdev_priv(dev); + + eth_hw_addr_set(dev, (u8 *)phys_addr); + dev->base_addr = ioaddr; + dev->irq = irq; + dev->if_port = if_port; + lp->type = type; +} + +static int el3_isa_match(struct device *pdev, unsigned int ndev) +{ + struct net_device *dev; + int ioaddr, isa_irq, if_port, err; + unsigned int iobase; + __be16 phys_addr[3]; + + while ((err = el3_isa_id_sequence(phys_addr)) == 2) + ; /* Skip to next card when PnP card found */ + if (err == 1) + return 0; + + iobase = id_read_eeprom(8); + if_port = iobase >> 14; + ioaddr = 0x200 + ((iobase & 0x1f) << 4); + if (irq[el3_cards] > 1 && irq[el3_cards] < 16) + isa_irq = irq[el3_cards]; + else + isa_irq = id_read_eeprom(9) >> 12; + + dev = alloc_etherdev(sizeof(struct el3_private)); + if (!dev) + return -ENOMEM; + + SET_NETDEV_DEV(dev, pdev); + + if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-isa")) { + free_netdev(dev); + return 0; + } + + /* Set the adaptor tag so that the next card can be found. */ + outb(0xd0 + ++current_tag, id_port); + + /* Activate the adaptor at the EEPROM location. */ + outb((ioaddr >> 4) | 0xe0, id_port); + + EL3WINDOW(0); + if (inw(ioaddr) != 0x6d50) { + free_netdev(dev); + return 0; + } + + /* Free the interrupt so that some other card can use it. */ + outw(0x0f00, ioaddr + WN0_IRQ); + + el3_dev_fill(dev, phys_addr, ioaddr, isa_irq, if_port, EL3_ISA); + dev_set_drvdata(pdev, dev); + if (el3_common_init(dev)) { + free_netdev(dev); + return 0; + } + + el3_devs[el3_cards++] = dev; + return 1; +} + +static void el3_isa_remove(struct device *pdev, + unsigned int ndev) +{ + el3_device_remove(pdev); + dev_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static int el3_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + current_tag = 0; + return el3_suspend(dev, state); +} + +static int el3_isa_resume(struct device *dev, unsigned int n) +{ + struct net_device *ndev = dev_get_drvdata(dev); + int ioaddr = ndev->base_addr, err; + __be16 phys_addr[3]; + + while ((err = el3_isa_id_sequence(phys_addr)) == 2) + ; /* Skip to next card when PnP card found */ + if (err == 1) + return 0; + /* Set the adaptor tag so that the next card can be found. */ + outb(0xd0 + ++current_tag, id_port); + /* Enable the card */ + outb((ioaddr >> 4) | 0xe0, id_port); + EL3WINDOW(0); + if (inw(ioaddr) != 0x6d50) + return 1; + /* Free the interrupt so that some other card can use it. */ + outw(0x0f00, ioaddr + WN0_IRQ); + return el3_resume(dev); +} +#endif + +static struct isa_driver el3_isa_driver = { + .match = el3_isa_match, + .remove = el3_isa_remove, +#ifdef CONFIG_PM + .suspend = el3_isa_suspend, + .resume = el3_isa_resume, +#endif + .driver = { + .name = "3c509" + }, +}; +static int isa_registered; + +#ifdef CONFIG_PNP +static const struct pnp_device_id el3_pnp_ids[] = { + { .id = "TCM5090" }, /* 3Com Etherlink III (TP) */ + { .id = "TCM5091" }, /* 3Com Etherlink III */ + { .id = "TCM5094" }, /* 3Com Etherlink III (combo) */ + { .id = "TCM5095" }, /* 3Com Etherlink III (TPO) */ + { .id = "TCM5098" }, /* 3Com Etherlink III (TPC) */ + { .id = "PNP80f7" }, /* 3Com Etherlink III compatible */ + { .id = "PNP80f8" }, /* 3Com Etherlink III compatible */ + { .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, el3_pnp_ids); + +static int el3_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id) +{ + short i; + int ioaddr, irq, if_port; + __be16 phys_addr[3]; + struct net_device *dev = NULL; + int err; + + ioaddr = pnp_port_start(pdev, 0); + if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-pnp")) + return -EBUSY; + irq = pnp_irq(pdev, 0); + EL3WINDOW(0); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + if_port = read_eeprom(ioaddr, 8) >> 14; + dev = alloc_etherdev(sizeof(struct el3_private)); + if (!dev) { + release_region(ioaddr, EL3_IO_EXTENT); + return -ENOMEM; + } + SET_NETDEV_DEV(dev, &pdev->dev); + + el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_PNP); + pnp_set_drvdata(pdev, dev); + err = el3_common_init(dev); + + if (err) { + pnp_set_drvdata(pdev, NULL); + free_netdev(dev); + return err; + } + + el3_devs[el3_cards++] = dev; + return 0; +} + +static void el3_pnp_remove(struct pnp_dev *pdev) +{ + el3_common_remove(pnp_get_drvdata(pdev)); + pnp_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static int el3_pnp_suspend(struct pnp_dev *pdev, pm_message_t state) +{ + return el3_suspend(&pdev->dev, state); +} + +static int el3_pnp_resume(struct pnp_dev *pdev) +{ + return el3_resume(&pdev->dev); +} +#endif + +static struct pnp_driver el3_pnp_driver = { + .name = "3c509", + .id_table = el3_pnp_ids, + .probe = el3_pnp_probe, + .remove = el3_pnp_remove, +#ifdef CONFIG_PM + .suspend = el3_pnp_suspend, + .resume = el3_pnp_resume, +#endif +}; +static int pnp_registered; +#endif /* CONFIG_PNP */ + +#ifdef CONFIG_EISA +static const struct eisa_device_id el3_eisa_ids[] = { + { "TCM5090" }, + { "TCM5091" }, + { "TCM5092" }, + { "TCM5093" }, + { "TCM5094" }, + { "TCM5095" }, + { "TCM5098" }, + { "" } +}; +MODULE_DEVICE_TABLE(eisa, el3_eisa_ids); + +static int el3_eisa_probe (struct device *device); + +static struct eisa_driver el3_eisa_driver = { + .id_table = el3_eisa_ids, + .driver = { + .name = "3c579", + .probe = el3_eisa_probe, + .remove = el3_device_remove, + .suspend = el3_suspend, + .resume = el3_resume, + } +}; +static int eisa_registered; +#endif + +static const struct net_device_ops netdev_ops = { + .ndo_open = el3_open, + .ndo_stop = el3_close, + .ndo_start_xmit = el3_start_xmit, + .ndo_get_stats = el3_get_stats, + .ndo_set_rx_mode = set_multicast_list, + .ndo_tx_timeout = el3_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = el3_poll_controller, +#endif +}; + +static int el3_common_init(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + int err; + static const char * const if_names[] = { + "10baseT", "AUI", "undefined", "BNC" + }; + + spin_lock_init(&lp->lock); + + if (dev->mem_start & 0x05) { /* xcvr codes 1/3/4/12 */ + dev->if_port = (dev->mem_start & 0x0f); + } else { /* xcvr codes 0/8 */ + /* use eeprom value, but save user's full-duplex selection */ + dev->if_port |= (dev->mem_start & 0x08); + } + + /* The EL3-specific entries in the device structure. */ + dev->netdev_ops = &netdev_ops; + dev->watchdog_timeo = TX_TIMEOUT; + dev->ethtool_ops = ðtool_ops; + + err = register_netdev(dev); + if (err) { + pr_err("Failed to register 3c5x9 at %#3.3lx, IRQ %d.\n", + dev->base_addr, dev->irq); + release_region(dev->base_addr, EL3_IO_EXTENT); + return err; + } + + pr_info("%s: 3c5x9 found at %#3.3lx, %s port, address %pM, IRQ %d.\n", + dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)], + dev->dev_addr, dev->irq); + + return 0; + +} + +static void el3_common_remove (struct net_device *dev) +{ + unregister_netdev (dev); + release_region(dev->base_addr, EL3_IO_EXTENT); + free_netdev (dev); +} + +#ifdef CONFIG_EISA +static int el3_eisa_probe(struct device *device) +{ + short i; + int ioaddr, irq, if_port; + __be16 phys_addr[3]; + struct net_device *dev = NULL; + struct eisa_device *edev; + int err; + + /* Yeepee, The driver framework is calling us ! */ + edev = to_eisa_device (device); + ioaddr = edev->base_addr; + + if (!request_region(ioaddr, EL3_IO_EXTENT, "3c579-eisa")) + return -EBUSY; + + /* Change the register set to the configuration window 0. */ + outw(SelectWindow | 0, ioaddr + 0xC80 + EL3_CMD); + + irq = inw(ioaddr + WN0_IRQ) >> 12; + if_port = inw(ioaddr + 6)>>14; + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + + /* Restore the "Product ID" to the EEPROM read register. */ + read_eeprom(ioaddr, 3); + + dev = alloc_etherdev(sizeof (struct el3_private)); + if (dev == NULL) { + release_region(ioaddr, EL3_IO_EXTENT); + return -ENOMEM; + } + + SET_NETDEV_DEV(dev, device); + + el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_EISA); + eisa_set_drvdata (edev, dev); + err = el3_common_init(dev); + + if (err) { + eisa_set_drvdata (edev, NULL); + free_netdev(dev); + return err; + } + + el3_devs[el3_cards++] = dev; + return 0; +} +#endif + +/* This remove works for all device types. + * + * The net dev must be stored in the driver data field */ +static int el3_device_remove(struct device *device) +{ + struct net_device *dev; + + dev = dev_get_drvdata(device); + + el3_common_remove (dev); + return 0; +} + +/* Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. + */ +static ushort read_eeprom(int ioaddr, int index) +{ + outw(EEPROM_READ + index, ioaddr + 10); + /* Pause for at least 162 us. for the read to take place. + Some chips seem to require much longer */ + mdelay(2); + return inw(ioaddr + 12); +} + +/* Read a word from the EEPROM when in the ISA ID probe state. */ +static ushort id_read_eeprom(int index) +{ + int bit, word = 0; + + /* Issue read command, and pause for at least 162 us. for it to complete. + Assume extra-fast 16Mhz bus. */ + outb(EEPROM_READ + index, id_port); + + /* Pause for at least 162 us. for the read to take place. */ + /* Some chips seem to require much longer */ + mdelay(4); + + for (bit = 15; bit >= 0; bit--) + word = (word << 1) + (inb(id_port) & 0x01); + + if (el3_debug > 3) + pr_debug(" 3c509 EEPROM word %d %#4.4x.\n", index, word); + + return word; +} + + +static int +el3_open(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int i; + + outw(TxReset, ioaddr + EL3_CMD); + outw(RxReset, ioaddr + EL3_CMD); + outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + + i = request_irq(dev->irq, el3_interrupt, 0, dev->name, dev); + if (i) + return i; + + EL3WINDOW(0); + if (el3_debug > 3) + pr_debug("%s: Opening, IRQ %d status@%x %4.4x.\n", dev->name, + dev->irq, ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS)); + + el3_up(dev); + + if (el3_debug > 3) + pr_debug("%s: Opened 3c509 IRQ %d status %4.4x.\n", + dev->name, dev->irq, inw(ioaddr + EL3_STATUS)); + + return 0; +} + +static void +el3_tx_timeout (struct net_device *dev, unsigned int txqueue) +{ + int ioaddr = dev->base_addr; + + /* Transmitter timeout, serious problems. */ + pr_warn("%s: transmit timed out, Tx_status %2.2x status %4.4x Tx FIFO room %d\n", + dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS), + inw(ioaddr + TX_FREE)); + dev->stats.tx_errors++; + netif_trans_update(dev); /* prevent tx timeout */ + /* Issue TX_RESET and TX_START commands. */ + outw(TxReset, ioaddr + EL3_CMD); + outw(TxEnable, ioaddr + EL3_CMD); + netif_wake_queue(dev); +} + + +static netdev_tx_t +el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + unsigned long flags; + + netif_stop_queue (dev); + + dev->stats.tx_bytes += skb->len; + + if (el3_debug > 4) { + pr_debug("%s: el3_start_xmit(length = %u) called, status %4.4x.\n", + dev->name, skb->len, inw(ioaddr + EL3_STATUS)); + } + /* + * We lock the driver against other processors. Note + * we don't need to lock versus the IRQ as we suspended + * that. This means that we lose the ability to take + * an RX during a TX upload. That sucks a bit with SMP + * on an original 3c509 (2K buffer) + * + * Using disable_irq stops us crapping on other + * time sensitive devices. + */ + + spin_lock_irqsave(&lp->lock, flags); + + /* Put out the doubleword header... */ + outw(skb->len, ioaddr + TX_FIFO); + outw(0x00, ioaddr + TX_FIFO); + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + + if (inw(ioaddr + TX_FREE) > 1536) + netif_start_queue(dev); + else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + + spin_unlock_irqrestore(&lp->lock, flags); + + dev_consume_skb_any (skb); + + /* Clear the Tx status stack. */ + { + short tx_status; + int i = 4; + + while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { + if (tx_status & 0x38) dev->stats.tx_aborted_errors++; + if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); + if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } + } + return NETDEV_TX_OK; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t +el3_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct el3_private *lp; + int ioaddr, status; + int i = max_interrupt_work; + + lp = netdev_priv(dev); + spin_lock(&lp->lock); + + ioaddr = dev->base_addr; + + if (el3_debug > 4) { + status = inw(ioaddr + EL3_STATUS); + pr_debug("%s: interrupt, status %4.4x.\n", dev->name, status); + } + + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | StatsFull)) { + + if (status & RxComplete) + el3_rx(dev); + + if (status & TxAvailable) { + if (el3_debug > 5) + pr_debug(" TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + netif_wake_queue (dev); + } + if (status & (AdapterFailure | RxEarly | StatsFull | TxComplete)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) /* Empty statistics. */ + update_stats(dev); + if (status & RxEarly) { /* Rx early is unused. */ + el3_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & TxComplete) { /* Really Tx error. */ + short tx_status; + int i = 4; + + while (--i>0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { + if (tx_status & 0x38) dev->stats.tx_aborted_errors++; + if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); + if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } + } + if (status & AdapterFailure) { + /* Adapter failure requires Rx reset and reinit. */ + outw(RxReset, ioaddr + EL3_CMD); + /* Set the Rx filter to the current state. */ + outw(SetRxFilter | RxStation | RxBroadcast + | (dev->flags & IFF_ALLMULTI ? RxMulticast : 0) + | (dev->flags & IFF_PROMISC ? RxProm : 0), + ioaddr + EL3_CMD); + outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (--i < 0) { + pr_err("%s: Infinite loop in interrupt, status %4.4x.\n", + dev->name, status); + /* Clear all interrupts. */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); /* Ack IRQ */ + } + + if (el3_debug > 4) { + pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name, + inw(ioaddr + EL3_STATUS)); + } + spin_unlock(&lp->lock); + return IRQ_HANDLED; +} + + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling receive - used by netconsole and other diagnostic tools + * to allow network i/o with interrupts disabled. + */ +static void el3_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + el3_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +static struct net_device_stats * +el3_get_stats(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + unsigned long flags; + + /* + * This is fast enough not to bother with disable IRQ + * stuff. + */ + + spin_lock_irqsave(&lp->lock, flags); + update_stats(dev); + spin_unlock_irqrestore(&lp->lock, flags); + return &dev->stats; +} + +/* Update statistics. We change to register window 6, so this should be run + single-threaded if the device is active. This is expected to be a rare + operation, and it's simpler for the rest of the driver to assume that + window 1 is always valid rather than use a special window-state variable. + */ +static void update_stats(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + if (el3_debug > 5) + pr_debug(" Updating the statistics.\n"); + /* Turn off statistics updates while reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + dev->stats.tx_carrier_errors += inb(ioaddr + 0); + dev->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + dev->stats.collisions += inb(ioaddr + 3); + dev->stats.tx_window_errors += inb(ioaddr + 4); + dev->stats.rx_fifo_errors += inb(ioaddr + 5); + dev->stats.tx_packets += inb(ioaddr + 6); + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + inw(ioaddr + 10); /* Total Rx and Tx octets. */ + inw(ioaddr + 12); + + /* Back to window 1, and turn statistics back on. */ + EL3WINDOW(1); + outw(StatsEnable, ioaddr + EL3_CMD); +} + +static int +el3_rx(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + short rx_status; + + if (el3_debug > 5) + pr_debug(" In rx_packet(), status %4.4x, rx_status %4.4x.\n", + inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); + while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + + outw(RxDiscard, ioaddr + EL3_CMD); + dev->stats.rx_errors++; + switch (error) { + case 0x0000: dev->stats.rx_over_errors++; break; + case 0x0800: dev->stats.rx_length_errors++; break; + case 0x1000: dev->stats.rx_frame_errors++; break; + case 0x1800: dev->stats.rx_length_errors++; break; + case 0x2000: dev->stats.rx_frame_errors++; break; + case 0x2800: dev->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = netdev_alloc_skb(dev, pkt_len + 5); + if (el3_debug > 4) + pr_debug("Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb_reserve(skb, 2); /* Align IP on 16 byte */ + + /* 'skb->data' points to the start of sk_buff data area. */ + insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), + (pkt_len + 3) >> 2); + + outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ + skb->protocol = eth_type_trans(skb,dev); + netif_rx(skb); + dev->stats.rx_bytes += pkt_len; + dev->stats.rx_packets++; + continue; + } + outw(RxDiscard, ioaddr + EL3_CMD); + dev->stats.rx_dropped++; + if (el3_debug) + pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + } + inw(ioaddr + EL3_STATUS); /* Delay. */ + while (inw(ioaddr + EL3_STATUS) & 0x1000) + pr_debug(" Waiting for 3c509 to discard packet, status %x.\n", + inw(ioaddr + EL3_STATUS) ); + } + + return 0; +} + +/* + * Set or clear the multicast filter for this adaptor. + */ +static void +set_multicast_list(struct net_device *dev) +{ + unsigned long flags; + struct el3_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; + int mc_count = netdev_mc_count(dev); + + if (el3_debug > 1) { + static int old; + if (old != mc_count) { + old = mc_count; + pr_debug("%s: Setting Rx mode to %d addresses.\n", + dev->name, mc_count); + } + } + spin_lock_irqsave(&lp->lock, flags); + if (dev->flags&IFF_PROMISC) { + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, + ioaddr + EL3_CMD); + } + else if (mc_count || (dev->flags&IFF_ALLMULTI)) { + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD); + } + else + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); + spin_unlock_irqrestore(&lp->lock, flags); +} + +static int +el3_close(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + struct el3_private *lp = netdev_priv(dev); + + if (el3_debug > 2) + pr_debug("%s: Shutting down ethercard.\n", dev->name); + + el3_down(dev); + + free_irq(dev->irq, dev); + /* Switching back to window 0 disables the IRQ. */ + EL3WINDOW(0); + if (lp->type != EL3_EISA) { + /* But we explicitly zero the IRQ line select anyway. Don't do + * it on EISA cards, it prevents the module from getting an + * IRQ after unload+reload... */ + outw(0x0f00, ioaddr + WN0_IRQ); + } + + return 0; +} + +static int +el3_link_ok(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + u16 tmp; + + EL3WINDOW(4); + tmp = inw(ioaddr + WN4_MEDIA); + EL3WINDOW(1); + return tmp & (1<<11); +} + +static void +el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_link_ksettings *cmd) +{ + u16 tmp; + int ioaddr = dev->base_addr; + u32 supported; + + EL3WINDOW(0); + /* obtain current transceiver via WN4_MEDIA? */ + tmp = inw(ioaddr + WN0_ADDR_CONF); + switch (tmp >> 14) { + case 0: + cmd->base.port = PORT_TP; + break; + case 1: + cmd->base.port = PORT_AUI; + break; + case 3: + cmd->base.port = PORT_BNC; + break; + default: + break; + } + + cmd->base.duplex = DUPLEX_HALF; + supported = 0; + tmp = inw(ioaddr + WN0_CONF_CTRL); + if (tmp & (1<<13)) + supported |= SUPPORTED_AUI; + if (tmp & (1<<12)) + supported |= SUPPORTED_BNC; + if (tmp & (1<<9)) { + supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full; /* hmm... */ + EL3WINDOW(4); + tmp = inw(ioaddr + WN4_NETDIAG); + if (tmp & FD_ENABLE) + cmd->base.duplex = DUPLEX_FULL; + } + + ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, + supported); + cmd->base.speed = SPEED_10; + EL3WINDOW(1); +} + +static int +el3_netdev_set_ecmd(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + u16 tmp; + int ioaddr = dev->base_addr; + + if (cmd->base.speed != SPEED_10) + return -EINVAL; + if ((cmd->base.duplex != DUPLEX_HALF) && + (cmd->base.duplex != DUPLEX_FULL)) + return -EINVAL; + + /* change XCVR type */ + EL3WINDOW(0); + tmp = inw(ioaddr + WN0_ADDR_CONF); + switch (cmd->base.port) { + case PORT_TP: + tmp &= ~(3<<14); + dev->if_port = 0; + break; + case PORT_AUI: + tmp |= (1<<14); + dev->if_port = 1; + break; + case PORT_BNC: + tmp |= (3<<14); + dev->if_port = 3; + break; + default: + return -EINVAL; + } + + outw(tmp, ioaddr + WN0_ADDR_CONF); + if (dev->if_port == 3) { + /* fire up the DC-DC convertor if BNC gets enabled */ + tmp = inw(ioaddr + WN0_ADDR_CONF); + if (tmp & (3 << 14)) { + outw(StartCoax, ioaddr + EL3_CMD); + udelay(800); + } else + return -EIO; + } + + EL3WINDOW(4); + tmp = inw(ioaddr + WN4_NETDIAG); + if (cmd->base.duplex == DUPLEX_FULL) + tmp |= FD_ENABLE; + else + tmp &= ~FD_ENABLE; + outw(tmp, ioaddr + WN4_NETDIAG); + EL3WINDOW(1); + + return 0; +} + +static void el3_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strscpy(info->driver, DRV_NAME, sizeof(info->driver)); +} + +static int el3_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) +{ + struct el3_private *lp = netdev_priv(dev); + + spin_lock_irq(&lp->lock); + el3_netdev_get_ecmd(dev, cmd); + spin_unlock_irq(&lp->lock); + return 0; +} + +static int el3_set_link_ksettings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + struct el3_private *lp = netdev_priv(dev); + int ret; + + spin_lock_irq(&lp->lock); + ret = el3_netdev_set_ecmd(dev, cmd); + spin_unlock_irq(&lp->lock); + return ret; +} + +static u32 el3_get_link(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + u32 ret; + + spin_lock_irq(&lp->lock); + ret = el3_link_ok(dev); + spin_unlock_irq(&lp->lock); + return ret; +} + +static u32 el3_get_msglevel(struct net_device *dev) +{ + return el3_debug; +} + +static void el3_set_msglevel(struct net_device *dev, u32 v) +{ + el3_debug = v; +} + +static const struct ethtool_ops ethtool_ops = { + .get_drvinfo = el3_get_drvinfo, + .get_link = el3_get_link, + .get_msglevel = el3_get_msglevel, + .set_msglevel = el3_set_msglevel, + .get_link_ksettings = el3_get_link_ksettings, + .set_link_ksettings = el3_set_link_ksettings, +}; + +static void +el3_down(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + netif_stop_queue(dev); + + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == 3) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + else if (dev->if_port == 0) { + /* Disable link beat and jabber, if_port may change here next open(). */ + EL3WINDOW(4); + outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA); + } + + outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + + update_stats(dev); +} + +static void +el3_up(struct net_device *dev) +{ + int i, sw_info, net_diag; + int ioaddr = dev->base_addr; + + /* Activating the board required and does no harm otherwise */ + outw(0x0001, ioaddr + 4); + + /* Set the IRQ line. */ + outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ); + + /* Set the station address in window 2 each time opened. */ + EL3WINDOW(2); + + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + + if ((dev->if_port & 0x03) == 3) /* BNC interface */ + /* Start the thinnet transceiver. We should really wait 50ms...*/ + outw(StartCoax, ioaddr + EL3_CMD); + else if ((dev->if_port & 0x03) == 0) { /* 10baseT interface */ + /* Combine secondary sw_info word (the adapter level) and primary + sw_info word (duplex setting plus other useless bits) */ + EL3WINDOW(0); + sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) | + (read_eeprom(ioaddr, 0x0d) & 0xBff0); + + EL3WINDOW(4); + net_diag = inw(ioaddr + WN4_NETDIAG); + net_diag = (net_diag | FD_ENABLE); /* temporarily assume full-duplex will be set */ + pr_info("%s: ", dev->name); + switch (dev->if_port & 0x0c) { + case 12: + /* force full-duplex mode if 3c5x9b */ + if (sw_info & 0x000f) { + pr_cont("Forcing 3c5x9b full-duplex mode"); + break; + } + fallthrough; + case 8: + /* set full-duplex mode based on eeprom config setting */ + if ((sw_info & 0x000f) && (sw_info & 0x8000)) { + pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)"); + break; + } + fallthrough; + default: + /* xcvr=(0 || 4) OR user has an old 3c5x9 non "B" model */ + pr_cont("Setting 3c5x9/3c5x9B half-duplex mode"); + net_diag = (net_diag & ~FD_ENABLE); /* disable full duplex */ + } + + outw(net_diag, ioaddr + WN4_NETDIAG); + pr_cont(" if_port: %d, sw_info: %4.4x\n", dev->if_port, sw_info); + if (el3_debug > 3) + pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", dev->name, net_diag); + /* Enable link beat and jabber check. */ + outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA); + } + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 9; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + + /* Switch to register set 1 for normal use. */ + EL3WINDOW(1); + + /* Accept b-case and phys addr only. */ + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull, + ioaddr + EL3_CMD); + + netif_start_queue(dev); +} + +/* Power Management support functions */ +#ifdef CONFIG_PM + +static int +el3_suspend(struct device *pdev, pm_message_t state) +{ + unsigned long flags; + struct net_device *dev; + struct el3_private *lp; + int ioaddr; + + dev = dev_get_drvdata(pdev); + lp = netdev_priv(dev); + ioaddr = dev->base_addr; + + spin_lock_irqsave(&lp->lock, flags); + + if (netif_running(dev)) + netif_device_detach(dev); + + el3_down(dev); + outw(PowerDown, ioaddr + EL3_CMD); + + spin_unlock_irqrestore(&lp->lock, flags); + return 0; +} + +static int +el3_resume(struct device *pdev) +{ + unsigned long flags; + struct net_device *dev; + struct el3_private *lp; + int ioaddr; + + dev = dev_get_drvdata(pdev); + lp = netdev_priv(dev); + ioaddr = dev->base_addr; + + spin_lock_irqsave(&lp->lock, flags); + + outw(PowerUp, ioaddr + EL3_CMD); + EL3WINDOW(0); + el3_up(dev); + + if (netif_running(dev)) + netif_device_attach(dev); + + spin_unlock_irqrestore(&lp->lock, flags); + return 0; +} + +#endif /* CONFIG_PM */ + +module_param(debug,int, 0); +module_param_hw_array(irq, int, irq, NULL, 0); +module_param(max_interrupt_work, int, 0); +MODULE_PARM_DESC(debug, "debug level (0-6)"); +MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)"); +MODULE_PARM_DESC(max_interrupt_work, "maximum events handled per interrupt"); +#ifdef CONFIG_PNP +module_param(nopnp, int, 0); +MODULE_PARM_DESC(nopnp, "disable ISA PnP support (0-1)"); +#endif /* CONFIG_PNP */ +MODULE_DESCRIPTION("3Com Etherlink III (3c509, 3c509B, 3c529, 3c579) ethernet driver"); +MODULE_LICENSE("GPL"); + +static int __init el3_init_module(void) +{ + int ret = 0; + + if (debug >= 0) + el3_debug = debug; + +#ifdef CONFIG_PNP + if (!nopnp) { + ret = pnp_register_driver(&el3_pnp_driver); + if (!ret) + pnp_registered = 1; + } +#endif + /* Select an open I/O location at 0x1*0 to do ISA contention select. */ + /* Start with 0x110 to avoid some sound cards.*/ + for (id_port = 0x110 ; id_port < 0x200; id_port += 0x10) { + if (!request_region(id_port, 1, "3c509-control")) + continue; + outb(0x00, id_port); + outb(0xff, id_port); + if (inb(id_port) & 0x01) + break; + else + release_region(id_port, 1); + } + if (id_port >= 0x200) { + id_port = 0; + pr_err("No I/O port available for 3c509 activation.\n"); + } else { + ret = isa_register_driver(&el3_isa_driver, EL3_MAX_CARDS); + if (!ret) + isa_registered = 1; + } +#ifdef CONFIG_EISA + ret = eisa_driver_register(&el3_eisa_driver); + if (!ret) + eisa_registered = 1; +#endif + +#ifdef CONFIG_PNP + if (pnp_registered) + ret = 0; +#endif + if (isa_registered) + ret = 0; +#ifdef CONFIG_EISA + if (eisa_registered) + ret = 0; +#endif + return ret; +} + +static void __exit el3_cleanup_module(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_driver(&el3_pnp_driver); +#endif + if (isa_registered) + isa_unregister_driver(&el3_isa_driver); + if (id_port) + release_region(id_port, 1); +#ifdef CONFIG_EISA + if (eisa_registered) + eisa_driver_unregister(&el3_eisa_driver); +#endif +} + +module_init (el3_init_module); +module_exit (el3_cleanup_module); diff --git a/drivers/net/ethernet/3com/Kconfig b/drivers/net/ethernet/3com/Kconfig index 399cb6c56198..81db16744f94 100644 --- a/drivers/net/ethernet/3com/Kconfig +++ b/drivers/net/ethernet/3com/Kconfig @@ -17,6 +17,20 @@ config NET_VENDOR_3COM if NET_VENDOR_3COM +config EL3 + tristate "3c509/3c579 \"EtherLink III\" support" + depends on (ISA || EISA) + help + If you have a network (Ethernet) card belonging to the 3Com + EtherLinkIII series, say Y here. + + If your card is not working you may need to use the DOS + setup disk to disable Plug & Play mode, and to select the default + media type. + + To compile this driver as a module, choose M here. The module + will be called 3c509. + config VORTEX tristate "3c590/3c900 series (592/595/597) \"Vortex/Boomerang\" support" depends on (PCI || EISA) && HAS_IOPORT_MAP diff --git a/drivers/net/ethernet/3com/Makefile b/drivers/net/ethernet/3com/Makefile index 5c4d07f1d456..2c65e472196f 100644 --- a/drivers/net/ethernet/3com/Makefile +++ b/drivers/net/ethernet/3com/Makefile @@ -3,5 +3,6 @@ # Makefile for the 3Com Ethernet device drivers # +obj-$(CONFIG_EL3) += 3c509.o obj-$(CONFIG_VORTEX) += 3c59x.o obj-$(CONFIG_TYPHOON) += typhoon.o -- cgit v1.2.3 From 029a6b3a14bf02e6f59ce6ecd10f9d003334c612 Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Wed, 20 May 2026 12:18:53 +0100 Subject: ethernet: 3c509: Fix AUI transceiver type selection The transceiver type is held in bits 15:14 of the Address Configuration Register, with the values of 0b00, 0b01, and 0b11 denoting TP, AUI, and BNC types respectively. Therefore switching from BNC to AUI requires bits to be cleared before setting bit 14 or the setting won't change. NB this has always been wrong ever since this code was added in 2.5.42. Signed-off-by: Maciej W. Rozycki Link: https://patch.msgid.link/alpine.DEB.2.21.2605201205160.1450@angie.orcam.me.uk Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/3com/3c509.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c index fb68339e1511..67b9a3f4de5e 100644 --- a/drivers/net/ethernet/3com/3c509.c +++ b/drivers/net/ethernet/3com/3c509.c @@ -1099,6 +1099,7 @@ el3_netdev_set_ecmd(struct net_device *dev, dev->if_port = 0; break; case PORT_AUI: + tmp &= ~(3<<14); tmp |= (1<<14); dev->if_port = 1; break; -- cgit v1.2.3 From 240117bb51b95ce93ec28c7c9439c9a87d7b120c Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Wed, 20 May 2026 12:18:57 +0100 Subject: ethernet: 3c509: Add GPL 2.0 SPDX license identifier This driver has landed with Linux 0.99.13k, which was covered by the GNU General Public License version 2, and no further conditions as to licensing terms have been specified within the copyright notice included with the driver itself. Signed-off-by: Maciej W. Rozycki Link: https://patch.msgid.link/alpine.DEB.2.21.2605201206370.1450@angie.orcam.me.uk Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/3com/3c509.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c index 67b9a3f4de5e..6ebd3358e31b 100644 --- a/drivers/net/ethernet/3com/3c509.c +++ b/drivers/net/ethernet/3com/3c509.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */ /* Written 1993-2000 by Donald Becker. -- cgit v1.2.3 From 014767c709a44b4e0a0bf70ee9101fb73f4e288b Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Wed, 20 May 2026 12:19:06 +0100 Subject: ethernet: 3c509: Fix most coding style issues Update the driver for our current coding style according to output from `checkpatch.pl' and manual code review, where no change to binary code results, as indicated by `objdump -dr'. Exceptions are as follows: - incomplete reverse xmas tree in set_multicast_list(), as that would change binary output, - referring el3_start_xmit() verbatim rather than via `__func__' with pr_debug(), likewise, - a bunch of pr_cont() calls, likewise, - a long udelay() call in el3_netdev_set_ecmd() made under a spinlock, likewise plus it's not eligible for conversion to a sleep in the first place, - a blank line at the start of a block in el3_interrupt(), to improve readability where the first statement would otherwise visually merge with the controlling expression of the enclosing `while' statement. These issues are benign and depending on circumstances may be adressed with suitable code refactoring later on. Signed-off-by: Maciej W. Rozycki Link: https://patch.msgid.link/alpine.DEB.2.21.2605201208280.1450@angie.orcam.me.uk Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/3com/3c509.c | 845 +++++++++++++++++++++----------------- 1 file changed, 469 insertions(+), 376 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c index 6ebd3358e31b..f23be7425daf 100644 --- a/drivers/net/ethernet/3com/3c509.c +++ b/drivers/net/ethernet/3com/3c509.c @@ -1,94 +1,99 @@ // SPDX-License-Identifier: GPL-2.0 /* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */ /* - Written 1993-2000 by Donald Becker. - - Copyright 1994-2000 by Donald Becker. - Copyright 1993 United States Government as represented by the - Director, National Security Agency. This software may be used and - distributed according to the terms of the GNU General Public License, - incorporated herein by reference. - - This driver is for the 3Com EtherLinkIII series. - - The author may be reached as becker@scyld.com, or C/O - Scyld Computing Corporation - 410 Severn Ave., Suite 210 - Annapolis MD 21403 - - Known limitations: - Because of the way 3c509 ISA detection works it's difficult to predict - a priori which of several ISA-mode cards will be detected first. - - This driver does not use predictive interrupt mode, resulting in higher - packet latency but lower overhead. If interrupts are disabled for an - unusually long time it could also result in missed packets, but in - practice this rarely happens. - - - FIXES: - Alan Cox: Removed the 'Unexpected interrupt' bug. - Michael Meskes: Upgraded to Donald Becker's version 1.07. - Alan Cox: Increased the eeprom delay. Regardless of - what the docs say some people definitely - get problems with lower (but in card spec) - delays - v1.10 4/21/97 Fixed module code so that multiple cards may be detected, - other cleanups. -djb - Andrea Arcangeli: Upgraded to Donald Becker's version 1.12. - Rick Payne: Fixed SMP race condition - v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable variable -djb - v1.14 10/15/97 Avoided waiting..discard message for fast machines -djb - v1.15 1/31/98 Faster recovery for Tx errors. -djb - v1.16 2/3/98 Different ID port handling to avoid sound cards. -djb - v1.18 12Mar2001 Andrew Morton - - Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz) - - Reviewed against 1.18 from scyld.com - v1.18a 17Nov2001 Jeff Garzik - - ethtool support - v1.18b 1Mar2002 Zwane Mwaikambo - - Power Management support - v1.18c 1Mar2002 David Ruggiero - - Full duplex support - v1.19 16Oct2002 Zwane Mwaikambo - - Additional ethtool features - v1.19a 28Oct2002 Davud Ruggiero - - Increase *read_eeprom udelay to workaround oops with 2 cards. - v1.19b 08Nov2002 Marc Zyngier - - Introduce driver model for EISA cards. - v1.20 04Feb2008 Ondrej Zary - - convert to isa_driver and pnp_driver and some cleanups -*/ + * Written 1993-2000 by Donald Becker. + * + * Copyright 1994-2000 by Donald Becker. + * Copyright 1993 United States Government as represented by the + * Director, National Security Agency. This software may be used and + * distributed according to the terms of the GNU General Public License, + * incorporated herein by reference. + * + * This driver is for the 3Com EtherLinkIII series. + * + * The author may be reached as becker@scyld.com, or C/O + * Scyld Computing Corporation + * 410 Severn Ave., Suite 210 + * Annapolis MD 21403 + * + * Known limitations: + * Because of the way 3c509 ISA detection works it's difficult to predict + * a priori which of several ISA-mode cards will be detected first. + * + * This driver does not use predictive interrupt mode, resulting in higher + * packet latency but lower overhead. If interrupts are disabled for an + * unusually long time it could also result in missed packets, but in + * practice this rarely happens. + * + * + * FIXES: + * Alan Cox: Removed the 'Unexpected interrupt' bug. + * Michael Meskes: Upgraded to Donald Becker's version 1.07. + * Alan Cox: Increased the eeprom delay. Regardless of + * what the docs say some people definitely + * get problems with lower (but in card spec) + * delays. + * v1.10 4/21/97 Fixed module code so that multiple cards may be + * detected, other cleanups. -djb + * Andrea Arcangeli: Upgraded to Donald Becker's version 1.12. + * Rick Payne: Fixed SMP race condition. + * v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable + * variable. -djb + * v1.14 10/15/97 Avoided waiting..discard message for fast + * machines. -djb + * v1.15 1/31/98 Faster recovery for Tx errors. -djb + * v1.16 2/3/98 Different ID port handling to avoid sound + * cards. -djb + * v1.18 12Mar2001 Andrew Morton + * - Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz) + * - Reviewed against 1.18 from scyld.com + * v1.18a 17Nov2001 Jeff Garzik + * - ethtool support. + * v1.18b 1Mar2002 Zwane Mwaikambo + * - Power Management support. + * v1.18c 1Mar2002 David Ruggiero + * - Full duplex support. + * v1.19 16Oct2002 Zwane Mwaikambo + * - Additional ethtool features. + * v1.19a 28Oct2002 David Ruggiero + * - Increase *read_eeprom udelay to workaround oops with + * 2 cards. + * v1.19b 08Nov2002 Marc Zyngier + * - Introduce driver model for EISA cards. + * v1.20 04Feb2008 Ondrej Zary + * - convert to isa_driver and pnp_driver and some + * cleanups. + */ #define DRV_NAME "3c509" /* A few values that may be tweaked. */ /* Time in jiffies before concluding the transmitter is hung. */ -#define TX_TIMEOUT (400*HZ/1000) +#define TX_TIMEOUT (400 * HZ / 1000) -#include -#include -#include -#include -#include +#include +#include /* for udelay() */ +#include +#include #include +#include +#include #include -#include #include +#include +#include +#include +#include +#include #include -#include #include +#include #include -#include /* for udelay() */ #include -#include -#include -#include -#include - +#include #include -#include + #include #ifdef EL3_DEBUG @@ -98,14 +103,15 @@ static int el3_debug = 2; #endif /* Used to do a global count of all the cards in the system. Must be - * a global variable so that the eisa probe routines can increment - * it */ -static int el3_cards = 0; + * a global variable so that the eisa probe routines can increment it. + */ +static int el3_cards; #define EL3_MAX_CARDS 8 /* To minimize the size of the driver source I only define operating - constants if they are used several times. You'll need the manual - anyway if you want to understand driver details. */ + * constants if they are used several times. You'll need the manual + * anyway if you want to understand driver details. + */ /* Offsets from base I/O address. */ #define EL3_DATA 0x00 #define EL3_CMD 0x0e @@ -114,60 +120,90 @@ static int el3_cards = 0; #define EL3_IO_EXTENT 16 -#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) - +#define EL3WINDOW(win_num) outw(SELECT_WINDOW + (win_num), ioaddr + EL3_CMD) /* The top five bits written to EL3_CMD are a command, the lower - 11 bits are the parameter, if applicable. */ + * 11 bits are the parameter, if applicable. + */ enum c509cmd { - TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, - RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, - TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, - FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, - SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, - SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, - StatsDisable = 22<<11, StopCoax = 23<<11, PowerUp = 27<<11, - PowerDown = 28<<11, PowerAuto = 29<<11}; + TOTAL_RESET = 0 << 11, + SELECT_WINDOW = 1 << 11, + START_COAX = 2 << 11, + RX_DISABLE = 3 << 11, + RX_ENABLE = 4 << 11, + RX_RESET = 5 << 11, + RX_DISCARD = 8 << 11, + TX_ENABLE = 9 << 11, + TX_DISABLE = 10 << 11, + TX_RESET = 11 << 11, + FAKE_INTR = 12 << 11, + ACK_INTR = 13 << 11, + SET_INTR_ENB = 14 << 11, + SET_STATUS_ENB = 15 << 11, + SET_RX_FILTER = 16 << 11, + SET_RX_THRESHOLD = 17 << 11, + SET_TX_THRESHOLD = 18 << 11, + SET_TX_START = 19 << 11, + STATS_ENABLE = 21 << 11, + STATS_DISABLE = 22 << 11, + STOP_COAX = 23 << 11, + POWER_UP = 27 << 11, + POWER_DOWN = 28 << 11, + POWER_AUTO = 29 << 11, +}; enum c509status { - IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, - TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, - IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, }; + INT_LATCH = 0x0001, + ADAPTER_FAILURE = 0x0002, + TX_COMPLETE = 0x0004, + TX_AVAILABLE = 0x0008, + RX_COMPLETE = 0x0010, + RX_EARLY = 0x0020, + INT_REQ = 0x0040, + STATS_FULL = 0x0080, + CMD_BUSY = 0x1000, +}; -/* The SetRxFilter command accepts the following classes: */ -enum RxFilter { - RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; +/* The SET_RX_FILTER command accepts the following classes: */ +enum rx_filter { + RX_STATION = 1, + RX_MULTICAST = 2, + RX_BROADCAST = 4, + RX_PROM = 8, +}; /* Register window 1 offsets, the window used in normal operation. */ #define TX_FIFO 0x00 #define RX_FIFO 0x00 -#define RX_STATUS 0x08 -#define TX_STATUS 0x0B -#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ - -#define WN0_CONF_CTRL 0x04 /* Window 0: Configuration control register */ -#define WN0_ADDR_CONF 0x06 /* Window 0: Address configuration register */ -#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ -#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ -#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ -#define WN4_NETDIAG 0x06 /* Window 4: Net diagnostic */ -#define FD_ENABLE 0x8000 /* Enable full-duplex ("external loopback") */ +#define RX_STATUS 0x08 +#define TX_STATUS 0x0B +#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ + +#define WN0_CONF_CTRL 0x04 /* Window 0: Configuration control register. */ +#define WN0_ADDR_CONF 0x06 /* Window 0: Address configuration register. */ +#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ +#define WN4_NETDIAG 0x06 /* Window 4: Net diagnostic. */ +#define FD_ENABLE 0x8000 /* Enable full-duplex ("external loopback"). */ /* * Must be a power of two (we use a binary and in the - * circular queue) + * circular queue). */ #define SKB_QUEUE_SIZE 64 enum el3_cardtype { EL3_ISA, EL3_PNP, EL3_EISA }; struct el3_private { + /* for device access */ spinlock_t lock; /* skb send-queue */ int head, size; struct sk_buff *queue[SKB_QUEUE_SIZE]; enum el3_cardtype type; }; + static int id_port; static int current_tag; static struct net_device *el3_devs[EL3_MAX_CARDS]; @@ -193,7 +229,7 @@ static struct net_device_stats *el3_get_stats(struct net_device *dev); static int el3_rx(struct net_device *dev); static int el3_close(struct net_device *dev); static void set_multicast_list(struct net_device *dev); -static void el3_tx_timeout (struct net_device *dev, unsigned int txqueue); +static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue); static void el3_down(struct net_device *dev); static void el3_up(struct net_device *dev); static const struct ethtool_ops ethtool_ops; @@ -205,24 +241,23 @@ static int el3_resume(struct device *); #define el3_resume NULL #endif - -/* generic device remove for all device types */ -static int el3_device_remove (struct device *device); +/* Generic device remove for all device types. */ +static int el3_device_remove(struct device *device); #ifdef CONFIG_NET_POLL_CONTROLLER static void el3_poll_controller(struct net_device *dev); #endif -/* Return 0 on success, 1 on error, 2 when found already detected PnP card */ +/* Return 0 on success, 1 on error, 2 when found already detected PnP card. */ static int el3_isa_id_sequence(__be16 *phys_addr) { short lrs_state = 0xff; int i; /* ISA boards are detected by sending the ID sequence to the - ID_PORT. We find cards past the first by setting the 'current_tag' - on cards as they are found. Cards with their tag set will not - respond to subsequent ID sequences. */ - + * ID_PORT. We find cards past the first by setting the 'current_tag' + * on cards as they are found. Cards with their tag set will not + * respond to subsequent ID sequences. + */ outb(0x00, id_port); outb(0x00, id_port); for (i = 0; i < 255; i++) { @@ -238,24 +273,33 @@ static int el3_isa_id_sequence(__be16 *phys_addr) if (id_read_eeprom(7) != 0x6d50) return 1; /* Read in EEPROM data, which does contention-select. - Only the lowest address board will stay "on-line". - 3Com got the byte order backwards. */ + * Only the lowest address board will stay "on-line". + * 3Com got the byte order backwards. + */ for (i = 0; i < 3; i++) phys_addr[i] = htons(id_read_eeprom(i)); #ifdef CONFIG_PNP if (!nopnp) { /* The ISA PnP 3c509 cards respond to the ID sequence too. - This check is needed in order not to register them twice. */ + * This check is needed in order not to register them twice. + */ for (i = 0; i < el3_cards; i++) { struct el3_private *lp = netdev_priv(el3_devs[i]); + if (lp->type == EL3_PNP && - ether_addr_equal((u8 *)phys_addr, el3_devs[i]->dev_addr)) { + ether_addr_equal((u8 *)phys_addr, + el3_devs[i]->dev_addr)) { if (el3_debug > 3) pr_debug("3c509 with address %02x %02x %02x %02x %02x %02x was found by ISAPnP\n", - phys_addr[0] & 0xff, phys_addr[0] >> 8, - phys_addr[1] & 0xff, phys_addr[1] >> 8, - phys_addr[2] & 0xff, phys_addr[2] >> 8); - /* Set the adaptor tag so that the next card can be found. */ + phys_addr[0] & 0xff, + phys_addr[0] >> 8, + phys_addr[1] & 0xff, + phys_addr[1] >> 8, + phys_addr[2] & 0xff, + phys_addr[2] >> 8); + /* Set the adaptor tag so that the next card + * can be found. + */ outb(0xd0 + ++current_tag, id_port); return 2; } @@ -263,7 +307,6 @@ static int el3_isa_id_sequence(__be16 *phys_addr) } #endif /* CONFIG_PNP */ return 0; - } static void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr, @@ -280,8 +323,8 @@ static void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr, static int el3_isa_match(struct device *pdev, unsigned int ndev) { - struct net_device *dev; int ioaddr, isa_irq, if_port, err; + struct net_device *dev; unsigned int iobase; __be16 phys_addr[3]; @@ -335,8 +378,7 @@ static int el3_isa_match(struct device *pdev, unsigned int ndev) return 1; } -static void el3_isa_remove(struct device *pdev, - unsigned int ndev) +static void el3_isa_remove(struct device *pdev, unsigned int ndev) { el3_device_remove(pdev); dev_set_drvdata(pdev, NULL); @@ -384,6 +426,7 @@ static struct isa_driver el3_isa_driver = { .name = "3c509" }, }; + static int isa_registered; #ifdef CONFIG_PNP @@ -401,10 +444,10 @@ MODULE_DEVICE_TABLE(pnp, el3_pnp_ids); static int el3_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id) { - short i; + struct net_device *dev = NULL; int ioaddr, irq, if_port; __be16 phys_addr[3]; - struct net_device *dev = NULL; + short i; int err; ioaddr = pnp_port_start(pdev, 0); @@ -464,6 +507,7 @@ static struct pnp_driver el3_pnp_driver = { .resume = el3_pnp_resume, #endif }; + static int pnp_registered; #endif /* CONFIG_PNP */ @@ -480,7 +524,7 @@ static const struct eisa_device_id el3_eisa_ids[] = { }; MODULE_DEVICE_TABLE(eisa, el3_eisa_ids); -static int el3_eisa_probe (struct device *device); +static int el3_eisa_probe(struct device *device); static struct eisa_driver el3_eisa_driver = { .id_table = el3_eisa_ids, @@ -492,17 +536,18 @@ static struct eisa_driver el3_eisa_driver = { .resume = el3_resume, } }; + static int eisa_registered; #endif static const struct net_device_ops netdev_ops = { - .ndo_open = el3_open, - .ndo_stop = el3_close, - .ndo_start_xmit = el3_start_xmit, - .ndo_get_stats = el3_get_stats, + .ndo_open = el3_open, + .ndo_stop = el3_close, + .ndo_start_xmit = el3_start_xmit, + .ndo_get_stats = el3_get_stats, .ndo_set_rx_mode = set_multicast_list, - .ndo_tx_timeout = el3_tx_timeout, - .ndo_set_mac_address = eth_mac_addr, + .ndo_tx_timeout = el3_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, .ndo_validate_addr = eth_validate_addr, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = el3_poll_controller, @@ -511,11 +556,11 @@ static const struct net_device_ops netdev_ops = { static int el3_common_init(struct net_device *dev) { - struct el3_private *lp = netdev_priv(dev); - int err; - static const char * const if_names[] = { + static const char *const if_names[] = { "10baseT", "AUI", "undefined", "BNC" }; + struct el3_private *lp = netdev_priv(dev); + int err; spin_lock_init(&lp->lock); @@ -534,56 +579,55 @@ static int el3_common_init(struct net_device *dev) err = register_netdev(dev); if (err) { pr_err("Failed to register 3c5x9 at %#3.3lx, IRQ %d.\n", - dev->base_addr, dev->irq); + dev->base_addr, dev->irq); release_region(dev->base_addr, EL3_IO_EXTENT); return err; } pr_info("%s: 3c5x9 found at %#3.3lx, %s port, address %pM, IRQ %d.\n", - dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)], - dev->dev_addr, dev->irq); + dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)], + dev->dev_addr, dev->irq); return 0; - } -static void el3_common_remove (struct net_device *dev) +static void el3_common_remove(struct net_device *dev) { - unregister_netdev (dev); + unregister_netdev(dev); release_region(dev->base_addr, EL3_IO_EXTENT); - free_netdev (dev); + free_netdev(dev); } #ifdef CONFIG_EISA static int el3_eisa_probe(struct device *device) { - short i; - int ioaddr, irq, if_port; - __be16 phys_addr[3]; struct net_device *dev = NULL; struct eisa_device *edev; + int ioaddr, irq, if_port; + __be16 phys_addr[3]; + short i; int err; /* Yeepee, The driver framework is calling us ! */ - edev = to_eisa_device (device); + edev = to_eisa_device(device); ioaddr = edev->base_addr; if (!request_region(ioaddr, EL3_IO_EXTENT, "3c579-eisa")) return -EBUSY; /* Change the register set to the configuration window 0. */ - outw(SelectWindow | 0, ioaddr + 0xC80 + EL3_CMD); + outw(SELECT_WINDOW | 0, ioaddr + 0xC80 + EL3_CMD); irq = inw(ioaddr + WN0_IRQ) >> 12; - if_port = inw(ioaddr + 6)>>14; + if_port = inw(ioaddr + 6) >> 14; for (i = 0; i < 3; i++) phys_addr[i] = htons(read_eeprom(ioaddr, i)); /* Restore the "Product ID" to the EEPROM read register. */ read_eeprom(ioaddr, 3); - dev = alloc_etherdev(sizeof (struct el3_private)); - if (dev == NULL) { + dev = alloc_etherdev(sizeof(struct el3_private)); + if (!dev) { release_region(ioaddr, EL3_IO_EXTENT); return -ENOMEM; } @@ -591,11 +635,11 @@ static int el3_eisa_probe(struct device *device) SET_NETDEV_DEV(dev, device); el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_EISA); - eisa_set_drvdata (edev, dev); + eisa_set_drvdata(edev, dev); err = el3_common_init(dev); if (err) { - eisa_set_drvdata (edev, NULL); + eisa_set_drvdata(edev, NULL); free_netdev(dev); return err; } @@ -607,25 +651,27 @@ static int el3_eisa_probe(struct device *device) /* This remove works for all device types. * - * The net dev must be stored in the driver data field */ + * The net dev must be stored in the driver data field. + */ static int el3_device_remove(struct device *device) { struct net_device *dev; dev = dev_get_drvdata(device); - el3_common_remove (dev); + el3_common_remove(dev); return 0; } /* Read a word from the EEPROM using the regular EEPROM access register. - Assume that we are in register window zero. + * Assume that we are in register window zero. */ static ushort read_eeprom(int ioaddr, int index) { outw(EEPROM_READ + index, ioaddr + 10); - /* Pause for at least 162 us. for the read to take place. - Some chips seem to require much longer */ + /* Pause for at least 162 us for the read to take place. + * Some chips seem to require much longer. + */ mdelay(2); return inw(ioaddr + 12); } @@ -635,12 +681,14 @@ static ushort id_read_eeprom(int index) { int bit, word = 0; - /* Issue read command, and pause for at least 162 us. for it to complete. - Assume extra-fast 16Mhz bus. */ + /* Issue read command, and pause for at least 162 us for it to + * complete. Assume extra-fast 16MHz bus. + */ outb(EEPROM_READ + index, id_port); - /* Pause for at least 162 us. for the read to take place. */ - /* Some chips seem to require much longer */ + /* Pause for at least 162 us for the read to take place. + * Some chips seem to require much longer. + */ mdelay(4); for (bit = 15; bit >= 0; bit--) @@ -652,16 +700,14 @@ static ushort id_read_eeprom(int index) return word; } - -static int -el3_open(struct net_device *dev) +static int el3_open(struct net_device *dev) { int ioaddr = dev->base_addr; int i; - outw(TxReset, ioaddr + EL3_CMD); - outw(RxReset, ioaddr + EL3_CMD); - outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + outw(TX_RESET, ioaddr + EL3_CMD); + outw(RX_RESET, ioaddr + EL3_CMD); + outw(SET_STATUS_ENB | 0x00, ioaddr + EL3_CMD); i = request_irq(dev->irq, el3_interrupt, 0, dev->name, dev); if (i) @@ -669,20 +715,20 @@ el3_open(struct net_device *dev) EL3WINDOW(0); if (el3_debug > 3) - pr_debug("%s: Opening, IRQ %d status@%x %4.4x.\n", dev->name, - dev->irq, ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS)); + pr_debug("%s: Opening, IRQ %d status@%x %4.4x.\n", + dev->name, dev->irq, + ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS)); el3_up(dev); if (el3_debug > 3) pr_debug("%s: Opened 3c509 IRQ %d status %4.4x.\n", - dev->name, dev->irq, inw(ioaddr + EL3_STATUS)); + dev->name, dev->irq, inw(ioaddr + EL3_STATUS)); return 0; } -static void -el3_tx_timeout (struct net_device *dev, unsigned int txqueue) +static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue) { int ioaddr = dev->base_addr; @@ -693,33 +739,31 @@ el3_tx_timeout (struct net_device *dev, unsigned int txqueue) dev->stats.tx_errors++; netif_trans_update(dev); /* prevent tx timeout */ /* Issue TX_RESET and TX_START commands. */ - outw(TxReset, ioaddr + EL3_CMD); - outw(TxEnable, ioaddr + EL3_CMD); + outw(TX_RESET, ioaddr + EL3_CMD); + outw(TX_ENABLE, ioaddr + EL3_CMD); netif_wake_queue(dev); } - -static netdev_tx_t -el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct el3_private *lp = netdev_priv(dev); int ioaddr = dev->base_addr; unsigned long flags; - netif_stop_queue (dev); + netif_stop_queue(dev); dev->stats.tx_bytes += skb->len; if (el3_debug > 4) { pr_debug("%s: el3_start_xmit(length = %u) called, status %4.4x.\n", - dev->name, skb->len, inw(ioaddr + EL3_STATUS)); + dev->name, skb->len, inw(ioaddr + EL3_STATUS)); } /* * We lock the driver against other processors. Note * we don't need to lock versus the IRQ as we suspended * that. This means that we lose the ability to take * an RX during a TX upload. That sucks a bit with SMP - * on an original 3c509 (2K buffer) + * on an original 3c509 (2K buffer). * * Using disable_irq stops us crapping on other * time sensitive devices. @@ -733,39 +777,43 @@ el3_start_xmit(struct sk_buff *skb, struct net_device *dev) /* ... and the packet rounded to a doubleword. */ outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); - if (inw(ioaddr + TX_FREE) > 1536) + if (inw(ioaddr + TX_FREE) > 1536) { netif_start_queue(dev); - else + } else { /* Interrupt us when the FIFO has room for max-sized packet. */ - outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + outw(SET_TX_THRESHOLD + 1536, ioaddr + EL3_CMD); + } spin_unlock_irqrestore(&lp->lock, flags); - dev_consume_skb_any (skb); + dev_consume_skb_any(skb); /* Clear the Tx status stack. */ { short tx_status; int i = 4; - while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { - if (tx_status & 0x38) dev->stats.tx_aborted_errors++; - if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); - if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); - outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { + if (tx_status & 0x38) + dev->stats.tx_aborted_errors++; + if (tx_status & 0x30) + outw(TX_RESET, ioaddr + EL3_CMD); + if (tx_status & 0x3C) + outw(TX_ENABLE, ioaddr + EL3_CMD); + /* Pop the status stack. */ + outb(0x00, ioaddr + TX_STATUS); } } return NETDEV_TX_OK; } /* The EL3 interrupt handler. */ -static irqreturn_t -el3_interrupt(int irq, void *dev_id) +static irqreturn_t el3_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; + int i = max_interrupt_work; struct el3_private *lp; int ioaddr, status; - int i = max_interrupt_work; lp = netdev_priv(dev); spin_lock(&lp->lock); @@ -778,70 +826,89 @@ el3_interrupt(int irq, void *dev_id) } while ((status = inw(ioaddr + EL3_STATUS)) & - (IntLatch | RxComplete | StatsFull)) { + (INT_LATCH | RX_COMPLETE | STATS_FULL)) { - if (status & RxComplete) + if (status & RX_COMPLETE) el3_rx(dev); - if (status & TxAvailable) { + if (status & TX_AVAILABLE) { if (el3_debug > 5) pr_debug(" TX room bit was handled.\n"); /* There's room in the FIFO for a full-sized packet. */ - outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); - netif_wake_queue (dev); + outw(ACK_INTR | TX_AVAILABLE, ioaddr + EL3_CMD); + netif_wake_queue(dev); } - if (status & (AdapterFailure | RxEarly | StatsFull | TxComplete)) { + if (status & + (ADAPTER_FAILURE | RX_EARLY | STATS_FULL | TX_COMPLETE)) { /* Handle all uncommon interrupts. */ - if (status & StatsFull) /* Empty statistics. */ + if (status & STATS_FULL) { + /* Empty statistics. */ update_stats(dev); - if (status & RxEarly) { /* Rx early is unused. */ + } + if (status & RX_EARLY) { + /* Rx early is unused. */ el3_rx(dev); - outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + outw(ACK_INTR | RX_EARLY, ioaddr + EL3_CMD); } - if (status & TxComplete) { /* Really Tx error. */ + if (status & TX_COMPLETE) { + /* Really Tx error. */ short tx_status; int i = 4; - while (--i>0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { - if (tx_status & 0x38) dev->stats.tx_aborted_errors++; - if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); - if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); - outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + while (--i > 0 && + ((tx_status = inb(ioaddr + TX_STATUS)) + > 0)) { + if (tx_status & 0x38) + dev->stats.tx_aborted_errors++; + if (tx_status & 0x30) + outw(TX_RESET, + ioaddr + EL3_CMD); + if (tx_status & 0x3C) + outw(TX_ENABLE, + ioaddr + EL3_CMD); + /* Pop the status stack. */ + outb(0x00, ioaddr + TX_STATUS); } } - if (status & AdapterFailure) { - /* Adapter failure requires Rx reset and reinit. */ - outw(RxReset, ioaddr + EL3_CMD); + if (status & ADAPTER_FAILURE) { + /* Adapter failure requires Rx reset + * and reinit. + */ + outw(RX_RESET, ioaddr + EL3_CMD); /* Set the Rx filter to the current state. */ - outw(SetRxFilter | RxStation | RxBroadcast - | (dev->flags & IFF_ALLMULTI ? RxMulticast : 0) - | (dev->flags & IFF_PROMISC ? RxProm : 0), - ioaddr + EL3_CMD); - outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ - outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + outw((SET_RX_FILTER | RX_STATION | + RX_BROADCAST | + (dev->flags & IFF_ALLMULTI ? + RX_MULTICAST : 0) | + (dev->flags & IFF_PROMISC ? + RX_PROM : 0)), + ioaddr + EL3_CMD); + /* Re-enable the receiver. */ + outw(RX_ENABLE, ioaddr + EL3_CMD); + outw(ACK_INTR | ADAPTER_FAILURE, + ioaddr + EL3_CMD); } } if (--i < 0) { pr_err("%s: Infinite loop in interrupt, status %4.4x.\n", - dev->name, status); + dev->name, status); /* Clear all interrupts. */ - outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + outw(ACK_INTR | 0xFF, ioaddr + EL3_CMD); break; } /* Acknowledge the IRQ. */ - outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); /* Ack IRQ */ + outw(ACK_INTR | INT_REQ | INT_LATCH, ioaddr + EL3_CMD); } if (el3_debug > 4) { pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name, - inw(ioaddr + EL3_STATUS)); + inw(ioaddr + EL3_STATUS)); } spin_unlock(&lp->lock); return IRQ_HANDLED; } - #ifdef CONFIG_NET_POLL_CONTROLLER /* * Polling receive - used by netconsole and other diagnostic tools @@ -855,28 +922,23 @@ static void el3_poll_controller(struct net_device *dev) } #endif -static struct net_device_stats * -el3_get_stats(struct net_device *dev) +static struct net_device_stats *el3_get_stats(struct net_device *dev) { struct el3_private *lp = netdev_priv(dev); unsigned long flags; - /* - * This is fast enough not to bother with disable IRQ - * stuff. - */ - + /* This is fast enough not to bother with disable IRQ stuff. */ spin_lock_irqsave(&lp->lock, flags); update_stats(dev); spin_unlock_irqrestore(&lp->lock, flags); return &dev->stats; } -/* Update statistics. We change to register window 6, so this should be run - single-threaded if the device is active. This is expected to be a rare - operation, and it's simpler for the rest of the driver to assume that - window 1 is always valid rather than use a special window-state variable. - */ +/* Update statistics. We change to register window 6, so this should be run + * single-threaded if the device is active. This is expected to be a rare + * operation, and it's simpler for the rest of the driver to assume that + * window 1 is always valid rather than use a special window-state variable. + */ static void update_stats(struct net_device *dev) { int ioaddr = dev->base_addr; @@ -884,10 +946,10 @@ static void update_stats(struct net_device *dev) if (el3_debug > 5) pr_debug(" Updating the statistics.\n"); /* Turn off statistics updates while reading. */ - outw(StatsDisable, ioaddr + EL3_CMD); + outw(STATS_DISABLE, ioaddr + EL3_CMD); /* Switch to the stats window, and read everything. */ EL3WINDOW(6); - dev->stats.tx_carrier_errors += inb(ioaddr + 0); + dev->stats.tx_carrier_errors += inb(ioaddr + 0); dev->stats.tx_heartbeat_errors += inb(ioaddr + 1); /* Multiple collisions. */ inb(ioaddr + 2); dev->stats.collisions += inb(ioaddr + 3); @@ -901,31 +963,42 @@ static void update_stats(struct net_device *dev) /* Back to window 1, and turn statistics back on. */ EL3WINDOW(1); - outw(StatsEnable, ioaddr + EL3_CMD); + outw(STATS_ENABLE, ioaddr + EL3_CMD); } -static int -el3_rx(struct net_device *dev) +static int el3_rx(struct net_device *dev) { int ioaddr = dev->base_addr; short rx_status; if (el3_debug > 5) pr_debug(" In rx_packet(), status %4.4x, rx_status %4.4x.\n", - inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); + inw(ioaddr + EL3_STATUS), inw(ioaddr + RX_STATUS)); while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) { - if (rx_status & 0x4000) { /* Error, update stats. */ + if (rx_status & 0x4000) { + /* Error, update stats. */ short error = rx_status & 0x3800; - outw(RxDiscard, ioaddr + EL3_CMD); + outw(RX_DISCARD, ioaddr + EL3_CMD); dev->stats.rx_errors++; switch (error) { - case 0x0000: dev->stats.rx_over_errors++; break; - case 0x0800: dev->stats.rx_length_errors++; break; - case 0x1000: dev->stats.rx_frame_errors++; break; - case 0x1800: dev->stats.rx_length_errors++; break; - case 0x2000: dev->stats.rx_frame_errors++; break; - case 0x2800: dev->stats.rx_crc_errors++; break; + case 0x0000: + dev->stats.rx_over_errors++; + break; + case 0x0800: + dev->stats.rx_length_errors++; + break; + case 0x1000: + dev->stats.rx_frame_errors++; + break; + case 0x1800: + dev->stats.rx_length_errors++; + break; + case 0x2000: + dev->stats.rx_frame_errors++; + break; + case 0x2800: + dev->stats.rx_crc_errors++; break; } } else { short pkt_len = rx_status & 0x7ff; @@ -934,49 +1007,51 @@ el3_rx(struct net_device *dev) skb = netdev_alloc_skb(dev, pkt_len + 5); if (el3_debug > 4) pr_debug("Receiving packet size %d status %4.4x.\n", - pkt_len, rx_status); - if (skb != NULL) { - skb_reserve(skb, 2); /* Align IP on 16 byte */ - - /* 'skb->data' points to the start of sk_buff data area. */ - insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), - (pkt_len + 3) >> 2); - - outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ - skb->protocol = eth_type_trans(skb,dev); + pkt_len, rx_status); + if (skb) { + /* Align IP on 16 byte. */ + skb_reserve(skb, 2); + + /* 'skb->data' points to the start of sk_buff + * data area. + */ + insl(ioaddr + RX_FIFO, skb_put(skb, pkt_len), + (pkt_len + 3) >> 2); + + /* Pop top Rx packet. */ + outw(RX_DISCARD, ioaddr + EL3_CMD); + skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); dev->stats.rx_bytes += pkt_len; dev->stats.rx_packets++; continue; } - outw(RxDiscard, ioaddr + EL3_CMD); + outw(RX_DISCARD, ioaddr + EL3_CMD); dev->stats.rx_dropped++; if (el3_debug) pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n", - dev->name, pkt_len); + dev->name, pkt_len); } - inw(ioaddr + EL3_STATUS); /* Delay. */ + inw(ioaddr + EL3_STATUS); /* Delay. */ while (inw(ioaddr + EL3_STATUS) & 0x1000) pr_debug(" Waiting for 3c509 to discard packet, status %x.\n", - inw(ioaddr + EL3_STATUS) ); + inw(ioaddr + EL3_STATUS)); } return 0; } -/* - * Set or clear the multicast filter for this adaptor. - */ -static void -set_multicast_list(struct net_device *dev) +/* Set or clear the multicast filter for this adaptor. */ +static void set_multicast_list(struct net_device *dev) { - unsigned long flags; struct el3_private *lp = netdev_priv(dev); int ioaddr = dev->base_addr; int mc_count = netdev_mc_count(dev); + unsigned long flags; if (el3_debug > 1) { static int old; + if (old != mc_count) { old = mc_count; pr_debug("%s: Setting Rx mode to %d addresses.\n", @@ -984,23 +1059,24 @@ set_multicast_list(struct net_device *dev) } } spin_lock_irqsave(&lp->lock, flags); - if (dev->flags&IFF_PROMISC) { - outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, - ioaddr + EL3_CMD); - } - else if (mc_count || (dev->flags&IFF_ALLMULTI)) { - outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD); + if (dev->flags & IFF_PROMISC) { + outw((SET_RX_FILTER | RX_STATION | RX_MULTICAST | + RX_BROADCAST | RX_PROM), + ioaddr + EL3_CMD); + } else if (mc_count || (dev->flags & IFF_ALLMULTI)) { + outw(SET_RX_FILTER | RX_STATION | RX_MULTICAST | RX_BROADCAST, + ioaddr + EL3_CMD); + } else { + outw(SET_RX_FILTER | RX_STATION | RX_BROADCAST, + ioaddr + EL3_CMD); } - else - outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); spin_unlock_irqrestore(&lp->lock, flags); } -static int -el3_close(struct net_device *dev) +static int el3_close(struct net_device *dev) { - int ioaddr = dev->base_addr; struct el3_private *lp = netdev_priv(dev); + int ioaddr = dev->base_addr; if (el3_debug > 2) pr_debug("%s: Shutting down ethercard.\n", dev->name); @@ -1013,15 +1089,15 @@ el3_close(struct net_device *dev) if (lp->type != EL3_EISA) { /* But we explicitly zero the IRQ line select anyway. Don't do * it on EISA cards, it prevents the module from getting an - * IRQ after unload+reload... */ + * IRQ after unload+reload... + */ outw(0x0f00, ioaddr + WN0_IRQ); } return 0; } -static int -el3_link_ok(struct net_device *dev) +static int el3_link_ok(struct net_device *dev) { int ioaddr = dev->base_addr; u16 tmp; @@ -1029,18 +1105,18 @@ el3_link_ok(struct net_device *dev) EL3WINDOW(4); tmp = inw(ioaddr + WN4_MEDIA); EL3WINDOW(1); - return tmp & (1<<11); + return tmp & (1 << 11); } -static void -el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_link_ksettings *cmd) +static void el3_netdev_get_ecmd(struct net_device *dev, + struct ethtool_link_ksettings *cmd) { - u16 tmp; int ioaddr = dev->base_addr; u32 supported; + u16 tmp; EL3WINDOW(0); - /* obtain current transceiver via WN4_MEDIA? */ + /* Obtain current transceiver via WN4_MEDIA? */ tmp = inw(ioaddr + WN0_ADDR_CONF); switch (tmp >> 14) { case 0: @@ -1059,13 +1135,13 @@ el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_link_ksettings *cmd) cmd->base.duplex = DUPLEX_HALF; supported = 0; tmp = inw(ioaddr + WN0_CONF_CTRL); - if (tmp & (1<<13)) + if (tmp & (1 << 13)) supported |= SUPPORTED_AUI; - if (tmp & (1<<12)) + if (tmp & (1 << 12)) supported |= SUPPORTED_BNC; - if (tmp & (1<<9)) { + if (tmp & (1 << 9)) { supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half | - SUPPORTED_10baseT_Full; /* hmm... */ + SUPPORTED_10baseT_Full; /* hmm... */ EL3WINDOW(4); tmp = inw(ioaddr + WN4_NETDIAG); if (tmp & FD_ENABLE) @@ -1078,17 +1154,15 @@ el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_link_ksettings *cmd) EL3WINDOW(1); } -static int -el3_netdev_set_ecmd(struct net_device *dev, - const struct ethtool_link_ksettings *cmd) +static int el3_netdev_set_ecmd(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) { - u16 tmp; int ioaddr = dev->base_addr; + u16 tmp; if (cmd->base.speed != SPEED_10) return -EINVAL; - if ((cmd->base.duplex != DUPLEX_HALF) && - (cmd->base.duplex != DUPLEX_FULL)) + if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) return -EINVAL; /* change XCVR type */ @@ -1096,16 +1170,16 @@ el3_netdev_set_ecmd(struct net_device *dev, tmp = inw(ioaddr + WN0_ADDR_CONF); switch (cmd->base.port) { case PORT_TP: - tmp &= ~(3<<14); + tmp &= ~(3 << 14); dev->if_port = 0; break; case PORT_AUI: - tmp &= ~(3<<14); - tmp |= (1<<14); + tmp &= ~(3 << 14); + tmp |= 1 << 14; dev->if_port = 1; break; case PORT_BNC: - tmp |= (3<<14); + tmp |= 3 << 14; dev->if_port = 3; break; default: @@ -1114,13 +1188,14 @@ el3_netdev_set_ecmd(struct net_device *dev, outw(tmp, ioaddr + WN0_ADDR_CONF); if (dev->if_port == 3) { - /* fire up the DC-DC convertor if BNC gets enabled */ + /* Fire up the DC-DC converter if BNC gets enabled. */ tmp = inw(ioaddr + WN0_ADDR_CONF); if (tmp & (3 << 14)) { - outw(StartCoax, ioaddr + EL3_CMD); + outw(START_COAX, ioaddr + EL3_CMD); udelay(800); - } else + } else { return -EIO; + } } EL3WINDOW(4); @@ -1135,7 +1210,8 @@ el3_netdev_set_ecmd(struct net_device *dev, return 0; } -static void el3_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +static void el3_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) { strscpy(info->driver, DRV_NAME, sizeof(info->driver)); } @@ -1193,41 +1269,41 @@ static const struct ethtool_ops ethtool_ops = { .set_link_ksettings = el3_set_link_ksettings, }; -static void -el3_down(struct net_device *dev) +static void el3_down(struct net_device *dev) { int ioaddr = dev->base_addr; netif_stop_queue(dev); /* Turn off statistics ASAP. We update lp->stats below. */ - outw(StatsDisable, ioaddr + EL3_CMD); + outw(STATS_DISABLE, ioaddr + EL3_CMD); /* Disable the receiver and transmitter. */ - outw(RxDisable, ioaddr + EL3_CMD); - outw(TxDisable, ioaddr + EL3_CMD); + outw(RX_DISABLE, ioaddr + EL3_CMD); + outw(TX_DISABLE, ioaddr + EL3_CMD); - if (dev->if_port == 3) + if (dev->if_port == 3) { /* Turn off thinnet power. Green! */ - outw(StopCoax, ioaddr + EL3_CMD); - else if (dev->if_port == 0) { - /* Disable link beat and jabber, if_port may change here next open(). */ + outw(STOP_COAX, ioaddr + EL3_CMD); + } else if (dev->if_port == 0) { + /* Disable link beat and jabber, if_port may change here next + * open(). + */ EL3WINDOW(4); outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA); } - outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + outw(SET_INTR_ENB | 0x0000, ioaddr + EL3_CMD); update_stats(dev); } -static void -el3_up(struct net_device *dev) +static void el3_up(struct net_device *dev) { - int i, sw_info, net_diag; int ioaddr = dev->base_addr; + int i, sw_info, net_diag; - /* Activating the board required and does no harm otherwise */ + /* Activating the board required and does no harm otherwise. */ outw(0x0001, ioaddr + 4); /* Set the IRQ line. */ @@ -1239,51 +1315,67 @@ el3_up(struct net_device *dev) for (i = 0; i < 6; i++) outb(dev->dev_addr[i], ioaddr + i); - if ((dev->if_port & 0x03) == 3) /* BNC interface */ - /* Start the thinnet transceiver. We should really wait 50ms...*/ - outw(StartCoax, ioaddr + EL3_CMD); - else if ((dev->if_port & 0x03) == 0) { /* 10baseT interface */ - /* Combine secondary sw_info word (the adapter level) and primary - sw_info word (duplex setting plus other useless bits) */ + if ((dev->if_port & 0x03) == 3) { + /* BNC interface */ + + /* Start the thinnet transceiver. We should really wait + * 50ms... + */ + outw(START_COAX, ioaddr + EL3_CMD); + } else if ((dev->if_port & 0x03) == 0) { + /* 10baseT interface */ + + /* Combine secondary sw_info word (the adapter level) and + * primary sw_info word (duplex setting plus other useless + * bits). + */ EL3WINDOW(0); sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) | - (read_eeprom(ioaddr, 0x0d) & 0xBff0); + (read_eeprom(ioaddr, 0x0d) & 0xBff0); EL3WINDOW(4); net_diag = inw(ioaddr + WN4_NETDIAG); - net_diag = (net_diag | FD_ENABLE); /* temporarily assume full-duplex will be set */ + /* Temporarily assume full-duplex will be set. */ + net_diag = (net_diag | FD_ENABLE); pr_info("%s: ", dev->name); switch (dev->if_port & 0x0c) { - case 12: - /* force full-duplex mode if 3c5x9b */ - if (sw_info & 0x000f) { - pr_cont("Forcing 3c5x9b full-duplex mode"); - break; - } - fallthrough; - case 8: - /* set full-duplex mode based on eeprom config setting */ - if ((sw_info & 0x000f) && (sw_info & 0x8000)) { - pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)"); - break; - } - fallthrough; - default: - /* xcvr=(0 || 4) OR user has an old 3c5x9 non "B" model */ - pr_cont("Setting 3c5x9/3c5x9B half-duplex mode"); - net_diag = (net_diag & ~FD_ENABLE); /* disable full duplex */ + case 12: + /* Force full-duplex mode if 3c5x9b. */ + if (sw_info & 0x000f) { + pr_cont("Forcing 3c5x9b full-duplex mode"); + break; + } + fallthrough; + case 8: + /* Set full-duplex mode based on eeprom config + * setting. + */ + if ((sw_info & 0x000f) && (sw_info & 0x8000)) { + pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)"); + break; + } + fallthrough; + default: + /* xcvr = (0 || 4) OR user has an old 3c5x9 non "B" + * model. + */ + pr_cont("Setting 3c5x9/3c5x9B half-duplex mode"); + /* Disable full duplex. */ + net_diag = (net_diag & ~FD_ENABLE); } outw(net_diag, ioaddr + WN4_NETDIAG); - pr_cont(" if_port: %d, sw_info: %4.4x\n", dev->if_port, sw_info); + pr_cont(" if_port: %d, sw_info: %4.4x\n", + dev->if_port, sw_info); if (el3_debug > 3) - pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", dev->name, net_diag); + pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", + dev->name, net_diag); /* Enable link beat and jabber check. */ outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA); } /* Switch to the stats window, and clear all stats by reading. */ - outw(StatsDisable, ioaddr + EL3_CMD); + outw(STATS_DISABLE, ioaddr + EL3_CMD); EL3WINDOW(6); for (i = 0; i < 9; i++) inb(ioaddr + i); @@ -1294,18 +1386,22 @@ el3_up(struct net_device *dev) EL3WINDOW(1); /* Accept b-case and phys addr only. */ - outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); - outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ - - outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ - outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + outw(SET_RX_FILTER | RX_STATION | RX_BROADCAST, ioaddr + EL3_CMD); + /* Turn on statistics. */ + outw(STATS_ENABLE, ioaddr + EL3_CMD); + + /* Enable the receiver. */ + outw(RX_ENABLE, ioaddr + EL3_CMD); + /* Enable transmitter. */ + outw(TX_ENABLE, ioaddr + EL3_CMD); /* Allow status bits to be seen. */ - outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + outw(SET_STATUS_ENB | 0xff, ioaddr + EL3_CMD); /* Ack all pending events, and set active indicator mask. */ - outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, - ioaddr + EL3_CMD); - outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull, - ioaddr + EL3_CMD); + outw(ACK_INTR | INT_LATCH | TX_AVAILABLE | RX_EARLY | INT_REQ, + ioaddr + EL3_CMD); + outw((SET_INTR_ENB | INT_LATCH | TX_AVAILABLE | TX_COMPLETE | + RX_COMPLETE | STATS_FULL), + ioaddr + EL3_CMD); netif_start_queue(dev); } @@ -1313,12 +1409,11 @@ el3_up(struct net_device *dev) /* Power Management support functions */ #ifdef CONFIG_PM -static int -el3_suspend(struct device *pdev, pm_message_t state) +static int el3_suspend(struct device *pdev, pm_message_t state) { - unsigned long flags; struct net_device *dev; struct el3_private *lp; + unsigned long flags; int ioaddr; dev = dev_get_drvdata(pdev); @@ -1331,18 +1426,17 @@ el3_suspend(struct device *pdev, pm_message_t state) netif_device_detach(dev); el3_down(dev); - outw(PowerDown, ioaddr + EL3_CMD); + outw(POWER_DOWN, ioaddr + EL3_CMD); spin_unlock_irqrestore(&lp->lock, flags); return 0; } -static int -el3_resume(struct device *pdev) +static int el3_resume(struct device *pdev) { - unsigned long flags; struct net_device *dev; struct el3_private *lp; + unsigned long flags; int ioaddr; dev = dev_get_drvdata(pdev); @@ -1351,7 +1445,7 @@ el3_resume(struct device *pdev) spin_lock_irqsave(&lp->lock, flags); - outw(PowerUp, ioaddr + EL3_CMD); + outw(POWER_UP, ioaddr + EL3_CMD); EL3WINDOW(0); el3_up(dev); @@ -1364,7 +1458,7 @@ el3_resume(struct device *pdev) #endif /* CONFIG_PM */ -module_param(debug,int, 0); +module_param(debug, int, 0); module_param_hw_array(irq, int, irq, NULL, 0); module_param(max_interrupt_work, int, 0); MODULE_PARM_DESC(debug, "debug level (0-6)"); @@ -1393,15 +1487,14 @@ static int __init el3_init_module(void) #endif /* Select an open I/O location at 0x1*0 to do ISA contention select. */ /* Start with 0x110 to avoid some sound cards.*/ - for (id_port = 0x110 ; id_port < 0x200; id_port += 0x10) { + for (id_port = 0x110; id_port < 0x200; id_port += 0x10) { if (!request_region(id_port, 1, "3c509-control")) continue; outb(0x00, id_port); outb(0xff, id_port); if (inb(id_port) & 0x01) break; - else - release_region(id_port, 1); + release_region(id_port, 1); } if (id_port >= 0x200) { id_port = 0; @@ -1446,5 +1539,5 @@ static void __exit el3_cleanup_module(void) #endif } -module_init (el3_init_module); -module_exit (el3_cleanup_module); +module_init(el3_init_module); +module_exit(el3_cleanup_module); -- cgit v1.2.3 From 9a1730245e416d11ad5c0f2c100061d61cc43f60 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Wed, 20 May 2026 20:43:20 +0200 Subject: net: bcmgenet: keep RBUF EEE/PM disabled Setting RBUF_EEE_EN | RBUF_PM_EN in RBUF_ENERGY_CTRL breaks the RX path on GENET hardware once MAC EEE becomes active. RX traffic stops flowing while the link stays up and the usual descriptor/RX error counters remain quiet. In that state the MAC still accepts frames (rbuf_ovflow_cnt keeps climbing) but RBUF no longer forwards them to DMA, so rx_packets is no longer incremented at the netdev level. On some boards the corruption ends up as a paging fault in skb_release_data via bcmgenet_rx_poll on an LPI exit. Reproduced on Pi 4B (BCM2711 + BCM54213PE) and confirmed by Florian Fainelli on an internal Broadcom 4908-family board with the same crash signature. RBUF_PM_EN is not publicly documented. This shows up more often now that phy_support_eee() enables EEE by default, but it also affects older kernels as soon as TX LPI is turned on via ethtool, so it is not specific to recent changes. Always clear RBUF_EEE_EN | RBUF_PM_EN in bcmgenet_eee_enable_set so the bits stay off across resets. UMAC and TBUF setup is left alone so TX-side EEE keeps working. Link: https://github.com/raspberrypi/linux/issues/7304 Fixes: 6ef398ea60d9 ("net: bcmgenet: add EEE support") Cc: stable@vger.kernel.org Signed-off-by: Nicolai Buchwitz Reviewed-by: Florian Fainelli Link: https://patch.msgid.link/20260520184320.652053-1-nb@tipi-net.de Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 54f71b1e85fc..7c11cf916762 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -1368,13 +1368,12 @@ void bcmgenet_eee_enable_set(struct net_device *dev, bool enable) reg &= ~(TBUF_EEE_EN | TBUF_PM_EN); bcmgenet_writel(reg, priv->base + off); - /* Do the same for thing for RBUF */ + /* RBUF EEE/PM can break the RX path on GENET. Keep it disabled. */ reg = bcmgenet_rbuf_readl(priv, RBUF_ENERGY_CTRL); - if (enable) - reg |= RBUF_EEE_EN | RBUF_PM_EN; - else + if (reg & (RBUF_EEE_EN | RBUF_PM_EN)) { reg &= ~(RBUF_EEE_EN | RBUF_PM_EN); - bcmgenet_rbuf_writel(priv, reg, RBUF_ENERGY_CTRL); + bcmgenet_rbuf_writel(priv, reg, RBUF_ENERGY_CTRL); + } if (!enable && priv->clk_eee_enabled) { clk_disable_unprepare(priv->clk_eee); -- cgit v1.2.3 From 8c84c5ec4aaff6ad7aac49935e050fed6b360a28 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:13 +0800 Subject: net: enetc: fix incorrect mailbox message status returned to VFs There are two cases where VFs receive an incorrect success status from the PF mailbox message handler, misleading them into believing their requests have been fulfilled: In enetc_msg_handle_rxmsg(), *status is pre-initialized to ENETC_MSG_CMD_STATUS_OK. When an unsupported command type is received, the default case only logs an error without updating *status, so it remains as ENETC_MSG_CMD_STATUS_OK. In enetc_msg_pf_set_vf_primary_mac_addr(), when the PF has already assigned a MAC address for the VF (ENETC_VF_FLAG_PF_SET_MAC is set), the function rejects the request but returns ENETC_MSG_CMD_STATUS_OK instead of ENETC_MSG_CMD_STATUS_FAIL. Therefore, correct the status value for the two cases mentioned above. Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-2-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index a12fd54a475f..27d4bb65e017 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -493,11 +493,13 @@ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, return ENETC_MSG_CMD_STATUS_FAIL; addr = cmd->mac.sa_data; - if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) + if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) { dev_warn(dev, "Attempt to override PF set mac addr for VF%d\n", vf_id); - else - enetc_pf_set_primary_mac_addr(&pf->si->hw, vf_id + 1, addr); + return ENETC_MSG_CMD_STATUS_FAIL; + } + + enetc_pf_set_primary_mac_addr(&pf->si->hw, vf_id + 1, addr); return ENETC_MSG_CMD_STATUS_OK; } @@ -509,7 +511,6 @@ void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status) struct enetc_msg_cmd_header *cmd_hdr; u16 cmd_type; - *status = ENETC_MSG_CMD_STATUS_OK; cmd_hdr = (struct enetc_msg_cmd_header *)msg->vaddr; cmd_type = cmd_hdr->type; @@ -518,6 +519,7 @@ void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status) *status = enetc_msg_pf_set_vf_primary_mac_addr(pf, vf_id); break; default: + *status = ENETC_MSG_CMD_STATUS_FAIL; dev_err(dev, "command not supported (cmd_type: 0x%x)\n", cmd_type); } -- cgit v1.2.3 From 5027266dea471e140f93dd534845c9c4f43219a3 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:14 +0800 Subject: net: enetc: fix missing error code when pf->vf_state allocation fails In enetc_pf_probe(), when the memory allocation for pf->vf_state fails, the code jumps to the error handling label but the variable 'err' is not assigned an appropriate error code beforehand. This causes the function to return 0 (success) on an allocation failure path, misleading the caller into thinking the probe succeeded. So set err to -ENOMEM before jumping to the error handling label when the allocation for pf->vf_state returns NULL. Fixes: e15c5506dd39 ("net: enetc: allocate vf_state during PF probes") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-3-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 27d4bb65e017..b743b6d33ccc 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -962,8 +962,10 @@ static int enetc_pf_probe(struct pci_dev *pdev, if (pf->total_vfs) { pf->vf_state = kzalloc_objs(struct enetc_vf_state, pf->total_vfs); - if (!pf->vf_state) + if (!pf->vf_state) { + err = -ENOMEM; goto err_alloc_vf_state; + } } err = enetc_setup_mac_addresses(node, pf); -- cgit v1.2.3 From 4a995d37b537f437daa01752d39cf44c6ba9ee2c Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:15 +0800 Subject: net: enetc: add ratelimiting to VF mailbox error messages Sashiko reported that a buggy or malicious guest VM can flood the host kernel log by repeatedly sending VF-to-PF messages at a high rate, degrading host performance and hiding important system logs [1]. Fix by replacing dev_err()/dev_warn() with dev_err_ratelimited(), limiting output to the default kernel ratelimit. This ensures errors are still logged for debugging while preventing log flooding attacks. Link: https://sashiko.dev/#/patchset/20260511080805.2052495-1-wei.fang%40nxp.com #1 Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-4-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index b743b6d33ccc..dea3a92c4722 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -494,8 +494,9 @@ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, addr = cmd->mac.sa_data; if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) { - dev_warn(dev, "Attempt to override PF set mac addr for VF%d\n", - vf_id); + dev_err_ratelimited(dev, + "VF%d attempted to override PF set MAC\n", + vf_id); return ENETC_MSG_CMD_STATUS_FAIL; } @@ -520,8 +521,9 @@ void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status) break; default: *status = ENETC_MSG_CMD_STATUS_FAIL; - dev_err(dev, "command not supported (cmd_type: 0x%x)\n", - cmd_type); + dev_err_ratelimited(dev, + "command not supported (cmd_type: 0x%x)\n", + cmd_type); } } -- cgit v1.2.3 From c666fa632fe628c34904bcd59aeb96bf08e40d31 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:16 +0800 Subject: net: enetc: fix TOCTOU race and validate VF MAC address Sashiko reported that the PF driver accepts arbitrary MAC address from from VF mailbox messages without proper validation, creating a security vulnerability [1]. In enetc_msg_pf_set_vf_primary_mac_addr(), the MAC address is extracted directly from the message buffer (cmd->mac.sa_data) and programmed into hardware via pf->ops->set_si_primary_mac() without any validity checks. A malicious VF can configure a multicast, broadcast, or all-zero MAC address. Therefore, a validation to check the MAC address provided by VF is required. However, simply checking the MAC address is not enough, because it also has the potential TOCTOU race [2]: The code reads the MAC address from the DMA buffer to validate it via is_valid_ether_addr(), if validation passes, reads the same DMA buffer a second time when calling enetc_pf_set_primary_mac_addr() to program the hardware. A malicious VF can exploit this window by overwriting the MAC address in the DMA buffer between the validation check and the hardware programming, bypassing the validation entirely. Therefore, allocate a local buffer in enetc_msg_handle_rxmsg() and copy the message content from the DMA buffer via memcpy() before processing. This ensures the PF operates on a stable snapshot that the VF cannot modify. Link: https://sashiko.dev/#/patchset/20260511080805.2052495-1-wei.fang%40nxp.com #1 Link: https://sashiko.dev/#/patchset/20260513103021.2190593-1-wei.fang%40nxp.com #2 Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-5-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 39 +++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index dea3a92c4722..09c642040892 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -478,21 +478,24 @@ static void enetc_configure_port(struct enetc_pf *pf) /* Messaging */ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, - int vf_id) + int vf_id, void *msg) { struct enetc_vf_state *vf_state = &pf->vf_state[vf_id]; - struct enetc_msg_swbd *msg = &pf->rxmsg[vf_id]; - struct enetc_msg_cmd_set_primary_mac *cmd; + struct enetc_msg_cmd_set_primary_mac *cmd = msg; struct device *dev = &pf->si->pdev->dev; - u16 cmd_id; + u16 cmd_id = cmd->header.id; char *addr; - cmd = (struct enetc_msg_cmd_set_primary_mac *)msg->vaddr; - cmd_id = cmd->header.id; if (cmd_id != ENETC_MSG_CMD_MNG_ADD) return ENETC_MSG_CMD_STATUS_FAIL; addr = cmd->mac.sa_data; + if (!is_valid_ether_addr(addr)) { + dev_err_ratelimited(dev, "VF%d attempted to set invalid MAC\n", + vf_id); + return ENETC_MSG_CMD_STATUS_FAIL; + } + if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) { dev_err_ratelimited(dev, "VF%d attempted to override PF set MAC\n", @@ -507,17 +510,33 @@ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status) { - struct enetc_msg_swbd *msg = &pf->rxmsg[vf_id]; + struct enetc_msg_swbd *msg_swbd = &pf->rxmsg[vf_id]; struct device *dev = &pf->si->pdev->dev; struct enetc_msg_cmd_header *cmd_hdr; u16 cmd_type; + u8 *msg; - cmd_hdr = (struct enetc_msg_cmd_header *)msg->vaddr; + msg = kzalloc_objs(*msg, msg_swbd->size); + if (!msg) { + dev_err_ratelimited(dev, + "Failed to allocate message buffer\n"); + *status = ENETC_MSG_CMD_STATUS_FAIL; + return; + } + + /* Currently, only ENETC_MSG_CMD_MNG_MAC command is supported, so + * only sizeof(struct enetc_msg_cmd_set_primary_mac) bytes need to + * be copied. This data already includes the cmd_type field, so it + * can correctly return an error code. + */ + memcpy(msg, msg_swbd->vaddr, + sizeof(struct enetc_msg_cmd_set_primary_mac)); + cmd_hdr = (struct enetc_msg_cmd_header *)msg; cmd_type = cmd_hdr->type; switch (cmd_type) { case ENETC_MSG_CMD_MNG_MAC: - *status = enetc_msg_pf_set_vf_primary_mac_addr(pf, vf_id); + *status = enetc_msg_pf_set_vf_primary_mac_addr(pf, vf_id, msg); break; default: *status = ENETC_MSG_CMD_STATUS_FAIL; @@ -525,6 +544,8 @@ void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status) "command not supported (cmd_type: 0x%x)\n", cmd_type); } + + kfree(msg); } #ifdef CONFIG_PCI_IOV -- cgit v1.2.3 From f262f5d893327a7131ed25ac8dd01ed7024bcc18 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:17 +0800 Subject: net: enetc: fix race condition in VF MAC address configuration Sashiko reported a potential race condition between the VF message handler and administrative VF MAC configuration from the host [1]. The VF message handler (enetc_msg_pf_set_vf_primary_mac_addr) runs asynchronously in a workqueue context and accesses vf_state->flags without any locking. Concurrently, the host can administratively change the VF MAC address via enetc_pf_set_vf_mac(), which executes under RTNL lock and modifies both vf_state->flags and hardware registers. This creates two race windows: 1) TOCTOU race on vf_state->flags: The check of ENETC_VF_FLAG_PF_SET_MAC and subsequent MAC programming are not atomic, allowing the flag state to change between check and use. 2) Torn MAC address writes: Hardware MAC programming requires multiple non-atomic register writes (__raw_writel for lower 32 bits and __raw_writew for upper 16 bits). Concurrent updates from VF mailbox and PF admin paths can interleave these operations, resulting in a corrupted MAC address being programmed into the hardware. Fix by introducing a per-VF mutex to serialize access to vf_state and hardware MAC register updates. Both enetc_pf_set_vf_mac() and enetc_msg_pf_set_vf_primary_mac_addr() now acquire this lock before accessing vf_state->flags or programming the MAC address, ensuring atomic read-modify-write sequences and preventing register write interleaving. Link: https://sashiko.dev/#/patchset/20260511080805.2052495-1-wei.fang%40nxp.com #1 Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-6-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 10 ++++++++++ drivers/net/ethernet/freescale/enetc/enetc_pf.h | 1 + 2 files changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 09c642040892..8e11a023d516 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -252,8 +252,12 @@ static int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac) return -EADDRNOTAVAIL; vf_state = &pf->vf_state[vf]; + + mutex_lock(&vf_state->lock); vf_state->flags |= ENETC_VF_FLAG_PF_SET_MAC; enetc_pf_set_primary_mac_addr(&priv->si->hw, vf + 1, mac); + mutex_unlock(&vf_state->lock); + return 0; } @@ -496,7 +500,9 @@ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, return ENETC_MSG_CMD_STATUS_FAIL; } + mutex_lock(&vf_state->lock); if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) { + mutex_unlock(&vf_state->lock); dev_err_ratelimited(dev, "VF%d attempted to override PF set MAC\n", vf_id); @@ -504,6 +510,7 @@ static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf, } enetc_pf_set_primary_mac_addr(&pf->si->hw, vf_id + 1, addr); + mutex_unlock(&vf_state->lock); return ENETC_MSG_CMD_STATUS_OK; } @@ -989,6 +996,9 @@ static int enetc_pf_probe(struct pci_dev *pdev, err = -ENOMEM; goto err_alloc_vf_state; } + + for (int i = 0; i < pf->total_vfs; i++) + mutex_init(&pf->vf_state[i].lock); } err = enetc_setup_mac_addresses(node, pf); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index ae407e9e9ee7..35d484858c7b 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -14,6 +14,7 @@ enum enetc_vf_flags { }; struct enetc_vf_state { + struct mutex lock; /* Prevent concurrent access */ enum enetc_vf_flags flags; }; -- cgit v1.2.3 From adb4599979cd00d5d426f26cf78b65264217e35b Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:18 +0800 Subject: net: enetc: fix DMA write to freed memory in enetc_msg_free_mbx() The teardown sequence in enetc_msg_psi_free() frees the DMA buffer before clearing the device's DMA address registers. If a VF sends a message or a pending DMA transfer completes within this window, the hardware will perform a DMA write into the kernel memory that has already been returned to the allocator. The result is silent memory corruption that can affect arbitrary kernel data structures. Therefore, clear the DMA address registers before the DMA buffer is freed. Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-7-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_msg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index 40d22ebe9224..b4d7457097e6 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -96,12 +96,12 @@ static void enetc_msg_free_mbx(struct enetc_si *si, int idx) struct enetc_hw *hw = &si->hw; struct enetc_msg_swbd *msg; + enetc_wr(hw, ENETC_PSIVMSGRCVAR0(idx), 0); + enetc_wr(hw, ENETC_PSIVMSGRCVAR1(idx), 0); + msg = &pf->rxmsg[idx]; dma_free_coherent(&si->pdev->dev, msg->size, msg->vaddr, msg->dma); memset(msg, 0, sizeof(*msg)); - - enetc_wr(hw, ENETC_PSIVMSGRCVAR0(idx), 0); - enetc_wr(hw, ENETC_PSIVMSGRCVAR1(idx), 0); } int enetc_msg_psi_init(struct enetc_pf *pf) -- cgit v1.2.3 From f8ae63de2a872fa3b68c287c35379f6d73d38a5d Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:19 +0800 Subject: net: enetc: fix unbounded loop and interrupt handling in VF-to-PF messaging The enetc_msg_task() function has several issues that need to be addressed: 1. Unbounded loop causing potential DoS: enetc_msg_task() processes VF-to-PF mailbox messages in an unbounded for(;;) loop that keeps polling ENETC_PSIMSGRR until no MR bits are set. A malicious guest VM can exploit this by continuously sending messages at a high rate - immediately sending a new message as soon as the PF acknowledges the previous one. Since the worker thread never yields or enforces a processing budget, the mr_mask check frequently evaluates to non-zero, causing the PF to spin indefinitely and starving other tasks. Fix this by replacing the unbounded loop with a single snapshot read at task entry. The task processes only the VFs whose MR bits were set at that point, then re-enables message interrupts before returning. This bounds work per invocation to at most num_vfs iterations. No messages are lost because the message interrupt is disabled in enetc_msg_psi_msix() before scheduling enetc_msg_task(), so any new messages arriving during processing will trigger a fresh interrupt once re-enabled, scheduling another task invocation. 2. Write order of ENETC_PSIIDR and ENETC_PSIMSGRR: Both ENETC_PSIIDR and ENETC_PSIMSGRR contain MR bits indicating messages have been received from VSIs, but only ENETC_PSIIDR trigger the CPU interrupt. Previously, ENETC_PSIMSGRR was written before ENETC_PSIIDR. Writing ENETC_PSIMSGRR returns the message code to the VSI in its upper 16 bits, signaling to the VF that message processing is complete and it may send the next message. If the VF sends a new message before ENETC_PSIIDR is written, the subsequent w1c write to ENETC_PSIIDR would inadvertently clear the MR bit set by the new message, causing the interrupt to be lost and the new message to go unprocessed. Therefore, write ENETC_PSIIDR first to clear the interrupt source, then write ENETC_PSIMSGRR to acknowledge the message to the VSI. 3. Check both ENETC_PSIMSGRR and ENETC_PSIIDR for mr_status: The write order change above introduces a potential race: if a VF sends a new message in the window between the ENETC_PSIIDR w1c and the ENETC_PSIMSGRR w1c, the ENETC_PSIMSGRR MR bit for the new message may not be set. If mr_status was derived solely from ENETC_PSIMSGRR, this message would never be detected despite ENETC_PSIIDR retaining its MR bit, leading to an unacknowledged interrupt storm. Fix this by computing mr_status as the union of both ENETC_PSIMSGRR and ENETC_PSIIDR MR bits, ensuring all pending messages are detected regardless of which register reflects the new message state. Additionally, rename the per-register MR macros (ENETC_PSI*_MR_MASK, ENETC_PSI*_MR) to register-agnostic names (ENETC_PSIMR_MASK, ENETC_PSIMR_BIT) since the MR bit layout is shared across ENETC_PSIMSGRR, ENETC_PSIIER, and ENETC_PSIIDR. Make the mask macro dynamic based on the actual number of active VFs rather than hardcoded. Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260520064421.91569-8-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_hw.h | 15 ++++-- drivers/net/ethernet/freescale/enetc/enetc_msg.c | 65 ++++++++++++++---------- 2 files changed, 49 insertions(+), 31 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index 662e4fbafb74..e58cc81d199d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -56,11 +56,21 @@ static inline u32 enetc_vsi_set_msize(u32 size) } #define ENETC_PSIMSGRR 0x204 -#define ENETC_PSIMSGRR_MR_MASK GENMASK(2, 1) -#define ENETC_PSIMSGRR_MR(n) BIT((n) + 1) /* n = VSI index */ #define ENETC_PSIVMSGRCVAR0(n) (0x210 + (n) * 0x8) /* n = VSI index */ #define ENETC_PSIVMSGRCVAR1(n) (0x214 + (n) * 0x8) +/* Message received mask, n is the active number of VSIs. + * It is available for ENETC_PSIMSGRR, ENETC_PSIIER, and + * ENETC_PSIIDR registers. + */ +#define ENETC_PSIMR_MASK(n) \ + ({ typeof(n) _n = (n); (_n) ? GENMASK((_n), 1) : 0; }) + +/* Message received bit, n is VSI index. It is available for + * ENETC_PSIMSGRR, ENETC_PSIIER, and ENETC_PSIIDR registers. + */ +#define ENETC_PSIMR_BIT(n) BIT((n) + 1) + #define ENETC_VSIMSGSR 0x204 /* RO */ #define ENETC_VSIMSGSR_MB BIT(0) #define ENETC_VSIMSGSR_MS BIT(1) @@ -94,7 +104,6 @@ static inline u32 enetc_vsi_set_msize(u32 size) #define ENETC_SICAPR1 0x904 #define ENETC_PSIIER 0xa00 -#define ENETC_PSIIER_MR_MASK GENMASK(2, 1) #define ENETC_PSIIDR 0xa08 #define ENETC_SITXIDR 0xa18 #define ENETC_SIRXIDR 0xa28 diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index b4d7457097e6..3136e8321e4d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -3,18 +3,25 @@ #include "enetc_pf.h" -static void enetc_msg_disable_mr_int(struct enetc_hw *hw) +static void enetc_msg_disable_mr_int(struct enetc_pf *pf) { - u32 psiier = enetc_rd(hw, ENETC_PSIIER); + struct enetc_hw *hw = &pf->si->hw; + u32 psiier; + + psiier = enetc_rd(hw, ENETC_PSIIER) & ~ENETC_PSIMR_MASK(pf->num_vfs); + /* disable MR int source(s) */ - enetc_wr(hw, ENETC_PSIIER, psiier & ~ENETC_PSIIER_MR_MASK); + enetc_wr(hw, ENETC_PSIIER, psiier); } -static void enetc_msg_enable_mr_int(struct enetc_hw *hw) +static void enetc_msg_enable_mr_int(struct enetc_pf *pf) { - u32 psiier = enetc_rd(hw, ENETC_PSIIER); + struct enetc_hw *hw = &pf->si->hw; + u32 psiier; - enetc_wr(hw, ENETC_PSIIER, psiier | ENETC_PSIIER_MR_MASK); + psiier = enetc_rd(hw, ENETC_PSIIER) | ENETC_PSIMR_MASK(pf->num_vfs); + + enetc_wr(hw, ENETC_PSIIER, psiier); } static irqreturn_t enetc_msg_psi_msix(int irq, void *data) @@ -22,7 +29,7 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data) struct enetc_si *si = (struct enetc_si *)data; struct enetc_pf *pf = enetc_si_priv(si); - enetc_msg_disable_mr_int(&si->hw); + enetc_msg_disable_mr_int(pf); schedule_work(&pf->msg_task); return IRQ_HANDLED; @@ -31,33 +38,35 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data) static void enetc_msg_task(struct work_struct *work) { struct enetc_pf *pf = container_of(work, struct enetc_pf, msg_task); + u32 mr_mask = ENETC_PSIMR_MASK(pf->num_vfs); struct enetc_hw *hw = &pf->si->hw; - unsigned long mr_mask; + u32 mr_status; int i; - for (;;) { - mr_mask = enetc_rd(hw, ENETC_PSIMSGRR) & ENETC_PSIMSGRR_MR_MASK; - if (!mr_mask) { - /* re-arm MR interrupts, w1c the IDR reg */ - enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIIER_MR_MASK); - enetc_msg_enable_mr_int(hw); - return; - } + mr_status = (enetc_rd(hw, ENETC_PSIMSGRR) & mr_mask) | + (enetc_rd(hw, ENETC_PSIIDR) & mr_mask); + if (!mr_status) + goto out; + + for (i = 0; i < pf->num_vfs; i++) { + u32 psimsgrr; + u16 msg_code; - for (i = 0; i < pf->num_vfs; i++) { - u32 psimsgrr; - u16 msg_code; + if (!(ENETC_PSIMR_BIT(i) & mr_status)) + continue; - if (!(ENETC_PSIMSGRR_MR(i) & mr_mask)) - continue; + enetc_msg_handle_rxmsg(pf, i, &msg_code); - enetc_msg_handle_rxmsg(pf, i, &msg_code); + /* w1c to clear the corresponding VF MR bit */ + enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIMR_BIT(i)); - psimsgrr = ENETC_SIMSGSR_SET_MC(msg_code); - psimsgrr |= ENETC_PSIMSGRR_MR(i); /* w1c */ - enetc_wr(hw, ENETC_PSIMSGRR, psimsgrr); - } + psimsgrr = ENETC_SIMSGSR_SET_MC(msg_code); + psimsgrr |= ENETC_PSIMR_BIT(i); /* w1c */ + enetc_wr(hw, ENETC_PSIMSGRR, psimsgrr); } + +out: + enetc_msg_enable_mr_int(pf); } /* Init */ @@ -133,7 +142,7 @@ int enetc_msg_psi_init(struct enetc_pf *pf) } /* enable MR interrupts */ - enetc_msg_enable_mr_int(&si->hw); + enetc_msg_enable_mr_int(pf); return 0; @@ -154,7 +163,7 @@ void enetc_msg_psi_free(struct enetc_pf *pf) cancel_work_sync(&pf->msg_task); /* disable MR interrupts */ - enetc_msg_disable_mr_int(&si->hw); + enetc_msg_disable_mr_int(pf); for (i = 0; i < pf->num_vfs; i++) enetc_msg_free_mbx(si, i); -- cgit v1.2.3 From 54362b0176080b905dbd0651ee3dbb295da41541 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:20 +0800 Subject: net: enetc: fix init and teardown order to prevent use of unsafe resources Sashiko reported a potential issue in enetc_msg_psi_init() where the IRQ handler is registered before DMA resources are fully initialized [1]. The current initialization sequence is: 1. request_irq(enetc_msg_psi_msix) <- IRQ handler registered 2. INIT_WORK(&pf->msg_task, ...) <- work_struct initialized 3. enetc_msg_alloc_mbx() <- mailbox DMA allocated This ordering is unsafe because if a spurious interrupt or pending interrupt from a previous device state fires immediately after request_irq() returns, the registered ISR enetc_msg_psi_msix() will execute and unconditionally call: schedule_work(&pf->msg_task) At this point, pf->msg_task has not been initialized by INIT_WORK(), so the work_struct contains garbage values in its internal linked list pointers (work_struct->entry). Passing an uninitialized work_struct to schedule_work() could corrupt the kernel's workqueue linked lists, potentially leading to: - Kernel panic in __queue_work() - Memory corruption in workqueue data structures - System deadlock or undefined behavior Additionally, even if the work_struct was initialized, the mailbox DMA buffers (pf->rxmsg[]) may not yet be allocated when the work handler enetc_msg_task() runs, resulting in NULL pointer dereference. Fix by reordering the initialization sequence to ensure all resources are properly initialized before the interrupt handler can execute: 1. enetc_msg_alloc_mbx() <- Allocate all mailboxes 2. INIT_WORK(&pf->msg_task, ...) <- Initialize work first 3. request_irq(enetc_msg_psi_msix) <- Register IRQ last 4. Configure hardware & enable MR interrupts This guarantees that when enetc_msg_psi_msix() runs: - pf->msg_task is properly initialized (safe for schedule_work) - pf->rxmsg[] buffers are allocated (safe for work handler access) - Hardware is configured appropriately As the inverse of enetc_msg_psi_init(), enetc_msg_psi_free() also has similar problems. For example, if a pending interrupt fires between enetc_msg_free_mbx() and free_irq(), the ISR enetc_msg_psi_msix() may schedule the work handler again via schedule_work(), which could then access already-freed DMA buffers (pf->rxmsg[]), leading to use-after-free and potential memory corruption. Therefore, the order of enetc_msg_psi_free() is adjusted: 1. enetc_msg_disable_mr_int() <- Stop new interrupts first 2. free_irq() <- Ensure no IRQ handler can run 3. cancel_work_sync() <- Wait for any pending work 4. enetc_msg_disable_mr_int() <- Re-disable in case work re-enabled it 5. enetc_msg_free_mbx() <- Safe to free DMA buffers now Link: https://sashiko.dev/#/patchset/20260511080805.2052495-1-wei.fang%40nxp.com #1 Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-9-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_msg.c | 35 ++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index 3136e8321e4d..c09635e7eb3d 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -118,6 +118,15 @@ int enetc_msg_psi_init(struct enetc_pf *pf) struct enetc_si *si = pf->si; int vector, i, err; + for (i = 0; i < pf->num_vfs; i++) { + err = enetc_msg_alloc_mbx(si, i); + if (err) + goto free_mbx; + } + + /* initialize PSI mailbox */ + INIT_WORK(&pf->msg_task, enetc_msg_task); + /* register message passing interrupt handler */ snprintf(pf->msg_int_name, sizeof(pf->msg_int_name), "%s-vfmsg", si->ndev->name); @@ -126,32 +135,21 @@ int enetc_msg_psi_init(struct enetc_pf *pf) if (err) { dev_err(&si->pdev->dev, "PSI messaging: request_irq() failed!\n"); - return err; + goto free_mbx; } /* set one IRQ entry for PSI message receive notification (SI int) */ enetc_wr(&si->hw, ENETC_SIMSIVR, ENETC_SI_INT_IDX); - /* initialize PSI mailbox */ - INIT_WORK(&pf->msg_task, enetc_msg_task); - - for (i = 0; i < pf->num_vfs; i++) { - err = enetc_msg_alloc_mbx(si, i); - if (err) - goto err_init_mbx; - } - /* enable MR interrupts */ enetc_msg_enable_mr_int(pf); return 0; -err_init_mbx: +free_mbx: for (i--; i >= 0; i--) enetc_msg_free_mbx(si, i); - free_irq(vector, si); - return err; } @@ -160,14 +158,17 @@ void enetc_msg_psi_free(struct enetc_pf *pf) struct enetc_si *si = pf->si; int i; + /* disable MR interrupts */ + enetc_msg_disable_mr_int(pf); + + /* de-register message passing interrupt handler */ + free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si); + cancel_work_sync(&pf->msg_task); - /* disable MR interrupts */ + /* MR interrupts may be re-enabled by workqueue */ enetc_msg_disable_mr_int(pf); for (i = 0; i < pf->num_vfs; i++) enetc_msg_free_mbx(si, i); - - /* de-register message passing interrupt handler */ - free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si); } -- cgit v1.2.3 From 9e68817f12d5935dbf73f2fe6e6299644f6de1b6 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Wed, 20 May 2026 14:44:21 +0800 Subject: net: enetc: avoid VF->PF mailbox timeout during SR-IOV teardown During SR-IOV teardown, enetc_msg_psi_free() disables the MR interrupt before pci_disable_sriov() removes the VFs. If a VF sends a mailbox message during this window, the PF cannot receive it, causing the VF to timeout waiting for a reply. Since the timeout occurs during SR-IOV teardown when the VF is about to be removed anyway, it has no functional impact on operation. However, more messages will be added in the future, some visible error logs may confuse users. So fix it by calling pci_disable_sriov() first to remove all VFs, then safely clean up the mailbox resources. This eliminates the race window where VFs could send messages to an unresponsive PF. Fixes: beb74ac878c8 ("enetc: Add vf to pf messaging support") Signed-off-by: Wei Fang Reviewed-by: Harshitha Ramamurthy Link: https://patch.msgid.link/20260520064421.91569-10-wei.fang@nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 8e11a023d516..3206b3daa1a0 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -563,9 +563,9 @@ static int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs) int err; if (!num_vfs) { + pci_disable_sriov(pdev); enetc_msg_psi_free(pf); pf->num_vfs = 0; - pci_disable_sriov(pdev); } else { pf->num_vfs = num_vfs; -- cgit v1.2.3 From c9b7598eb013c6dbf2526dc050364bd8dc24f0d3 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Wed, 20 May 2026 23:31:15 +0300 Subject: irqchip/renesas-rzt2h: Use pm_runtime_put_sync() in probe error path pm_runtime_put() may trigger the idle check after pm_runtime_disable() is run as part of devm_pm_runtime_enable()'s cleanup action, leaving runtime PM active. Use pm_runtime_put_sync() to ensure the idle check runs synchronously. Fixes: 13e7b3305b64 ("irqchip: Add RZ/{T2H,N2H} Interrupt Controller (ICU) driver") Signed-off-by: Cosmin Tanislav Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260520203117.1516442-2-cosmin-gabriel.tanislav.xa@renesas.com --- drivers/irqchip/irq-renesas-rzt2h.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/irqchip/irq-renesas-rzt2h.c b/drivers/irqchip/irq-renesas-rzt2h.c index 53cf80e1155a..ecb69da55508 100644 --- a/drivers/irqchip/irq-renesas-rzt2h.c +++ b/drivers/irqchip/irq-renesas-rzt2h.c @@ -265,7 +265,7 @@ static int rzt2h_icu_init(struct platform_device *pdev, struct device_node *pare irq_domain = irq_domain_create_hierarchy(parent_domain, 0, RZT2H_ICU_NUM_IRQ, dev_fwnode(dev), &rzt2h_icu_domain_ops, priv); if (!irq_domain) { - pm_runtime_put(dev); + pm_runtime_put_sync(dev); return -ENOMEM; } -- cgit v1.2.3 From 654ddf855bebd8d45a6e707f5dc2344921f5e0cf Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 19 May 2026 22:28:01 +0200 Subject: platform/x86: bitland-mifs-wmi: add CONFIG_LEDS_CLASS dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The newly added driver requires the LED classdev support and causes a link failure when that is disabled: x86_64-linux-ld: vmlinux.o: in function `bitland_mifs_wmi_probe': bitland-mifs-wmi.c:(.text+0xede02a): undefined reference to `devm_led_classdev_register_ext' Fixes: dc1ec4fa86b2 ("platform/x86: bitland-mifs-wmi: Add new Bitland MIFS WMI driver") Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260519202804.1339581-1-arnd@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2ffa4ecf65b0..7a4956088300 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -118,6 +118,7 @@ config BITLAND_MIFS_WMI depends on ACPI_WMI depends on HWMON depends on INPUT + depends on LEDS_CLASS depends on POWER_SUPPLY select ACPI_PLATFORM_PROFILE select INPUT_SPARSEKMAP -- cgit v1.2.3 From 53676e4d44d6b38c8a0d9bff331f170ae2e41bbe Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 18 May 2026 15:17:14 -0700 Subject: drm/msm: Restore second parameter name in purge() and evict() After commit 3392291fc509 ("drm/msm: Fix shrinker deadlock"), all supported versions of clang warn (or error with CONFIG_WERROR=y): drivers/gpu/drm/msm/msm_gem_shrinker.c:105:58: error: omitting the parameter name in a function definition is a C23 extension [-Werror,-Wc23-extensions] 105 | purge(struct drm_gem_object *obj, struct ww_acquire_ctx *) | ^ drivers/gpu/drm/msm/msm_gem_shrinker.c:117:58: error: omitting the parameter name in a function definition is a C23 extension [-Werror,-Wc23-extensions] 117 | evict(struct drm_gem_object *obj, struct ww_acquire_ctx *) | ^ 2 errors generated. With older but supported versions of GCC, this is an unconditional hard error: drivers/gpu/drm/msm/msm_gem_shrinker.c: In function 'purge': drivers/gpu/drm/msm/msm_gem_shrinker.c:105:35: error: parameter name omitted purge(struct drm_gem_object *obj, struct ww_acquire_ctx *) ^~~~~~~~~~~~~~~~~~~~~~~ drivers/gpu/drm/msm/msm_gem_shrinker.c: In function 'evict': drivers/gpu/drm/msm/msm_gem_shrinker.c:117:35: error: parameter name omitted evict(struct drm_gem_object *obj, struct ww_acquire_ctx *) ^~~~~~~~~~~~~~~~~~~~~~~ Restore the parameter name to clear up the warnings, renaming it "unused" to make it clear it is only needed to satisfy the prototype of drm_gem_lru_scan(). Cc: stable@vger.kernel.org Fixes: 3392291fc509 ("drm/msm: Fix shrinker deadlock") Signed-off-by: Nathan Chancellor Signed-off-by: Linus Torvalds --- drivers/gpu/drm/msm/msm_gem_shrinker.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/msm/msm_gem_shrinker.c b/drivers/gpu/drm/msm/msm_gem_shrinker.c index c8dda2b68cff..9d2788f79ace 100644 --- a/drivers/gpu/drm/msm/msm_gem_shrinker.c +++ b/drivers/gpu/drm/msm/msm_gem_shrinker.c @@ -102,7 +102,7 @@ out_unlock: } static bool -purge(struct drm_gem_object *obj, struct ww_acquire_ctx *) +purge(struct drm_gem_object *obj, struct ww_acquire_ctx *unused) { if (!is_purgeable(to_msm_bo(obj))) return false; @@ -114,7 +114,7 @@ purge(struct drm_gem_object *obj, struct ww_acquire_ctx *) } static bool -evict(struct drm_gem_object *obj, struct ww_acquire_ctx *) +evict(struct drm_gem_object *obj, struct ww_acquire_ctx *unused) { if (is_unevictable(to_msm_bo(obj))) return false; -- cgit v1.2.3